#![deny(missing_docs)]
pub mod cipher;
pub mod error;
pub mod header;
pub mod kdf;
pub mod replay;
pub use cipher::{SFrameDecryptor, SFrameEncryptor};
pub use error::SFrameError;
pub use header::SFrameHeader;
pub use kdf::{CipherSuite, derive_base_key};
use gbp_mls::MlsContext;
use kdf::derive_participant;
pub struct SFrameSession {
base_key: [u8; 32],
epoch: u64,
suite: CipherSuite,
}
impl SFrameSession {
pub fn new(base_key: [u8; 32], epoch: u64, suite: CipherSuite) -> Self {
Self {
base_key,
epoch,
suite,
}
}
pub fn from_mls(
mls: &MlsContext,
label: &str,
suite: CipherSuite,
) -> Result<Self, SFrameError> {
let epoch = mls.epoch();
let base_key = derive_base_key(mls, label, epoch)?;
Ok(Self::new(base_key, epoch, suite))
}
pub fn epoch(&self) -> u64 {
self.epoch
}
pub fn suite(&self) -> CipherSuite {
self.suite
}
pub fn encryptor(&self, leaf_index: u32) -> SFrameEncryptor {
let kid = SFrameHeader::kid_from(self.epoch, leaf_index);
let keys = derive_participant(&self.base_key, leaf_index, self.suite);
SFrameEncryptor::new(keys, kid, self.suite)
}
pub fn decryptor(&self) -> SFrameDecryptor {
SFrameDecryptor::new(self.base_key, self.epoch, self.suite)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_session(epoch: u64) -> SFrameSession {
SFrameSession::new([0x42u8; 32], epoch, CipherSuite::Aes128Gcm)
}
#[test]
fn encrypt_decrypt_roundtrip_128() {
let session = test_session(1);
let mut enc = session.encryptor(0);
let mut dec = session.decryptor();
let frame = b"hello sframe";
let payload = enc.encrypt(frame, b"").unwrap();
let (plain, leaf) = dec.decrypt(&payload, b"").unwrap();
assert_eq!(plain, frame);
assert_eq!(leaf, 0);
}
#[test]
fn encrypt_decrypt_roundtrip_256() {
let session = SFrameSession::new([0x11u8; 32], 5, CipherSuite::Aes256Gcm);
let mut enc = session.encryptor(3);
let mut dec = session.decryptor();
let frame = b"audio payload aes256";
let payload = enc.encrypt(frame, b"rtp-header").unwrap();
let (plain, leaf) = dec.decrypt(&payload, b"rtp-header").unwrap();
assert_eq!(plain, frame);
assert_eq!(leaf, 3);
}
#[test]
fn wrong_aad_fails_decryption() {
let session = test_session(0);
let mut enc = session.encryptor(0);
let mut dec = session.decryptor();
let payload = enc.encrypt(b"data", b"correct-aad").unwrap();
assert!(dec.decrypt(&payload, b"wrong-aad").is_err());
}
#[test]
fn replay_rejected() {
let session = test_session(0);
let mut enc = session.encryptor(1);
let mut dec = session.decryptor();
let payload = enc.encrypt(b"frame", b"").unwrap();
dec.decrypt(&payload, b"").unwrap();
assert!(dec.decrypt(&payload, b"").is_err());
}
#[test]
fn multi_sender() {
let session = test_session(2);
let mut enc0 = session.encryptor(0);
let mut enc1 = session.encryptor(1);
let mut dec = session.decryptor();
let p0 = enc0.encrypt(b"from-0", b"").unwrap();
let p1 = enc1.encrypt(b"from-1", b"").unwrap();
let (msg0, leaf0) = dec.decrypt(&p0, b"").unwrap();
let (msg1, leaf1) = dec.decrypt(&p1, b"").unwrap();
assert_eq!(msg0, b"from-0");
assert_eq!(leaf0, 0);
assert_eq!(msg1, b"from-1");
assert_eq!(leaf1, 1);
}
#[test]
fn epoch_mismatch_rejected() {
let session_a = test_session(1);
let session_b = test_session(2);
let mut enc = session_a.encryptor(0);
let mut dec = session_b.decryptor();
let payload = enc.encrypt(b"stale", b"").unwrap();
assert!(dec.decrypt(&payload, b"").is_err());
}
}