1 | //! Key schedule maintenance for TLS1.3 |
2 | |
3 | use alloc::boxed::Box; |
4 | use alloc::string::ToString; |
5 | |
6 | use crate::common_state::{CommonState, Side}; |
7 | use crate::crypto::cipher::{AeadKey, Iv, MessageDecrypter, Tls13AeadAlgorithm}; |
8 | use crate::crypto::tls13::{Hkdf, HkdfExpander, OkmBlock, OutputLengthError, expand}; |
9 | use crate::crypto::{SharedSecret, hash, hmac}; |
10 | use crate::error::Error; |
11 | use crate::msgs::message::Message; |
12 | use crate::suites::PartiallyExtractedSecrets; |
13 | use crate::{KeyLog, Tls13CipherSuite, quic}; |
14 | |
15 | /// The kinds of secret we can extract from `KeySchedule`. |
16 | #[derive (Debug, Clone, Copy, PartialEq)] |
17 | enum SecretKind { |
18 | ResumptionPskBinderKey, |
19 | ClientEarlyTrafficSecret, |
20 | ClientHandshakeTrafficSecret, |
21 | ServerHandshakeTrafficSecret, |
22 | ClientApplicationTrafficSecret, |
23 | ServerApplicationTrafficSecret, |
24 | ExporterMasterSecret, |
25 | ResumptionMasterSecret, |
26 | DerivedSecret, |
27 | ServerEchConfirmationSecret, |
28 | ServerEchHrrConfirmationSecret, |
29 | } |
30 | |
31 | impl SecretKind { |
32 | fn to_bytes(self) -> &'static [u8] { |
33 | use self::SecretKind::*; |
34 | match self { |
35 | ResumptionPskBinderKey => b"res binder" , |
36 | ClientEarlyTrafficSecret => b"c e traffic" , |
37 | ClientHandshakeTrafficSecret => b"c hs traffic" , |
38 | ServerHandshakeTrafficSecret => b"s hs traffic" , |
39 | ClientApplicationTrafficSecret => b"c ap traffic" , |
40 | ServerApplicationTrafficSecret => b"s ap traffic" , |
41 | ExporterMasterSecret => b"exp master" , |
42 | ResumptionMasterSecret => b"res master" , |
43 | DerivedSecret => b"derived" , |
44 | // https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-18#section-7.2 |
45 | ServerEchConfirmationSecret => b"ech accept confirmation" , |
46 | // https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-18#section-7.2.1 |
47 | ServerEchHrrConfirmationSecret => b"hrr ech accept confirmation" , |
48 | } |
49 | } |
50 | |
51 | fn log_label(self) -> Option<&'static str> { |
52 | use self::SecretKind::*; |
53 | Some(match self { |
54 | ClientEarlyTrafficSecret => "CLIENT_EARLY_TRAFFIC_SECRET" , |
55 | ClientHandshakeTrafficSecret => "CLIENT_HANDSHAKE_TRAFFIC_SECRET" , |
56 | ServerHandshakeTrafficSecret => "SERVER_HANDSHAKE_TRAFFIC_SECRET" , |
57 | ClientApplicationTrafficSecret => "CLIENT_TRAFFIC_SECRET_0" , |
58 | ServerApplicationTrafficSecret => "SERVER_TRAFFIC_SECRET_0" , |
59 | ExporterMasterSecret => "EXPORTER_SECRET" , |
60 | _ => { |
61 | return None; |
62 | } |
63 | }) |
64 | } |
65 | } |
66 | |
67 | /// This is the TLS1.3 key schedule. It stores the current secret and |
68 | /// the type of hash. This isn't used directly; but only through the |
69 | /// typestates. |
70 | struct KeySchedule { |
71 | current: Box<dyn HkdfExpander>, |
72 | suite: &'static Tls13CipherSuite, |
73 | } |
74 | |
75 | // We express the state of a contained KeySchedule using these |
76 | // typestates. This means we can write code that cannot accidentally |
77 | // (e.g.) encrypt application data using a KeySchedule solely constructed |
78 | // with an empty or trivial secret, or extract the wrong kind of secrets |
79 | // at a given point. |
80 | |
81 | /// KeySchedule for early data stage. |
82 | pub(crate) struct KeyScheduleEarly { |
83 | ks: KeySchedule, |
84 | } |
85 | |
86 | impl KeyScheduleEarly { |
87 | pub(crate) fn new(suite: &'static Tls13CipherSuite, secret: &[u8]) -> Self { |
88 | Self { |
89 | ks: KeySchedule::new(suite, secret), |
90 | } |
91 | } |
92 | |
93 | pub(crate) fn client_early_traffic_secret( |
94 | &self, |
95 | hs_hash: &hash::Output, |
96 | key_log: &dyn KeyLog, |
97 | client_random: &[u8; 32], |
98 | common: &mut CommonState, |
99 | ) { |
100 | let client_early_traffic_secret = self.ks.derive_logged_secret( |
101 | SecretKind::ClientEarlyTrafficSecret, |
102 | hs_hash.as_ref(), |
103 | key_log, |
104 | client_random, |
105 | ); |
106 | |
107 | match common.side { |
108 | Side::Client => self |
109 | .ks |
110 | .set_encrypter(&client_early_traffic_secret, common), |
111 | Side::Server => self |
112 | .ks |
113 | .set_decrypter(&client_early_traffic_secret, common), |
114 | } |
115 | |
116 | if common.is_quic() { |
117 | // If 0-RTT should be rejected, this will be clobbered by ExtensionProcessing |
118 | // before the application can see. |
119 | common.quic.early_secret = Some(client_early_traffic_secret); |
120 | } |
121 | } |
122 | |
123 | pub(crate) fn resumption_psk_binder_key_and_sign_verify_data( |
124 | &self, |
125 | hs_hash: &hash::Output, |
126 | ) -> hmac::Tag { |
127 | let resumption_psk_binder_key = self |
128 | .ks |
129 | .derive_for_empty_hash(SecretKind::ResumptionPskBinderKey); |
130 | self.ks |
131 | .sign_verify_data(&resumption_psk_binder_key, hs_hash) |
132 | } |
133 | } |
134 | |
135 | /// Pre-handshake key schedule |
136 | /// |
137 | /// The inner `KeySchedule` is either constructed without any secrets based on the HKDF algorithm |
138 | /// or is extracted from a `KeyScheduleEarly`. This can then be used to derive the `KeyScheduleHandshakeStart`. |
139 | pub(crate) struct KeySchedulePreHandshake { |
140 | ks: KeySchedule, |
141 | } |
142 | |
143 | impl KeySchedulePreHandshake { |
144 | pub(crate) fn new(suite: &'static Tls13CipherSuite) -> Self { |
145 | Self { |
146 | ks: KeySchedule::new_with_empty_secret(suite), |
147 | } |
148 | } |
149 | |
150 | pub(crate) fn into_handshake( |
151 | mut self, |
152 | shared_secret: SharedSecret, |
153 | ) -> KeyScheduleHandshakeStart { |
154 | self.ks |
155 | .input_secret(shared_secret.secret_bytes()); |
156 | KeyScheduleHandshakeStart { ks: self.ks } |
157 | } |
158 | } |
159 | |
160 | impl From<KeyScheduleEarly> for KeySchedulePreHandshake { |
161 | fn from(KeyScheduleEarly { ks: KeySchedule }: KeyScheduleEarly) -> Self { |
162 | Self { ks } |
163 | } |
164 | } |
165 | |
166 | /// KeySchedule during handshake. |
167 | pub(crate) struct KeyScheduleHandshakeStart { |
168 | ks: KeySchedule, |
169 | } |
170 | |
171 | impl KeyScheduleHandshakeStart { |
172 | pub(crate) fn derive_client_handshake_secrets( |
173 | mut self, |
174 | early_data_enabled: bool, |
175 | hs_hash: hash::Output, |
176 | suite: &'static Tls13CipherSuite, |
177 | key_log: &dyn KeyLog, |
178 | client_random: &[u8; 32], |
179 | common: &mut CommonState, |
180 | ) -> KeyScheduleHandshake { |
181 | debug_assert_eq!(common.side, Side::Client); |
182 | // Suite might have changed due to resumption |
183 | self.ks.suite = suite; |
184 | let new = self.into_handshake(hs_hash, key_log, client_random, common); |
185 | |
186 | // Decrypt with the peer's key, encrypt with our own key |
187 | new.ks |
188 | .set_decrypter(&new.server_handshake_traffic_secret, common); |
189 | |
190 | if !early_data_enabled { |
191 | // Set the client encryption key for handshakes if early data is not used |
192 | new.ks |
193 | .set_encrypter(&new.client_handshake_traffic_secret, common); |
194 | } |
195 | |
196 | new |
197 | } |
198 | |
199 | pub(crate) fn derive_server_handshake_secrets( |
200 | self, |
201 | hs_hash: hash::Output, |
202 | key_log: &dyn KeyLog, |
203 | client_random: &[u8; 32], |
204 | common: &mut CommonState, |
205 | ) -> KeyScheduleHandshake { |
206 | debug_assert_eq!(common.side, Side::Server); |
207 | let new = self.into_handshake(hs_hash, key_log, client_random, common); |
208 | |
209 | // Set up to encrypt with handshake secrets, but decrypt with early_data keys. |
210 | // If not doing early_data after all, this is corrected later to the handshake |
211 | // keys (now stored in key_schedule). |
212 | new.ks |
213 | .set_encrypter(&new.server_handshake_traffic_secret, common); |
214 | new |
215 | } |
216 | |
217 | pub(crate) fn server_ech_confirmation_secret( |
218 | &mut self, |
219 | client_hello_inner_random: &[u8], |
220 | hs_hash: hash::Output, |
221 | ) -> [u8; 8] { |
222 | /* |
223 | Per ietf-tls-esni-17 section 7.2: |
224 | <https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-17#section-7.2> |
225 | accept_confirmation = HKDF-Expand-Label( |
226 | HKDF-Extract(0, ClientHelloInner.random), |
227 | "ech accept confirmation", |
228 | transcript_ech_conf,8) |
229 | */ |
230 | hkdf_expand_label( |
231 | self.ks |
232 | .suite |
233 | .hkdf_provider |
234 | .extract_from_secret(None, client_hello_inner_random) |
235 | .as_ref(), |
236 | SecretKind::ServerEchConfirmationSecret.to_bytes(), |
237 | hs_hash.as_ref(), |
238 | ) |
239 | } |
240 | |
241 | fn into_handshake( |
242 | self, |
243 | hs_hash: hash::Output, |
244 | key_log: &dyn KeyLog, |
245 | client_random: &[u8; 32], |
246 | common: &mut CommonState, |
247 | ) -> KeyScheduleHandshake { |
248 | // Use an empty handshake hash for the initial handshake. |
249 | let client_secret = self.ks.derive_logged_secret( |
250 | SecretKind::ClientHandshakeTrafficSecret, |
251 | hs_hash.as_ref(), |
252 | key_log, |
253 | client_random, |
254 | ); |
255 | |
256 | let server_secret = self.ks.derive_logged_secret( |
257 | SecretKind::ServerHandshakeTrafficSecret, |
258 | hs_hash.as_ref(), |
259 | key_log, |
260 | client_random, |
261 | ); |
262 | |
263 | if common.is_quic() { |
264 | common.quic.hs_secrets = Some(quic::Secrets::new( |
265 | client_secret.clone(), |
266 | server_secret.clone(), |
267 | self.ks.suite, |
268 | self.ks.suite.quic.unwrap(), |
269 | common.side, |
270 | common.quic.version, |
271 | )); |
272 | } |
273 | |
274 | KeyScheduleHandshake { |
275 | ks: self.ks, |
276 | client_handshake_traffic_secret: client_secret, |
277 | server_handshake_traffic_secret: server_secret, |
278 | } |
279 | } |
280 | } |
281 | |
282 | pub(crate) struct KeyScheduleHandshake { |
283 | ks: KeySchedule, |
284 | client_handshake_traffic_secret: OkmBlock, |
285 | server_handshake_traffic_secret: OkmBlock, |
286 | } |
287 | |
288 | impl KeyScheduleHandshake { |
289 | pub(crate) fn sign_server_finish(&self, hs_hash: &hash::Output) -> hmac::Tag { |
290 | self.ks |
291 | .sign_finish(&self.server_handshake_traffic_secret, hs_hash) |
292 | } |
293 | |
294 | pub(crate) fn set_handshake_encrypter(&self, common: &mut CommonState) { |
295 | debug_assert_eq!(common.side, Side::Client); |
296 | self.ks |
297 | .set_encrypter(&self.client_handshake_traffic_secret, common); |
298 | } |
299 | |
300 | pub(crate) fn set_handshake_decrypter( |
301 | &self, |
302 | skip_requested: Option<usize>, |
303 | common: &mut CommonState, |
304 | ) { |
305 | debug_assert_eq!(common.side, Side::Server); |
306 | let secret = &self.client_handshake_traffic_secret; |
307 | match skip_requested { |
308 | None => self.ks.set_decrypter(secret, common), |
309 | Some(max_early_data_size) => common |
310 | .record_layer |
311 | .set_message_decrypter_with_trial_decryption( |
312 | self.ks |
313 | .derive_decrypter(&self.client_handshake_traffic_secret), |
314 | max_early_data_size, |
315 | ), |
316 | } |
317 | } |
318 | |
319 | pub(crate) fn into_traffic_with_client_finished_pending( |
320 | self, |
321 | hs_hash: hash::Output, |
322 | key_log: &dyn KeyLog, |
323 | client_random: &[u8; 32], |
324 | common: &mut CommonState, |
325 | ) -> KeyScheduleTrafficWithClientFinishedPending { |
326 | debug_assert_eq!(common.side, Side::Server); |
327 | |
328 | let traffic = KeyScheduleTraffic::new(self.ks, hs_hash, key_log, client_random); |
329 | let (_client_secret, server_secret) = ( |
330 | &traffic.current_client_traffic_secret, |
331 | &traffic.current_server_traffic_secret, |
332 | ); |
333 | |
334 | traffic |
335 | .ks |
336 | .set_encrypter(server_secret, common); |
337 | |
338 | if common.is_quic() { |
339 | common.quic.traffic_secrets = Some(quic::Secrets::new( |
340 | _client_secret.clone(), |
341 | server_secret.clone(), |
342 | traffic.ks.suite, |
343 | traffic.ks.suite.quic.unwrap(), |
344 | common.side, |
345 | common.quic.version, |
346 | )); |
347 | } |
348 | |
349 | KeyScheduleTrafficWithClientFinishedPending { |
350 | handshake_client_traffic_secret: self.client_handshake_traffic_secret, |
351 | traffic, |
352 | } |
353 | } |
354 | |
355 | pub(crate) fn into_pre_finished_client_traffic( |
356 | self, |
357 | pre_finished_hash: hash::Output, |
358 | handshake_hash: hash::Output, |
359 | key_log: &dyn KeyLog, |
360 | client_random: &[u8; 32], |
361 | ) -> (KeyScheduleClientBeforeFinished, hmac::Tag) { |
362 | let traffic = KeyScheduleTraffic::new(self.ks, pre_finished_hash, key_log, client_random); |
363 | let tag = traffic |
364 | .ks |
365 | .sign_finish(&self.client_handshake_traffic_secret, &handshake_hash); |
366 | (KeyScheduleClientBeforeFinished { traffic }, tag) |
367 | } |
368 | } |
369 | |
370 | pub(crate) struct KeyScheduleClientBeforeFinished { |
371 | traffic: KeyScheduleTraffic, |
372 | } |
373 | |
374 | impl KeyScheduleClientBeforeFinished { |
375 | pub(crate) fn into_traffic(self, common: &mut CommonState) -> KeyScheduleTraffic { |
376 | debug_assert_eq!(common.side, Side::Client); |
377 | let (client_secret, server_secret) = ( |
378 | &self |
379 | .traffic |
380 | .current_client_traffic_secret, |
381 | &self |
382 | .traffic |
383 | .current_server_traffic_secret, |
384 | ); |
385 | |
386 | self.traffic |
387 | .ks |
388 | .set_decrypter(server_secret, common); |
389 | self.traffic |
390 | .ks |
391 | .set_encrypter(client_secret, common); |
392 | |
393 | if common.is_quic() { |
394 | common.quic.traffic_secrets = Some(quic::Secrets::new( |
395 | client_secret.clone(), |
396 | server_secret.clone(), |
397 | self.traffic.ks.suite, |
398 | self.traffic.ks.suite.quic.unwrap(), |
399 | common.side, |
400 | common.quic.version, |
401 | )); |
402 | } |
403 | |
404 | self.traffic |
405 | } |
406 | } |
407 | |
408 | /// KeySchedule during traffic stage, retaining the ability to calculate the client's |
409 | /// finished verify_data. The traffic stage key schedule can be extracted from it |
410 | /// through signing the client finished hash. |
411 | pub(crate) struct KeyScheduleTrafficWithClientFinishedPending { |
412 | handshake_client_traffic_secret: OkmBlock, |
413 | traffic: KeyScheduleTraffic, |
414 | } |
415 | |
416 | impl KeyScheduleTrafficWithClientFinishedPending { |
417 | pub(crate) fn update_decrypter(&self, common: &mut CommonState) { |
418 | debug_assert_eq!(common.side, Side::Server); |
419 | self.traffic |
420 | .ks |
421 | .set_decrypter(&self.handshake_client_traffic_secret, common); |
422 | } |
423 | |
424 | pub(crate) fn sign_client_finish( |
425 | self, |
426 | hs_hash: &hash::Output, |
427 | common: &mut CommonState, |
428 | ) -> (KeyScheduleTraffic, hmac::Tag) { |
429 | debug_assert_eq!(common.side, Side::Server); |
430 | let tag = self |
431 | .traffic |
432 | .ks |
433 | .sign_finish(&self.handshake_client_traffic_secret, hs_hash); |
434 | |
435 | // Install keying to read future messages. |
436 | self.traffic.ks.set_decrypter( |
437 | &self |
438 | .traffic |
439 | .current_client_traffic_secret, |
440 | common, |
441 | ); |
442 | |
443 | (self.traffic, tag) |
444 | } |
445 | } |
446 | |
447 | /// KeySchedule during traffic stage. All traffic & exporter keys are guaranteed |
448 | /// to be available. |
449 | pub(crate) struct KeyScheduleTraffic { |
450 | ks: KeySchedule, |
451 | current_client_traffic_secret: OkmBlock, |
452 | current_server_traffic_secret: OkmBlock, |
453 | current_exporter_secret: OkmBlock, |
454 | } |
455 | |
456 | impl KeyScheduleTraffic { |
457 | fn new( |
458 | mut ks: KeySchedule, |
459 | hs_hash: hash::Output, |
460 | key_log: &dyn KeyLog, |
461 | client_random: &[u8; 32], |
462 | ) -> Self { |
463 | ks.input_empty(); |
464 | |
465 | let current_client_traffic_secret = ks.derive_logged_secret( |
466 | SecretKind::ClientApplicationTrafficSecret, |
467 | hs_hash.as_ref(), |
468 | key_log, |
469 | client_random, |
470 | ); |
471 | |
472 | let current_server_traffic_secret = ks.derive_logged_secret( |
473 | SecretKind::ServerApplicationTrafficSecret, |
474 | hs_hash.as_ref(), |
475 | key_log, |
476 | client_random, |
477 | ); |
478 | |
479 | let current_exporter_secret = ks.derive_logged_secret( |
480 | SecretKind::ExporterMasterSecret, |
481 | hs_hash.as_ref(), |
482 | key_log, |
483 | client_random, |
484 | ); |
485 | |
486 | Self { |
487 | ks, |
488 | current_client_traffic_secret, |
489 | current_server_traffic_secret, |
490 | current_exporter_secret, |
491 | } |
492 | } |
493 | |
494 | pub(crate) fn update_encrypter_and_notify(&mut self, common: &mut CommonState) { |
495 | let secret = self.next_application_traffic_secret(common.side); |
496 | common.enqueue_key_update_notification(); |
497 | self.ks.set_encrypter(&secret, common); |
498 | } |
499 | |
500 | pub(crate) fn request_key_update_and_update_encrypter( |
501 | &mut self, |
502 | common: &mut CommonState, |
503 | ) -> Result<(), Error> { |
504 | common.check_aligned_handshake()?; |
505 | common.send_msg_encrypt(Message::build_key_update_request().into()); |
506 | let secret = self.next_application_traffic_secret(common.side); |
507 | self.ks.set_encrypter(&secret, common); |
508 | Ok(()) |
509 | } |
510 | |
511 | pub(crate) fn update_decrypter(&mut self, common: &mut CommonState) { |
512 | let secret = self.next_application_traffic_secret(common.side.peer()); |
513 | self.ks.set_decrypter(&secret, common); |
514 | } |
515 | |
516 | pub(crate) fn next_application_traffic_secret(&mut self, side: Side) -> OkmBlock { |
517 | let current = match side { |
518 | Side::Client => &mut self.current_client_traffic_secret, |
519 | Side::Server => &mut self.current_server_traffic_secret, |
520 | }; |
521 | |
522 | let secret = self.ks.derive_next(current); |
523 | *current = secret.clone(); |
524 | secret |
525 | } |
526 | |
527 | pub(crate) fn export_keying_material( |
528 | &self, |
529 | out: &mut [u8], |
530 | label: &[u8], |
531 | context: Option<&[u8]>, |
532 | ) -> Result<(), Error> { |
533 | self.ks |
534 | .export_keying_material(&self.current_exporter_secret, out, label, context) |
535 | } |
536 | |
537 | pub(crate) fn extract_secrets(&self, side: Side) -> Result<PartiallyExtractedSecrets, Error> { |
538 | fn expand( |
539 | secret: &OkmBlock, |
540 | hkdf: &'static dyn Hkdf, |
541 | aead_key_len: usize, |
542 | ) -> (AeadKey, Iv) { |
543 | let expander = hkdf.expander_for_okm(secret); |
544 | |
545 | ( |
546 | hkdf_expand_label_aead_key(expander.as_ref(), aead_key_len, b"key" , &[]), |
547 | hkdf_expand_label(expander.as_ref(), b"iv" , &[]), |
548 | ) |
549 | } |
550 | |
551 | let (client_key, client_iv) = expand( |
552 | &self.current_client_traffic_secret, |
553 | self.ks.suite.hkdf_provider, |
554 | self.ks.suite.aead_alg.key_len(), |
555 | ); |
556 | let (server_key, server_iv) = expand( |
557 | &self.current_server_traffic_secret, |
558 | self.ks.suite.hkdf_provider, |
559 | self.ks.suite.aead_alg.key_len(), |
560 | ); |
561 | let client_secrets = self |
562 | .ks |
563 | .suite |
564 | .aead_alg |
565 | .extract_keys(client_key, client_iv)?; |
566 | let server_secrets = self |
567 | .ks |
568 | .suite |
569 | .aead_alg |
570 | .extract_keys(server_key, server_iv)?; |
571 | |
572 | let (tx, rx) = match side { |
573 | Side::Client => (client_secrets, server_secrets), |
574 | Side::Server => (server_secrets, client_secrets), |
575 | }; |
576 | Ok(PartiallyExtractedSecrets { tx, rx }) |
577 | } |
578 | } |
579 | |
580 | pub(crate) struct ResumptionSecret<'a> { |
581 | kst: &'a KeyScheduleTraffic, |
582 | resumption_master_secret: OkmBlock, |
583 | } |
584 | |
585 | impl<'a> ResumptionSecret<'a> { |
586 | pub(crate) fn new(kst: &'a KeyScheduleTraffic, hs_hash: &hash::Output) -> Self { |
587 | ResumptionSecret { |
588 | kst, |
589 | resumption_master_secret: kst |
590 | .ks |
591 | .derive(kind:SecretKind::ResumptionMasterSecret, hs_hash.as_ref()), |
592 | } |
593 | } |
594 | |
595 | pub(crate) fn derive_ticket_psk(&self, nonce: &[u8]) -> OkmBlock { |
596 | self.kst |
597 | .ks |
598 | .derive_ticket_psk(&self.resumption_master_secret, nonce) |
599 | } |
600 | } |
601 | |
602 | impl KeySchedule { |
603 | fn new(suite: &'static Tls13CipherSuite, secret: &[u8]) -> Self { |
604 | Self { |
605 | current: suite |
606 | .hkdf_provider |
607 | .extract_from_secret(None, secret), |
608 | suite, |
609 | } |
610 | } |
611 | |
612 | fn set_encrypter(&self, secret: &OkmBlock, common: &mut CommonState) { |
613 | let expander = self |
614 | .suite |
615 | .hkdf_provider |
616 | .expander_for_okm(secret); |
617 | let key = derive_traffic_key(expander.as_ref(), self.suite.aead_alg); |
618 | let iv = derive_traffic_iv(expander.as_ref()); |
619 | |
620 | common |
621 | .record_layer |
622 | .set_message_encrypter( |
623 | self.suite.aead_alg.encrypter(key, iv), |
624 | self.suite.common.confidentiality_limit, |
625 | ); |
626 | } |
627 | |
628 | fn set_decrypter(&self, secret: &OkmBlock, common: &mut CommonState) { |
629 | common |
630 | .record_layer |
631 | .set_message_decrypter(self.derive_decrypter(secret)); |
632 | } |
633 | |
634 | fn derive_decrypter(&self, secret: &OkmBlock) -> Box<dyn MessageDecrypter> { |
635 | let expander = self |
636 | .suite |
637 | .hkdf_provider |
638 | .expander_for_okm(secret); |
639 | let key = derive_traffic_key(expander.as_ref(), self.suite.aead_alg); |
640 | let iv = derive_traffic_iv(expander.as_ref()); |
641 | self.suite.aead_alg.decrypter(key, iv) |
642 | } |
643 | |
644 | fn new_with_empty_secret(suite: &'static Tls13CipherSuite) -> Self { |
645 | Self { |
646 | current: suite |
647 | .hkdf_provider |
648 | .extract_from_zero_ikm(None), |
649 | suite, |
650 | } |
651 | } |
652 | |
653 | /// Input the empty secret. |
654 | fn input_empty(&mut self) { |
655 | let salt = self.derive_for_empty_hash(SecretKind::DerivedSecret); |
656 | self.current = self |
657 | .suite |
658 | .hkdf_provider |
659 | .extract_from_zero_ikm(Some(salt.as_ref())); |
660 | } |
661 | |
662 | /// Input the given secret. |
663 | fn input_secret(&mut self, secret: &[u8]) { |
664 | let salt = self.derive_for_empty_hash(SecretKind::DerivedSecret); |
665 | self.current = self |
666 | .suite |
667 | .hkdf_provider |
668 | .extract_from_secret(Some(salt.as_ref()), secret); |
669 | } |
670 | |
671 | /// Derive a secret of given `kind`, using current handshake hash `hs_hash`. |
672 | fn derive(&self, kind: SecretKind, hs_hash: &[u8]) -> OkmBlock { |
673 | hkdf_expand_label_block(self.current.as_ref(), kind.to_bytes(), hs_hash) |
674 | } |
675 | |
676 | fn derive_logged_secret( |
677 | &self, |
678 | kind: SecretKind, |
679 | hs_hash: &[u8], |
680 | key_log: &dyn KeyLog, |
681 | client_random: &[u8; 32], |
682 | ) -> OkmBlock { |
683 | let output = self.derive(kind, hs_hash); |
684 | |
685 | let log_label = kind |
686 | .log_label() |
687 | .expect("not a loggable secret" ); |
688 | if key_log.will_log(log_label) { |
689 | key_log.log(log_label, client_random, output.as_ref()); |
690 | } |
691 | output |
692 | } |
693 | |
694 | /// Derive a secret of given `kind` using the hash of the empty string |
695 | /// for the handshake hash. Useful only for |
696 | /// `SecretKind::ResumptionPSKBinderKey` and |
697 | /// `SecretKind::DerivedSecret`. |
698 | fn derive_for_empty_hash(&self, kind: SecretKind) -> OkmBlock { |
699 | let empty_hash = self |
700 | .suite |
701 | .common |
702 | .hash_provider |
703 | .start() |
704 | .finish(); |
705 | self.derive(kind, empty_hash.as_ref()) |
706 | } |
707 | |
708 | /// Sign the finished message consisting of `hs_hash` using a current |
709 | /// traffic secret. |
710 | fn sign_finish(&self, base_key: &OkmBlock, hs_hash: &hash::Output) -> hmac::Tag { |
711 | self.sign_verify_data(base_key, hs_hash) |
712 | } |
713 | |
714 | /// Sign the finished message consisting of `hs_hash` using the key material |
715 | /// `base_key`. |
716 | fn sign_verify_data(&self, base_key: &OkmBlock, hs_hash: &hash::Output) -> hmac::Tag { |
717 | let expander = self |
718 | .suite |
719 | .hkdf_provider |
720 | .expander_for_okm(base_key); |
721 | let hmac_key = hkdf_expand_label_block(expander.as_ref(), b"finished" , &[]); |
722 | |
723 | self.suite |
724 | .hkdf_provider |
725 | .hmac_sign(&hmac_key, hs_hash.as_ref()) |
726 | } |
727 | |
728 | /// Derive the next application traffic secret, returning it. |
729 | fn derive_next(&self, base_key: &OkmBlock) -> OkmBlock { |
730 | let expander = self |
731 | .suite |
732 | .hkdf_provider |
733 | .expander_for_okm(base_key); |
734 | hkdf_expand_label_block(expander.as_ref(), b"traffic upd" , &[]) |
735 | } |
736 | |
737 | /// Derive the PSK to use given a resumption_master_secret and |
738 | /// ticket_nonce. |
739 | fn derive_ticket_psk(&self, rms: &OkmBlock, nonce: &[u8]) -> OkmBlock { |
740 | let expander = self |
741 | .suite |
742 | .hkdf_provider |
743 | .expander_for_okm(rms); |
744 | hkdf_expand_label_block(expander.as_ref(), b"resumption" , nonce) |
745 | } |
746 | |
747 | fn export_keying_material( |
748 | &self, |
749 | current_exporter_secret: &OkmBlock, |
750 | out: &mut [u8], |
751 | label: &[u8], |
752 | context: Option<&[u8]>, |
753 | ) -> Result<(), Error> { |
754 | let secret = { |
755 | let h_empty = self |
756 | .suite |
757 | .common |
758 | .hash_provider |
759 | .hash(&[]); |
760 | |
761 | let expander = self |
762 | .suite |
763 | .hkdf_provider |
764 | .expander_for_okm(current_exporter_secret); |
765 | hkdf_expand_label_block(expander.as_ref(), label, h_empty.as_ref()) |
766 | }; |
767 | |
768 | let h_context = self |
769 | .suite |
770 | .common |
771 | .hash_provider |
772 | .hash(context.unwrap_or(&[])); |
773 | |
774 | let expander = self |
775 | .suite |
776 | .hkdf_provider |
777 | .expander_for_okm(&secret); |
778 | hkdf_expand_label_slice(expander.as_ref(), b"exporter" , h_context.as_ref(), out) |
779 | .map_err(|_| Error::General("exporting too much" .to_string())) |
780 | } |
781 | } |
782 | |
783 | /// [HKDF-Expand-Label] where the output is an AEAD key. |
784 | /// |
785 | /// [HKDF-Expand-Label]: <https://www.rfc-editor.org/rfc/rfc8446#section-7.1> |
786 | pub fn derive_traffic_key( |
787 | expander: &dyn HkdfExpander, |
788 | aead_alg: &dyn Tls13AeadAlgorithm, |
789 | ) -> AeadKey { |
790 | hkdf_expand_label_aead_key(expander, aead_alg.key_len(), label:b"key" , &[]) |
791 | } |
792 | |
793 | /// [HKDF-Expand-Label] where the output is an IV. |
794 | /// |
795 | /// [HKDF-Expand-Label]: <https://www.rfc-editor.org/rfc/rfc8446#section-7.1> |
796 | pub fn derive_traffic_iv(expander: &dyn HkdfExpander) -> Iv { |
797 | hkdf_expand_label(expander, label:b"iv" , &[]) |
798 | } |
799 | |
800 | /// [HKDF-Expand-Label] where the output length is a compile-time constant, and therefore |
801 | /// it is infallible. |
802 | /// |
803 | /// [HKDF-Expand-Label]: <https://www.rfc-editor.org/rfc/rfc8446#section-7.1> |
804 | pub(crate) fn hkdf_expand_label<T: From<[u8; N]>, const N: usize>( |
805 | expander: &dyn HkdfExpander, |
806 | label: &[u8], |
807 | context: &[u8], |
808 | ) -> T { |
809 | hkdf_expand_label_inner(expander, label, context, N, |e: &dyn HkdfExpander, info: &[&[u8]]| expand(expander:e, info)) |
810 | } |
811 | |
812 | /// [HKDF-Expand-Label] where the output is one block in size. |
813 | pub(crate) fn hkdf_expand_label_block( |
814 | expander: &dyn HkdfExpander, |
815 | label: &[u8], |
816 | context: &[u8], |
817 | ) -> OkmBlock { |
818 | hkdf_expand_label_inner(expander, label, context, n:expander.hash_len(), |e: &dyn HkdfExpander, info: &[&[u8]]| { |
819 | e.expand_block(info) |
820 | }) |
821 | } |
822 | |
823 | /// [HKDF-Expand-Label] where the output is an AEAD key. |
824 | pub(crate) fn hkdf_expand_label_aead_key( |
825 | expander: &dyn HkdfExpander, |
826 | key_len: usize, |
827 | label: &[u8], |
828 | context: &[u8], |
829 | ) -> AeadKey { |
830 | hkdf_expand_label_inner(expander, label, context, n:key_len, |e: &dyn HkdfExpander, info: &[&[u8]]| { |
831 | let key: AeadKey = expand(expander:e, info); |
832 | key.with_length(key_len) |
833 | }) |
834 | } |
835 | |
836 | /// [HKDF-Expand-Label] where the output is a slice. |
837 | /// |
838 | /// This can fail because HKDF-Expand is limited in its maximum output length. |
839 | fn hkdf_expand_label_slice( |
840 | expander: &dyn HkdfExpander, |
841 | label: &[u8], |
842 | context: &[u8], |
843 | output: &mut [u8], |
844 | ) -> Result<(), OutputLengthError> { |
845 | hkdf_expand_label_inner(expander, label, context, n:output.len(), |e: &dyn HkdfExpander, info: &[&[u8]]| { |
846 | e.expand_slice(info, output) |
847 | }) |
848 | } |
849 | |
850 | pub(crate) fn server_ech_hrr_confirmation_secret( |
851 | hkdf_provider: &'static dyn Hkdf, |
852 | client_hello_inner_random: &[u8], |
853 | hs_hash: hash::Output, |
854 | ) -> [u8; 8] { |
855 | /* |
856 | Per ietf-tls-esni-17 section 7.2.1: |
857 | <https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-17#section-7.2.1> |
858 | hrr_accept_confirmation = HKDF-Expand-Label( |
859 | HKDF-Extract(0, ClientHelloInner1.random), |
860 | "hrr ech accept confirmation", |
861 | transcript_hrr_ech_conf, |
862 | 8) |
863 | */ |
864 | hkdf_expand_label( |
865 | expander:hkdf_provider |
866 | .extract_from_secret(None, client_hello_inner_random) |
867 | .as_ref(), |
868 | label:SecretKind::ServerEchHrrConfirmationSecret.to_bytes(), |
869 | context:hs_hash.as_ref(), |
870 | ) |
871 | } |
872 | |
873 | fn hkdf_expand_label_inner<F, T>( |
874 | expander: &dyn HkdfExpander, |
875 | label: &[u8], |
876 | context: &[u8], |
877 | n: usize, |
878 | f: F, |
879 | ) -> T |
880 | where |
881 | F: FnOnce(&dyn HkdfExpander, &[&[u8]]) -> T, |
882 | { |
883 | const LABEL_PREFIX: &[u8] = b"tls13 " ; |
884 | |
885 | let output_len: [u8; _] = u16::to_be_bytes(self:n as u16); |
886 | let label_len: [u8; _] = u8::to_be_bytes((LABEL_PREFIX.len() + label.len()) as u8); |
887 | let context_len: [u8; _] = u8::to_be_bytes(self:context.len() as u8); |
888 | |
889 | let info: &[&[u8]; 6] = &[ |
890 | &output_len[..], |
891 | &label_len[..], |
892 | LABEL_PREFIX, |
893 | label, |
894 | &context_len[..], |
895 | context, |
896 | ]; |
897 | |
898 | f(expander, info) |
899 | } |
900 | |
901 | #[cfg (test)] |
902 | #[macro_rules_attribute::apply(test_for_each_provider)] |
903 | mod tests { |
904 | use core::fmt::Debug; |
905 | use std::prelude::v1::*; |
906 | use std::vec; |
907 | |
908 | use super::provider::ring_like::aead; |
909 | use super::provider::tls13::{ |
910 | TLS13_AES_128_GCM_SHA256_INTERNAL, TLS13_CHACHA20_POLY1305_SHA256_INTERNAL, |
911 | }; |
912 | use super::{KeySchedule, SecretKind, derive_traffic_iv, derive_traffic_key}; |
913 | use crate::KeyLog; |
914 | |
915 | #[test ] |
916 | fn test_vectors() { |
917 | /* These test vectors generated with OpenSSL. */ |
918 | let hs_start_hash = [ |
919 | 0xec, 0x14, 0x7a, 0x06, 0xde, 0xa3, 0xc8, 0x84, 0x6c, 0x02, 0xb2, 0x23, 0x8e, 0x41, |
920 | 0xbd, 0xdc, 0x9d, 0x89, 0xf9, 0xae, 0xa1, 0x7b, 0x5e, 0xfd, 0x4d, 0x74, 0x82, 0xaf, |
921 | 0x75, 0x88, 0x1c, 0x0a, |
922 | ]; |
923 | |
924 | let hs_full_hash = [ |
925 | 0x75, 0x1a, 0x3d, 0x4a, 0x14, 0xdf, 0xab, 0xeb, 0x68, 0xe9, 0x2c, 0xa5, 0x91, 0x8e, |
926 | 0x24, 0x08, 0xb9, 0xbc, 0xb0, 0x74, 0x89, 0x82, 0xec, 0x9c, 0x32, 0x30, 0xac, 0x30, |
927 | 0xbb, 0xeb, 0x23, 0xe2, |
928 | ]; |
929 | |
930 | let ecdhe_secret = [ |
931 | 0xe7, 0xb8, 0xfe, 0xf8, 0x90, 0x3b, 0x52, 0x0c, 0xb9, 0xa1, 0x89, 0x71, 0xb6, 0x9d, |
932 | 0xd4, 0x5d, 0xca, 0x53, 0xce, 0x2f, 0x12, 0xbf, 0x3b, 0xef, 0x93, 0x15, 0xe3, 0x12, |
933 | 0x71, 0xdf, 0x4b, 0x40, |
934 | ]; |
935 | |
936 | let client_hts = [ |
937 | 0x61, 0x7b, 0x35, 0x07, 0x6b, 0x9d, 0x0e, 0x08, 0xcf, 0x73, 0x1d, 0x94, 0xa8, 0x66, |
938 | 0x14, 0x78, 0x41, 0x09, 0xef, 0x25, 0x55, 0x51, 0x92, 0x1d, 0xd4, 0x6e, 0x04, 0x01, |
939 | 0x35, 0xcf, 0x46, 0xab, |
940 | ]; |
941 | |
942 | let client_hts_key = [ |
943 | 0x62, 0xd0, 0xdd, 0x00, 0xf6, 0x96, 0x19, 0xd3, 0xb8, 0x19, 0x3a, 0xb4, 0xa0, 0x95, |
944 | 0x85, 0xa7, |
945 | ]; |
946 | |
947 | let client_hts_iv = [ |
948 | 0xff, 0xf7, 0x5d, 0xf5, 0xad, 0x35, 0xd5, 0xcb, 0x3c, 0x53, 0xf3, 0xa9, |
949 | ]; |
950 | |
951 | let server_hts = [ |
952 | 0xfc, 0xf7, 0xdf, 0xe6, 0x4f, 0xa2, 0xc0, 0x4f, 0x62, 0x35, 0x38, 0x7f, 0x43, 0x4e, |
953 | 0x01, 0x42, 0x23, 0x36, 0xd9, 0xc0, 0x39, 0xde, 0x68, 0x47, 0xa0, 0xb9, 0xdd, 0xcf, |
954 | 0x29, 0xa8, 0x87, 0x59, |
955 | ]; |
956 | |
957 | let server_hts_key = [ |
958 | 0x04, 0x67, 0xf3, 0x16, 0xa8, 0x05, 0xb8, 0xc4, 0x97, 0xee, 0x67, 0x04, 0x7b, 0xbc, |
959 | 0xbc, 0x54, |
960 | ]; |
961 | |
962 | let server_hts_iv = [ |
963 | 0xde, 0x83, 0xa7, 0x3e, 0x9d, 0x81, 0x4b, 0x04, 0xc4, 0x8b, 0x78, 0x09, |
964 | ]; |
965 | |
966 | let client_ats = [ |
967 | 0xc1, 0x4a, 0x6d, 0x79, 0x76, 0xd8, 0x10, 0x2b, 0x5a, 0x0c, 0x99, 0x51, 0x49, 0x3f, |
968 | 0xee, 0x87, 0xdc, 0xaf, 0xf8, 0x2c, 0x24, 0xca, 0xb2, 0x14, 0xe8, 0xbe, 0x71, 0xa8, |
969 | 0x20, 0x6d, 0xbd, 0xa5, |
970 | ]; |
971 | |
972 | let client_ats_key = [ |
973 | 0xcc, 0x9f, 0x5f, 0x98, 0x0b, 0x5f, 0x10, 0x30, 0x6c, 0xba, 0xd7, 0xbe, 0x98, 0xd7, |
974 | 0x57, 0x2e, |
975 | ]; |
976 | |
977 | let client_ats_iv = [ |
978 | 0xb8, 0x09, 0x29, 0xe8, 0xd0, 0x2c, 0x70, 0xf6, 0x11, 0x62, 0xed, 0x6b, |
979 | ]; |
980 | |
981 | let server_ats = [ |
982 | 0x2c, 0x90, 0x77, 0x38, 0xd3, 0xf8, 0x37, 0x02, 0xd1, 0xe4, 0x59, 0x8f, 0x48, 0x48, |
983 | 0x53, 0x1d, 0x9f, 0x93, 0x65, 0x49, 0x1b, 0x9f, 0x7f, 0x52, 0xc8, 0x22, 0x29, 0x0d, |
984 | 0x4c, 0x23, 0x21, 0x92, |
985 | ]; |
986 | |
987 | let server_ats_key = [ |
988 | 0x0c, 0xb2, 0x95, 0x62, 0xd8, 0xd8, 0x8f, 0x48, 0xb0, 0x2c, 0xbf, 0xbe, 0xd7, 0xe6, |
989 | 0x2b, 0xb3, |
990 | ]; |
991 | |
992 | let server_ats_iv = [ |
993 | 0x0d, 0xb2, 0x8f, 0x98, 0x85, 0x86, 0xa1, 0xb7, 0xe4, 0xd5, 0xc6, 0x9c, |
994 | ]; |
995 | |
996 | let mut ks = KeySchedule::new_with_empty_secret(TLS13_CHACHA20_POLY1305_SHA256_INTERNAL); |
997 | ks.input_secret(&ecdhe_secret); |
998 | |
999 | assert_traffic_secret( |
1000 | &ks, |
1001 | SecretKind::ClientHandshakeTrafficSecret, |
1002 | &hs_start_hash, |
1003 | &client_hts, |
1004 | &client_hts_key, |
1005 | &client_hts_iv, |
1006 | ); |
1007 | |
1008 | assert_traffic_secret( |
1009 | &ks, |
1010 | SecretKind::ServerHandshakeTrafficSecret, |
1011 | &hs_start_hash, |
1012 | &server_hts, |
1013 | &server_hts_key, |
1014 | &server_hts_iv, |
1015 | ); |
1016 | |
1017 | ks.input_empty(); |
1018 | |
1019 | assert_traffic_secret( |
1020 | &ks, |
1021 | SecretKind::ClientApplicationTrafficSecret, |
1022 | &hs_full_hash, |
1023 | &client_ats, |
1024 | &client_ats_key, |
1025 | &client_ats_iv, |
1026 | ); |
1027 | |
1028 | assert_traffic_secret( |
1029 | &ks, |
1030 | SecretKind::ServerApplicationTrafficSecret, |
1031 | &hs_full_hash, |
1032 | &server_ats, |
1033 | &server_ats_key, |
1034 | &server_ats_iv, |
1035 | ); |
1036 | } |
1037 | |
1038 | fn assert_traffic_secret( |
1039 | ks: &KeySchedule, |
1040 | kind: SecretKind, |
1041 | hash: &[u8], |
1042 | expected_traffic_secret: &[u8], |
1043 | expected_key: &[u8], |
1044 | expected_iv: &[u8], |
1045 | ) { |
1046 | #[derive (Debug)] |
1047 | struct Log<'a>(&'a [u8]); |
1048 | impl KeyLog for Log<'_> { |
1049 | fn log(&self, _label: &str, _client_random: &[u8], secret: &[u8]) { |
1050 | assert_eq!(self.0, secret); |
1051 | } |
1052 | } |
1053 | let log = Log(expected_traffic_secret); |
1054 | let traffic_secret = ks.derive_logged_secret(kind, hash, &log, &[0; 32]); |
1055 | |
1056 | // Since we can't test key equality, we test the output of sealing with the key instead. |
1057 | let aead_alg = &aead::AES_128_GCM; |
1058 | let expander = TLS13_AES_128_GCM_SHA256_INTERNAL |
1059 | .hkdf_provider |
1060 | .expander_for_okm(&traffic_secret); |
1061 | let key = derive_traffic_key( |
1062 | expander.as_ref(), |
1063 | TLS13_AES_128_GCM_SHA256_INTERNAL.aead_alg, |
1064 | ); |
1065 | let key = aead::UnboundKey::new(aead_alg, key.as_ref()).unwrap(); |
1066 | let seal_output = seal_zeroes(key); |
1067 | let expected_key = aead::UnboundKey::new(aead_alg, expected_key).unwrap(); |
1068 | let expected_seal_output = seal_zeroes(expected_key); |
1069 | assert_eq!(seal_output, expected_seal_output); |
1070 | assert!(seal_output.len() >= 48); // Sanity check. |
1071 | |
1072 | let iv = derive_traffic_iv(expander.as_ref()); |
1073 | assert_eq!(iv.as_ref(), expected_iv); |
1074 | } |
1075 | |
1076 | fn seal_zeroes(key: aead::UnboundKey) -> Vec<u8> { |
1077 | let key = aead::LessSafeKey::new(key); |
1078 | let mut seal_output = vec![0; 32]; |
1079 | key.seal_in_place_append_tag( |
1080 | aead::Nonce::assume_unique_for_key([0; aead::NONCE_LEN]), |
1081 | aead::Aad::empty(), |
1082 | &mut seal_output, |
1083 | ) |
1084 | .unwrap(); |
1085 | seal_output |
1086 | } |
1087 | } |
1088 | |
1089 | #[cfg (all(test, bench))] |
1090 | #[macro_rules_attribute::apply(bench_for_each_provider)] |
1091 | mod benchmarks { |
1092 | #[bench ] |
1093 | fn bench_sha256(b: &mut test::Bencher) { |
1094 | use core::fmt::Debug; |
1095 | |
1096 | use super::provider::tls13::TLS13_CHACHA20_POLY1305_SHA256_INTERNAL; |
1097 | use super::{KeySchedule, SecretKind, derive_traffic_iv, derive_traffic_key}; |
1098 | use crate::KeyLog; |
1099 | |
1100 | fn extract_traffic_secret(ks: &KeySchedule, kind: SecretKind) { |
1101 | #[derive (Debug)] |
1102 | struct Log; |
1103 | |
1104 | impl KeyLog for Log { |
1105 | fn log(&self, _label: &str, _client_random: &[u8], _secret: &[u8]) {} |
1106 | } |
1107 | |
1108 | let hash = [0u8; 32]; |
1109 | let traffic_secret = ks.derive_logged_secret(kind, &hash, &Log, &[0u8; 32]); |
1110 | let traffic_secret_expander = TLS13_CHACHA20_POLY1305_SHA256_INTERNAL |
1111 | .hkdf_provider |
1112 | .expander_for_okm(&traffic_secret); |
1113 | test::black_box(derive_traffic_key( |
1114 | traffic_secret_expander.as_ref(), |
1115 | TLS13_CHACHA20_POLY1305_SHA256_INTERNAL.aead_alg, |
1116 | )); |
1117 | test::black_box(derive_traffic_iv(traffic_secret_expander.as_ref())); |
1118 | } |
1119 | |
1120 | b.iter(|| { |
1121 | let mut ks = |
1122 | KeySchedule::new_with_empty_secret(TLS13_CHACHA20_POLY1305_SHA256_INTERNAL); |
1123 | ks.input_secret(&[0u8; 32]); |
1124 | |
1125 | extract_traffic_secret(&ks, SecretKind::ClientHandshakeTrafficSecret); |
1126 | extract_traffic_secret(&ks, SecretKind::ServerHandshakeTrafficSecret); |
1127 | |
1128 | ks.input_empty(); |
1129 | |
1130 | extract_traffic_secret(&ks, SecretKind::ClientApplicationTrafficSecret); |
1131 | extract_traffic_secret(&ks, SecretKind::ServerApplicationTrafficSecret); |
1132 | }); |
1133 | } |
1134 | } |
1135 | |