| /// Key schedule maintenance for TLS1.3 |
| |
| use ring::{aead, hkdf::{self, KeyType as _}, hmac, digest}; |
| use crate::error::TLSError; |
| use crate::cipher::{Iv, IvLen}; |
| use crate::msgs::base::PayloadU8; |
| use crate::KeyLog; |
| |
| /// The kinds of secret we can extract from `KeySchedule`. |
| #[derive(Debug, Clone, Copy, PartialEq)] |
| enum SecretKind { |
| ResumptionPSKBinderKey, |
| ClientEarlyTrafficSecret, |
| ClientHandshakeTrafficSecret, |
| ServerHandshakeTrafficSecret, |
| ClientApplicationTrafficSecret, |
| ServerApplicationTrafficSecret, |
| ExporterMasterSecret, |
| ResumptionMasterSecret, |
| DerivedSecret, |
| } |
| |
| impl SecretKind { |
| fn to_bytes(self) -> &'static [u8] { |
| match self { |
| SecretKind::ResumptionPSKBinderKey => b"res binder", |
| SecretKind::ClientEarlyTrafficSecret => b"c e traffic", |
| SecretKind::ClientHandshakeTrafficSecret => b"c hs traffic", |
| SecretKind::ServerHandshakeTrafficSecret => b"s hs traffic", |
| SecretKind::ClientApplicationTrafficSecret => b"c ap traffic", |
| SecretKind::ServerApplicationTrafficSecret => b"s ap traffic", |
| SecretKind::ExporterMasterSecret => b"exp master", |
| SecretKind::ResumptionMasterSecret => b"res master", |
| SecretKind::DerivedSecret => b"derived", |
| } |
| } |
| |
| fn log_label(self) -> Option<&'static str> { |
| use self::SecretKind::*; |
| Some(match self { |
| ClientEarlyTrafficSecret => "CLIENT_EARLY_TRAFFIC_SECRET", |
| ClientHandshakeTrafficSecret => "CLIENT_HANDSHAKE_TRAFFIC_SECRET", |
| ServerHandshakeTrafficSecret => "SERVER_HANDSHAKE_TRAFFIC_SECRET", |
| ClientApplicationTrafficSecret => "CLIENT_TRAFFIC_SECRET_0", |
| ServerApplicationTrafficSecret => "SERVER_TRAFFIC_SECRET_0", |
| ExporterMasterSecret => "EXPORTER_SECRET", |
| _ => { return None; } |
| }) |
| } |
| } |
| |
| /// This is the TLS1.3 key schedule. It stores the current secret and |
| /// the type of hash. This isn't used directly; but only through the |
| /// typestates. |
| struct KeySchedule { |
| current: hkdf::Prk, |
| algorithm: ring::hkdf::Algorithm, |
| } |
| |
| // We express the state of a contained KeySchedule using these |
| // typestates. This means we can write code that cannot accidentally |
| // (eg) encrypt application data using a KeySchedule solely constructed |
| // with an empty or trivial secret, or extract the wrong kind of secrets |
| // at a given point. |
| |
| /// KeySchedule for early data stage. |
| pub struct KeyScheduleEarly { |
| ks: KeySchedule, |
| } |
| |
| impl KeyScheduleEarly { |
| pub fn new(algorithm: hkdf::Algorithm, secret: &[u8]) -> KeyScheduleEarly { |
| KeyScheduleEarly { ks: KeySchedule::new(algorithm, secret) } |
| } |
| |
| pub fn client_early_traffic_secret(&self, |
| hs_hash: &[u8], |
| key_log: &dyn KeyLog, |
| client_random: &[u8; 32]) -> hkdf::Prk { |
| self.ks.derive_logged_secret(SecretKind::ClientEarlyTrafficSecret, |
| hs_hash, key_log, client_random) |
| } |
| |
| pub fn resumption_psk_binder_key_and_sign_verify_data(&self, hs_hash: &[u8]) -> Vec<u8> { |
| let resumption_psk_binder_key = self.ks.derive_for_empty_hash(SecretKind::ResumptionPSKBinderKey); |
| self.ks.sign_verify_data(&resumption_psk_binder_key, hs_hash) |
| } |
| |
| pub fn into_handshake(mut self, secret: &[u8]) -> KeyScheduleHandshake { |
| self.ks.input_secret(secret); |
| KeyScheduleHandshake { |
| ks: self.ks, |
| current_client_traffic_secret: None, |
| current_server_traffic_secret: None, |
| } |
| } |
| } |
| |
| /// KeySchedule for skipping early data stage. No secrets can be extracted |
| /// (since there are none), but the handshake secret can be input. |
| pub struct KeyScheduleNonSecret { |
| ks: KeySchedule, |
| } |
| |
| impl KeyScheduleNonSecret { |
| pub fn new(algorithm: hkdf::Algorithm) -> KeyScheduleNonSecret { |
| KeyScheduleNonSecret { ks: KeySchedule::new_with_empty_secret(algorithm) } |
| } |
| |
| pub fn into_handshake(mut self, secret: &[u8]) -> KeyScheduleHandshake { |
| self.ks.input_secret(secret); |
| KeyScheduleHandshake { |
| ks: self.ks, |
| current_client_traffic_secret: None, |
| current_server_traffic_secret: None, |
| } |
| } |
| } |
| |
| /// KeySchedule during handshake. |
| pub struct KeyScheduleHandshake { |
| ks: KeySchedule, |
| current_client_traffic_secret: Option<hkdf::Prk>, |
| current_server_traffic_secret: Option<hkdf::Prk>, |
| } |
| |
| impl KeyScheduleHandshake { |
| pub fn client_handshake_traffic_secret(&mut self, hs_hash: &[u8], |
| key_log: &dyn KeyLog, |
| client_random: &[u8; 32]) -> hkdf::Prk { |
| let secret = self.ks.derive_logged_secret(SecretKind::ClientHandshakeTrafficSecret, |
| hs_hash, key_log, client_random); |
| self.current_client_traffic_secret = Some(secret.clone()); |
| secret |
| } |
| |
| pub fn server_handshake_traffic_secret(&mut self, hs_hash: &[u8], |
| key_log: &dyn KeyLog, |
| client_random: &[u8; 32]) -> hkdf::Prk { |
| let secret = self.ks.derive_logged_secret(SecretKind::ServerHandshakeTrafficSecret, |
| hs_hash, key_log, client_random); |
| self.current_server_traffic_secret = Some(secret.clone()); |
| secret |
| } |
| |
| pub fn sign_server_finish(&self, hs_hash: &[u8]) -> Vec<u8> { |
| self.ks.sign_finish(self.current_server_traffic_secret.as_ref().unwrap(), hs_hash) |
| } |
| |
| pub fn into_traffic_with_client_finished_pending(mut self) -> KeyScheduleTrafficWithClientFinishedPending { |
| self.ks.input_empty(); |
| KeyScheduleTrafficWithClientFinishedPending { |
| ks: self.ks, |
| handshake_client_traffic_secret: self.current_client_traffic_secret.unwrap(), |
| current_client_traffic_secret: None, |
| current_server_traffic_secret: None, |
| current_exporter_secret: None |
| } |
| } |
| } |
| |
| /// KeySchedule during traffic stage, retaining the ability to calculate the client's |
| /// finished verify_data, and incrementally generate the first traffic keys. |
| pub struct KeyScheduleTrafficWithClientFinishedPending { |
| ks: KeySchedule, |
| handshake_client_traffic_secret: hkdf::Prk, |
| current_client_traffic_secret: Option<hkdf::Prk>, |
| current_server_traffic_secret: Option<hkdf::Prk>, |
| current_exporter_secret: Option<hkdf::Prk>, |
| } |
| |
| impl KeyScheduleTrafficWithClientFinishedPending { |
| pub fn sign_client_finish(&self, hs_hash: &[u8]) -> Vec<u8> { |
| self.ks.sign_finish(&self.handshake_client_traffic_secret, hs_hash) |
| } |
| |
| pub fn server_application_traffic_secret(&mut self, hs_hash: &[u8], |
| key_log: &dyn KeyLog, |
| client_random: &[u8; 32]) -> hkdf::Prk { |
| let secret = self.ks.derive_logged_secret(SecretKind::ServerApplicationTrafficSecret, |
| hs_hash, key_log, client_random); |
| self.current_server_traffic_secret = Some(secret.clone()); |
| secret |
| } |
| |
| pub fn client_application_traffic_secret(&mut self, hs_hash: &[u8], |
| key_log: &dyn KeyLog, |
| client_random: &[u8; 32]) -> hkdf::Prk { |
| let secret = self.ks.derive_logged_secret(SecretKind::ClientApplicationTrafficSecret, |
| hs_hash, key_log, client_random); |
| self.current_client_traffic_secret = Some(secret.clone()); |
| secret |
| } |
| |
| pub fn exporter_master_secret(&mut self, hs_hash: &[u8], |
| key_log: &dyn KeyLog, |
| client_random: &[u8; 32]) { |
| let secret = self.ks.derive_logged_secret(SecretKind::ExporterMasterSecret, |
| hs_hash, key_log, client_random); |
| self.current_exporter_secret = Some(secret); |
| } |
| |
| pub fn into_traffic(self) -> KeyScheduleTraffic { |
| KeyScheduleTraffic { |
| ks: self.ks, |
| current_client_traffic_secret: self.current_client_traffic_secret.unwrap(), |
| current_server_traffic_secret: self.current_server_traffic_secret.unwrap(), |
| current_exporter_secret: self.current_exporter_secret.unwrap(), |
| } |
| } |
| } |
| |
| |
| /// KeySchedule during traffic stage. All traffic & exporter keys are guaranteed |
| /// to be available. |
| pub struct KeyScheduleTraffic { |
| ks: KeySchedule, |
| current_client_traffic_secret: hkdf::Prk, |
| current_server_traffic_secret: hkdf::Prk, |
| current_exporter_secret: hkdf::Prk, |
| } |
| |
| impl KeyScheduleTraffic { |
| pub fn next_server_application_traffic_secret(&mut self) -> hkdf::Prk { |
| let secret = self.ks.derive_next(&self.current_server_traffic_secret); |
| self.current_server_traffic_secret = secret.clone(); |
| secret |
| } |
| |
| pub fn next_client_application_traffic_secret(&mut self) -> hkdf::Prk { |
| let secret = self.ks.derive_next(&self.current_client_traffic_secret); |
| self.current_client_traffic_secret = secret.clone(); |
| secret |
| } |
| |
| pub fn resumption_master_secret_and_derive_ticket_psk(&self, hs_hash: &[u8], nonce: &[u8]) -> Vec<u8> { |
| let resumption_master_secret = self.ks.derive(self.ks.algorithm(), |
| SecretKind::ResumptionMasterSecret, |
| hs_hash); |
| self.ks.derive_ticket_psk(&resumption_master_secret, nonce) |
| } |
| |
| pub fn export_keying_material(&self, |
| out: &mut [u8], |
| label: &[u8], |
| context: Option<&[u8]>) -> Result<(), TLSError> { |
| self.ks.export_keying_material(&self.current_exporter_secret, |
| out, label, context) |
| } |
| } |
| |
| impl KeySchedule { |
| fn new(algorithm: hkdf::Algorithm, secret: &[u8]) -> KeySchedule { |
| let zeroes = [0u8; digest::MAX_OUTPUT_LEN]; |
| let zeroes = &zeroes[..algorithm.len()]; |
| let salt = hkdf::Salt::new(algorithm, &zeroes); |
| KeySchedule { |
| current: salt.extract(secret), |
| algorithm, |
| } |
| } |
| |
| #[inline] |
| fn algorithm(&self) -> hkdf::Algorithm { self.algorithm } |
| |
| fn new_with_empty_secret(algorithm: hkdf::Algorithm) -> KeySchedule { |
| let zeroes = [0u8; digest::MAX_OUTPUT_LEN]; |
| Self::new(algorithm, &zeroes[..algorithm.len()]) |
| } |
| |
| /// Input the empty secret. |
| fn input_empty(&mut self) { |
| let zeroes = [0u8; digest::MAX_OUTPUT_LEN]; |
| self.input_secret(&zeroes[..self.algorithm.len()]); |
| } |
| |
| /// Input the given secret. |
| fn input_secret(&mut self, secret: &[u8]) { |
| let salt: hkdf::Salt = self.derive_for_empty_hash(SecretKind::DerivedSecret); |
| self.current = salt.extract(secret); |
| } |
| |
| /// Derive a secret of given `kind`, using current handshake hash `hs_hash`. |
| fn derive<T, L>(&self, key_type: L, kind: SecretKind, hs_hash: &[u8]) -> T |
| where |
| T: for <'a> From<hkdf::Okm<'a, L>>, |
| L: hkdf::KeyType, |
| { |
| hkdf_expand(&self.current, key_type, kind.to_bytes(), hs_hash) |
| } |
| |
| fn derive_logged_secret(&self, kind: SecretKind, hs_hash: &[u8], |
| key_log: &dyn KeyLog, client_random: &[u8; 32]) |
| -> hkdf::Prk |
| { |
| let log_label = kind.log_label().expect("not a loggable secret"); |
| if key_log.will_log(log_label) { |
| let secret = self.derive::<PayloadU8, _>(PayloadU8Len(self.algorithm.len()), kind, hs_hash) |
| .into_inner(); |
| key_log.log(log_label, client_random, &secret); |
| } |
| self.derive(self.algorithm, kind, hs_hash) |
| } |
| |
| /// Derive a secret of given `kind` using the hash of the empty string |
| /// for the handshake hash. Useful only for |
| /// `SecretKind::ResumptionPSKBinderKey` and |
| /// `SecretKind::DerivedSecret`. |
| fn derive_for_empty_hash<T>(&self, kind: SecretKind) -> T |
| where |
| T: for <'a> From<hkdf::Okm<'a, hkdf::Algorithm>> |
| { |
| let digest_alg = self.algorithm.hmac_algorithm().digest_algorithm(); |
| let empty_hash = digest::digest(digest_alg, &[]); |
| self.derive(self.algorithm, kind, empty_hash.as_ref()) |
| } |
| |
| /// Sign the finished message consisting of `hs_hash` using a current |
| /// traffic secret. |
| fn sign_finish(&self, base_key: &hkdf::Prk, hs_hash: &[u8]) -> Vec<u8> { |
| self.sign_verify_data(base_key, hs_hash) |
| } |
| |
| /// Sign the finished message consisting of `hs_hash` using the key material |
| /// `base_key`. |
| fn sign_verify_data(&self, base_key: &hkdf::Prk, hs_hash: &[u8]) -> Vec<u8> { |
| let hmac_alg = self.algorithm.hmac_algorithm(); |
| let hmac_key = hkdf_expand(base_key, hmac_alg, b"finished", &[]); |
| hmac::sign(&hmac_key, hs_hash) |
| .as_ref() |
| .to_vec() |
| } |
| |
| /// Derive the next application traffic secret, returning it. |
| fn derive_next(&self, base_key: &hkdf::Prk) -> hkdf::Prk { |
| hkdf_expand(&base_key, self.algorithm, b"traffic upd", &[]) |
| } |
| |
| /// Derive the PSK to use given a resumption_master_secret and |
| /// ticket_nonce. |
| fn derive_ticket_psk(&self, rms: &hkdf::Prk, nonce: &[u8]) -> Vec<u8> { |
| let payload: PayloadU8 = hkdf_expand(rms, PayloadU8Len(self.algorithm.len()), b"resumption", nonce); |
| payload.into_inner() |
| } |
| |
| fn export_keying_material(&self, |
| current_exporter_secret: &hkdf::Prk, |
| out: &mut [u8], |
| label: &[u8], |
| context: Option<&[u8]>) -> Result<(), TLSError> { |
| let digest_alg = self.algorithm.hmac_algorithm().digest_algorithm(); |
| |
| let h_empty = digest::digest(digest_alg, &[]); |
| let secret: hkdf::Prk = |
| hkdf_expand(current_exporter_secret, self.algorithm, label, h_empty.as_ref()); |
| |
| let h_context = digest::digest(digest_alg, context.unwrap_or(&[])); |
| |
| // TODO: Test what happens when this fails |
| hkdf_expand_info(&secret, PayloadU8Len(out.len()), b"exporter", h_context.as_ref(), |
| |okm| okm.fill(out)) |
| .map_err(|_| TLSError::General("exporting too much".to_string())) |
| } |
| } |
| |
| pub(crate) fn hkdf_expand<T, L>(secret: &hkdf::Prk, key_type: L, label: &[u8], context: &[u8]) -> T |
| where |
| T: for <'a> From<hkdf::Okm<'a, L>>, |
| L: hkdf::KeyType, |
| { |
| hkdf_expand_info(secret, key_type, label, context, |okm| okm.into()) |
| } |
| |
| fn hkdf_expand_info<F, T, L>(secret: &hkdf::Prk, key_type: L, label: &[u8], context: &[u8], f: F) |
| -> T |
| where |
| F: for<'b> FnOnce(hkdf::Okm<'b, L>) -> T, |
| L: hkdf::KeyType |
| { |
| const LABEL_PREFIX: &[u8] = b"tls13 "; |
| |
| let output_len = u16::to_be_bytes(key_type.len() as u16); |
| let label_len = u8::to_be_bytes((LABEL_PREFIX.len() + label.len()) as u8); |
| let context_len = u8::to_be_bytes(context.len() as u8); |
| |
| let info = &[&output_len[..], &label_len[..], LABEL_PREFIX, label, &context_len[..], context]; |
| let okm = secret.expand(info, key_type).unwrap(); |
| |
| f(okm) |
| } |
| |
| pub(crate) struct PayloadU8Len(pub(crate) usize); |
| impl hkdf::KeyType for PayloadU8Len { |
| fn len(&self) -> usize { self.0 } |
| } |
| |
| impl From<hkdf::Okm<'_, PayloadU8Len>> for PayloadU8 { |
| fn from(okm: hkdf::Okm<PayloadU8Len>) -> Self { |
| let mut r = vec![0u8;okm.len().0]; |
| okm.fill(&mut r[..]).unwrap(); |
| PayloadU8::new(r) |
| } |
| } |
| |
| pub fn derive_traffic_key(secret: &hkdf::Prk, aead_algorithm: &'static aead::Algorithm) |
| -> aead::UnboundKey |
| { |
| hkdf_expand(secret, aead_algorithm, b"key", &[]) |
| } |
| |
| pub(crate) fn derive_traffic_iv(secret: &hkdf::Prk) -> Iv { |
| hkdf_expand(secret, IvLen, b"iv", &[]) |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::{KeySchedule, SecretKind, derive_traffic_key, derive_traffic_iv}; |
| use ring::{aead, hkdf}; |
| use crate::KeyLog; |
| |
| #[test] |
| fn test_vectors() { |
| /* These test vectors generated with OpenSSL. */ |
| let hs_start_hash = [ |
| 0xec, 0x14, 0x7a, 0x06, 0xde, 0xa3, 0xc8, 0x84, 0x6c, 0x02, 0xb2, 0x23, 0x8e, |
| 0x41, 0xbd, 0xdc, 0x9d, 0x89, 0xf9, 0xae, 0xa1, 0x7b, 0x5e, 0xfd, 0x4d, 0x74, |
| 0x82, 0xaf, 0x75, 0x88, 0x1c, 0x0a |
| ]; |
| |
| let hs_full_hash = [ |
| 0x75, 0x1a, 0x3d, 0x4a, 0x14, 0xdf, 0xab, 0xeb, 0x68, 0xe9, 0x2c, 0xa5, 0x91, |
| 0x8e, 0x24, 0x08, 0xb9, 0xbc, 0xb0, 0x74, 0x89, 0x82, 0xec, 0x9c, 0x32, 0x30, |
| 0xac, 0x30, 0xbb, 0xeb, 0x23, 0xe2 |
| ]; |
| |
| let ecdhe_secret = [ |
| 0xe7, 0xb8, 0xfe, 0xf8, 0x90, 0x3b, 0x52, 0x0c, 0xb9, 0xa1, 0x89, 0x71, 0xb6, |
| 0x9d, 0xd4, 0x5d, 0xca, 0x53, 0xce, 0x2f, 0x12, 0xbf, 0x3b, 0xef, 0x93, 0x15, |
| 0xe3, 0x12, 0x71, 0xdf, 0x4b, 0x40 |
| ]; |
| |
| let client_hts = [ |
| 0x61, 0x7b, 0x35, 0x07, 0x6b, 0x9d, 0x0e, 0x08, 0xcf, 0x73, 0x1d, 0x94, 0xa8, |
| 0x66, 0x14, 0x78, 0x41, 0x09, 0xef, 0x25, 0x55, 0x51, 0x92, 0x1d, 0xd4, 0x6e, |
| 0x04, 0x01, 0x35, 0xcf, 0x46, 0xab |
| ]; |
| |
| let client_hts_key = [ |
| 0x62, 0xd0, 0xdd, 0x00, 0xf6, 0x96, 0x19, 0xd3, 0xb8, 0x19, 0x3a, 0xb4, 0xa0, |
| 0x95, 0x85, 0xa7 |
| ]; |
| |
| let client_hts_iv = [ |
| 0xff, 0xf7, 0x5d, 0xf5, 0xad, 0x35, 0xd5, 0xcb, 0x3c, 0x53, 0xf3, 0xa9 |
| ]; |
| |
| let server_hts = [ |
| 0xfc, 0xf7, 0xdf, 0xe6, 0x4f, 0xa2, 0xc0, 0x4f, 0x62, 0x35, 0x38, 0x7f, 0x43, |
| 0x4e, 0x01, 0x42, 0x23, 0x36, 0xd9, 0xc0, 0x39, 0xde, 0x68, 0x47, 0xa0, 0xb9, |
| 0xdd, 0xcf, 0x29, 0xa8, 0x87, 0x59 |
| ]; |
| |
| let server_hts_key = [ |
| 0x04, 0x67, 0xf3, 0x16, 0xa8, 0x05, 0xb8, 0xc4, 0x97, 0xee, 0x67, 0x04, 0x7b, |
| 0xbc, 0xbc, 0x54 |
| ]; |
| |
| let server_hts_iv = [ |
| 0xde, 0x83, 0xa7, 0x3e, 0x9d, 0x81, 0x4b, 0x04, 0xc4, 0x8b, 0x78, 0x09 |
| ]; |
| |
| let client_ats = [ |
| 0xc1, 0x4a, 0x6d, 0x79, 0x76, 0xd8, 0x10, 0x2b, 0x5a, 0x0c, 0x99, 0x51, 0x49, |
| 0x3f, 0xee, 0x87, 0xdc, 0xaf, 0xf8, 0x2c, 0x24, 0xca, 0xb2, 0x14, 0xe8, 0xbe, |
| 0x71, 0xa8, 0x20, 0x6d, 0xbd, 0xa5 |
| ]; |
| |
| let client_ats_key = [ |
| 0xcc, 0x9f, 0x5f, 0x98, 0x0b, 0x5f, 0x10, 0x30, 0x6c, 0xba, 0xd7, 0xbe, 0x98, |
| 0xd7, 0x57, 0x2e |
| ]; |
| |
| let client_ats_iv = [ |
| 0xb8, 0x09, 0x29, 0xe8, 0xd0, 0x2c, 0x70, 0xf6, 0x11, 0x62, 0xed, 0x6b |
| ]; |
| |
| let server_ats = [ |
| 0x2c, 0x90, 0x77, 0x38, 0xd3, 0xf8, 0x37, 0x02, 0xd1, 0xe4, 0x59, 0x8f, 0x48, |
| 0x48, 0x53, 0x1d, 0x9f, 0x93, 0x65, 0x49, 0x1b, 0x9f, 0x7f, 0x52, 0xc8, 0x22, |
| 0x29, 0x0d, 0x4c, 0x23, 0x21, 0x92 |
| ]; |
| |
| let server_ats_key = [ |
| 0x0c, 0xb2, 0x95, 0x62, 0xd8, 0xd8, 0x8f, 0x48, 0xb0, 0x2c, 0xbf, 0xbe, 0xd7, |
| 0xe6, 0x2b, 0xb3 |
| ]; |
| |
| let server_ats_iv = [ |
| 0x0d, 0xb2, 0x8f, 0x98, 0x85, 0x86, 0xa1, 0xb7, 0xe4, 0xd5, 0xc6, 0x9c |
| ]; |
| |
| let hkdf = hkdf::HKDF_SHA256; |
| let mut ks = KeySchedule::new_with_empty_secret(hkdf); |
| ks.input_secret(&ecdhe_secret); |
| |
| assert_traffic_secret( |
| &ks, |
| SecretKind::ClientHandshakeTrafficSecret, |
| &hs_start_hash, |
| &client_hts, |
| &client_hts_key, |
| &client_hts_iv); |
| |
| assert_traffic_secret( |
| &ks, |
| SecretKind::ServerHandshakeTrafficSecret, |
| &hs_start_hash, |
| &server_hts, |
| &server_hts_key, |
| &server_hts_iv); |
| |
| ks.input_empty(); |
| |
| assert_traffic_secret( |
| &ks, |
| SecretKind::ClientApplicationTrafficSecret, |
| &hs_full_hash, |
| &client_ats, |
| &client_ats_key, |
| &client_ats_iv); |
| |
| assert_traffic_secret( |
| &ks, |
| SecretKind::ServerApplicationTrafficSecret, |
| &hs_full_hash, |
| &server_ats, |
| &server_ats_key, |
| &server_ats_iv); |
| } |
| |
| fn assert_traffic_secret( |
| ks: &KeySchedule, |
| kind: SecretKind, |
| hash: &[u8], |
| expected_traffic_secret: &[u8], |
| expected_key: &[u8], |
| expected_iv: &[u8], |
| ) { |
| struct Log<'a>(&'a [u8]); |
| impl KeyLog for Log<'_> { |
| fn log(&self, _label: &str, _client_random: &[u8], secret: &[u8]) { |
| assert_eq!(self.0, secret); |
| } |
| } |
| let log = Log(expected_traffic_secret); |
| let traffic_secret = ks.derive_logged_secret(kind, &hash, &log, &[0; 32]); |
| |
| // Since we can't test key equality, we test the output of sealing with the key instead. |
| let aead_alg = &aead::AES_128_GCM; |
| let key = derive_traffic_key(&traffic_secret, aead_alg); |
| let seal_output = seal_zeroes(key); |
| let expected_key = aead::UnboundKey::new(aead_alg, expected_key).unwrap(); |
| let expected_seal_output = seal_zeroes(expected_key); |
| assert_eq!(seal_output, expected_seal_output); |
| assert!(seal_output.len() >= 48); // Sanity check. |
| |
| let iv = derive_traffic_iv(&traffic_secret); |
| assert_eq!(iv.value(), expected_iv); |
| } |
| |
| fn seal_zeroes(key: aead::UnboundKey) -> Vec<u8> { |
| let key = aead::LessSafeKey::new(key); |
| let mut seal_output = vec![0; 32]; |
| key.seal_in_place_append_tag( |
| aead::Nonce::assume_unique_for_key([0; aead::NONCE_LEN]), |
| aead::Aad::empty(), |
| &mut seal_output) |
| .unwrap(); |
| seal_output |
| } |
| } |