we (web engine): Experimental web browser project to understand the limits of Claude
at js-bytecode 891 lines 30 kB view raw
1//! TLS 1.3 key schedule (RFC 8446 §7). 2//! 3//! Derives encryption keys, IVs, and Finished verify data from shared secrets 4//! using the HKDF-based key schedule defined in RFC 8446. 5 6use we_crypto::hkdf::{hkdf_expand, hkdf_extract}; 7use we_crypto::hmac::{HashFunction, Hmac}; 8use we_crypto::sha2::{Sha256, Sha384}; 9 10use super::record::CipherSuite; 11 12// --------------------------------------------------------------------------- 13// Hash abstraction for cipher suites 14// --------------------------------------------------------------------------- 15 16/// Trait linking a cipher suite to its hash function for the key schedule. 17trait KeyScheduleHash: HashFunction + Clone { 18 fn hash(data: &[u8]) -> Vec<u8>; 19 fn hash_len() -> usize; 20} 21 22impl KeyScheduleHash for Sha256 { 23 fn hash(data: &[u8]) -> Vec<u8> { 24 we_crypto::sha2::sha256(data).to_vec() 25 } 26 fn hash_len() -> usize { 27 32 28 } 29} 30 31impl KeyScheduleHash for Sha384 { 32 fn hash(data: &[u8]) -> Vec<u8> { 33 we_crypto::sha2::sha384(data).to_vec() 34 } 35 fn hash_len() -> usize { 36 48 37 } 38} 39 40// --------------------------------------------------------------------------- 41// HKDF-Expand-Label (RFC 8446 §7.1) 42// --------------------------------------------------------------------------- 43 44/// HKDF-Expand-Label as defined in RFC 8446 §7.1: 45/// 46/// ```text 47/// HKDF-Expand-Label(Secret, Label, Context, Length) = 48/// HKDF-Expand(Secret, HkdfLabel, Length) 49/// 50/// struct { 51/// uint16 length = Length; 52/// opaque label<7..255> = "tls13 " + Label; 53/// opaque context<0..255> = Context; 54/// } HkdfLabel; 55/// ``` 56fn hkdf_expand_label<H: HashFunction>( 57 secret: &[u8], 58 label: &[u8], 59 context: &[u8], 60 length: usize, 61) -> Vec<u8> { 62 let full_label = [b"tls13 ", label].concat(); 63 64 // Build HkdfLabel structure 65 let mut hkdf_label = Vec::with_capacity(2 + 1 + full_label.len() + 1 + context.len()); 66 hkdf_label.extend_from_slice(&(length as u16).to_be_bytes()); 67 hkdf_label.push(full_label.len() as u8); 68 hkdf_label.extend_from_slice(&full_label); 69 hkdf_label.push(context.len() as u8); 70 hkdf_label.extend_from_slice(context); 71 72 hkdf_expand::<H>(secret, &hkdf_label, length).expect("HKDF-Expand-Label length within bounds") 73} 74 75/// Derive-Secret as defined in RFC 8446 §7.1: 76/// 77/// ```text 78/// Derive-Secret(Secret, Label, Messages) = 79/// HKDF-Expand-Label(Secret, Label, Transcript-Hash(Messages), Hash.length) 80/// ``` 81fn derive_secret<H: KeyScheduleHash>( 82 secret: &[u8], 83 label: &[u8], 84 transcript_hash: &[u8], 85) -> Vec<u8> { 86 hkdf_expand_label::<H>(secret, label, transcript_hash, H::hash_len()) 87} 88 89// --------------------------------------------------------------------------- 90// Transcript hash 91// --------------------------------------------------------------------------- 92 93/// Running transcript hash over handshake messages. 94#[derive(Clone)] 95pub struct TranscriptHash { 96 suite: CipherSuite, 97 sha256: Option<Sha256>, 98 sha384: Option<Sha384>, 99} 100 101impl TranscriptHash { 102 /// Create a new transcript hash for the given cipher suite. 103 pub fn new(suite: CipherSuite) -> Self { 104 match suite { 105 CipherSuite::Aes128Gcm | CipherSuite::Chacha20Poly1305 => Self { 106 suite, 107 sha256: Some(Sha256::new()), 108 sha384: None, 109 }, 110 CipherSuite::Aes256Gcm => Self { 111 suite, 112 sha256: None, 113 sha384: Some(Sha384::new()), 114 }, 115 } 116 } 117 118 /// Update the transcript with a handshake message. 119 pub fn update(&mut self, data: &[u8]) { 120 if let Some(h) = &mut self.sha256 { 121 h.update(data); 122 } 123 if let Some(h) = &mut self.sha384 { 124 h.update(data); 125 } 126 } 127 128 /// Get the current transcript hash value (does not consume the hasher). 129 pub fn current_hash(&self) -> Vec<u8> { 130 match self.suite { 131 CipherSuite::Aes128Gcm | CipherSuite::Chacha20Poly1305 => { 132 self.sha256.clone().unwrap().finalize().to_vec() 133 } 134 CipherSuite::Aes256Gcm => self.sha384.clone().unwrap().finalize().to_vec(), 135 } 136 } 137 138 /// Hash length in bytes. 139 pub fn hash_len(&self) -> usize { 140 match self.suite { 141 CipherSuite::Aes128Gcm | CipherSuite::Chacha20Poly1305 => 32, 142 CipherSuite::Aes256Gcm => 48, 143 } 144 } 145} 146 147// --------------------------------------------------------------------------- 148// Traffic keys 149// --------------------------------------------------------------------------- 150 151/// Derived traffic encryption key and IV. 152#[derive(Debug, Clone, PartialEq, Eq)] 153pub struct TrafficKeys { 154 pub key: Vec<u8>, 155 pub iv: Vec<u8>, 156} 157 158// --------------------------------------------------------------------------- 159// Key schedule (RFC 8446 §7.1) 160// --------------------------------------------------------------------------- 161 162/// TLS 1.3 key schedule state machine. 163/// 164/// Progresses through: Early -> Handshake -> Master, deriving traffic secrets 165/// at each stage. 166pub struct KeySchedule { 167 suite: CipherSuite, 168 early_secret: Vec<u8>, 169 handshake_secret: Option<Vec<u8>>, 170 master_secret: Option<Vec<u8>>, 171 client_handshake_traffic_secret: Option<Vec<u8>>, 172 server_handshake_traffic_secret: Option<Vec<u8>>, 173 client_app_traffic_secret: Option<Vec<u8>>, 174 server_app_traffic_secret: Option<Vec<u8>>, 175} 176 177impl KeySchedule { 178 /// Create a new key schedule, computing the Early Secret. 179 /// 180 /// For TLS 1.3 without PSK, pass `None` for `psk` (uses a zero-filled key). 181 pub fn new(suite: CipherSuite, psk: Option<&[u8]>) -> Self { 182 let hash_len = hash_len_for_suite(suite); 183 let zero_key = vec![0u8; hash_len]; 184 let psk = psk.unwrap_or(&zero_key); 185 let salt = vec![0u8; hash_len]; 186 187 let early_secret = extract_for_suite(suite, &salt, psk); 188 189 Self { 190 suite, 191 early_secret, 192 handshake_secret: None, 193 master_secret: None, 194 client_handshake_traffic_secret: None, 195 server_handshake_traffic_secret: None, 196 client_app_traffic_secret: None, 197 server_app_traffic_secret: None, 198 } 199 } 200 201 /// Derive handshake secrets from the shared ECDHE secret and transcript hash. 202 /// 203 /// The transcript hash should cover ClientHello...ServerHello. 204 pub fn derive_handshake_secrets(&mut self, shared_secret: &[u8], transcript_hash: &[u8]) { 205 let derived = self.derive_secret_inner(b"derived", &empty_hash(self.suite)); 206 let handshake_secret = extract_for_suite(self.suite, &derived, shared_secret); 207 208 self.client_handshake_traffic_secret = 209 Some(self.derive_secret_from(&handshake_secret, b"c hs traffic", transcript_hash)); 210 self.server_handshake_traffic_secret = 211 Some(self.derive_secret_from(&handshake_secret, b"s hs traffic", transcript_hash)); 212 213 self.handshake_secret = Some(handshake_secret); 214 } 215 216 /// Derive application traffic secrets from the transcript hash. 217 /// 218 /// The transcript hash should cover ClientHello...server Finished. 219 pub fn derive_app_secrets(&mut self, transcript_hash: &[u8]) { 220 let hs_secret = self 221 .handshake_secret 222 .as_ref() 223 .expect("must call derive_handshake_secrets first"); 224 225 let derived = 226 derive_secret_for_suite(self.suite, hs_secret, b"derived", &empty_hash(self.suite)); 227 let zero_ikm = vec![0u8; hash_len_for_suite(self.suite)]; 228 let master_secret = extract_for_suite(self.suite, &derived, &zero_ikm); 229 230 self.client_app_traffic_secret = Some(derive_secret_for_suite( 231 self.suite, 232 &master_secret, 233 b"c ap traffic", 234 transcript_hash, 235 )); 236 self.server_app_traffic_secret = Some(derive_secret_for_suite( 237 self.suite, 238 &master_secret, 239 b"s ap traffic", 240 transcript_hash, 241 )); 242 243 self.master_secret = Some(master_secret); 244 } 245 246 /// Get the client handshake traffic keys. 247 pub fn client_handshake_keys(&self) -> TrafficKeys { 248 let secret = self 249 .client_handshake_traffic_secret 250 .as_ref() 251 .expect("handshake secrets not derived"); 252 self.derive_traffic_keys(secret) 253 } 254 255 /// Get the server handshake traffic keys. 256 pub fn server_handshake_keys(&self) -> TrafficKeys { 257 let secret = self 258 .server_handshake_traffic_secret 259 .as_ref() 260 .expect("handshake secrets not derived"); 261 self.derive_traffic_keys(secret) 262 } 263 264 /// Get the client application traffic keys. 265 pub fn client_app_keys(&self) -> TrafficKeys { 266 let secret = self 267 .client_app_traffic_secret 268 .as_ref() 269 .expect("app secrets not derived"); 270 self.derive_traffic_keys(secret) 271 } 272 273 /// Get the server application traffic keys. 274 pub fn server_app_keys(&self) -> TrafficKeys { 275 let secret = self 276 .server_app_traffic_secret 277 .as_ref() 278 .expect("app secrets not derived"); 279 self.derive_traffic_keys(secret) 280 } 281 282 /// Compute the Finished verify data (RFC 8446 §4.4.4). 283 /// 284 /// `base_key` is the handshake traffic secret for the sender. 285 /// `transcript_hash` is the hash of all handshake messages up to (but not 286 /// including) the Finished message. 287 pub fn compute_finished_verify_data(&self, base_key: &[u8], transcript_hash: &[u8]) -> Vec<u8> { 288 let hash_len = hash_len_for_suite(self.suite); 289 let finished_key = expand_label_for_suite(self.suite, base_key, b"finished", &[], hash_len); 290 291 // HMAC(finished_key, transcript_hash) 292 hmac_for_suite(self.suite, &finished_key, transcript_hash) 293 } 294 295 /// Get the client handshake traffic secret. 296 pub fn client_handshake_traffic_secret(&self) -> Option<&[u8]> { 297 self.client_handshake_traffic_secret.as_deref() 298 } 299 300 /// Get the server handshake traffic secret. 301 pub fn server_handshake_traffic_secret(&self) -> Option<&[u8]> { 302 self.server_handshake_traffic_secret.as_deref() 303 } 304 305 /// Get the client application traffic secret. 306 pub fn client_app_traffic_secret(&self) -> Option<&[u8]> { 307 self.client_app_traffic_secret.as_deref() 308 } 309 310 /// Get the server application traffic secret. 311 pub fn server_app_traffic_secret(&self) -> Option<&[u8]> { 312 self.server_app_traffic_secret.as_deref() 313 } 314 315 /// Get the early secret. 316 pub fn early_secret(&self) -> &[u8] { 317 &self.early_secret 318 } 319 320 /// Get the handshake secret. 321 pub fn handshake_secret(&self) -> Option<&[u8]> { 322 self.handshake_secret.as_deref() 323 } 324 325 /// Get the master secret. 326 pub fn master_secret(&self) -> Option<&[u8]> { 327 self.master_secret.as_deref() 328 } 329 330 /// Get the cipher suite. 331 pub fn cipher_suite(&self) -> CipherSuite { 332 self.suite 333 } 334 335 // -- internal helpers -- 336 337 fn derive_secret_inner(&self, label: &[u8], transcript_hash: &[u8]) -> Vec<u8> { 338 derive_secret_for_suite(self.suite, &self.early_secret, label, transcript_hash) 339 } 340 341 fn derive_secret_from(&self, secret: &[u8], label: &[u8], transcript_hash: &[u8]) -> Vec<u8> { 342 derive_secret_for_suite(self.suite, secret, label, transcript_hash) 343 } 344 345 fn derive_traffic_keys(&self, secret: &[u8]) -> TrafficKeys { 346 let key_len = self.suite.key_len(); 347 let iv_len = self.suite.iv_len(); 348 let key = expand_label_for_suite(self.suite, secret, b"key", &[], key_len); 349 let iv = expand_label_for_suite(self.suite, secret, b"iv", &[], iv_len); 350 TrafficKeys { key, iv } 351 } 352} 353 354// --------------------------------------------------------------------------- 355// Suite-dispatched helpers 356// --------------------------------------------------------------------------- 357 358fn hash_len_for_suite(suite: CipherSuite) -> usize { 359 match suite { 360 CipherSuite::Aes128Gcm | CipherSuite::Chacha20Poly1305 => 32, 361 CipherSuite::Aes256Gcm => 48, 362 } 363} 364 365fn empty_hash(suite: CipherSuite) -> Vec<u8> { 366 match suite { 367 CipherSuite::Aes128Gcm | CipherSuite::Chacha20Poly1305 => Sha256::hash(&[]), 368 CipherSuite::Aes256Gcm => Sha384::hash(&[]), 369 } 370} 371 372fn extract_for_suite(suite: CipherSuite, salt: &[u8], ikm: &[u8]) -> Vec<u8> { 373 match suite { 374 CipherSuite::Aes128Gcm | CipherSuite::Chacha20Poly1305 => hkdf_extract::<Sha256>(salt, ikm), 375 CipherSuite::Aes256Gcm => hkdf_extract::<Sha384>(salt, ikm), 376 } 377} 378 379fn expand_label_for_suite( 380 suite: CipherSuite, 381 secret: &[u8], 382 label: &[u8], 383 context: &[u8], 384 length: usize, 385) -> Vec<u8> { 386 match suite { 387 CipherSuite::Aes128Gcm | CipherSuite::Chacha20Poly1305 => { 388 hkdf_expand_label::<Sha256>(secret, label, context, length) 389 } 390 CipherSuite::Aes256Gcm => hkdf_expand_label::<Sha384>(secret, label, context, length), 391 } 392} 393 394fn derive_secret_for_suite( 395 suite: CipherSuite, 396 secret: &[u8], 397 label: &[u8], 398 transcript_hash: &[u8], 399) -> Vec<u8> { 400 match suite { 401 CipherSuite::Aes128Gcm | CipherSuite::Chacha20Poly1305 => { 402 derive_secret::<Sha256>(secret, label, transcript_hash) 403 } 404 CipherSuite::Aes256Gcm => derive_secret::<Sha384>(secret, label, transcript_hash), 405 } 406} 407 408fn hmac_for_suite(suite: CipherSuite, key: &[u8], data: &[u8]) -> Vec<u8> { 409 match suite { 410 CipherSuite::Aes128Gcm | CipherSuite::Chacha20Poly1305 => { 411 let mut hmac = Hmac::<Sha256>::new(key); 412 hmac.update(data); 413 hmac.finalize() 414 } 415 CipherSuite::Aes256Gcm => { 416 let mut hmac = Hmac::<Sha384>::new(key); 417 hmac.update(data); 418 hmac.finalize() 419 } 420 } 421} 422 423// --------------------------------------------------------------------------- 424// Tests 425// --------------------------------------------------------------------------- 426 427#[cfg(test)] 428mod tests { 429 use super::*; 430 431 // -- HKDF-Expand-Label tests -- 432 433 #[test] 434 fn hkdf_expand_label_produces_correct_length() { 435 let secret = vec![0x42u8; 32]; 436 let result = hkdf_expand_label::<Sha256>(&secret, b"key", &[], 16); 437 assert_eq!(result.len(), 16); 438 } 439 440 #[test] 441 fn hkdf_expand_label_produces_correct_length_48() { 442 let secret = vec![0x42u8; 48]; 443 let result = hkdf_expand_label::<Sha384>(&secret, b"key", &[], 32); 444 assert_eq!(result.len(), 32); 445 } 446 447 #[test] 448 fn hkdf_expand_label_with_context() { 449 let secret = vec![0x42u8; 32]; 450 let ctx = b"some context data"; 451 let result = hkdf_expand_label::<Sha256>(&secret, b"iv", ctx, 12); 452 assert_eq!(result.len(), 12); 453 } 454 455 #[test] 456 fn hkdf_expand_label_different_labels_different_output() { 457 let secret = vec![0x42u8; 32]; 458 let r1 = hkdf_expand_label::<Sha256>(&secret, b"key", &[], 16); 459 let r2 = hkdf_expand_label::<Sha256>(&secret, b"iv", &[], 16); 460 assert_ne!(r1, r2); 461 } 462 463 #[test] 464 fn hkdf_expand_label_different_contexts_different_output() { 465 let secret = vec![0x42u8; 32]; 466 let r1 = hkdf_expand_label::<Sha256>(&secret, b"key", b"ctx1", 16); 467 let r2 = hkdf_expand_label::<Sha256>(&secret, b"key", b"ctx2", 16); 468 assert_ne!(r1, r2); 469 } 470 471 // -- Transcript hash tests -- 472 473 #[test] 474 fn transcript_hash_sha256_empty() { 475 let th = TranscriptHash::new(CipherSuite::Aes128Gcm); 476 let hash = th.current_hash(); 477 assert_eq!(hash.len(), 32); 478 // SHA-256 of empty string 479 let expected = we_crypto::sha2::sha256(&[]); 480 assert_eq!(hash, expected.to_vec()); 481 } 482 483 #[test] 484 fn transcript_hash_sha384_empty() { 485 let th = TranscriptHash::new(CipherSuite::Aes256Gcm); 486 let hash = th.current_hash(); 487 assert_eq!(hash.len(), 48); 488 let expected = we_crypto::sha2::sha384(&[]); 489 assert_eq!(hash, expected.to_vec()); 490 } 491 492 #[test] 493 fn transcript_hash_incremental() { 494 let mut th = TranscriptHash::new(CipherSuite::Aes128Gcm); 495 th.update(b"hello"); 496 th.update(b" world"); 497 let hash = th.current_hash(); 498 499 let expected = we_crypto::sha2::sha256(b"hello world"); 500 assert_eq!(hash, expected.to_vec()); 501 } 502 503 #[test] 504 fn transcript_hash_chacha_uses_sha256() { 505 let th = TranscriptHash::new(CipherSuite::Chacha20Poly1305); 506 assert_eq!(th.hash_len(), 32); 507 let hash = th.current_hash(); 508 assert_eq!(hash.len(), 32); 509 } 510 511 #[test] 512 fn transcript_hash_clone_independence() { 513 let mut th = TranscriptHash::new(CipherSuite::Aes128Gcm); 514 th.update(b"first"); 515 let th_clone = th.clone(); 516 th.update(b"second"); 517 518 let hash1 = th.current_hash(); 519 let hash2 = th_clone.current_hash(); 520 assert_ne!(hash1, hash2); 521 } 522 523 // -- Key schedule tests -- 524 525 #[test] 526 fn key_schedule_early_secret_no_psk() { 527 let ks = KeySchedule::new(CipherSuite::Aes128Gcm, None); 528 assert_eq!(ks.early_secret().len(), 32); 529 } 530 531 #[test] 532 fn key_schedule_early_secret_with_psk() { 533 let psk = vec![0xABu8; 32]; 534 let ks1 = KeySchedule::new(CipherSuite::Aes128Gcm, Some(&psk)); 535 let ks2 = KeySchedule::new(CipherSuite::Aes128Gcm, None); 536 // Different PSK should produce different early secret 537 assert_ne!(ks1.early_secret(), ks2.early_secret()); 538 } 539 540 #[test] 541 fn key_schedule_early_secret_aes256() { 542 let ks = KeySchedule::new(CipherSuite::Aes256Gcm, None); 543 assert_eq!(ks.early_secret().len(), 48); 544 } 545 546 #[test] 547 fn key_schedule_early_secret_chacha() { 548 let ks = KeySchedule::new(CipherSuite::Chacha20Poly1305, None); 549 assert_eq!(ks.early_secret().len(), 32); 550 } 551 552 #[test] 553 fn key_schedule_derive_handshake_secrets() { 554 let mut ks = KeySchedule::new(CipherSuite::Aes128Gcm, None); 555 let shared_secret = vec![0x55u8; 32]; 556 let transcript = we_crypto::sha2::sha256(b"ClientHello...ServerHello"); 557 558 ks.derive_handshake_secrets(&shared_secret, &transcript); 559 560 assert!(ks.handshake_secret().is_some()); 561 assert!(ks.client_handshake_traffic_secret().is_some()); 562 assert!(ks.server_handshake_traffic_secret().is_some()); 563 assert_eq!(ks.client_handshake_traffic_secret().unwrap().len(), 32); 564 assert_eq!(ks.server_handshake_traffic_secret().unwrap().len(), 32); 565 // Client and server traffic secrets must differ 566 assert_ne!( 567 ks.client_handshake_traffic_secret(), 568 ks.server_handshake_traffic_secret() 569 ); 570 } 571 572 #[test] 573 fn key_schedule_derive_app_secrets() { 574 let mut ks = KeySchedule::new(CipherSuite::Aes128Gcm, None); 575 let shared_secret = vec![0x55u8; 32]; 576 let hs_hash = we_crypto::sha2::sha256(b"handshake transcript"); 577 let app_hash = we_crypto::sha2::sha256(b"full transcript including Finished"); 578 579 ks.derive_handshake_secrets(&shared_secret, &hs_hash); 580 ks.derive_app_secrets(&app_hash); 581 582 assert!(ks.master_secret().is_some()); 583 assert!(ks.client_app_traffic_secret().is_some()); 584 assert!(ks.server_app_traffic_secret().is_some()); 585 assert_ne!( 586 ks.client_app_traffic_secret(), 587 ks.server_app_traffic_secret() 588 ); 589 } 590 591 #[test] 592 fn key_schedule_traffic_keys_correct_lengths_aes128() { 593 let mut ks = KeySchedule::new(CipherSuite::Aes128Gcm, None); 594 let shared_secret = vec![0x55u8; 32]; 595 let transcript = we_crypto::sha2::sha256(b"test"); 596 597 ks.derive_handshake_secrets(&shared_secret, &transcript); 598 599 let keys = ks.client_handshake_keys(); 600 assert_eq!(keys.key.len(), 16); // AES-128 key 601 assert_eq!(keys.iv.len(), 12); // 12-byte IV 602 } 603 604 #[test] 605 fn key_schedule_traffic_keys_correct_lengths_aes256() { 606 let mut ks = KeySchedule::new(CipherSuite::Aes256Gcm, None); 607 let shared_secret = vec![0x55u8; 48]; 608 let transcript = we_crypto::sha2::sha384(b"test"); 609 610 ks.derive_handshake_secrets(&shared_secret, &transcript); 611 612 let keys = ks.client_handshake_keys(); 613 assert_eq!(keys.key.len(), 32); // AES-256 key 614 assert_eq!(keys.iv.len(), 12); 615 } 616 617 #[test] 618 fn key_schedule_traffic_keys_correct_lengths_chacha() { 619 let mut ks = KeySchedule::new(CipherSuite::Chacha20Poly1305, None); 620 let shared_secret = vec![0x55u8; 32]; 621 let transcript = we_crypto::sha2::sha256(b"test"); 622 623 ks.derive_handshake_secrets(&shared_secret, &transcript); 624 625 let keys = ks.server_handshake_keys(); 626 assert_eq!(keys.key.len(), 32); // ChaCha20 key 627 assert_eq!(keys.iv.len(), 12); 628 } 629 630 #[test] 631 fn key_schedule_client_server_keys_differ() { 632 let mut ks = KeySchedule::new(CipherSuite::Aes128Gcm, None); 633 let shared_secret = vec![0x55u8; 32]; 634 let transcript = we_crypto::sha2::sha256(b"test"); 635 636 ks.derive_handshake_secrets(&shared_secret, &transcript); 637 638 let client = ks.client_handshake_keys(); 639 let server = ks.server_handshake_keys(); 640 assert_ne!(client.key, server.key); 641 assert_ne!(client.iv, server.iv); 642 } 643 644 #[test] 645 fn key_schedule_app_keys_differ_from_handshake_keys() { 646 let mut ks = KeySchedule::new(CipherSuite::Aes128Gcm, None); 647 let shared_secret = vec![0x55u8; 32]; 648 let hs_hash = we_crypto::sha2::sha256(b"hs"); 649 let app_hash = we_crypto::sha2::sha256(b"app"); 650 651 ks.derive_handshake_secrets(&shared_secret, &hs_hash); 652 let hs_keys = ks.client_handshake_keys(); 653 654 ks.derive_app_secrets(&app_hash); 655 let app_keys = ks.client_app_keys(); 656 657 assert_ne!(hs_keys.key, app_keys.key); 658 assert_ne!(hs_keys.iv, app_keys.iv); 659 } 660 661 #[test] 662 fn key_schedule_different_shared_secrets_produce_different_keys() { 663 let transcript = we_crypto::sha2::sha256(b"test"); 664 665 let mut ks1 = KeySchedule::new(CipherSuite::Aes128Gcm, None); 666 ks1.derive_handshake_secrets(&vec![0x11u8; 32], &transcript); 667 668 let mut ks2 = KeySchedule::new(CipherSuite::Aes128Gcm, None); 669 ks2.derive_handshake_secrets(&vec![0x22u8; 32], &transcript); 670 671 assert_ne!( 672 ks1.client_handshake_keys().key, 673 ks2.client_handshake_keys().key 674 ); 675 } 676 677 #[test] 678 fn key_schedule_different_transcripts_produce_different_keys() { 679 let shared_secret = vec![0x55u8; 32]; 680 681 let mut ks1 = KeySchedule::new(CipherSuite::Aes128Gcm, None); 682 ks1.derive_handshake_secrets(&shared_secret, &we_crypto::sha2::sha256(b"transcript1")); 683 684 let mut ks2 = KeySchedule::new(CipherSuite::Aes128Gcm, None); 685 ks2.derive_handshake_secrets(&shared_secret, &we_crypto::sha2::sha256(b"transcript2")); 686 687 assert_ne!( 688 ks1.client_handshake_keys().key, 689 ks2.client_handshake_keys().key 690 ); 691 } 692 693 // -- Finished verify data tests -- 694 695 #[test] 696 fn finished_verify_data_correct_length() { 697 let mut ks = KeySchedule::new(CipherSuite::Aes128Gcm, None); 698 let shared_secret = vec![0x55u8; 32]; 699 let transcript = we_crypto::sha2::sha256(b"test"); 700 ks.derive_handshake_secrets(&shared_secret, &transcript); 701 702 let verify_data = ks.compute_finished_verify_data( 703 ks.client_handshake_traffic_secret().unwrap(), 704 &transcript, 705 ); 706 assert_eq!(verify_data.len(), 32); 707 } 708 709 #[test] 710 fn finished_verify_data_correct_length_384() { 711 let mut ks = KeySchedule::new(CipherSuite::Aes256Gcm, None); 712 let shared_secret = vec![0x55u8; 48]; 713 let transcript = we_crypto::sha2::sha384(b"test"); 714 ks.derive_handshake_secrets(&shared_secret, &transcript); 715 716 let verify_data = ks.compute_finished_verify_data( 717 ks.client_handshake_traffic_secret().unwrap(), 718 &transcript, 719 ); 720 assert_eq!(verify_data.len(), 48); 721 } 722 723 #[test] 724 fn finished_verify_data_deterministic() { 725 let mut ks = KeySchedule::new(CipherSuite::Aes128Gcm, None); 726 let shared_secret = vec![0x55u8; 32]; 727 let transcript = we_crypto::sha2::sha256(b"test"); 728 ks.derive_handshake_secrets(&shared_secret, &transcript); 729 730 let secret = ks.client_handshake_traffic_secret().unwrap().to_vec(); 731 let vd1 = ks.compute_finished_verify_data(&secret, &transcript); 732 let vd2 = ks.compute_finished_verify_data(&secret, &transcript); 733 assert_eq!(vd1, vd2); 734 } 735 736 #[test] 737 fn finished_verify_data_different_for_client_server() { 738 let mut ks = KeySchedule::new(CipherSuite::Aes128Gcm, None); 739 let shared_secret = vec![0x55u8; 32]; 740 let transcript = we_crypto::sha2::sha256(b"test"); 741 ks.derive_handshake_secrets(&shared_secret, &transcript); 742 743 let client_vd = ks.compute_finished_verify_data( 744 ks.client_handshake_traffic_secret().unwrap(), 745 &transcript, 746 ); 747 let server_vd = ks.compute_finished_verify_data( 748 ks.server_handshake_traffic_secret().unwrap(), 749 &transcript, 750 ); 751 assert_ne!(client_vd, server_vd); 752 } 753 754 // -- RFC 8446 §A test vector (SHA-256 suite) -- 755 // Using constructed vectors to verify the key schedule structure is correct. 756 757 #[test] 758 fn key_schedule_full_flow_aes128() { 759 let mut ks = KeySchedule::new(CipherSuite::Aes128Gcm, None); 760 761 // Simulate handshake 762 let mut transcript = TranscriptHash::new(CipherSuite::Aes128Gcm); 763 transcript.update(b"ClientHello"); 764 transcript.update(b"ServerHello"); 765 let hs_hash = transcript.current_hash(); 766 767 let shared_secret = vec![0x01u8; 32]; // ECDHE shared secret 768 ks.derive_handshake_secrets(&shared_secret, &hs_hash); 769 770 // Get handshake keys 771 let c_hs_keys = ks.client_handshake_keys(); 772 let s_hs_keys = ks.server_handshake_keys(); 773 assert_eq!(c_hs_keys.key.len(), 16); 774 assert_eq!(s_hs_keys.key.len(), 16); 775 776 // Continue transcript 777 transcript.update(b"EncryptedExtensions"); 778 transcript.update(b"Certificate"); 779 transcript.update(b"CertificateVerify"); 780 781 // Server Finished 782 let server_finished_hash = transcript.current_hash(); 783 let s_vd = ks.compute_finished_verify_data( 784 ks.server_handshake_traffic_secret().unwrap(), 785 &server_finished_hash, 786 ); 787 assert_eq!(s_vd.len(), 32); 788 789 transcript.update(b"Finished"); 790 let app_hash = transcript.current_hash(); 791 792 // Derive application secrets 793 ks.derive_app_secrets(&app_hash); 794 let c_app_keys = ks.client_app_keys(); 795 let s_app_keys = ks.server_app_keys(); 796 assert_eq!(c_app_keys.key.len(), 16); 797 assert_eq!(s_app_keys.key.len(), 16); 798 assert_ne!(c_app_keys.key, s_app_keys.key); 799 } 800 801 #[test] 802 fn key_schedule_full_flow_aes256() { 803 let mut ks = KeySchedule::new(CipherSuite::Aes256Gcm, None); 804 let mut transcript = TranscriptHash::new(CipherSuite::Aes256Gcm); 805 transcript.update(b"ClientHello"); 806 transcript.update(b"ServerHello"); 807 808 let shared_secret = vec![0x01u8; 48]; 809 ks.derive_handshake_secrets(&shared_secret, &transcript.current_hash()); 810 811 let keys = ks.client_handshake_keys(); 812 assert_eq!(keys.key.len(), 32); 813 assert_eq!(keys.iv.len(), 12); 814 815 transcript.update(b"rest of handshake"); 816 ks.derive_app_secrets(&transcript.current_hash()); 817 818 let app_keys = ks.client_app_keys(); 819 assert_eq!(app_keys.key.len(), 32); 820 } 821 822 #[test] 823 fn key_schedule_full_flow_chacha20() { 824 let mut ks = KeySchedule::new(CipherSuite::Chacha20Poly1305, None); 825 let mut transcript = TranscriptHash::new(CipherSuite::Chacha20Poly1305); 826 transcript.update(b"ClientHello"); 827 transcript.update(b"ServerHello"); 828 829 let shared_secret = vec![0x01u8; 32]; 830 ks.derive_handshake_secrets(&shared_secret, &transcript.current_hash()); 831 832 let keys = ks.server_handshake_keys(); 833 assert_eq!(keys.key.len(), 32); 834 assert_eq!(keys.iv.len(), 12); 835 836 transcript.update(b"rest of handshake"); 837 ks.derive_app_secrets(&transcript.current_hash()); 838 839 let app_keys = ks.server_app_keys(); 840 assert_eq!(app_keys.key.len(), 32); 841 } 842 843 // -- Derive-Secret tests -- 844 845 #[test] 846 fn derive_secret_different_labels() { 847 let secret = vec![0x42u8; 32]; 848 let ctx = we_crypto::sha2::sha256(b"context"); 849 let s1 = derive_secret::<Sha256>(&secret, b"label1", &ctx); 850 let s2 = derive_secret::<Sha256>(&secret, b"label2", &ctx); 851 assert_ne!(s1, s2); 852 } 853 854 #[test] 855 fn derive_secret_sha384() { 856 let secret = vec![0x42u8; 48]; 857 let ctx = we_crypto::sha2::sha384(b"context"); 858 let result = derive_secret::<Sha384>(&secret, b"test", &ctx); 859 assert_eq!(result.len(), 48); 860 } 861 862 // -- Helper function tests -- 863 864 #[test] 865 fn hash_len_for_suite_correct() { 866 assert_eq!(hash_len_for_suite(CipherSuite::Aes128Gcm), 32); 867 assert_eq!(hash_len_for_suite(CipherSuite::Aes256Gcm), 48); 868 assert_eq!(hash_len_for_suite(CipherSuite::Chacha20Poly1305), 32); 869 } 870 871 #[test] 872 fn empty_hash_correct_length() { 873 assert_eq!(empty_hash(CipherSuite::Aes128Gcm).len(), 32); 874 assert_eq!(empty_hash(CipherSuite::Aes256Gcm).len(), 48); 875 assert_eq!(empty_hash(CipherSuite::Chacha20Poly1305).len(), 32); 876 } 877 878 #[test] 879 fn empty_hash_matches_sha256_empty() { 880 let result = empty_hash(CipherSuite::Aes128Gcm); 881 let expected = we_crypto::sha2::sha256(&[]).to_vec(); 882 assert_eq!(result, expected); 883 } 884 885 #[test] 886 fn empty_hash_matches_sha384_empty() { 887 let result = empty_hash(CipherSuite::Aes256Gcm); 888 let expected = we_crypto::sha2::sha384(&[]).to_vec(); 889 assert_eq!(result, expected); 890 } 891}