use crate::hash::{Digest, Hmac, Sha256, Sha384};
use crate::kdf::{hkdf_expand, hkdf_extract};
use alloc::vec::Vec;
const MAX_SECRET: usize = 64;
#[derive(Clone, Copy)]
pub(crate) struct Secret {
buf: [u8; MAX_SECRET],
len: u8,
}
impl Secret {
pub(crate) fn new(bytes: &[u8]) -> Self {
debug_assert!(bytes.len() <= MAX_SECRET);
let mut buf = [0u8; MAX_SECRET];
buf[..bytes.len()].copy_from_slice(bytes);
Secret {
buf,
len: bytes.len() as u8,
}
}
pub(crate) fn as_slice(&self) -> &[u8] {
&self.buf[..self.len as usize]
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum HashAlg {
Sha256,
Sha384,
}
impl HashAlg {
pub(crate) fn output_len(self) -> usize {
match self {
HashAlg::Sha256 => 32,
HashAlg::Sha384 => 48,
}
}
pub(crate) fn hash(self, messages: &[u8]) -> Secret {
match self {
HashAlg::Sha256 => Secret::new(Sha256::digest(messages).as_ref()),
HashAlg::Sha384 => Secret::new(Sha384::digest(messages).as_ref()),
}
}
}
fn expand_label<D: Digest>(secret: &[u8], label: &[u8], context: &[u8], out: &mut [u8]) {
let label_length = u16::try_from(out.len())
.expect("HKDF output length must fit in u16 (RFC 8446 §7.1 HkdfLabel.length)");
let mut info = Vec::with_capacity(4 + 6 + label.len() + context.len());
info.extend_from_slice(&label_length.to_be_bytes());
info.push((6 + label.len()) as u8);
info.extend_from_slice(b"tls13 ");
info.extend_from_slice(label);
info.push(context.len() as u8);
info.extend_from_slice(context);
let mut prk = D::zeroed_output();
prk.as_mut().copy_from_slice(secret);
hkdf_expand::<D>(&prk, &info, out);
}
pub(crate) fn expand_label_dyn(
alg: HashAlg,
secret: &[u8],
label: &[u8],
context: &[u8],
out: &mut [u8],
) {
match alg {
HashAlg::Sha256 => expand_label::<Sha256>(secret, label, context, out),
HashAlg::Sha384 => expand_label::<Sha384>(secret, label, context, out),
}
}
pub(crate) fn derive_secret(
alg: HashAlg,
secret: &[u8],
label: &[u8],
transcript_hash: &[u8],
) -> Secret {
let mut out = [0u8; MAX_SECRET];
let n = alg.output_len();
expand_label_dyn(alg, secret, label, transcript_hash, &mut out[..n]);
Secret::new(&out[..n])
}
pub(crate) fn extract(alg: HashAlg, salt: &[u8], ikm: &[u8]) -> Secret {
match alg {
HashAlg::Sha256 => Secret::new(hkdf_extract::<Sha256>(salt, ikm).as_ref()),
HashAlg::Sha384 => Secret::new(hkdf_extract::<Sha384>(salt, ikm).as_ref()),
}
}
pub(crate) struct KeySchedule {
alg: HashAlg,
secret: Secret,
}
impl KeySchedule {
pub(crate) fn new(alg: HashAlg) -> Self {
let zeros = [0u8; MAX_SECRET];
let n = alg.output_len();
let early = extract(alg, &[], &zeros[..n]);
KeySchedule { alg, secret: early }
}
pub(crate) fn with_psk(alg: HashAlg, psk: &[u8]) -> Self {
let early = extract(alg, &[], psk);
KeySchedule { alg, secret: early }
}
#[cfg(test)]
pub(crate) fn early_secret(&self) -> Secret {
self.secret
}
pub(crate) fn binder_key(&self, label: &[u8]) -> Secret {
let empty_hash = self.alg.hash(&[]);
derive_secret(
self.alg,
self.secret.as_slice(),
label,
empty_hash.as_slice(),
)
}
#[allow(dead_code)]
pub(crate) fn client_early_traffic_secret(&self, transcript: &[u8]) -> Secret {
derive_secret(self.alg, self.secret.as_slice(), b"c e traffic", transcript)
}
pub(crate) fn enter_handshake(&mut self, ecdhe: &[u8]) {
let derived = self.derive_for_next();
self.secret = extract(self.alg, derived.as_slice(), ecdhe);
}
#[cfg(feature = "ech")]
pub(crate) fn current_secret_bytes(&self) -> &[u8] {
self.secret.as_slice()
}
pub(crate) fn enter_master(&mut self) {
let derived = self.derive_for_next();
let zeros = [0u8; MAX_SECRET];
let n = self.alg.output_len();
self.secret = extract(self.alg, derived.as_slice(), &zeros[..n]);
}
fn derive_for_next(&self) -> Secret {
let empty = self.alg.hash(&[]);
derive_secret(
self.alg,
self.secret.as_slice(),
b"derived",
empty.as_slice(),
)
}
pub(crate) fn client_handshake_traffic_secret(&self, transcript: &[u8]) -> Secret {
derive_secret(
self.alg,
self.secret.as_slice(),
b"c hs traffic",
transcript,
)
}
pub(crate) fn server_handshake_traffic_secret(&self, transcript: &[u8]) -> Secret {
derive_secret(
self.alg,
self.secret.as_slice(),
b"s hs traffic",
transcript,
)
}
pub(crate) fn client_application_traffic_secret(&self, transcript: &[u8]) -> Secret {
derive_secret(
self.alg,
self.secret.as_slice(),
b"c ap traffic",
transcript,
)
}
pub(crate) fn server_application_traffic_secret(&self, transcript: &[u8]) -> Secret {
derive_secret(
self.alg,
self.secret.as_slice(),
b"s ap traffic",
transcript,
)
}
pub(crate) fn exporter_master_secret(&self, transcript: &[u8]) -> Secret {
derive_secret(self.alg, self.secret.as_slice(), b"exp master", transcript)
}
pub(crate) fn resumption_master_secret(&self, transcript: &[u8]) -> Secret {
derive_secret(self.alg, self.secret.as_slice(), b"res master", transcript)
}
}
pub(crate) fn psk_from_resumption(alg: HashAlg, rms: &Secret, ticket_nonce: &[u8], out: &mut [u8]) {
expand_label_dyn(alg, rms.as_slice(), b"resumption", ticket_nonce, out);
}
pub(crate) fn binder_finished_key(alg: HashAlg, binder_key: &Secret) -> Secret {
let mut out = [0u8; MAX_SECRET];
let n = alg.output_len();
expand_label_dyn(alg, binder_key.as_slice(), b"finished", &[], &mut out[..n]);
Secret::new(&out[..n])
}
pub(crate) fn tls_exporter(
alg: HashAlg,
exporter_master_secret: &Secret,
label: &[u8],
context: &[u8],
out: &mut [u8],
) {
let empty_hash = alg.hash(&[]);
let mut export = [0u8; MAX_SECRET];
let n = alg.output_len();
expand_label_dyn(
alg,
exporter_master_secret.as_slice(),
label,
empty_hash.as_slice(),
&mut export[..n],
);
let ctx_hash = alg.hash(context);
expand_label_dyn(alg, &export[..n], b"exporter", ctx_hash.as_slice(), out);
}
pub(crate) fn next_traffic_secret(alg: HashAlg, prev: &Secret) -> Secret {
let mut next = [0u8; MAX_SECRET];
let n = alg.output_len();
expand_label_dyn(alg, prev.as_slice(), b"traffic upd", &[], &mut next[..n]);
Secret::new(&next[..n])
}
pub(crate) fn traffic_key_iv(alg: HashAlg, secret: &Secret, key_len: usize) -> (Vec<u8>, [u8; 12]) {
let mut key = alloc::vec![0u8; key_len];
expand_label_dyn(alg, secret.as_slice(), b"key", &[], &mut key);
let mut iv = [0u8; 12];
expand_label_dyn(alg, secret.as_slice(), b"iv", &[], &mut iv);
(key, iv)
}
pub(crate) fn finished_key(alg: HashAlg, secret: &Secret) -> Secret {
let mut out = [0u8; MAX_SECRET];
let n = alg.output_len();
expand_label_dyn(alg, secret.as_slice(), b"finished", &[], &mut out[..n]);
Secret::new(&out[..n])
}
pub(crate) fn finished_verify_data(
alg: HashAlg,
traffic_secret: &Secret,
transcript_hash: &[u8],
) -> Secret {
let fk = finished_key(alg, traffic_secret);
match alg {
HashAlg::Sha256 => {
Secret::new(Hmac::<Sha256>::mac(fk.as_slice(), transcript_hash).as_ref())
}
HashAlg::Sha384 => {
Secret::new(Hmac::<Sha384>::mac(fk.as_slice(), transcript_hash).as_ref())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::from_hex;
#[test]
fn rfc8448_key_schedule() {
let alg = HashAlg::Sha256;
let ecdhe =
from_hex::<32>("8bd4054fb55b9d63fdfbacf9f04b9f0d35e6d63f537563efd46272900f89492d");
let transcript_ch_sh =
from_hex::<32>("860c06edc07858ee8e78f0e7428c58edd6b43f2ca3e6e95f02ed063cf0e1cad8");
let mut ks = KeySchedule::new(alg);
assert_eq!(
ks.early_secret().as_slice(),
&from_hex::<32>("33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a")[..]
);
ks.enter_handshake(&ecdhe);
assert_eq!(
ks.secret.as_slice(),
&from_hex::<32>("1dc826e93606aa6fdc0aadc12f741b01046aa6b99f691ed221a9f0ca043fbeac")[..]
);
let chts = ks.client_handshake_traffic_secret(&transcript_ch_sh);
let shts = ks.server_handshake_traffic_secret(&transcript_ch_sh);
assert_eq!(
chts.as_slice(),
&from_hex::<32>("b3eddb126e067f35a780b3abf45e2d8f3b1a950738f52e9600746a0e27a55a21")[..]
);
assert_eq!(
shts.as_slice(),
&from_hex::<32>("b67b7d690cc16c4e75e54213cb2d37b4e9c912bcded9105d42befd59d391ad38")[..]
);
let (skey, siv) = traffic_key_iv(alg, &shts, 16);
assert_eq!(skey, from_hex::<16>("3fce516009c21727d0f2e4e86ee403bc"));
assert_eq!(siv, from_hex::<12>("5d313eb2671276ee13000b30"));
let (ckey, civ) = traffic_key_iv(alg, &chts, 16);
assert_eq!(ckey, from_hex::<16>("dbfaa693d1762c5b666af5d950258d01"));
assert_eq!(civ, from_hex::<12>("5bd3c71b836e0b76bb73265f"));
let sfin = finished_key(alg, &shts);
assert_eq!(
sfin.as_slice(),
&from_hex::<32>("008d3b66f816ea559f96b537e885c31fc068bf492c652f01f288a1d8cdc19fc8")[..]
);
ks.enter_master();
assert_eq!(
ks.secret.as_slice(),
&from_hex::<32>("18df06843d13a08bf2a449844c5f8a478001bc4d4c627984d5a41da8d0402919")[..]
);
}
#[test]
fn psk_resumption_plumbing_self_consistent() {
let alg = HashAlg::Sha256;
let psk = [0x42u8; 32];
let transcript_ch = [0xa1u8; 32];
let transcript_ch_cf = [0xb2u8; 32];
let mut ks = KeySchedule::with_psk(alg, &psk);
let res_bk = ks.binder_key(b"res binder");
let ext_bk = ks.binder_key(b"ext binder");
assert_ne!(res_bk.as_slice(), ext_bk.as_slice());
let bfk = binder_finished_key(alg, &res_bk);
assert_ne!(bfk.as_slice(), res_bk.as_slice());
let cets = ks.client_early_traffic_secret(&transcript_ch);
assert_ne!(cets.as_slice(), res_bk.as_slice());
ks.enter_handshake(&[0u8; 32]);
ks.enter_master();
let rms = ks.resumption_master_secret(&transcript_ch_cf);
let mut psk_out = [0u8; 32];
psk_from_resumption(alg, &rms, &[1, 2, 3, 4], &mut psk_out);
let mut psk_out2 = [0u8; 32];
psk_from_resumption(alg, &rms, &[1, 2, 3, 4], &mut psk_out2);
assert_eq!(psk_out, psk_out2);
let mut psk_other = [0u8; 32];
psk_from_resumption(alg, &rms, &[1, 2, 3, 5], &mut psk_other);
assert_ne!(psk_out, psk_other);
}
}