1 /// This module contains optional APIs for implementing QUIC TLS.
2 use crate::client::{ClientConfig, ClientSession, ClientSessionImpl};
3 use crate::error::TLSError;
4 use crate::key_schedule::hkdf_expand;
5 use crate::msgs::enums::{AlertDescription, ContentType, ProtocolVersion};
6 use crate::msgs::handshake::{ClientExtension, ServerExtension};
7 use crate::msgs::message::{Message, MessagePayload};
8 use crate::server::{ServerConfig, ServerSession, ServerSessionImpl};
9 use crate::session::{Protocol, SessionCommon};
10 use crate::suites::{BulkAlgorithm, SupportedCipherSuite, TLS13_AES_128_GCM_SHA256};
11
12 use std::sync::Arc;
13
14 use ring::{aead, hkdf};
15 use webpki;
16
17 /// Secrets used to encrypt/decrypt traffic
18 #[derive(Clone, Debug)]
19 pub(crate) struct Secrets {
20 /// Secret used to encrypt packets transmitted by the client
21 pub client: hkdf::Prk,
22 /// Secret used to encrypt packets transmitted by the server
23 pub server: hkdf::Prk,
24 }
25
26 impl Secrets {
local_remote(&self, is_client: bool) -> (&hkdf::Prk, &hkdf::Prk)27 fn local_remote(&self, is_client: bool) -> (&hkdf::Prk, &hkdf::Prk) {
28 if is_client {
29 (&self.client, &self.server)
30 } else {
31 (&self.server, &self.client)
32 }
33 }
34 }
35
36 /// Generic methods for QUIC sessions
37 pub trait QuicExt {
38 /// Return the TLS-encoded transport parameters for the session's peer.
get_quic_transport_parameters(&self) -> Option<&[u8]>39 fn get_quic_transport_parameters(&self) -> Option<&[u8]>;
40
41 /// Compute the keys for encrypting/decrypting 0-RTT packets, if available
get_0rtt_keys(&self) -> Option<DirectionalKeys>42 fn get_0rtt_keys(&self) -> Option<DirectionalKeys>;
43
44 /// Consume unencrypted TLS handshake data.
45 ///
46 /// Handshake data obtained from separate encryption levels should be supplied in separate calls.
read_hs(&mut self, plaintext: &[u8]) -> Result<(), TLSError>47 fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), TLSError>;
48
49 /// Emit unencrypted TLS handshake data.
50 ///
51 /// When this returns `Some(_)`, the new keys must be used for future handshake data.
write_hs(&mut self, buf: &mut Vec<u8>) -> Option<Keys>52 fn write_hs(&mut self, buf: &mut Vec<u8>) -> Option<Keys>;
53
54 /// Emit the TLS description code of a fatal alert, if one has arisen.
55 ///
56 /// Check after `read_hs` returns `Err(_)`.
get_alert(&self) -> Option<AlertDescription>57 fn get_alert(&self) -> Option<AlertDescription>;
58
59 /// Compute the keys to use following a 1-RTT key update
60 ///
61 /// Must not be called until the handshake is complete
next_1rtt_keys(&mut self) -> PacketKeySet62 fn next_1rtt_keys(&mut self) -> PacketKeySet;
63 }
64
65 impl QuicExt for ClientSession {
get_quic_transport_parameters(&self) -> Option<&[u8]>66 fn get_quic_transport_parameters(&self) -> Option<&[u8]> {
67 self.imp
68 .common
69 .quic
70 .params
71 .as_ref()
72 .map(|v| v.as_ref())
73 }
74
get_0rtt_keys(&self) -> Option<DirectionalKeys>75 fn get_0rtt_keys(&self) -> Option<DirectionalKeys> {
76 Some(DirectionalKeys::new(
77 self.imp.resumption_ciphersuite?,
78 self.imp
79 .common
80 .quic
81 .early_secret
82 .as_ref()?,
83 ))
84 }
85
read_hs(&mut self, plaintext: &[u8]) -> Result<(), TLSError>86 fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), TLSError> {
87 read_hs(&mut self.imp.common, plaintext)?;
88 self.imp
89 .process_new_handshake_messages()
90 }
91
write_hs(&mut self, buf: &mut Vec<u8>) -> Option<Keys>92 fn write_hs(&mut self, buf: &mut Vec<u8>) -> Option<Keys> {
93 write_hs(&mut self.imp.common, buf)
94 }
95
get_alert(&self) -> Option<AlertDescription>96 fn get_alert(&self) -> Option<AlertDescription> {
97 self.imp.common.quic.alert
98 }
99
next_1rtt_keys(&mut self) -> PacketKeySet100 fn next_1rtt_keys(&mut self) -> PacketKeySet {
101 next_1rtt_keys(&mut self.imp.common)
102 }
103 }
104
105 impl QuicExt for ServerSession {
get_quic_transport_parameters(&self) -> Option<&[u8]>106 fn get_quic_transport_parameters(&self) -> Option<&[u8]> {
107 self.imp
108 .common
109 .quic
110 .params
111 .as_ref()
112 .map(|v| v.as_ref())
113 }
114
get_0rtt_keys(&self) -> Option<DirectionalKeys>115 fn get_0rtt_keys(&self) -> Option<DirectionalKeys> {
116 Some(DirectionalKeys::new(
117 self.imp.common.get_suite()?,
118 self.imp
119 .common
120 .quic
121 .early_secret
122 .as_ref()?,
123 ))
124 }
125
read_hs(&mut self, plaintext: &[u8]) -> Result<(), TLSError>126 fn read_hs(&mut self, plaintext: &[u8]) -> Result<(), TLSError> {
127 read_hs(&mut self.imp.common, plaintext)?;
128 self.imp
129 .process_new_handshake_messages()
130 }
write_hs(&mut self, buf: &mut Vec<u8>) -> Option<Keys>131 fn write_hs(&mut self, buf: &mut Vec<u8>) -> Option<Keys> {
132 write_hs(&mut self.imp.common, buf)
133 }
134
get_alert(&self) -> Option<AlertDescription>135 fn get_alert(&self) -> Option<AlertDescription> {
136 self.imp.common.quic.alert
137 }
138
next_1rtt_keys(&mut self) -> PacketKeySet139 fn next_1rtt_keys(&mut self) -> PacketKeySet {
140 next_1rtt_keys(&mut self.imp.common)
141 }
142 }
143
144 /// Keys used to communicate in a single direction
145 pub struct DirectionalKeys {
146 /// Encrypts or decrypts a packet's headers
147 pub header: aead::quic::HeaderProtectionKey,
148 /// Encrypts or decrypts the payload of a packet
149 pub packet: PacketKey,
150 }
151
152 impl DirectionalKeys {
new(suite: &'static SupportedCipherSuite, secret: &hkdf::Prk) -> Self153 fn new(suite: &'static SupportedCipherSuite, secret: &hkdf::Prk) -> Self {
154 let hp_alg = match suite.bulk {
155 BulkAlgorithm::AES_128_GCM => &aead::quic::AES_128,
156 BulkAlgorithm::AES_256_GCM => &aead::quic::AES_256,
157 BulkAlgorithm::CHACHA20_POLY1305 => &aead::quic::CHACHA20,
158 };
159
160 Self {
161 header: hkdf_expand(secret, hp_alg, b"quic hp", &[]),
162 packet: PacketKey::new(suite, secret),
163 }
164 }
165 }
166
167 /// Keys to encrypt or decrypt the payload of a packet
168 pub struct PacketKey {
169 /// Encrypts or decrypts a packet's payload
170 pub key: aead::LessSafeKey,
171 /// Computes unique nonces for each packet
172 pub iv: Iv,
173 }
174
175 impl PacketKey {
new(suite: &'static SupportedCipherSuite, secret: &hkdf::Prk) -> Self176 fn new(suite: &'static SupportedCipherSuite, secret: &hkdf::Prk) -> Self {
177 Self {
178 key: aead::LessSafeKey::new(hkdf_expand(
179 secret,
180 suite.aead_algorithm,
181 b"quic key",
182 &[],
183 )),
184 iv: hkdf_expand(secret, IvLen, b"quic iv", &[]),
185 }
186 }
187 }
188
189 /// Packet protection keys for bidirectional 1-RTT communication
190 pub struct PacketKeySet {
191 /// Encrypts outgoing packets
192 pub local: PacketKey,
193 /// Decrypts incoming packets
194 pub remote: PacketKey,
195 }
196
197 /// Computes unique nonces for each packet
198 pub struct Iv([u8; aead::NONCE_LEN]);
199
200 impl Iv {
201 /// Compute the nonce to use for encrypting or decrypting `packet_number`
nonce_for(&self, packet_number: u64) -> ring::aead::Nonce202 pub fn nonce_for(&self, packet_number: u64) -> ring::aead::Nonce {
203 let mut out = [0; aead::NONCE_LEN];
204 out[4..].copy_from_slice(&packet_number.to_be_bytes());
205 for (out, inp) in out.iter_mut().zip(self.0.iter()) {
206 *out ^= inp;
207 }
208 aead::Nonce::assume_unique_for_key(out)
209 }
210 }
211
212 impl From<hkdf::Okm<'_, IvLen>> for Iv {
from(okm: hkdf::Okm<IvLen>) -> Self213 fn from(okm: hkdf::Okm<IvLen>) -> Self {
214 let mut iv = [0; aead::NONCE_LEN];
215 okm.fill(&mut iv[..]).unwrap();
216 Iv(iv)
217 }
218 }
219
220 struct IvLen;
221
222 impl hkdf::KeyType for IvLen {
len(&self) -> usize223 fn len(&self) -> usize {
224 aead::NONCE_LEN
225 }
226 }
227
228 /// Complete set of keys used to communicate with the peer
229 pub struct Keys {
230 /// Encrypts outgoing packets
231 pub local: DirectionalKeys,
232 /// Decrypts incoming packets
233 pub remote: DirectionalKeys,
234 }
235
236 impl Keys {
237 /// Construct keys for use with initial packets
initial( initial_salt: &hkdf::Salt, client_dst_connection_id: &[u8], is_client: bool, ) -> Self238 pub fn initial(
239 initial_salt: &hkdf::Salt,
240 client_dst_connection_id: &[u8],
241 is_client: bool,
242 ) -> Self {
243 const CLIENT_LABEL: &[u8] = b"client in";
244 const SERVER_LABEL: &[u8] = b"server in";
245 let hs_secret = initial_salt.extract(client_dst_connection_id);
246
247 let secrets = Secrets {
248 client: hkdf_expand(&hs_secret, hkdf::HKDF_SHA256, CLIENT_LABEL, &[]),
249 server: hkdf_expand(&hs_secret, hkdf::HKDF_SHA256, SERVER_LABEL, &[]),
250 };
251 Self::new(&TLS13_AES_128_GCM_SHA256, is_client, &secrets)
252 }
253
new(suite: &'static SupportedCipherSuite, is_client: bool, secrets: &Secrets) -> Self254 fn new(suite: &'static SupportedCipherSuite, is_client: bool, secrets: &Secrets) -> Self {
255 let (local, remote) = secrets.local_remote(is_client);
256 Keys {
257 local: DirectionalKeys::new(suite, local),
258 remote: DirectionalKeys::new(suite, remote),
259 }
260 }
261 }
262
read_hs(this: &mut SessionCommon, plaintext: &[u8]) -> Result<(), TLSError>263 fn read_hs(this: &mut SessionCommon, plaintext: &[u8]) -> Result<(), TLSError> {
264 if this
265 .handshake_joiner
266 .take_message(Message {
267 typ: ContentType::Handshake,
268 version: ProtocolVersion::TLSv1_3,
269 payload: MessagePayload::new_opaque(plaintext.into()),
270 })
271 .is_none()
272 {
273 this.quic.alert = Some(AlertDescription::DecodeError);
274 return Err(TLSError::CorruptMessage);
275 }
276 Ok(())
277 }
278
write_hs(this: &mut SessionCommon, buf: &mut Vec<u8>) -> Option<Keys>279 fn write_hs(this: &mut SessionCommon, buf: &mut Vec<u8>) -> Option<Keys> {
280 while let Some((_, msg)) = this.quic.hs_queue.pop_front() {
281 buf.extend_from_slice(&msg);
282 if let Some(&(true, _)) = this.quic.hs_queue.front() {
283 if this.quic.hs_secrets.is_some() {
284 // Allow the caller to switch keys before proceeding.
285 break;
286 }
287 }
288 }
289 if let Some(secrets) = this.quic.hs_secrets.take() {
290 return Some(Keys::new(this.get_suite_assert(), this.is_client, &secrets));
291 }
292 if let Some(secrets) = this.quic.traffic_secrets.as_ref() {
293 if !this.quic.returned_traffic_keys {
294 this.quic.returned_traffic_keys = true;
295 return Some(Keys::new(this.get_suite_assert(), this.is_client, &secrets));
296 }
297 }
298 None
299 }
300
next_1rtt_keys(this: &mut SessionCommon) -> PacketKeySet301 fn next_1rtt_keys(this: &mut SessionCommon) -> PacketKeySet {
302 let hkdf_alg = this.get_suite_assert().hkdf_algorithm;
303 let secrets = this
304 .quic
305 .traffic_secrets
306 .as_ref()
307 .expect("traffic keys not yet available");
308
309 let next = next_1rtt_secrets(hkdf_alg, secrets);
310
311 let (local, remote) = next.local_remote(this.is_client);
312 let keys = PacketKeySet {
313 local: PacketKey::new(this.get_suite_assert(), local),
314 remote: PacketKey::new(this.get_suite_assert(), remote),
315 };
316
317 this.quic.traffic_secrets = Some(next);
318 keys
319 }
320
next_1rtt_secrets(hkdf_alg: hkdf::Algorithm, prev: &Secrets) -> Secrets321 fn next_1rtt_secrets(hkdf_alg: hkdf::Algorithm, prev: &Secrets) -> Secrets {
322 Secrets {
323 client: hkdf_expand(&prev.client, hkdf_alg, b"quic ku", &[]),
324 server: hkdf_expand(&prev.server, hkdf_alg, b"quic ku", &[]),
325 }
326 }
327
328 /// Methods specific to QUIC client sessions
329 pub trait ClientQuicExt {
330 /// Make a new QUIC ClientSession. This differs from `ClientSession::new()`
331 /// in that it takes an extra argument, `params`, which contains the
332 /// TLS-encoded transport parameters to send.
new_quic( config: &Arc<ClientConfig>, hostname: webpki::DNSNameRef, params: Vec<u8>, ) -> ClientSession333 fn new_quic(
334 config: &Arc<ClientConfig>,
335 hostname: webpki::DNSNameRef,
336 params: Vec<u8>,
337 ) -> ClientSession {
338 assert!(
339 config
340 .versions
341 .iter()
342 .all(|x| x.get_u16() >= ProtocolVersion::TLSv1_3.get_u16()),
343 "QUIC requires TLS version >= 1.3"
344 );
345 let mut imp = ClientSessionImpl::new(config);
346 imp.common.protocol = Protocol::Quic;
347 imp.start_handshake(
348 hostname.into(),
349 vec![ClientExtension::TransportParameters(params)],
350 );
351 ClientSession { imp }
352 }
353 }
354
355 impl ClientQuicExt for ClientSession {}
356
357 /// Methods specific to QUIC server sessions
358 pub trait ServerQuicExt {
359 /// Make a new QUIC ServerSession. This differs from `ServerSession::new()`
360 /// in that it takes an extra argument, `params`, which contains the
361 /// TLS-encoded transport parameters to send.
new_quic(config: &Arc<ServerConfig>, params: Vec<u8>) -> ServerSession362 fn new_quic(config: &Arc<ServerConfig>, params: Vec<u8>) -> ServerSession {
363 assert!(
364 config
365 .versions
366 .iter()
367 .all(|x| x.get_u16() >= ProtocolVersion::TLSv1_3.get_u16()),
368 "QUIC requires TLS version >= 1.3"
369 );
370 assert!(
371 config.max_early_data_size == 0 || config.max_early_data_size == 0xffff_ffff,
372 "QUIC sessions must set a max early data of 0 or 2^32-1"
373 );
374 let mut imp =
375 ServerSessionImpl::new(config, vec![ServerExtension::TransportParameters(params)]);
376 imp.common.protocol = Protocol::Quic;
377 ServerSession { imp }
378 }
379 }
380
381 impl ServerQuicExt for ServerSession {}
382
383 #[cfg(test)]
384 mod test {
385 use super::*;
386
387 #[test]
initial_keys_test_vectors()388 fn initial_keys_test_vectors() {
389 // Test vectors based on draft 27
390 const INITIAL_SALT: [u8; 20] = [
391 0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4,
392 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02,
393 ];
394
395 const CONNECTION_ID: &[u8] = &[0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08];
396 const PACKET_NUMBER: u64 = 42;
397
398 let initial_salt = hkdf::Salt::new(hkdf::HKDF_SHA256, &INITIAL_SALT);
399 let server_keys = Keys::initial(&initial_salt, &CONNECTION_ID, false);
400 let client_keys = Keys::initial(&initial_salt, &CONNECTION_ID, true);
401
402 // Nonces
403 const SERVER_NONCE: [u8; 12] = [
404 0x5e, 0x5a, 0xe6, 0x51, 0xfd, 0x1e, 0x84, 0x95, 0xaf, 0x13, 0x50, 0xa1,
405 ];
406 assert_eq!(
407 server_keys
408 .local
409 .packet
410 .iv
411 .nonce_for(PACKET_NUMBER)
412 .as_ref(),
413 &SERVER_NONCE
414 );
415 assert_eq!(
416 client_keys
417 .remote
418 .packet
419 .iv
420 .nonce_for(PACKET_NUMBER)
421 .as_ref(),
422 &SERVER_NONCE
423 );
424 const CLIENT_NONCE: [u8; 12] = [
425 0x86, 0x81, 0x35, 0x94, 0x10, 0xa7, 0x0b, 0xb9, 0xc9, 0x2f, 0x04, 0x0a,
426 ];
427 assert_eq!(
428 server_keys
429 .remote
430 .packet
431 .iv
432 .nonce_for(PACKET_NUMBER)
433 .as_ref(),
434 &CLIENT_NONCE
435 );
436 assert_eq!(
437 client_keys
438 .local
439 .packet
440 .iv
441 .nonce_for(PACKET_NUMBER)
442 .as_ref(),
443 &CLIENT_NONCE
444 );
445
446 // Header encryption mask
447 const SAMPLE: &[u8] = &[
448 0x70, 0x02, 0x59, 0x6f, 0x99, 0xae, 0x67, 0xab, 0xf6, 0x5a, 0x58, 0x52, 0xf5, 0x4f,
449 0x58, 0xc3,
450 ];
451
452 const SERVER_MASK: [u8; 5] = [0x38, 0x16, 0x8a, 0x0c, 0x25];
453 assert_eq!(
454 server_keys
455 .local
456 .header
457 .new_mask(SAMPLE)
458 .unwrap(),
459 SERVER_MASK
460 );
461 assert_eq!(
462 client_keys
463 .remote
464 .header
465 .new_mask(SAMPLE)
466 .unwrap(),
467 SERVER_MASK
468 );
469 const CLIENT_MASK: [u8; 5] = [0xae, 0x96, 0x2e, 0x67, 0xec];
470 assert_eq!(
471 server_keys
472 .remote
473 .header
474 .new_mask(SAMPLE)
475 .unwrap(),
476 CLIENT_MASK
477 );
478 assert_eq!(
479 client_keys
480 .local
481 .header
482 .new_mask(SAMPLE)
483 .unwrap(),
484 CLIENT_MASK
485 );
486
487 const AAD: &[u8] = &[
488 0xc9, 0xff, 0x00, 0x00, 0x1b, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62,
489 0xb5, 0x00, 0x40, 0x74, 0x16, 0x8b,
490 ];
491 let aad = aead::Aad::from(AAD);
492 const PLAINTEXT: [u8; 12] = [
493 0x0d, 0x00, 0x00, 0x00, 0x00, 0x18, 0x41, 0x0a, 0x02, 0x00, 0x00, 0x56,
494 ];
495 let mut payload = PLAINTEXT;
496 let server_nonce = server_keys
497 .local
498 .packet
499 .iv
500 .nonce_for(PACKET_NUMBER);
501 let tag = server_keys
502 .local
503 .packet
504 .key
505 .seal_in_place_separate_tag(server_nonce, aad, &mut payload)
506 .unwrap();
507 assert_eq!(
508 payload,
509 [
510 0x0d, 0x91, 0x96, 0x31, 0xc0, 0xeb, 0x84, 0xf2, 0x88, 0x59, 0xfe, 0xc0
511 ]
512 );
513 assert_eq!(
514 tag.as_ref(),
515 &[
516 0xdf, 0xee, 0x06, 0x81, 0x9e, 0x7a, 0x08, 0x34, 0xe4, 0x94, 0x19, 0x79, 0x5f, 0xe0,
517 0xd7, 0x3f
518 ]
519 );
520
521 let aad = aead::Aad::from(AAD);
522 let mut payload = PLAINTEXT;
523 let client_nonce = client_keys
524 .local
525 .packet
526 .iv
527 .nonce_for(PACKET_NUMBER);
528 let tag = client_keys
529 .local
530 .packet
531 .key
532 .seal_in_place_separate_tag(client_nonce, aad, &mut payload)
533 .unwrap();
534 assert_eq!(
535 payload,
536 [
537 0x89, 0x6c, 0x66, 0x91, 0xe0, 0x9f, 0x47, 0x7a, 0x91, 0x42, 0xa4, 0x46
538 ]
539 );
540 assert_eq!(
541 tag.as_ref(),
542 &[
543 0xb6, 0xff, 0xef, 0x89, 0xd5, 0xcb, 0x53, 0xd0, 0x98, 0xf7, 0x40, 0xa, 0x8d, 0x97,
544 0x72, 0x6e
545 ]
546 );
547 }
548
549 #[test]
key_update_test_vector()550 fn key_update_test_vector() {
551 fn equal_prk(x: &hkdf::Prk, y: &hkdf::Prk) -> bool {
552 let mut x_data = [0; 16];
553 let mut y_data = [0; 16];
554 let x_okm = x
555 .expand(&[b"info"], &aead::quic::AES_128)
556 .unwrap();
557 x_okm.fill(&mut x_data[..]).unwrap();
558 let y_okm = y
559 .expand(&[b"info"], &aead::quic::AES_128)
560 .unwrap();
561 y_okm.fill(&mut y_data[..]).unwrap();
562 x_data == y_data
563 }
564
565 let initial = Secrets {
566 // Constant dummy values for reproducibility
567 client: hkdf::Prk::new_less_safe(
568 hkdf::HKDF_SHA256,
569 &[
570 0xb8, 0x76, 0x77, 0x08, 0xf8, 0x77, 0x23, 0x58, 0xa6, 0xea, 0x9f, 0xc4, 0x3e,
571 0x4a, 0xdd, 0x2c, 0x96, 0x1b, 0x3f, 0x52, 0x87, 0xa6, 0xd1, 0x46, 0x7e, 0xe0,
572 0xae, 0xab, 0x33, 0x72, 0x4d, 0xbf,
573 ],
574 ),
575 server: hkdf::Prk::new_less_safe(
576 hkdf::HKDF_SHA256,
577 &[
578 0x42, 0xdc, 0x97, 0x21, 0x40, 0xe0, 0xf2, 0xe3, 0x98, 0x45, 0xb7, 0x67, 0x61,
579 0x34, 0x39, 0xdc, 0x67, 0x58, 0xca, 0x43, 0x25, 0x9b, 0x87, 0x85, 0x06, 0x82,
580 0x4e, 0xb1, 0xe4, 0x38, 0xd8, 0x55,
581 ],
582 ),
583 };
584 let updated = next_1rtt_secrets(hkdf::HKDF_SHA256, &initial);
585
586 assert!(equal_prk(
587 &updated.client,
588 &hkdf::Prk::new_less_safe(
589 hkdf::HKDF_SHA256,
590 &[
591 0x42, 0xca, 0xc8, 0xc9, 0x1c, 0xd5, 0xeb, 0x40, 0x68, 0x2e, 0x43, 0x2e, 0xdf,
592 0x2d, 0x2b, 0xe9, 0xf4, 0x1a, 0x52, 0xca, 0x6b, 0x22, 0xd8, 0xe6, 0xcd, 0xb1,
593 0xe8, 0xac, 0xa9, 0x6, 0x1f, 0xce
594 ]
595 )
596 ));
597 assert!(equal_prk(
598 &updated.server,
599 &hkdf::Prk::new_less_safe(
600 hkdf::HKDF_SHA256,
601 &[
602 0xeb, 0x7f, 0x5e, 0x2a, 0x12, 0x3f, 0x40, 0x7d, 0xb4, 0x99, 0xe3, 0x61, 0xca,
603 0xe5, 0x90, 0xd4, 0xd9, 0x92, 0xe1, 0x4b, 0x7a, 0xce, 0x3, 0xc2, 0x44, 0xe0,
604 0x42, 0x21, 0x15, 0xb6, 0xd3, 0x8a
605 ]
606 )
607 ));
608 }
609 }
610