domain/base/opt/cookie.rs
1//! EDNS Option for DNS cookies.
2//!
3//! The option in this module – [`Cookie`] – is part of a simple mechanism
4//! that helps DNS servers to mitigate denial-of-service and amplification
5//! attacks called DNS cookies.
6//!
7//! In this mechanism, the client creates a client cookie and includes it in
8//! its request to a server. When answering, the server generates a server
9//! cookie from the client cookie and a secret and includes it in the
10//! response. When the client sends subsequent queries to the same server,
11//! it includes both the same client cookie as before and the server cookie
12//! it received, thus identifying itself as having sent a query before.
13//! Because server cookies are deterministic for a given client cookie, the
14//! server doesn’t need to keep any state other than the secret.
15//!
16//! The DNS Cookie mechanism is defined in [RFC 7873]. Guidance for creating
17//! client and server cookies is provided by [RFC 9018].
18//!
19//! [RFC 7873]: https://tools.ietf.org/html/rfc7873
20//! [RFC 9018]: https://tools.ietf.org/html/rfc9018
21
22use core::{fmt, hash};
23use octseq::array::Array;
24use octseq::builder::OctetsBuilder;
25use octseq::octets::Octets;
26use octseq::parse::Parser;
27use crate::base::Serial;
28use crate::utils::base16;
29use super::super::iana::OptionCode;
30use super::super::message_builder::OptBuilder;
31use super::super::wire::{Composer, ParseError};
32use super::{Opt, OptData, ComposeOptData, ParseOptData};
33
34
35//------------ Cookie --------------------------------------------------------
36
37/// Option data for a DNS cookie.
38///
39/// A value of this type carries two parts: A mandatory [`ClientCookie`] and
40/// an optional [`ServerCookie`]. The client cookie is chosen by, yes, the
41/// client and added to a request when contacting a specific server for the
42/// first time. When responding, a server calculates a server cookie from the
43/// client cookie and adds both of them to the response. The client remembers
44/// both and includes them in subsequent requests. The server can now check
45/// that the the server cookie was indeed calculated by it and treat the
46/// repeat customer differently.
47///
48/// While you can create a new cookie using the [`new`][Self::new] method,
49/// shortcuts are available for the standard workflow. A new initial cookie
50/// can be created via [`create_initial`][Self::create_initial]. As this will
51/// be a random client cookie, it needs the `rand` feature. The server can
52/// check whether a received cookie includes a server cookie created by it
53/// via the [`check_server_hash`][Self::check_server_hash] method. It needs
54/// the SipHash-2-4 algorithm and is thus available if the `siphasher` feature
55/// is enabled. The same feature also enables the
56/// [`create_response`][Self::create_response] method which creates the server
57/// cookie to be included in a response.
58#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
59#[cfg_attr(feature = "rand", derive(Default))]
60pub struct Cookie {
61 /// The client cookie.
62 client: ClientCookie,
63
64 /// The optional server cookie.
65 server: Option<ServerCookie>,
66}
67
68impl Cookie {
69 /// Creates a new cookie from client and optional server cookie.
70 #[must_use]
71 pub fn new(
72 client: ClientCookie,
73 server: Option<ServerCookie>
74 ) -> Self {
75 Cookie { client, server }
76 }
77
78 /// Returns the client cookie.
79 #[must_use]
80 pub fn client(&self) -> ClientCookie {
81 self.client
82 }
83
84 /// Returns a reference to the server cookie if present.
85 #[must_use]
86 pub fn server(&self) -> Option<&ServerCookie> {
87 self.server.as_ref()
88 }
89
90 /// Parses the cookie from its wire format.
91 pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
92 parser: &mut Parser<Octs>
93 ) -> Result<Self, ParseError> {
94 Ok(Cookie::new(
95 ClientCookie::parse(parser)?,
96 ServerCookie::parse_opt(parser)?,
97 ))
98 }
99
100 /// Returns whether the standard server cookie’s hash is correct.
101 ///
102 /// The `client_ip` is the source IP address of a request. The `secret`
103 /// is the server cookie secret. The timestamp is checked via the
104 /// `timestamp_ok` closure which is given the timestamp and should return
105 /// whether it is acceptable.
106 ///
107 /// Returns `false` if the cookie is not a server cookie, if it is but
108 /// not a standard server cookie, or if it is but either the timestamp
109 /// is not acceptable or the hash differs from what it should be.
110 ///
111 /// Thus, if this method returns `false`, there is no valid server cookie
112 /// and the server can proceed as if there was no server cookie as
113 /// described in section 5.2.3 of [RFC 7873].
114 ///
115 /// [RFC 7873]: https://tools.ietf.org/html/rfc7873
116 #[cfg(feature = "siphasher")]
117 pub fn check_server_hash(
118 &self,
119 client_ip: crate::base::net::IpAddr,
120 secret: &[u8; 16],
121 timestamp_ok: impl FnOnce(Serial) -> bool,
122 ) -> bool {
123 self.server.as_ref().and_then(|server| {
124 server.try_to_standard()
125 }).and_then(|server| {
126 timestamp_ok(server.timestamp()).then_some(server)
127 }).map(|server| {
128 server.check_hash(self.client(), client_ip, secret)
129 }).unwrap_or(false)
130 }
131
132 /// Creates a random client cookie for including in an initial request.
133 #[cfg(feature = "rand")]
134 #[must_use]
135 pub fn create_initial() -> Self {
136 Self::new(ClientCookie::new_random(), None)
137 }
138
139 /// Creates a standard format cookie option for sending a response.
140 ///
141 /// This method uses the client cookie and the additional values provided
142 /// to produce a cookie option that should be included in a response.
143 #[cfg(feature = "siphasher")]
144 pub fn create_response(
145 &self,
146 timestamp: Serial,
147 client_ip: crate::base::net::IpAddr,
148 secret: &[u8; 16]
149 ) -> Self {
150 Self::new(
151 self.client,
152 Some(
153 StandardServerCookie::calculate(
154 self.client, timestamp, client_ip, secret
155 ).into()
156 )
157 )
158 }
159}
160
161
162//--- OptData
163
164impl OptData for Cookie {
165 fn code(&self) -> OptionCode {
166 OptionCode::Cookie
167 }
168}
169
170impl<'a, Octs: AsRef<[u8]> + ?Sized> ParseOptData<'a, Octs> for Cookie {
171 fn parse_option(
172 code: OptionCode,
173 parser: &mut Parser<'a, Octs>,
174 ) -> Result<Option<Self>, ParseError> {
175 if code == OptionCode::Cookie {
176 Self::parse(parser).map(Some)
177 }
178 else {
179 Ok(None)
180 }
181 }
182}
183
184impl ComposeOptData for Cookie {
185 fn compose_len(&self) -> u16 {
186 match self.server.as_ref() {
187 Some(server) => {
188 ClientCookie::COMPOSE_LEN.checked_add(
189 server.compose_len()
190 ).expect("long server cookie")
191 }
192 None => ClientCookie::COMPOSE_LEN
193 }
194 }
195
196 fn compose_option<Target: OctetsBuilder + ?Sized>(
197 &self, target: &mut Target
198 ) -> Result<(), Target::AppendError> {
199 self.client.compose(target)?;
200 if let Some(server) = self.server.as_ref() {
201 server.compose(target)?;
202 }
203 Ok(())
204 }
205}
206
207impl fmt::Display for Cookie {
208 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
209 fmt::Display::fmt(&self.client, f)?;
210 if let Some(server) = self.server.as_ref() {
211 fmt::Display::fmt(server, f)?;
212 }
213 Ok(())
214 }
215}
216
217
218//--- Extending Opt and OptBuilder
219
220impl<Octs: Octets> Opt<Octs> {
221 /// Returns the first cookie option if present.
222 pub fn cookie(&self) -> Option<Cookie> {
223 self.first()
224 }
225}
226
227impl<'a, Target: Composer> OptBuilder<'a, Target> {
228 /// Appends a new cookie option.
229 pub fn cookie(
230 &mut self, cookie: Cookie,
231 ) -> Result<(), Target::AppendError> {
232 self.push(&cookie)
233 }
234
235 /// Appends a new initial client cookie.
236 ///
237 /// The appened cookie will have a random client cookie portion and no
238 /// server cookie. See [`Cookie`] for more information about cookies.
239 #[cfg(feature = "rand")]
240 pub fn initial_cookie(&mut self) -> Result<(), Target::AppendError> {
241 self.push(&Cookie::create_initial())
242 }
243}
244
245
246//------------ ClientCookie --------------------------------------------------
247
248/// A client cookie for DNS cookies.
249///
250/// The client cookies consists of exactly 8 octets. It is generated by a
251/// client for each server it sends queries to. It is important to use a
252/// different cookie for every server so a server cannot spoof answers for
253/// other servers.
254///
255/// Originally, it was suggested to include the client’s IP address when
256/// generating the cookie, but since the address may not be known when
257/// originating a request, this has been relaxed and it is now suggested that
258/// the cookies is just random data. If the `rand` feature is enabled, the
259/// `new`
260#[cfg_attr(feature = "rand", doc = "[`new_random`][ClientCookie::new_random]")]
261#[cfg_attr(not(feature = "rand"), doc = "`new_random`")]
262/// constructor can be used to generate such a random cookie. Otherwise,
263/// it needs to be created from the octets via
264/// [`from_octets`][ClientCookie::from_octets]. Similarly, the `Default`
265/// implementation will create a random cookie and is thus only available if
266/// the `rand` feature is enabled.
267#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
268pub struct ClientCookie([u8; 8]);
269
270impl ClientCookie {
271 /// Creates a new client cookie from the given octets.
272 #[must_use]
273 pub const fn from_octets(octets: [u8; 8]) -> Self {
274 Self(octets)
275 }
276
277 /// Creates a new random client cookie.
278 #[cfg(feature = "rand")]
279 #[must_use]
280 pub fn new_random() -> Self {
281 Self(rand::random())
282 }
283
284 /// Converts the cookie into its octets.
285 #[must_use]
286 pub fn into_octets(self) -> [u8; 8] {
287 self.0
288 }
289
290 /// Parses a client cookie from its wire format.
291 pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
292 parser: &mut Parser<Octs>
293 ) -> Result<Self, ParseError> {
294 let mut res = Self::from_octets([0; 8]);
295 parser.parse_buf(res.as_mut())?;
296 Ok(res)
297 }
298
299 /// The length of the wire format of a client cookie.
300 pub const COMPOSE_LEN: u16 = 8;
301
302 /// Appends the wire format of the client cookie to the target.
303 pub fn compose<Target: OctetsBuilder + ?Sized>(
304 &self, target: &mut Target
305 ) -> Result<(), Target::AppendError> {
306 target.append_slice(&self.0)
307 }
308}
309
310//--- Default
311
312#[cfg(feature = "rand")]
313impl Default for ClientCookie {
314 fn default() -> Self {
315 Self::new_random()
316 }
317}
318
319//--- From
320
321impl From<[u8; 8]> for ClientCookie {
322 fn from(src: [u8; 8]) -> Self {
323 Self::from_octets(src)
324 }
325}
326
327impl From<ClientCookie> for [u8; 8] {
328 fn from(src: ClientCookie) -> Self {
329 src.0
330 }
331}
332
333//--- AsRef and AsMut
334
335impl AsRef<[u8]> for ClientCookie {
336 fn as_ref(&self) -> &[u8] {
337 self.0.as_ref()
338 }
339}
340
341impl AsMut<[u8]> for ClientCookie {
342 fn as_mut(&mut self) -> &mut [u8] {
343 self.0.as_mut()
344 }
345}
346
347//--- Hash
348
349impl hash::Hash for ClientCookie {
350 fn hash<H: hash::Hasher>(&self, state: &mut H) {
351 state.write(&self.0)
352 }
353}
354
355//--- Display
356
357impl fmt::Display for ClientCookie {
358 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
359 base16::display(self.0.as_ref(), f)
360 }
361}
362
363
364//------------ ServerCookie --------------------------------------------------
365
366/// A server cookie for DNS cookies.
367///
368/// In the original specification, the server cookie was of variable length
369/// between 8 and 32 octets. It was supposed to be generated via some sort
370/// of message authentication code from the client cookie and a server secret.
371/// Leaving the concrete mechanism to the implementer resulted in
372/// interoperability problems if servers from multiple vendors were placed
373/// behind the same public address. Thus, [RFC 9018] defined a standard
374/// mechanism of the content and generation of the cookie.
375///
376/// This standard server cookie consists of a 1 octet version number
377/// (currently 1), 3 reserved octets that must be zero, a 4 octet timestamp
378/// as seconds since the Unix epoch, and 8 octets of hash value.
379///
380/// In version 1, the hash is calculated feeding the SipHash-2-4 that has been
381/// initialized with a server secret the concatenation of client cookie,
382/// version, reserved, timestamp, client IP address.
383///
384/// [RFC 9018]: https://tools.ietf.org/html/rfc9018
385#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
386pub struct ServerCookie(Array<32>);
387
388impl ServerCookie {
389 /// Creates a new server cookie from the given octets.
390 ///
391 /// # Panics
392 ///
393 /// The function panics if `octets` is shorter than 8 octets or longer
394 /// than 32.
395 #[must_use]
396 pub fn from_octets(slice: &[u8]) -> Self {
397 assert!(slice.len() >= 8, "server cookie shorter than 8 octets");
398 let mut res = Array::new();
399 res.append_slice(slice).expect("server cookie longer tha 32 octets");
400 Self(res)
401 }
402
403 /// Parses a server cookie from its wire format.
404 pub fn parse<Octs: AsRef<[u8]> + ?Sized>(
405 parser: &mut Parser<Octs>
406 ) -> Result<Self, ParseError> {
407 if parser.remaining() < 8 {
408 return Err(ParseError::form_error("short server cookie"))
409 }
410 let mut res = Array::new();
411 res.resize_raw(parser.remaining()).map_err(|_| {
412 ParseError::form_error("long server cookie")
413 })?;
414 parser.parse_buf(res.as_slice_mut())?;
415 Ok(Self(res))
416 }
417
418 /// Parses an optional server cookie from its wire format.
419 pub fn parse_opt<Octs: AsRef<[u8]> + ?Sized>(
420 parser: &mut Parser<Octs>
421 ) -> Result<Option<Self>, ParseError> {
422 if parser.remaining() > 0 {
423 Self::parse(parser).map(Some)
424 }
425 else {
426 Ok(None)
427 }
428 }
429
430 /// Converts the cookie into a standard cookie if possible.
431 ///
432 /// This is possible if the length of the cookie is 16 octets. Returns
433 /// `None` otherwise.
434 pub fn try_to_standard(&self) -> Option<StandardServerCookie> {
435 TryFrom::try_from(self.0.as_slice()).map(StandardServerCookie).ok()
436 }
437
438 /// Returns the length of the wire format of the cookie.
439 #[must_use]
440 pub fn compose_len(&self) -> u16 {
441 u16::try_from(self.0.len()).expect("long server cookie")
442 }
443
444 /// Appends the wire format of the cookie to the target.
445 pub fn compose<Target: OctetsBuilder + ?Sized>(
446 &self, target: &mut Target
447 ) -> Result<(), Target::AppendError> {
448 target.append_slice(self.0.as_ref())
449 }
450}
451
452//--- From
453
454impl From<StandardServerCookie> for ServerCookie {
455 fn from(src: StandardServerCookie) -> Self {
456 Self::from_octets(&src.0)
457 }
458}
459
460//--- AsRef
461
462impl AsRef<[u8]> for ServerCookie {
463 fn as_ref(&self) -> &[u8] {
464 self.0.as_ref()
465 }
466}
467
468//--- Display
469
470impl fmt::Display for ServerCookie {
471 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
472 base16::display(self.0.as_ref(), f)
473 }
474}
475
476
477//------------ StandardServerCookie ------------------------------------------
478
479/// An interoperable server cookie for DNS cookies.
480///
481/// In the original specification, the server cookie was of variable length
482/// and rules for its generation were left to the server implementers. This
483/// resulted in interoperability problems if servers from multiple vendors
484/// were placed behind the same public address. Thus, [RFC 9018] defined a
485/// standard mechanism of the content and generation of the cookie. This
486/// type is such a standard server cookie.
487///
488/// This standard server cookie consists of a 1 octet version number
489/// (currently 1), 3 reserved octets that must be zero, a 4 octet timestamp
490/// as seconds since the Unix epoch, and 8 octets of hash value.
491///
492/// In version 1, the hash is calculated feeding the SipHash-2-4 that has been
493/// initialized with a server secret the concatenation of client cookie,
494/// version, reserved, timestamp, client IP address. Generatin and checking
495/// the hash is available if the `siphasher` feature is enabled.
496///
497/// [RFC 9018]: https://tools.ietf.org/html/rfc9018
498#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
499pub struct StandardServerCookie(
500 // We let this type wrap a u8 array so we can provide AsRef<[u8]> it.
501 // This makes reading the timestamp a tiny bit expensive on certain
502 // systems, but so be it.
503 [u8; 16]
504);
505
506impl StandardServerCookie {
507 /// Creates a new server cookie from the provided components.
508 #[must_use]
509 pub fn new(
510 version: u8,
511 reserved: [u8; 3],
512 timestamp: Serial,
513 hash: [u8; 8]
514 ) -> Self {
515 let ts = timestamp.into_int().to_be_bytes();
516 Self(
517 [ version, reserved[0], reserved[1], reserved[2],
518 ts[0], ts[1], ts[2], ts[3],
519 hash[0], hash[1], hash[2], hash[3],
520 hash[4], hash[5], hash[6], hash[7],
521 ]
522 )
523 }
524
525 /// Calculates the server cookie for the given components.
526 #[cfg(feature = "siphasher")]
527 pub fn calculate(
528 client_cookie: ClientCookie,
529 timestamp: Serial,
530 client_ip: crate::base::net::IpAddr,
531 secret: &[u8; 16]
532 ) -> Self {
533 let mut res = Self::new(1, [0; 3], timestamp, [0; 8]);
534 res.set_hash(
535 res.calculate_hash(client_cookie, client_ip, secret)
536 );
537 res
538 }
539
540 /// Returns the version field of the cookie.
541 #[must_use]
542 pub fn version(self) -> u8 {
543 self.0[0]
544 }
545
546 /// Returns the reserved field of the cookie.
547 #[must_use]
548 pub fn reserved(self) -> [u8; 3] {
549 TryFrom::try_from(&self.0[1..4]).expect("bad slicing")
550 }
551
552 /// Returns the timestamp field of the cookie.
553 #[must_use]
554 pub fn timestamp(self) -> Serial {
555 Serial::from_be_bytes(
556 TryFrom::try_from(&self.0[4..8]).expect("bad slicing")
557 )
558 }
559
560 /// Returns the hash field of the cookie.
561 #[must_use]
562 pub fn hash(self) -> [u8; 8] {
563 TryFrom::try_from(&self.0[8..]).expect("bad slicing")
564 }
565
566 /// Sets the hash field to the given value.
567 pub fn set_hash(&mut self, hash: [u8; 8]) {
568 self.0[8..].copy_from_slice(&hash);
569 }
570
571 /// Returns whether the hash matches the given client cookie and secret.
572 #[cfg(feature = "siphasher")]
573 pub fn check_hash(
574 self,
575 client_cookie: ClientCookie,
576 client_ip: crate::base::net::IpAddr,
577 secret: &[u8; 16]
578 ) -> bool {
579 self.calculate_hash(client_cookie, client_ip, secret) == self.hash()
580 }
581
582 /// Calculates the hash value.
583 ///
584 /// The method takes the version, reserved, and timestamp fields from
585 /// `self` and the rest from the arguments. It returns the hash as an
586 /// octets array.
587 //
588 // XXX The hash implementation for SipHash-2-4 returns the result as
589 // a `u64` whereas RFC 9018 assumes it is returned as an octets array in
590 // a standard ordering. Somewhat surprisingly, this ordering turns out to
591 // be little endian.
592 #[cfg(feature = "siphasher")]
593 fn calculate_hash(
594 self,
595 client_cookie: ClientCookie,
596 client_ip: crate::base::net::IpAddr,
597 secret: &[u8; 16]
598 ) -> [u8; 8] {
599 use core::hash::{Hash, Hasher};
600 use crate::base::net::IpAddr;
601
602 let mut hasher = siphasher::sip::SipHasher24::new_with_key(secret);
603 client_cookie.hash(&mut hasher);
604 hasher.write(&self.0[..8]);
605 match client_ip {
606 IpAddr::V4(addr) => hasher.write(&addr.octets()),
607 IpAddr::V6(addr) => hasher.write(&addr.octets()),
608 }
609 hasher.finish().to_le_bytes()
610 }
611}
612
613//--- Display
614
615impl fmt::Display for StandardServerCookie {
616 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
617 base16::display(self.0.as_ref(), f)
618 }
619}
620
621
622//============ Tests =========================================================
623
624#[cfg(test)]
625mod test {
626 #[allow(unused_imports)]
627 use super::*;
628
629 /// Tests from Appendix A of RFC 9018.
630 #[cfg(all(feature = "siphasher", feature = "std"))]
631 mod standard_server {
632 use crate::base::net::{IpAddr, Ipv4Addr, Ipv6Addr};
633 use crate::base::wire::{compose_vec, parse_slice};
634 use super::*;
635
636 const CLIENT_1: IpAddr = IpAddr::V4(Ipv4Addr::new(198, 51, 100, 100));
637 const CLIENT_2: IpAddr = IpAddr::V4(Ipv4Addr::new(203, 0, 113, 203));
638 const CLIENT_6: IpAddr = IpAddr::V6(Ipv6Addr::new(
639 0x2001, 0xdb8, 0x220, 0x1, 0x59de, 0xd0f4, 0x8769, 0x82b8
640 ));
641
642 const SECRET: [u8; 16] = [
643 0xe5, 0xe9, 0x73, 0xe5, 0xa6, 0xb2, 0xa4, 0x3f,
644 0x48, 0xe7, 0xdc, 0x84, 0x9e, 0x37, 0xbf, 0xcf,
645 ];
646
647 /// A.1. Learning a New Server Cookie
648 #[test]
649 fn new_cookie() {
650 let request = Cookie::new(
651 ClientCookie::from_octets(
652 [ 0x24, 0x64, 0xc4, 0xab, 0xcf, 0x10, 0xc9, 0x57 ]
653 ),
654 None
655 );
656 assert_eq!(
657 compose_vec(|vec| request.compose_option(vec)),
658 base16::decode_vec("2464c4abcf10c957").unwrap()
659 );
660
661 assert_eq!(
662 compose_vec(|vec| {
663 request.create_response(
664 Serial(1559731985), CLIENT_1, &SECRET
665 ).compose_option(vec)
666 }),
667 base16::decode_vec(
668 "2464c4abcf10c957010000005cf79f111f8130c3eee29480"
669 ).unwrap()
670 );
671 }
672
673 /// A.2. The Same Client Learning a Renewed (Fresh) Server Cookie
674 #[test]
675 fn renew_cookie() {
676 let request = parse_slice(
677 &base16::decode_vec(
678 "2464c4abcf10c957010000005cf79f111f8130c3eee29480"
679 ).unwrap(),
680 Cookie::parse
681 ).unwrap();
682 assert!(
683 request.check_server_hash(
684 CLIENT_1, &SECRET,
685 |serial| serial == Serial(1559731985)
686 )
687 );
688
689 assert_eq!(
690 compose_vec(|vec| {
691 request.create_response(
692 Serial(1559734385), CLIENT_1, &SECRET
693 ).compose_option(vec)
694 }),
695 base16::decode_vec(
696 "2464c4abcf10c957010000005cf7a871d4a564a1442aca77"
697 ).unwrap()
698 );
699 }
700
701 /// A.3. Another Client Learning a Renewed Server Cookie
702 #[test]
703 fn non_zero_reserved() {
704 let request = parse_slice(
705 &base16::decode_vec(
706 "fc93fc62807ddb8601abcdef5cf78f71a314227b6679ebf5"
707 ).unwrap(),
708 Cookie::parse
709 ).unwrap();
710 assert!(
711 request.check_server_hash(
712 CLIENT_2, &SECRET,
713 |serial| serial == Serial(1559727985)
714 )
715 );
716
717 assert_eq!(
718 compose_vec(|vec| {
719 request.create_response(
720 Serial(1559734700), CLIENT_2, &SECRET
721 ).compose_option(vec)
722 }),
723 base16::decode_vec(
724 "fc93fc62807ddb86010000005cf7a9acf73a7810aca2381e"
725 ).unwrap()
726 );
727 }
728
729 /// A.4. IPv6 Query with Rolled Over Secret
730 #[test]
731 fn new_secret() {
732
733 const OLD_SECRET: [u8; 16] = [
734 0xdd, 0x3b, 0xdf, 0x93, 0x44, 0xb6, 0x78, 0xb1,
735 0x85, 0xa6, 0xf5, 0xcb, 0x60, 0xfc, 0xa7, 0x15,
736 ];
737 const NEW_SECRET: [u8; 16] = [
738 0x44, 0x55, 0x36, 0xbc, 0xd2, 0x51, 0x32, 0x98,
739 0x07, 0x5a, 0x5d, 0x37, 0x96, 0x63, 0xc9, 0x62,
740 ];
741
742 let request = parse_slice(
743 &base16::decode_vec(
744 "22681ab97d52c298010000005cf7c57926556bd0934c72f8"
745 ).unwrap(),
746 Cookie::parse
747 ).unwrap();
748 assert!(
749 !request.check_server_hash(
750 CLIENT_6, &NEW_SECRET,
751 |serial| serial == Serial(1559741817)
752 )
753 );
754 assert!(
755 request.check_server_hash(
756 CLIENT_6, &OLD_SECRET,
757 |serial| serial == Serial(1559741817)
758 )
759 );
760
761 assert_eq!(
762 compose_vec(|vec| {
763 request.create_response(
764 Serial(1559741961), CLIENT_6, &NEW_SECRET
765 ).compose_option(vec)
766 }),
767 base16::decode_vec(
768 "22681ab97d52c298010000005cf7c609a6bb79d16625507a"
769 ).unwrap()
770 );
771 }
772 }
773}
774