use super::crypto::hash::{BoxDigest, Digest};
use super::crypto::hmac::{expand_label, extract as hkdf_extract};
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SuiteHash {
Sha256,
Sha384,
}
impl SuiteHash {
pub(crate) fn hash_len(self) -> usize {
match self {
SuiteHash::Sha256 => 32,
SuiteHash::Sha384 => 48,
}
}
pub(crate) fn new_digest(self) -> BoxDigest {
match self {
SuiteHash::Sha256 => Box::new(super::crypto::hash::Sha256::new()),
SuiteHash::Sha384 => Box::new(super::crypto::hash::Sha384::new()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CipherSuite {
TlsAes128GcmSha256,
TlsAes256GcmSha384,
TlsChaCha20Poly1305Sha256,
}
impl CipherSuite {
pub(crate) fn wire(self) -> u16 {
match self {
CipherSuite::TlsAes128GcmSha256 => 0x1301,
CipherSuite::TlsAes256GcmSha384 => 0x1302,
CipherSuite::TlsChaCha20Poly1305Sha256 => 0x1303,
}
}
pub(crate) fn from_wire(v: u16) -> Option<Self> {
match v {
0x1301 => Some(CipherSuite::TlsAes128GcmSha256),
0x1302 => Some(CipherSuite::TlsAes256GcmSha384),
0x1303 => Some(CipherSuite::TlsChaCha20Poly1305Sha256),
_ => None,
}
}
pub(crate) fn hash(self) -> SuiteHash {
match self {
CipherSuite::TlsAes128GcmSha256 | CipherSuite::TlsChaCha20Poly1305Sha256 => {
SuiteHash::Sha256
}
CipherSuite::TlsAes256GcmSha384 => SuiteHash::Sha384,
}
}
pub(crate) fn key_len(self) -> usize {
match self {
CipherSuite::TlsAes128GcmSha256 => 16,
CipherSuite::TlsAes256GcmSha384 => 32,
CipherSuite::TlsChaCha20Poly1305Sha256 => 32,
}
}
pub(crate) fn seal(
self,
key: &[u8],
nonce: &[u8; 12],
aad: &[u8],
plaintext: &[u8],
) -> Option<Vec<u8>> {
match self {
CipherSuite::TlsAes128GcmSha256 | CipherSuite::TlsAes256GcmSha384 => {
super::crypto::gcm::seal(key, nonce, aad, plaintext)
}
CipherSuite::TlsChaCha20Poly1305Sha256 => {
let k: [u8; 32] = key.try_into().ok()?;
Some(super::crypto::chacha20poly1305::seal(
&k, nonce, aad, plaintext,
))
}
}
}
pub(crate) fn open(
self,
key: &[u8],
nonce: &[u8; 12],
aad: &[u8],
sealed: &[u8],
) -> Option<Vec<u8>> {
match self {
CipherSuite::TlsAes128GcmSha256 | CipherSuite::TlsAes256GcmSha384 => {
super::crypto::gcm::open(key, nonce, aad, sealed)
}
CipherSuite::TlsChaCha20Poly1305Sha256 => {
let k: [u8; 32] = key.try_into().ok()?;
super::crypto::chacha20poly1305::open(&k, nonce, aad, sealed)
}
}
}
}
pub(crate) struct Transcript {
digest: BoxDigest,
}
impl Transcript {
pub(crate) fn new(h: SuiteHash) -> Self {
Self {
digest: h.new_digest(),
}
}
pub(crate) fn update(&mut self, msg: &[u8]) {
self.digest.update(msg);
}
pub(crate) fn current_hash(&self) -> Vec<u8> {
let mut fork = self.digest.as_ref().fork();
fork.finalize()
}
}
fn derive_secret(h: SuiteHash, secret: &[u8], label: &[u8], transcript_hash: &[u8]) -> Vec<u8> {
let mut d = h.new_digest();
expand_label(d.as_mut(), secret, label, transcript_hash, h.hash_len())
}
pub(crate) fn expand_secret(
h: SuiteHash,
secret: &[u8],
label: &[u8],
context: &[u8],
len: usize,
) -> Vec<u8> {
let mut d = h.new_digest();
expand_label(d.as_mut(), secret, label, context, len)
}
pub(crate) fn update_traffic_secret(h: SuiteHash, secret: &[u8]) -> Vec<u8> {
expand_secret(h, secret, b"traffic upd", &[], h.hash_len())
}
#[derive(Debug, Clone)]
pub(crate) struct TrafficKeys {
pub(crate) key: [u8; 32],
pub(crate) iv: [u8; 12],
}
impl TrafficKeys {
pub(crate) fn from_secret(suite: CipherSuite, secret: &[u8]) -> Self {
let h = suite.hash();
let key = expand_secret(h, secret, b"key", &[], suite.key_len());
let iv = expand_secret(h, secret, b"iv", &[], 12);
let mut k = [0u8; 32];
k[..key.len()].copy_from_slice(&key);
let mut i = [0u8; 12];
i.copy_from_slice(&iv);
Self { key: k, iv: i }
}
}
#[derive(Debug, Clone)]
pub(crate) struct KeySchedule {
suite: CipherSuite,
handshake_secret: Vec<u8>,
master_secret: Vec<u8>,
c_hs: Vec<u8>,
s_hs: Vec<u8>,
c_ap: Vec<u8>,
s_ap: Vec<u8>,
}
impl KeySchedule {
pub(crate) fn handshake(suite: CipherSuite, ecdhe: &[u8; 32], transcript_hash: &[u8]) -> Self {
let h = suite.hash();
let zeros = vec![0u8; h.hash_len()];
let early = hkdf_extract(&mut h.new_digest(), &zeros, &zeros);
let empty_hash = {
let mut d = h.new_digest();
d.finalize()
};
let derived = derive_secret(h, &early, b"derived", &empty_hash);
let handshake_secret = hkdf_extract(&mut h.new_digest(), &derived, ecdhe);
let c_hs = derive_secret(h, &handshake_secret, b"c hs traffic", transcript_hash);
let s_hs = derive_secret(h, &handshake_secret, b"s hs traffic", transcript_hash);
Self {
suite,
handshake_secret,
master_secret: Vec::new(),
c_hs,
s_hs,
c_ap: Vec::new(),
s_ap: Vec::new(),
}
}
pub(crate) fn handshake_with_psk(
suite: CipherSuite,
ecdhe: &[u8; 32],
psk: &[u8],
transcript_hash: &[u8],
) -> Self {
let h = suite.hash();
let zeros = vec![0u8; h.hash_len()];
let early = hkdf_extract(&mut h.new_digest(), &zeros, psk);
let empty_hash = {
let mut d = h.new_digest();
d.finalize()
};
let derived = derive_secret(h, &early, b"derived", &empty_hash);
let handshake_secret = hkdf_extract(&mut h.new_digest(), &derived, ecdhe);
let c_hs = derive_secret(h, &handshake_secret, b"c hs traffic", transcript_hash);
let s_hs = derive_secret(h, &handshake_secret, b"s hs traffic", transcript_hash);
Self {
suite,
handshake_secret,
master_secret: Vec::new(),
c_hs,
s_hs,
c_ap: Vec::new(),
s_ap: Vec::new(),
}
}
pub(crate) fn application(&mut self, transcript_hash: &[u8]) -> Result<(), super::TlsError> {
let h = self.suite.hash();
let empty_hash = {
let mut d = h.new_digest();
d.finalize()
};
let derived = derive_secret(h, &self.handshake_secret, b"derived", &empty_hash);
let zeros = vec![0u8; h.hash_len()];
self.master_secret = hkdf_extract(&mut h.new_digest(), &derived, &zeros);
self.c_ap = derive_secret(h, &self.master_secret, b"c ap traffic", transcript_hash);
self.s_ap = derive_secret(h, &self.master_secret, b"s ap traffic", transcript_hash);
Ok(())
}
pub(crate) fn client_handshake(&self) -> &[u8] {
&self.c_hs
}
pub(crate) fn server_handshake(&self) -> &[u8] {
&self.s_hs
}
pub(crate) fn client_handshake_keys(&self) -> TrafficKeys {
TrafficKeys::from_secret(self.suite, &self.c_hs)
}
pub(crate) fn server_handshake_keys(&self) -> TrafficKeys {
TrafficKeys::from_secret(self.suite, &self.s_hs)
}
pub(crate) fn client_application_keys(&self) -> TrafficKeys {
TrafficKeys::from_secret(self.suite, &self.c_ap)
}
pub(crate) fn client_application_secret(&self) -> &[u8] {
&self.c_ap
}
pub(crate) fn server_application_keys(&self) -> TrafficKeys {
TrafficKeys::from_secret(self.suite, &self.s_ap)
}
pub(crate) fn server_application_secret(&self) -> &[u8] {
&self.s_ap
}
pub(crate) fn finished_key(&self, secret: &[u8]) -> Vec<u8> {
let h = self.suite.hash();
expand_secret(h, secret, b"finished", &[], h.hash_len())
}
pub(crate) fn suite(&self) -> CipherSuite {
self.suite
}
pub(crate) fn resumption_master(&self, transcript_hash: &[u8]) -> Vec<u8> {
let h = self.suite.hash();
derive_secret(h, &self.master_secret, b"res master", transcript_hash)
}
pub(crate) fn resumption_psk(&self, transcript_hash: &[u8], ticket_nonce: &[u8]) -> Vec<u8> {
let res_master = self.resumption_master(transcript_hash);
let h = self.suite.hash();
expand_label(
h.new_digest().as_mut(),
&res_master,
b"resumption",
ticket_nonce,
h.hash_len(),
)
}
}
pub(crate) fn binder_key(suite: CipherSuite, psk: &[u8]) -> Vec<u8> {
let h = suite.hash();
let zeros = vec![0u8; h.hash_len()];
let early = hkdf_extract(&mut h.new_digest(), &zeros, psk);
let empty_hash = {
let mut d = h.new_digest();
d.finalize()
};
derive_secret(h, &early, b"res binder", &empty_hash)
}
#[cfg(test)]
mod tests {
use super::*;
fn hex(s: &str) -> Vec<u8> {
let s = s.replace(' ', "");
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn rfc8448_key_schedule_sha256() {
let ecdhe: [u8; 32] =
hex("8bd4054fb55b9d63fdfbacf9f04b9f0d35e6d63f537563efd46272900f89492d")
.try_into()
.unwrap();
let th: [u8; 32] = hex("860c06edc07858ee8e78f0e7428c58edd6b43f2ca3e6e95f02ed063cf0e1cad8")
.try_into()
.unwrap();
let ks = KeySchedule::handshake(CipherSuite::TlsAes128GcmSha256, &ecdhe, &th);
assert_eq!(
hex_str(ks.client_handshake()),
"b3eddb126e067f35a780b3abf45e2d8f3b1a950738f52e9600746a0e27a55a21"
);
assert_eq!(
hex_str(ks.server_handshake()),
"b67b7d690cc16c4e75e54213cb2d37b4e9c912bcded9105d42befd59d391ad38"
);
let empty = {
let mut d = CipherSuite::TlsAes128GcmSha256.hash().new_digest();
d.finalize()
};
let derived2 = derive_secret(
CipherSuite::TlsAes128GcmSha256.hash(),
ks.handshake_secret.clone().as_slice(),
b"derived",
&empty,
);
let master = hkdf_extract(
&mut CipherSuite::TlsAes128GcmSha256.hash().new_digest(),
&derived2,
&[0u8; 32],
);
assert_eq!(
hex_str(&master),
"18df06843d13a08bf2a449844c5f8a478001bc4d4c627984d5a41da8d0402919"
);
}
fn hex_str(v: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut s = String::with_capacity(v.len() * 2);
for &b in v {
s.push(HEX[(b >> 4) as usize] as char);
s.push(HEX[(b & 0x0f) as usize] as char);
}
s
}
}