gbp_sframe/kdf.rs
1use gbp_mls::MlsContext;
2
3use crate::error::SFrameError;
4
5/// SFrame ciphersuite selection.
6///
7/// `Aes128Gcm` is the standard choice; `Aes256Gcm` is available for
8/// high-assurance deployments.
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum CipherSuite {
11 /// AES-128-GCM with SHA-256 key expansion (RFC 9605 `AES_128_GCM_SHA256_128`).
12 Aes128Gcm,
13 /// AES-256-GCM with SHA-512 key expansion (RFC 9605 `AES_256_GCM_SHA512_128`).
14 Aes256Gcm,
15}
16
17impl CipherSuite {
18 /// Numeric discriminant used in the FFI (`0` = AES-128, `1` = AES-256).
19 pub fn from_u8(v: u8) -> Option<Self> {
20 match v {
21 0 => Some(Self::Aes128Gcm),
22 1 => Some(Self::Aes256Gcm),
23 _ => None,
24 }
25 }
26
27 /// Numeric discriminant.
28 pub fn as_u8(self) -> u8 {
29 match self {
30 Self::Aes128Gcm => 0,
31 Self::Aes256Gcm => 1,
32 }
33 }
34
35 /// Maps to the corresponding RFC 9605 suite in the `sframe` crate.
36 pub(crate) fn to_sframe(self) -> sframe::CipherSuite {
37 match self {
38 Self::Aes128Gcm => sframe::CipherSuite::AesGcm128Sha256,
39 Self::Aes256Gcm => sframe::CipherSuite::AesGcm256Sha512,
40 }
41 }
42}
43
44/// Derives the 32-byte SFrame base key from the MLS `ExportSecret`.
45///
46/// `label` is the application-defined export label (e.g. `"gbp/sframe v1"`).
47/// `epoch` is passed as an 8-byte big-endian context to bind the key to the
48/// current MLS epoch. Per-sender keys are then expanded from this base key by
49/// the `sframe` crate (RFC 9605 ยง5.2), keyed by the frame's KID.
50pub fn derive_base_key(mls: &MlsContext, label: &str, epoch: u64) -> Result<[u8; 32], SFrameError> {
51 let context = epoch.to_be_bytes();
52 let raw = mls
53 .export_raw(label, &context, 32)
54 .map_err(|e| SFrameError::MlsExport(e.to_string()))?;
55 let mut out = [0u8; 32];
56 out.copy_from_slice(&raw);
57 Ok(out)
58}