Skip to main content

streaming_crypto/core_api/crypto/
kdf.rs

1// ## src/crypto/kdf.rs
2
3//! crypto/kdf.rs
4//! HKDF-based session key derivation from master key and header salt.
5//!
6//! Supported PRFs: SHA-256, SHA-512, SHA3-256, SHA3-512, Blake3 derive_key.
7//!
8//! Design:
9//! - HKDF-Extract(master_key, salt) -> PRK
10//! - HKDF-Expand(PRK, info) -> session key (32 bytes)
11//! - HKDF-based session key derivation from master key and header salt.
12//! - Supports SHA-256, SHA-512, SHA3-256, SHA3-512, and Blake3 derive_key.
13//!
14//! Security notes:
15//! - Salt must be random per stream.
16//! - Info binds protocol identity and configuration.
17//! - Never use master_key directly for AEAD; always derive.
18//!
19//! Industry notes:
20//! - Mirrors TLS 1.3/QUIC key schedules: derive traffic keys via HKDF.
21//! - Salt must be random per stream. Info binds protocol identity.
22
23use crate::constants::prf_ids;
24use crate::headers::types::HeaderV1;
25use crate::crypto::types::{KEY_LEN_32, CryptoError};
26
27use hkdf::Hkdf;
28use sha2::{Sha256, Sha512};
29use sha3::{Sha3_256, Sha3_512};
30use blake3::derive_key;
31
32/// Summary: Build HKDF 'info' from header fields to bind protocol identity.
33/// Included fields: magic, version, alg_profile, cipher, hkdf_prf, compression,
34/// strategy, aad_domain, flags, chunk_size, key_id.
35/// Excludes reserved/telemetry.
36#[inline]
37fn build_info_from_header(header: &HeaderV1) -> Vec<u8> {
38    let mut info = Vec::with_capacity(64);
39    info.extend_from_slice(&header.magic);
40    info.extend_from_slice(&header.version.to_le_bytes());
41    info.extend_from_slice(&header.alg_profile.to_le_bytes());
42    info.extend_from_slice(&header.cipher.to_le_bytes());
43    info.extend_from_slice(&header.hkdf_prf.to_le_bytes());
44    info.extend_from_slice(&header.compression.to_le_bytes());
45    info.extend_from_slice(&header.strategy.to_le_bytes());
46    info.extend_from_slice(&header.aad_domain.to_le_bytes());
47    info.extend_from_slice(&header.flags.to_le_bytes());
48    info.extend_from_slice(&header.chunk_size.to_le_bytes());
49    info.extend_from_slice(&header.key_id.to_le_bytes());
50    info
51}
52
53/// Summary: Derive a 32-byte per-stream session key via HKDF from master_key + header.salt.
54/// - PRF chosen from header.hkdf_prf (SHA-256, SHA-512, optionally keyed BLAKE3).
55/// - 'info' binds protocol identity and configuration.
56/// Returns [u8;32] session key.
57///
58/// Errors:
59/// - Unsupported PRF selection returns CryptoError::UnsupportedPrf.
60///
61/// Security notes:
62/// - Never use master_key directly for AEAD; always derive.
63/// - Ensure header.salt is random per stream (validated in headers).
64#[inline]
65pub fn derive_session_key_32(
66    master_key: &[u8],
67    header: &HeaderV1,
68) -> Result<[u8; KEY_LEN_32], CryptoError> {
69    if header.salt.iter().all(|&b| b == 0) {
70        return Err(CryptoError::Failure("salt must not be all-zero".into()));
71    }
72
73    let info = build_info_from_header(header);
74
75    match header.hkdf_prf {
76        x if x == prf_ids::SHA256 => {
77            let hk = Hkdf::<Sha256>::new(Some(&header.salt), master_key);
78            let mut key = [0u8; KEY_LEN_32];
79            hk.expand(&info, &mut key)
80                .map_err(|_| CryptoError::Failure("HKDF expand failed (SHA-256)".into()))?;
81            Ok(key)
82        }
83
84        x if x == prf_ids::SHA512 => {
85            let hk = Hkdf::<Sha512>::new(Some(&header.salt), master_key);
86            let mut key = [0u8; KEY_LEN_32];
87            hk.expand(&info, &mut key)
88                .map_err(|_| CryptoError::Failure("HKDF expand failed (SHA-512)".into()))?;
89            Ok(key)
90        }
91
92        x if x == prf_ids::SHA3_256 => {
93            let hk = Hkdf::<Sha3_256>::new(Some(&header.salt), master_key);
94            let mut key = [0u8; KEY_LEN_32];
95            hk.expand(&info, &mut key)
96                .map_err(|_| CryptoError::Failure("HKDF expand failed (SHA3-256)".into()))?;
97            Ok(key)
98        }
99
100        x if x == prf_ids::SHA3_512 => {
101            let hk = Hkdf::<Sha3_512>::new(Some(&header.salt), master_key);
102            let mut key = [0u8; KEY_LEN_32];
103            hk.expand(&info, &mut key)
104                .map_err(|_| CryptoError::Failure("HKDF expand failed (SHA3-512)".into()))?;
105            Ok(key)
106        }
107
108        x if x == prf_ids::BLAKE3K => {
109            let material = [master_key, &header.salt, &info].concat();
110            let key = derive_key("RSE1|HKDF|SESSION", &material);
111            Ok(key)
112        }
113
114        other => Err(CryptoError::UnsupportedPrf { prf_id: other }),
115    }
116}
117