use super::schedule::HashAlg;
use crate::hash::{Digest, Hmac, Sha256, Sha384};
fn p_hash_impl<D: Digest>(secret: &[u8], seed: &[u8], out: &mut [u8]) {
let mut a = Hmac::<D>::new(secret).chain(seed).finalize();
let mut written = 0usize;
while written < out.len() {
let block = Hmac::<D>::new(secret)
.chain(a.as_ref())
.chain(seed)
.finalize();
let take = (out.len() - written).min(block.as_ref().len());
out[written..written + take].copy_from_slice(&block.as_ref()[..take]);
written += take;
if written >= out.len() {
break;
}
a = Hmac::<D>::new(secret).chain(a.as_ref()).finalize();
}
}
#[allow(dead_code)]
pub(crate) fn p_hash(hash: HashAlg, secret: &[u8], seed: &[u8], out: &mut [u8]) {
match hash {
HashAlg::Sha256 => p_hash_impl::<Sha256>(secret, seed, out),
HashAlg::Sha384 => p_hash_impl::<Sha384>(secret, seed, out),
}
}
#[allow(dead_code)]
pub(crate) fn prf(hash: HashAlg, secret: &[u8], label: &[u8], seed: &[u8], out: &mut [u8]) {
let mut combined = alloc::vec::Vec::with_capacity(label.len() + seed.len());
combined.extend_from_slice(label);
combined.extend_from_slice(seed);
p_hash(hash, secret, &combined, out);
}
#[allow(dead_code)]
pub(crate) fn master_secret(
hash: HashAlg,
premaster: &[u8],
client_random: &[u8; 32],
server_random: &[u8; 32],
) -> [u8; 48] {
let mut seed = [0u8; 64];
seed[..32].copy_from_slice(client_random);
seed[32..].copy_from_slice(server_random);
let mut out = [0u8; 48];
prf(hash, premaster, b"master secret", &seed, &mut out);
out
}
#[allow(dead_code)]
pub(crate) fn extended_master_secret(
hash: HashAlg,
premaster: &[u8],
session_hash: &[u8],
) -> [u8; 48] {
let mut out = [0u8; 48];
prf(
hash,
premaster,
b"extended master secret",
session_hash,
&mut out,
);
out
}
#[allow(dead_code)]
pub(crate) fn key_block(
hash: HashAlg,
master: &[u8; 48],
server_random: &[u8; 32],
client_random: &[u8; 32],
out: &mut [u8],
) {
let mut seed = [0u8; 64];
seed[..32].copy_from_slice(server_random);
seed[32..].copy_from_slice(client_random);
prf(hash, master, b"key expansion", &seed, out);
}
#[allow(dead_code)]
pub(crate) fn tls12_exporter(
hash: HashAlg,
master: &[u8; 48],
label: &[u8],
client_random: &[u8; 32],
server_random: &[u8; 32],
context: Option<&[u8]>,
out: &mut [u8],
) {
let extra = match context {
Some(c) => 2 + c.len(),
None => 0,
};
let mut seed = alloc::vec::Vec::with_capacity(64 + extra);
seed.extend_from_slice(client_random);
seed.extend_from_slice(server_random);
if let Some(c) = context {
let len = c.len() as u16;
seed.extend_from_slice(&len.to_be_bytes());
seed.extend_from_slice(c);
}
prf(hash, master, label, &seed, out);
}
#[allow(dead_code)]
pub(crate) fn finished_verify_data(
hash: HashAlg,
master: &[u8; 48],
label: &[u8],
transcript_hash: &[u8],
) -> [u8; 12] {
let mut out = [0u8; 12];
prf(hash, master, label, transcript_hash, &mut out);
out
}
#[cfg(feature = "tls-legacy")]
#[allow(dead_code)] pub(crate) fn prf_legacy(secret: &[u8], label: &[u8], seed: &[u8], out: &mut [u8]) {
use crate::hash::{Md5, Sha1};
let half = secret.len().div_ceil(2);
let s1 = &secret[..half];
let s2 = &secret[secret.len() - half..];
let mut combined = alloc::vec::Vec::with_capacity(label.len() + seed.len());
combined.extend_from_slice(label);
combined.extend_from_slice(seed);
let mut md5 = alloc::vec![0u8; out.len()];
p_hash_impl::<Md5>(s1, &combined, &mut md5);
p_hash_impl::<Sha1>(s2, &combined, out);
for (o, m) in out.iter_mut().zip(md5.iter()) {
*o ^= *m;
}
}
#[cfg(feature = "tls-legacy")]
#[allow(dead_code)] pub(crate) fn master_secret_legacy(
premaster: &[u8],
client_random: &[u8; 32],
server_random: &[u8; 32],
) -> [u8; 48] {
let mut seed = [0u8; 64];
seed[..32].copy_from_slice(client_random);
seed[32..].copy_from_slice(server_random);
let mut out = [0u8; 48];
prf_legacy(premaster, b"master secret", &seed, &mut out);
out
}
#[cfg(feature = "tls-legacy")]
#[allow(dead_code)] pub(crate) fn key_block_legacy(
master: &[u8; 48],
server_random: &[u8; 32],
client_random: &[u8; 32],
out: &mut [u8],
) {
let mut seed = [0u8; 64];
seed[..32].copy_from_slice(server_random);
seed[32..].copy_from_slice(client_random);
prf_legacy(master, b"key expansion", &seed, out);
}
#[cfg(feature = "tls-legacy")]
#[allow(dead_code)] pub(crate) fn finished_verify_data_legacy(
master: &[u8; 48],
label: &[u8],
md5_sha1_seed: &[u8],
) -> [u8; 12] {
let mut out = [0u8; 12];
prf_legacy(master, label, md5_sha1_seed, &mut out);
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn master_secret_deterministic_and_48_bytes() {
let premaster = [0x42u8; 48];
let cr = [0x11u8; 32];
let sr = [0x22u8; 32];
let ms1 = master_secret(HashAlg::Sha256, &premaster, &cr, &sr);
let ms2 = master_secret(HashAlg::Sha256, &premaster, &cr, &sr);
assert_eq!(ms1, ms2, "master_secret must be deterministic");
assert_eq!(ms1.len(), 48);
let other_pm = [0x43u8; 48];
let ms3 = master_secret(HashAlg::Sha256, &other_pm, &cr, &sr);
assert_ne!(ms1, ms3);
let ms4 = master_secret(HashAlg::Sha256, &premaster, &sr, &cr);
assert_ne!(ms1, ms4);
let ms_sha384 = master_secret(HashAlg::Sha384, &premaster, &cr, &sr);
assert_ne!(ms1, ms_sha384);
}
#[test]
fn extended_master_secret_differs_from_legacy() {
let premaster = [0x42u8; 48];
let session_hash = [0xa5u8; 32];
let ems1 = extended_master_secret(HashAlg::Sha256, &premaster, &session_hash);
let ems2 = extended_master_secret(HashAlg::Sha256, &premaster, &session_hash);
assert_eq!(ems1, ems2, "EMS must be deterministic");
assert_eq!(ems1.len(), 48);
let mut other = session_hash;
other[0] ^= 1;
let ems3 = extended_master_secret(HashAlg::Sha256, &premaster, &other);
assert_ne!(ems1, ems3);
let cr = [0x11u8; 32];
let sr = [0x22u8; 32];
let legacy = master_secret(HashAlg::Sha256, &premaster, &cr, &sr);
let ems_sha384 = extended_master_secret(HashAlg::Sha384, &premaster, &[0xa5u8; 48]);
assert_ne!(ems1, legacy);
assert_ne!(ems1, ems_sha384);
}
#[test]
fn finished_verify_data_is_12_bytes() {
let master = [0x55u8; 48];
let transcript = [0xaau8; 32];
let vd_client =
finished_verify_data(HashAlg::Sha256, &master, b"client finished", &transcript);
let vd_server =
finished_verify_data(HashAlg::Sha256, &master, b"server finished", &transcript);
assert_eq!(vd_client.len(), 12);
assert_eq!(vd_server.len(), 12);
assert_ne!(vd_client, vd_server);
let vd_client_again =
finished_verify_data(HashAlg::Sha256, &master, b"client finished", &transcript);
assert_eq!(vd_client, vd_client_again);
let mut transcript2 = transcript;
transcript2[0] ^= 1;
let vd_other =
finished_verify_data(HashAlg::Sha256, &master, b"client finished", &transcript2);
assert_ne!(vd_client, vd_other);
}
#[test]
fn key_block_expansion() {
let master = [0x33u8; 48];
let cr = [0x77u8; 32];
let sr = [0x88u8; 32];
let mut kb1 = [0u8; 40];
key_block(HashAlg::Sha256, &master, &sr, &cr, &mut kb1);
let mut kb2 = [0u8; 40];
key_block(HashAlg::Sha256, &master, &sr, &cr, &mut kb2);
assert_eq!(kb1, kb2);
let mut kb3 = [0u8; 40];
key_block(HashAlg::Sha256, &master, &cr, &sr, &mut kb3);
assert_ne!(kb1, kb3);
let mut kb_long = [0u8; 80];
key_block(HashAlg::Sha256, &master, &sr, &cr, &mut kb_long);
assert_eq!(&kb_long[..40], &kb1[..]);
let mut kb_sha384 = [0u8; 40];
key_block(HashAlg::Sha384, &master, &sr, &cr, &mut kb_sha384);
assert_ne!(kb1, kb_sha384);
}
#[test]
fn tls12_exporter_branches_and_determinism() {
let master = [0x42u8; 48];
let cr = [0x11u8; 32];
let sr = [0x22u8; 32];
let mut a = [0u8; 32];
tls12_exporter(
HashAlg::Sha256,
&master,
b"EXPERIMENTAL-test",
&cr,
&sr,
None,
&mut a,
);
let mut b = [0u8; 32];
tls12_exporter(
HashAlg::Sha256,
&master,
b"EXPERIMENTAL-test",
&cr,
&sr,
None,
&mut b,
);
assert_eq!(a, b);
let mut long = [0u8; 80];
tls12_exporter(
HashAlg::Sha256,
&master,
b"EXPERIMENTAL-test",
&cr,
&sr,
None,
&mut long,
);
assert_eq!(&long[..32], &a[..]);
let mut other_label = [0u8; 32];
tls12_exporter(
HashAlg::Sha256,
&master,
b"EXPERIMENTAL-other",
&cr,
&sr,
None,
&mut other_label,
);
assert_ne!(a, other_label);
let mut no_ctx = [0u8; 32];
let mut empty_ctx = [0u8; 32];
tls12_exporter(
HashAlg::Sha256,
&master,
b"EXPERIMENTAL-test",
&cr,
&sr,
None,
&mut no_ctx,
);
tls12_exporter(
HashAlg::Sha256,
&master,
b"EXPERIMENTAL-test",
&cr,
&sr,
Some(&[]),
&mut empty_ctx,
);
assert_ne!(
no_ctx, empty_ctx,
"empty-context branch must differ from no-context branch"
);
let mut ctx1 = [0u8; 32];
let mut ctx2 = [0u8; 32];
tls12_exporter(
HashAlg::Sha256,
&master,
b"EXPERIMENTAL-test",
&cr,
&sr,
Some(b"alpha"),
&mut ctx1,
);
tls12_exporter(
HashAlg::Sha256,
&master,
b"EXPERIMENTAL-test",
&cr,
&sr,
Some(b"beta"),
&mut ctx2,
);
assert_ne!(ctx1, ctx2);
let mut sha384 = [0u8; 32];
tls12_exporter(
HashAlg::Sha384,
&master,
b"EXPERIMENTAL-test",
&cr,
&sr,
None,
&mut sha384,
);
assert_ne!(a, sha384);
}
#[test]
fn p_hash_prefix_extension() {
let secret = b"secret";
let seed = b"seed-bytes";
let mut short = [0u8; 16];
let mut long = [0u8; 64];
p_hash(HashAlg::Sha256, secret, seed, &mut short);
p_hash(HashAlg::Sha256, secret, seed, &mut long);
assert_eq!(&long[..16], &short[..]);
let mut short_sha384 = [0u8; 16];
p_hash(HashAlg::Sha384, secret, seed, &mut short_sha384);
assert_ne!(short, short_sha384);
}
#[cfg(feature = "tls-legacy")]
#[test]
fn prf_legacy_known_answer() {
let secret = [
1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, ];
let seed = [0xaau8; 16];
let mut out = [0u8; 32];
prf_legacy(&secret, b"test label", &seed, &mut out);
let expected = [
0x17, 0x6c, 0x29, 0x12, 0x66, 0xfb, 0x5e, 0xba, 0x61, 0xf1, 0x3f, 0xfb, 0xd7, 0x07,
0xeb, 0x0d, 0xdd, 0x55, 0xe9, 0xb9, 0x9e, 0xcd, 0xd5, 0x3b, 0x6d, 0x51, 0x5d, 0xe6,
0xd4, 0x69, 0x34, 0xd7,
];
assert_eq!(out, expected);
let ms = master_secret_legacy(&[0x42u8; 48], &[0x11u8; 32], &[0x22u8; 32]);
let ms_expected = [
0x32, 0x62, 0xd9, 0x1d, 0x8c, 0xc8, 0x75, 0xa4, 0x9b, 0x09, 0x20, 0x26, 0xc1, 0x9e,
0xe4, 0x7d, 0x09, 0xa5, 0x07, 0xa5, 0xcf, 0x5d, 0xc5, 0x02, 0x09, 0x64, 0x64, 0x12,
0x48, 0x48, 0xf7, 0xf3, 0x22, 0xa2, 0x9e, 0x26, 0x56, 0xaa, 0x91, 0xd4, 0xa2, 0x70,
0x8f, 0xee, 0x8e, 0xf3, 0x7b, 0x8b,
];
assert_eq!(ms, ms_expected);
}
#[cfg(feature = "tls-legacy")]
#[test]
fn prf_legacy_structure() {
let secret = [0x9bu8; 20];
let seed = [0x33u8; 24];
let mut a = [0u8; 48];
let mut b = [0u8; 48];
prf_legacy(&secret, b"key expansion", &seed, &mut a);
prf_legacy(&secret, b"key expansion", &seed, &mut b);
assert_eq!(a, b, "deterministic");
let mut long = [0u8; 96];
prf_legacy(&secret, b"key expansion", &seed, &mut long);
assert_eq!(&long[..48], &a[..], "prefix-extension");
let mut s2 = secret;
s2[19] ^= 1;
let mut c = [0u8; 48];
prf_legacy(&s2, b"key expansion", &seed, &mut c);
assert_ne!(a, c);
}
#[test]
fn prf_equals_p_hash_of_label_concat_seed() {
let secret = b"secret";
let label = b"master secret";
let seed = [0x99u8; 64];
let mut via_prf = [0u8; 48];
prf(HashAlg::Sha256, secret, label, &seed, &mut via_prf);
let mut combined = alloc::vec::Vec::with_capacity(label.len() + seed.len());
combined.extend_from_slice(label);
combined.extend_from_slice(&seed);
let mut via_p_hash = [0u8; 48];
p_hash(HashAlg::Sha256, secret, &combined, &mut via_p_hash);
assert_eq!(via_prf, via_p_hash);
}
}