]> git.bitcoin.ninja Git - dnssec-prover/blob - src/query.rs
Add a fuzzer of the proof building state machine
[dnssec-prover] / src / query.rs
1 //! This module exposes utilities for building DNSSEC proofs by directly querying a recursive
2 //! resolver.
3
4 use core::{cmp, ops};
5 use alloc::vec;
6 use alloc::vec::Vec;
7
8 #[cfg(feature = "std")]
9 use std::net::{SocketAddr, TcpStream};
10 #[cfg(feature = "std")]
11 use std::io::{Read, Write, Error, ErrorKind};
12
13 #[cfg(feature = "tokio")]
14 use tokio_crate::net::TcpStream as TokioTcpStream;
15 #[cfg(feature = "tokio")]
16 use tokio_crate::io::{AsyncReadExt, AsyncWriteExt};
17
18 use crate::rr::*;
19 use crate::ser::*;
20
21 // In testing use a rather small buffer to ensure we hit the allocation paths sometimes. In
22 // production, we should generally never actually need to go to heap as DNS messages are rarely
23 // larger than a KiB or two.
24 #[cfg(any(test, fuzzing))]
25 const STACK_BUF_LIMIT: u16 = 32;
26 #[cfg(not(any(test, fuzzing)))]
27 const STACK_BUF_LIMIT: u16 = 2048;
28
29 /// A buffer for storing queries and responses.
30 #[derive(Clone, PartialEq, Eq)]
31 pub struct QueryBuf {
32         buf: [u8; STACK_BUF_LIMIT as usize],
33         heap_buf: Vec<u8>,
34         len: u16,
35 }
36 impl QueryBuf {
37         fn new_zeroed(len: u16) -> Self {
38                 let heap_buf = if len > STACK_BUF_LIMIT { vec![0; len as usize] } else { Vec::new() };
39                 Self {
40                         buf: [0; STACK_BUF_LIMIT as usize],
41                         heap_buf,
42                         len
43                 }
44         }
45         pub(crate) fn extend_from_slice(&mut self, sl: &[u8]) {
46                 let new_len = self.len.saturating_add(sl.len() as u16);
47                 let was_heap = self.len > STACK_BUF_LIMIT;
48                 let is_heap = new_len > STACK_BUF_LIMIT;
49                 if was_heap != is_heap {
50                         self.heap_buf = vec![0; new_len as usize];
51                         self.heap_buf[..self.len as usize].copy_from_slice(&self.buf[..self.len as usize]);
52                 }
53                 let target = if is_heap {
54                         self.heap_buf.resize(new_len as usize, 0);
55                         &mut self.heap_buf[self.len as usize..]
56                 } else {
57                         &mut self.buf[self.len as usize..new_len as usize]
58                 };
59                 target.copy_from_slice(sl);
60                 self.len = new_len;
61         }
62 }
63 impl ops::Deref for QueryBuf {
64         type Target = [u8];
65         fn deref(&self) -> &[u8] {
66                 if self.len > STACK_BUF_LIMIT {
67                         &self.heap_buf
68                 } else {
69                         &self.buf[..self.len as usize]
70                 }
71         }
72 }
73 impl ops::DerefMut for QueryBuf {
74         fn deref_mut(&mut self) -> &mut [u8] {
75                 if self.len > STACK_BUF_LIMIT {
76                         &mut self.heap_buf
77                 } else {
78                         &mut self.buf[..self.len as usize]
79                 }
80         }
81 }
82
83 // We don't care about transaction IDs as we're only going to accept signed data. Thus, we use
84 // this constant instead of a random value.
85 const TXID: u16 = 0x4242;
86
87 fn build_query(domain: &Name, ty: u16) -> QueryBuf {
88         let mut query = QueryBuf::new_zeroed(0);
89         let query_msg_len: u16 = 2 + 2 + 8 + 2 + 2 + name_len(domain) + 11;
90         query.extend_from_slice(&query_msg_len.to_be_bytes());
91         query.extend_from_slice(&TXID.to_be_bytes());
92         query.extend_from_slice(&[0x01, 0x20]); // Flags: Recursive, Authenticated Data
93         query.extend_from_slice(&[0, 1, 0, 0, 0, 0, 0, 1]); // One question, One additional
94         write_name(&mut query, domain);
95         query.extend_from_slice(&ty.to_be_bytes());
96         query.extend_from_slice(&1u16.to_be_bytes()); // INternet class
97         query.extend_from_slice(&[0, 0, 0x29]); // . OPT
98         query.extend_from_slice(&0u16.to_be_bytes()); // 0 UDP payload size
99         query.extend_from_slice(&[0, 0]); // EDNS version 0
100         query.extend_from_slice(&0x8000u16.to_be_bytes()); // Accept DNSSEC RRs
101         query.extend_from_slice(&0u16.to_be_bytes()); // No additional data
102         query
103 }
104
105 #[cfg(fuzzing)]
106 /// Read some input and parse it as if it came from a server, for fuzzing.
107 pub fn fuzz_response(response: &[u8]) {
108         let (mut proof, mut names) = (Vec::new(), Vec::new());
109         let _ = handle_response(response, &mut proof, &mut names);
110 }
111
112 fn handle_response(resp: &[u8], proof: &mut Vec<u8>, rrsig_key_names: &mut Vec<Name>) -> Result<u32, ()> {
113         let mut read: &[u8] = resp;
114         if read_u16(&mut read)? != TXID { return Err(()); }
115         // 2 byte transaction ID
116         let flags = read_u16(&mut read)?;
117         if flags & 0b1000_0000_0000_0000 == 0 {
118                 return Err(());
119         }
120         if flags & 0b0111_1010_0000_0111 != 0 {
121                 return Err(());
122         }
123         if flags & 0b10_0000 == 0 {
124                 return Err(());
125         }
126         let questions = read_u16(&mut read)?;
127         if questions != 1 { return Err(()); }
128         let answers = read_u16(&mut read)?;
129         if answers == 0 { return Err(()); }
130         let _authorities = read_u16(&mut read)?;
131         let _additional = read_u16(&mut read)?;
132
133         for _ in 0..questions {
134                 read_wire_packet_name(&mut read, resp)?;
135                 read_u16(&mut read)?; // type
136                 read_u16(&mut read)?; // class
137         }
138
139         // Only read the answers (skip authorities and additional) as that's all we care about.
140         let mut min_ttl = u32::MAX;
141         for _ in 0..answers {
142                 let (rr, ttl) = parse_wire_packet_rr(&mut read, &resp)?;
143                 write_rr(&rr, ttl, proof);
144                 min_ttl = cmp::min(min_ttl, ttl);
145                 if let RR::RRSig(rrsig) = rr { rrsig_key_names.push(rrsig.key_name); }
146         }
147         Ok(min_ttl)
148 }
149
150 #[cfg(fuzzing)]
151 /// Read a stream of responses and handle them it as if they came from a server, for fuzzing.
152 pub fn fuzz_proof_builder(mut response_stream: &[u8]) {
153         let (mut builder, _) = ProofBuilder::new(&"example.com.".try_into().unwrap(), Txt::TYPE);
154         while builder.awaiting_responses() {
155                 let len = if let Ok(len) = read_u16(&mut response_stream) { len } else { return };
156                 let mut buf = QueryBuf::new_zeroed(len);
157                 if response_stream.len() < len as usize { return; }
158                 buf.copy_from_slice(&response_stream[..len as usize]);
159                 response_stream = &response_stream[len as usize..];
160                 let _ = builder.process_response(&buf);
161         }
162         let _ = builder.finish_proof();
163 }
164
165 const MAX_REQUESTS: usize = 10;
166 /// A simple state machine which will generate a series of queries and process the responses until
167 /// it has built a DNSSEC proof.
168 ///
169 /// A [`ProofBuilder`] driver starts with [`ProofBuilder::new`], fetching the state machine and
170 /// initial query. As long as [`ProofBuilder::awaiting_responses`] returns true, responses should
171 /// be read from the resolver. For each query response read from the DNS resolver,
172 /// [`ProofBuilder::process_response`] should be called, and each fresh query returned should be
173 /// sent to the resolver. Once [`ProofBuilder::awaiting_responses`] returns false,
174 /// [`ProofBuilder::finish_proof`] should be called to fetch the resulting proof.
175 pub struct ProofBuilder {
176         proof: Vec<u8>,
177         min_ttl: u32,
178         dnskeys_requested: Vec<Name>,
179         pending_queries: usize,
180         queries_made: usize,
181 }
182
183 impl ProofBuilder {
184         /// Constructs a new [`ProofBuilder`] and an initial query to send to the recursive resolver to
185         /// begin the proof building process.
186         ///
187         /// Given a correctly-functioning resolver the proof will ultimately be able to prove the
188         /// contents of any records with the given `ty`pe at the given `name` (as long as the given
189         /// `ty`pe is supported by this library).
190         ///
191         /// You can find constants for supported standard types in the [`crate::rr`] module.
192         pub fn new(name: &Name, ty: u16) -> (ProofBuilder, QueryBuf) {
193                 let initial_query = build_query(name, ty);
194                 (ProofBuilder {
195                         proof: Vec::new(),
196                         min_ttl: u32::MAX,
197                         dnskeys_requested: Vec::with_capacity(MAX_REQUESTS),
198                         pending_queries: 1,
199                         queries_made: 1,
200                 }, initial_query)
201         }
202
203         /// Returns true as long as further responses are expected from the resolver.
204         ///
205         /// As long as this returns true, responses should be read from the resolver and passed to
206         /// [`Self::process_response`]. Once this returns false, [`Self::finish_proof`] should be used
207         /// to (possibly) get the final proof.
208         pub fn awaiting_responses(&self) -> bool {
209                 self.pending_queries > 0 && self.queries_made <= MAX_REQUESTS
210         }
211
212         /// Processes a query response from the recursive resolver, returning a list of new queries to
213         /// send to the resolver.
214         pub fn process_response(&mut self, resp: &QueryBuf) -> Result<Vec<QueryBuf>, ()> {
215                 if self.pending_queries == 0 { return Err(()); }
216
217                 let mut rrsig_key_names = Vec::new();
218                 let min_ttl = handle_response(&resp, &mut self.proof, &mut rrsig_key_names)?;
219                 self.min_ttl = cmp::min(self.min_ttl, min_ttl);
220                 self.pending_queries -= 1;
221
222                 rrsig_key_names.sort_unstable();
223                 rrsig_key_names.dedup();
224
225                 let mut new_queries = Vec::with_capacity(2);
226                 for key_name in rrsig_key_names.drain(..) {
227                         if !self.dnskeys_requested.contains(&key_name) {
228                                 new_queries.push(build_query(&key_name, DnsKey::TYPE));
229                                 self.pending_queries += 1;
230                                 self.queries_made += 1;
231                                 self.dnskeys_requested.push(key_name.clone());
232
233                                 if key_name.as_str() != "." {
234                                         new_queries.push(build_query(&key_name, DS::TYPE));
235                                         self.pending_queries += 1;
236                                         self.queries_made += 1;
237                                 }
238                         }
239                 }
240                 if self.queries_made <= MAX_REQUESTS {
241                         Ok(new_queries)
242                 } else {
243                         Ok(Vec::new())
244                 }
245         }
246
247         /// Finalizes the proof, if one is available, and returns it as well as the TTL that should be
248         /// used to cache the proof (i.e. the lowest TTL of all records which were used to build the
249         /// proof).
250         pub fn finish_proof(self) -> Result<(Vec<u8>, u32), ()> {
251                 if self.pending_queries > 0 || self.queries_made > MAX_REQUESTS {
252                         Err(())
253                 } else {
254                         Ok((self.proof, self.min_ttl))
255                 }
256         }
257 }
258
259 #[cfg(feature = "std")]
260 fn send_query(stream: &mut TcpStream, query: &[u8]) -> Result<(), Error> {
261         stream.write_all(&query)?;
262         Ok(())
263 }
264
265 #[cfg(feature = "tokio")]
266 async fn send_query_async(stream: &mut TokioTcpStream, query: &[u8]) -> Result<(), Error> {
267         stream.write_all(&query).await?;
268         Ok(())
269 }
270
271 #[cfg(feature = "std")]
272 fn read_response(stream: &mut TcpStream) -> Result<QueryBuf, Error> {
273         let mut len_bytes = [0; 2];
274         stream.read_exact(&mut len_bytes)?;
275         let mut buf = QueryBuf::new_zeroed(u16::from_be_bytes(len_bytes));
276         stream.read_exact(&mut buf)?;
277         Ok(buf)
278 }
279
280 #[cfg(feature = "tokio")]
281 async fn read_response_async(stream: &mut TokioTcpStream) -> Result<QueryBuf, Error> {
282         let mut len_bytes = [0; 2];
283         stream.read_exact(&mut len_bytes).await?;
284         let mut buf = QueryBuf::new_zeroed(u16::from_be_bytes(len_bytes));
285         stream.read_exact(&mut buf).await?;
286         Ok(buf)
287 }
288
289 #[cfg(feature = "std")]
290 macro_rules! build_proof_impl {
291         ($stream: ident, $send_query: ident, $read_response: ident, $domain: expr, $ty: expr $(, $async_ok: tt)?) => { {
292                 // We require the initial query to have already gone out, and assume our resolver will
293                 // return any CNAMEs all the way to the final record in the response. From there, we just
294                 // have to take any RRSIGs in the response and walk them up to the root. We do so
295                 // iteratively, sending DNSKEY and DS lookups after every response, deduplicating requests
296                 // using `dnskeys_requested`.
297                 let (mut builder, initial_query) = ProofBuilder::new($domain, $ty);
298                 $send_query(&mut $stream, &initial_query)
299                         $(.await?; $async_ok)??; // Either await?; Ok(())?, or just ?
300                 while builder.awaiting_responses() {
301                         let response = $read_response(&mut $stream)
302                                 $(.await?; $async_ok)??; // Either await?; Ok(())?, or just ?
303                         let new_queries = builder.process_response(&response)
304                                 .map_err(|()| Error::new(ErrorKind::Other, "Bad response"))?;
305                         for query in new_queries {
306                                 $send_query(&mut $stream, &query)
307                                         $(.await?; $async_ok)??; // Either await?; Ok(())?, or just ?
308                         }
309                 }
310
311                 builder.finish_proof()
312                         .map_err(|()| Error::new(ErrorKind::Other, "Too many requests required"))
313         } }
314 }
315
316 #[cfg(feature = "std")]
317 fn build_proof(resolver: SocketAddr, domain: &Name, ty: u16) -> Result<(Vec<u8>, u32), Error> {
318         let mut stream = TcpStream::connect(resolver)?;
319         build_proof_impl!(stream, send_query, read_response, domain, ty)
320 }
321
322 #[cfg(feature = "tokio")]
323 async fn build_proof_async(resolver: SocketAddr, domain: &Name, ty: u16) -> Result<(Vec<u8>, u32), Error> {
324         let mut stream = TokioTcpStream::connect(resolver).await?;
325         build_proof_impl!(stream, send_query_async, read_response_async, domain, ty, { Ok::<(), Error>(()) })
326 }
327
328 /// Builds a DNSSEC proof for an A record by querying a recursive resolver, returning the proof as
329 /// well as the TTL for the proof provided by the recursive resolver.
330 ///
331 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
332 /// module to validate the records contained.
333 #[cfg(feature = "std")]
334 pub fn build_a_proof(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
335         build_proof(resolver, domain, A::TYPE)
336 }
337
338 /// Builds a DNSSEC proof for an AAAA record by querying a recursive resolver, returning the proof
339 /// as well as the TTL for the proof provided by the recursive resolver.
340 ///
341 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
342 /// module to validate the records contained.
343 #[cfg(feature = "std")]
344 pub fn build_aaaa_proof(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
345         build_proof(resolver, domain, AAAA::TYPE)
346 }
347
348 /// Builds a DNSSEC proof for an TXT record by querying a recursive resolver, returning the proof
349 /// as well as the TTL for the proof provided by the recursive resolver.
350 ///
351 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
352 /// module to validate the records contained.
353 #[cfg(feature = "std")]
354 pub fn build_txt_proof(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
355         build_proof(resolver, domain, Txt::TYPE)
356 }
357
358 /// Builds a DNSSEC proof for an TLSA record by querying a recursive resolver, returning the proof
359 /// as well as the TTL for the proof provided by the recursive resolver.
360 ///
361 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
362 /// module to validate the records contained.
363 #[cfg(feature = "std")]
364 pub fn build_tlsa_proof(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
365         build_proof(resolver, domain, TLSA::TYPE)
366 }
367
368
369 /// Builds a DNSSEC proof for an A record by querying a recursive resolver, returning the proof as
370 /// well as the TTL for the proof provided by the recursive resolver.
371 ///
372 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
373 /// module to validate the records contained.
374 #[cfg(feature = "tokio")]
375 pub async fn build_a_proof_async(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
376         build_proof_async(resolver, domain, A::TYPE).await
377 }
378
379 /// Builds a DNSSEC proof for an AAAA record by querying a recursive resolver, returning the proof
380 /// as well as the TTL for the proof provided by the recursive resolver.
381 ///
382 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
383 /// module to validate the records contained.
384 #[cfg(feature = "tokio")]
385 pub async fn build_aaaa_proof_async(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
386         build_proof_async(resolver, domain, AAAA::TYPE).await
387 }
388
389 /// Builds a DNSSEC proof for an TXT record by querying a recursive resolver, returning the proof
390 /// as well as the TTL for the proof provided by the recursive resolver.
391 ///
392 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
393 /// module to validate the records contained.
394 #[cfg(feature = "tokio")]
395 pub async fn build_txt_proof_async(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
396         build_proof_async(resolver, domain, Txt::TYPE).await
397 }
398
399 /// Builds a DNSSEC proof for an TLSA record by querying a recursive resolver, returning the proof
400 /// as well as the TTL for the proof provided by the recursive resolver.
401 ///
402 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
403 /// module to validate the records contained.
404 #[cfg(feature = "tokio")]
405 pub async fn build_tlsa_proof_async(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
406         build_proof_async(resolver, domain, TLSA::TYPE).await
407 }
408
409 #[cfg(all(feature = "validation", feature = "std", test))]
410 mod tests {
411         use super::*;
412         use crate::validation::*;
413
414         use rand::seq::SliceRandom;
415
416         use std::net::ToSocketAddrs;
417         use std::time::SystemTime;
418
419         #[test]
420         fn test_cloudflare_txt_query() {
421                 let sockaddr = "8.8.8.8:53".to_socket_addrs().unwrap().next().unwrap();
422                 let query_name = "cloudflare.com.".try_into().unwrap();
423                 let (proof, _) = build_txt_proof(sockaddr, &query_name).unwrap();
424
425                 let mut rrs = parse_rr_stream(&proof).unwrap();
426                 rrs.shuffle(&mut rand::rngs::OsRng);
427                 let verified_rrs = verify_rr_stream(&rrs).unwrap();
428                 assert!(verified_rrs.verified_rrs.len() > 1);
429
430                 let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
431                 assert!(verified_rrs.valid_from < now);
432                 assert!(verified_rrs.expires > now);
433         }
434
435         #[test]
436         fn test_sha1_query() {
437                 let sockaddr = "8.8.8.8:53".to_socket_addrs().unwrap().next().unwrap();
438                 let query_name = "benthecarman.com.".try_into().unwrap();
439                 let (proof, _) = build_a_proof(sockaddr, &query_name).unwrap();
440
441                 let mut rrs = parse_rr_stream(&proof).unwrap();
442                 rrs.shuffle(&mut rand::rngs::OsRng);
443                 let verified_rrs = verify_rr_stream(&rrs).unwrap();
444                 assert!(verified_rrs.verified_rrs.len() >= 1);
445
446                 let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
447                 assert!(verified_rrs.valid_from < now);
448                 assert!(verified_rrs.expires > now);
449         }
450
451         #[test]
452         fn test_txt_query() {
453                 let sockaddr = "8.8.8.8:53".to_socket_addrs().unwrap().next().unwrap();
454                 let query_name = "matt.user._bitcoin-payment.mattcorallo.com.".try_into().unwrap();
455                 let (proof, _) = build_txt_proof(sockaddr, &query_name).unwrap();
456
457                 let mut rrs = parse_rr_stream(&proof).unwrap();
458                 rrs.shuffle(&mut rand::rngs::OsRng);
459                 let verified_rrs = verify_rr_stream(&rrs).unwrap();
460                 assert_eq!(verified_rrs.verified_rrs.len(), 1);
461
462                 let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
463                 assert!(verified_rrs.valid_from < now);
464                 assert!(verified_rrs.expires > now);
465         }
466
467         #[test]
468         fn test_cname_query() {
469                 for resolver in ["1.1.1.1:53", "8.8.8.8:53", "9.9.9.9:53"] {
470                         let sockaddr = resolver.to_socket_addrs().unwrap().next().unwrap();
471                         let query_name = "cname_test.matcorallo.com.".try_into().unwrap();
472                         let (proof, _) = build_txt_proof(sockaddr, &query_name).unwrap();
473
474                         let mut rrs = parse_rr_stream(&proof).unwrap();
475                         rrs.shuffle(&mut rand::rngs::OsRng);
476                         let verified_rrs = verify_rr_stream(&rrs).unwrap();
477                         assert_eq!(verified_rrs.verified_rrs.len(), 2);
478
479                         let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
480                         assert!(verified_rrs.valid_from < now);
481                         assert!(verified_rrs.expires > now);
482
483                         let resolved_rrs = verified_rrs.resolve_name(&query_name);
484                         assert_eq!(resolved_rrs.len(), 1);
485                         if let RR::Txt(txt) = &resolved_rrs[0] {
486                                 assert_eq!(txt.name.as_str(), "txt_test.matcorallo.com.");
487                                 assert_eq!(txt.data, b"dnssec_prover_test");
488                         } else { panic!(); }
489                 }
490         }
491
492         #[cfg(feature = "tokio")]
493         use tokio_crate as tokio;
494
495         #[cfg(feature = "tokio")]
496         #[tokio::test]
497         async fn test_txt_query_async() {
498                 let sockaddr = "8.8.8.8:53".to_socket_addrs().unwrap().next().unwrap();
499                 let query_name = "matt.user._bitcoin-payment.mattcorallo.com.".try_into().unwrap();
500                 let (proof, _) = build_txt_proof_async(sockaddr, &query_name).await.unwrap();
501
502                 let mut rrs = parse_rr_stream(&proof).unwrap();
503                 rrs.shuffle(&mut rand::rngs::OsRng);
504                 let verified_rrs = verify_rr_stream(&rrs).unwrap();
505                 assert_eq!(verified_rrs.verified_rrs.len(), 1);
506
507                 let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
508                 assert!(verified_rrs.valid_from < now);
509                 assert!(verified_rrs.expires > now);
510         }
511
512         #[cfg(feature = "tokio")]
513         #[tokio::test]
514         async fn test_cross_domain_cname_query_async() {
515                 for resolver in ["1.1.1.1:53", "8.8.8.8:53", "9.9.9.9:53"] {
516                         let sockaddr = resolver.to_socket_addrs().unwrap().next().unwrap();
517                         let query_name = "wildcard.x_domain_cname_wild.matcorallo.com.".try_into().unwrap();
518                         let (proof, _) = build_txt_proof_async(sockaddr, &query_name).await.unwrap();
519
520                         let mut rrs = parse_rr_stream(&proof).unwrap();
521                         rrs.shuffle(&mut rand::rngs::OsRng);
522                         let verified_rrs = verify_rr_stream(&rrs).unwrap();
523                         assert_eq!(verified_rrs.verified_rrs.len(), 2);
524
525                         let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
526                         assert!(verified_rrs.valid_from < now);
527                         assert!(verified_rrs.expires > now);
528
529                         let resolved_rrs = verified_rrs.resolve_name(&query_name);
530                         assert_eq!(resolved_rrs.len(), 1);
531                         if let RR::Txt(txt) = &resolved_rrs[0] {
532                                 assert_eq!(txt.name.as_str(), "matt.user._bitcoin-payment.mattcorallo.com.");
533                                 assert!(txt.data.starts_with(b"bitcoin:"));
534                         } else { panic!(); }
535                 }
536         }
537 }