use crate::{
CipherSuite,
crypto::cipher_suite::CipherSuiteParams,
error::SframeError,
key::{DecryptionKey, EncryptionKey},
};
use log::error;
pub mod mls_key_id;
pub use mls_key_id::{MlsKeyId, MlsKeyIdBitRange};
pub trait MlsExporter {
type BaseKey: AsRef<[u8]>;
type Error: std::error::Error;
fn export_secret(
&self,
label: &str,
context: &[u8],
key_length: usize,
) -> Result<Self::BaseKey, Self::Error>;
}
macro_rules! mls_key {
($name:ident) => {
impl $name {
pub fn derive_from_mls(
cipher_suite: CipherSuite,
exporter: &impl MlsExporter,
key_id: MlsKeyId,
) -> crate::error::Result<Self> {
let params = CipherSuiteParams::from(cipher_suite);
let base_key = exporter
.export_secret("SFrame 1.0 Base Key", b"", params.key_len)
.map_err(|err| {
error!("Failed to export base key from MLS: {}", err);
SframeError::KeyDerivationFailure
})?;
$name::derive_from(cipher_suite, key_id, base_key)
}
}
};
}
mls_key!(DecryptionKey);
mls_key!(EncryptionKey);
#[cfg(test)]
mod test {
use super::{MlsExporter, MlsKeyId, MlsKeyIdBitRange};
use crate::{error::SframeError, key::EncryptionKey};
struct TestMlsExporter {
fail: bool,
}
impl MlsExporter for TestMlsExporter {
type BaseKey = &'static str;
type Error = SframeError;
fn export_secret(
&self,
_label: &str,
_context: &[u8],
_key_length: usize,
) -> Result<Self::BaseKey, Self::Error> {
if self.fail {
Err(SframeError::Other("FAIL".to_owned()))
} else {
Ok("BASE_KEY")
}
}
}
#[test]
fn derive_key_from_mls() {
let exporter = TestMlsExporter { fail: false };
let key_id = MlsKeyId::new(0u64, 3u64, 5u64, MlsKeyIdBitRange::new(4, 4));
let _key =
EncryptionKey::derive_from_mls(crate::CipherSuite::AesGcm256Sha512, &exporter, key_id)
.unwrap();
}
#[test]
fn derive_key_from_mls_failed_export() {
let exporter = TestMlsExporter { fail: true };
let key_id = MlsKeyId::new(0u64, 3u64, 5u64, MlsKeyIdBitRange::new(4, 4));
let result =
EncryptionKey::derive_from_mls(crate::CipherSuite::AesGcm256Sha512, &exporter, key_id);
assert!(result.is_err());
}
}