we (web engine): Experimental web browser project to understand the limits of Claude
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}