dnssec_prover/
query.rs

1//! This module exposes utilities for building DNSSEC proofs by directly querying a recursive
2//! resolver.
3
4use core::{cmp, ops};
5use alloc::vec;
6use alloc::vec::Vec;
7
8#[cfg(feature = "std")]
9use std::net::{SocketAddr, TcpStream};
10#[cfg(feature = "std")]
11use std::io::{Read, Write, Error, ErrorKind};
12
13#[cfg(feature = "tokio")]
14use tokio_crate::net::TcpStream as TokioTcpStream;
15#[cfg(feature = "tokio")]
16use tokio_crate::io::{AsyncReadExt, AsyncWriteExt};
17
18use crate::rr::*;
19use crate::ser::*;
20use crate::MAX_PROOF_STEPS;
21
22// In testing use a rather small buffer to ensure we hit the allocation paths sometimes. In
23// production, we should generally never actually need to go to heap as DNS messages are rarely
24// larger than a KiB or two.
25#[cfg(any(test, fuzzing))]
26const STACK_BUF_LIMIT: u16 = 32;
27#[cfg(not(any(test, fuzzing)))]
28const STACK_BUF_LIMIT: u16 = 2048;
29
30/// A buffer for storing queries and responses.
31#[derive(Clone, PartialEq, Eq)]
32pub struct QueryBuf {
33	buf: [u8; STACK_BUF_LIMIT as usize],
34	heap_buf: Vec<u8>,
35	len: u16,
36}
37impl QueryBuf {
38	/// Generates a new buffer of the given length, consisting of all zeros.
39	pub fn new_zeroed(len: u16) -> Self {
40		let heap_buf = if len > STACK_BUF_LIMIT { vec![0; len as usize] } else { Vec::new() };
41		Self {
42			buf: [0; STACK_BUF_LIMIT as usize],
43			heap_buf,
44			len
45		}
46	}
47	/// Extends the size of this buffer by appending the given slice.
48	///
49	/// If the total length of this buffer exceeds [`u16::MAX`] after appending, the buffer's state
50	/// is undefined, however pushing data beyond [`u16::MAX`] will not panic.
51	pub fn extend_from_slice(&mut self, sl: &[u8]) {
52		let new_len = self.len.saturating_add(sl.len() as u16);
53		let was_heap = self.len > STACK_BUF_LIMIT;
54		let is_heap = new_len > STACK_BUF_LIMIT;
55		if was_heap != is_heap {
56			self.heap_buf = vec![0; new_len as usize];
57			self.heap_buf[..self.len as usize].copy_from_slice(&self.buf[..self.len as usize]);
58		}
59		let target = if is_heap {
60			self.heap_buf.resize(new_len as usize, 0);
61			&mut self.heap_buf[self.len as usize..]
62		} else {
63			&mut self.buf[self.len as usize..new_len as usize]
64		};
65		target.copy_from_slice(sl);
66		self.len = new_len;
67	}
68	/// Converts this query into its bytes on the heap
69	pub fn into_vec(self) -> Vec<u8> {
70		if self.len > STACK_BUF_LIMIT {
71			self.heap_buf
72		} else {
73			self.buf[..self.len as usize].to_vec()
74		}
75	}
76}
77impl ops::Deref for QueryBuf {
78	type Target = [u8];
79	fn deref(&self) -> &[u8] {
80		if self.len > STACK_BUF_LIMIT {
81			&self.heap_buf
82		} else {
83			&self.buf[..self.len as usize]
84		}
85	}
86}
87impl ops::DerefMut for QueryBuf {
88	fn deref_mut(&mut self) -> &mut [u8] {
89		if self.len > STACK_BUF_LIMIT {
90			&mut self.heap_buf
91		} else {
92			&mut self.buf[..self.len as usize]
93		}
94	}
95}
96
97// We don't care about transaction IDs as we're only going to accept signed data.
98// Further, if we're querying over DoH, the RFC says we SHOULD use a transaction ID of 0 here.
99const TXID: u16 = 0;
100
101fn build_query(domain: &Name, ty: u16) -> QueryBuf {
102	let mut query = QueryBuf::new_zeroed(0);
103	query.extend_from_slice(&TXID.to_be_bytes());
104	query.extend_from_slice(&[0x01, 0x20]); // Flags: Recursive, Authenticated Data
105	query.extend_from_slice(&[0, 1, 0, 0, 0, 0, 0, 1]); // One question, One additional
106	write_name(&mut query, domain);
107	query.extend_from_slice(&ty.to_be_bytes());
108	query.extend_from_slice(&1u16.to_be_bytes()); // INternet class
109	query.extend_from_slice(&[0, 0, 0x29]); // . OPT
110	query.extend_from_slice(&0u16.to_be_bytes()); // 0 UDP payload size
111	query.extend_from_slice(&[0, 0]); // EDNS version 0
112	query.extend_from_slice(&0x8000u16.to_be_bytes()); // Accept DNSSEC RRs
113	query.extend_from_slice(&0u16.to_be_bytes()); // No additional data
114	query
115}
116
117/// Possible errors when building queries. Note that there are many possible errors, but only a
118/// handful of common ones are captured in the variants here.
119// Note that this is also duplicated in uniffi in the udl
120#[derive(PartialEq, Eq)]
121pub enum ProofBuildingError {
122	/// The server provided an invalid response.
123	///
124	/// We failed to parse the server's response or it contained nonsense that we couldn't
125	/// understand.
126	InvalidResponse,
127	/// The server we are querying gave us a response code of SERVFAIL or FORMERR.
128	///
129	/// This generally indicates it failed to connect to some DNS server required to resolve our
130	/// queries or it couldn't understand the response it got back from such a server.
131	ServerFailure,
132	/// The server we are querying gave us a response code of NXDOMAIN on our very first query.
133	///
134	/// This indicates the name being queried for does not exist.
135	NoSuchName,
136	/// The server we are querying gave us a response code of NXDOMAIN.
137	///
138	/// This indicates the server couldn't find an answer to one of our queries.
139	MissingRecord,
140	/// The server responded indicating it could not authenticate the response using DNSSEC.
141	///
142	/// This generally indicates that the data we are querying for was not DNSSEC-signed.
143	/// It could also indicate that the server we are trying to query using does not validate
144	/// DNSSEC.
145	Unauthenticated,
146	/// A query was provided when no query was expected.
147	///
148	/// This indicates a bug in the code driving the proof builder, rather than an issue with the
149	/// DNS.
150	NoResponseExpected,
151}
152
153impl core::fmt::Display for ProofBuildingError {
154	fn fmt(&self, fmt: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
155		match self {
156			ProofBuildingError::InvalidResponse =>
157				fmt.write_str("The server provided a response we could not understand"),
158			ProofBuildingError::ServerFailure =>
159				fmt.write_str("The server indicated it failed to talk to a required authorative DNS server"),
160			ProofBuildingError::NoSuchName =>
161				fmt.write_str("The server indicated the requested hostname does not exist"),
162			ProofBuildingError::MissingRecord =>
163				fmt.write_str("The server indicated one of the records we needed to build our proof did not exist"),
164			ProofBuildingError::Unauthenticated =>
165				fmt.write_str("The server indicated the records we needed were not DNSSEC-authenticated"),
166			ProofBuildingError::NoResponseExpected =>
167				fmt.write_str("Internal error in the proof building software"),
168		}
169	}
170}
171
172impl core::fmt::Debug for ProofBuildingError {
173	fn fmt(&self, fmt: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
174		core::fmt::Display::fmt(self, fmt)
175	}
176}
177
178#[cfg(feature = "std")]
179impl std::error::Error for ProofBuildingError {
180
181}
182
183#[cfg(dnssec_prover_fuzzing)]
184/// Read some input and parse it as if it came from a server, for fuzzing.
185pub fn fuzz_response(response: &[u8]) {
186	let (mut proof, mut names) = (Vec::new(), Vec::new());
187	let _ = handle_response(response, &mut proof, &mut names);
188}
189
190/// Handle a response, returning the minimum TTL of any answer.
191///
192/// Note that the caller must map errors of [`ProofBuildingError::MissingRecord`] to
193/// [`ProofBuildingError::NoSuchName`] if this was the first query!
194fn handle_response(resp: &[u8], proof: &mut Vec<u8>, rrsig_key_names: &mut Vec<Name>) -> Result<u32, ProofBuildingError> {
195	let mut read: &[u8] = resp;
196	let resp_txid = read_u16(&mut read).map_err(|()| ProofBuildingError::InvalidResponse)?;
197	if resp_txid != TXID { return Err(ProofBuildingError::InvalidResponse); }
198	// 2 byte transaction ID
199	let flags = read_u16(&mut read).map_err(|()| ProofBuildingError::InvalidResponse)?;
200	if flags & 0b1000_0000_0000_0000 == 0 {
201		// This message is tagged as a query, not a response?
202		return Err(ProofBuildingError::InvalidResponse);
203	}
204	if flags & 0b1111 == 2 || flags & 0b1111 == 1 {
205		return Err(ProofBuildingError::ServerFailure);
206	}
207	if flags & 0b1111 == 3 {
208		// NXDOMAIN, note that the caller should map this to NoSuchName if applicable.
209		return Err(ProofBuildingError::MissingRecord);
210	}
211	// Check that OPCODE, Truncation, and RCODE are all 0s
212	if flags & 0b0111_1010_0000_1111 != 0 {
213		return Err(ProofBuildingError::InvalidResponse);
214	}
215	if flags & 0b10_0000 == 0 {
216		// The AD bit was unset
217		return Err(ProofBuildingError::Unauthenticated);
218	}
219	let questions = read_u16(&mut read).map_err(|()| ProofBuildingError::InvalidResponse)?;
220	if questions != 1 { return Err(ProofBuildingError::InvalidResponse); }
221	let answers = read_u16(&mut read).map_err(|()| ProofBuildingError::InvalidResponse)?;
222	if answers == 0 { return Err(ProofBuildingError::InvalidResponse); }
223	let authorities = read_u16(&mut read).map_err(|()| ProofBuildingError::InvalidResponse)?;
224	let _additional = read_u16(&mut read).map_err(|()| ProofBuildingError::InvalidResponse)?;
225
226	for _ in 0..questions {
227		read_wire_packet_name(&mut read, resp).map_err(|()| ProofBuildingError::InvalidResponse)?;
228		read_u16(&mut read).map_err(|()| ProofBuildingError::InvalidResponse)?; // type
229		read_u16(&mut read).map_err(|()| ProofBuildingError::InvalidResponse)?; // class
230	}
231
232	// Only read the answers and NSEC records in authorities, skipping additional entirely.
233	let mut min_ttl = u32::MAX;
234	for _ in 0..answers {
235		let (rr, ttl) = parse_wire_packet_rr(&mut read, resp)
236			.map_err(|()| ProofBuildingError::InvalidResponse)?;
237		write_rr(&rr, ttl, proof);
238		min_ttl = cmp::min(min_ttl, ttl);
239		if let RR::RRSig(rrsig) = rr { rrsig_key_names.push(rrsig.key_name); }
240	}
241
242	for _ in 0..authorities {
243		// Only include records from the authority section if they are NSEC/3 (or signatures
244		// thereover). We don't care about NS records here.
245		let (rr, ttl) = parse_wire_packet_rr(&mut read, resp)
246			.map_err(|()| ProofBuildingError::InvalidResponse)?;
247		match &rr {
248			RR::RRSig(rrsig) => {
249				if rrsig.ty != NSec::TYPE && rrsig.ty != NSec3::TYPE {
250					continue;
251				}
252			},
253			RR::NSec(_)|RR::NSec3(_) => {},
254			_ => continue,
255		}
256		write_rr(&rr, ttl, proof);
257		min_ttl = cmp::min(min_ttl, ttl);
258		if let RR::RRSig(rrsig) = rr { rrsig_key_names.push(rrsig.key_name); }
259	}
260
261	Ok(min_ttl)
262}
263
264#[cfg(dnssec_prover_fuzzing)]
265/// Read a stream of responses and handle them it as if they came from a server, for fuzzing.
266pub fn fuzz_proof_builder(mut response_stream: &[u8]) {
267	let (mut builder, _) = ProofBuilder::new(&"example.com.".try_into().unwrap(), Txt::TYPE);
268	while builder.awaiting_responses() {
269		let len = if let Ok(len) = read_u16(&mut response_stream) { len } else { return };
270		let mut buf = QueryBuf::new_zeroed(len);
271		if response_stream.len() < len as usize { return; }
272		buf.copy_from_slice(&response_stream[..len as usize]);
273		response_stream = &response_stream[len as usize..];
274		let _ = builder.process_response(&buf);
275	}
276	let _ = builder.finish_proof();
277}
278
279/// A simple state machine which will generate a series of queries and process the responses until
280/// it has built a DNSSEC proof.
281///
282/// A [`ProofBuilder`] driver starts with [`ProofBuilder::new`], fetching the state machine and
283/// initial query. As long as [`ProofBuilder::awaiting_responses`] returns true, responses should
284/// be read from the resolver. For each query response read from the DNS resolver,
285/// [`ProofBuilder::process_response`] should be called, and each fresh query returned should be
286/// sent to the resolver. Once [`ProofBuilder::awaiting_responses`] returns false,
287/// [`ProofBuilder::finish_proof`] should be called to fetch the resulting proof.
288///
289/// To build a DNSSEC proof using a DoH server, take each [`QueryBuf`], encode it as base64url, and
290/// make a query to `https://doh-server/endpoint?dns=base64url_encoded_query` with an `Accept`
291/// header of `application/dns-message`. Each response, in raw binary, can be fed directly into
292/// [`ProofBuilder::process_response`].
293#[derive(Clone)]
294pub struct ProofBuilder {
295	proof: Vec<u8>,
296	min_ttl: u32,
297	dnskeys_requested: Vec<Name>,
298	pending_queries: usize,
299	queries_made: usize,
300}
301
302impl ProofBuilder {
303	/// Constructs a new [`ProofBuilder`] and an initial query to send to the recursive resolver to
304	/// begin the proof building process.
305	///
306	/// Given a correctly-functioning resolver the proof will ultimately be able to prove the
307	/// contents of any records with the given `ty`pe at the given `name` (as long as the given
308	/// `ty`pe is supported by this library).
309	///
310	/// You can find constants for supported standard types in the [`crate::rr`] module.
311	pub fn new(name: &Name, ty: u16) -> (ProofBuilder, QueryBuf) {
312		let initial_query = build_query(name, ty);
313		(ProofBuilder {
314			proof: Vec::new(),
315			min_ttl: u32::MAX,
316			dnskeys_requested: Vec::with_capacity(MAX_PROOF_STEPS),
317			pending_queries: 1,
318			queries_made: 1,
319		}, initial_query)
320	}
321
322	/// Returns true as long as further responses are expected from the resolver.
323	///
324	/// As long as this returns true, responses should be read from the resolver and passed to
325	/// [`Self::process_response`]. Once this returns false, [`Self::finish_proof`] should be used
326	/// to (possibly) get the final proof.
327	pub fn awaiting_responses(&self) -> bool {
328		self.pending_queries > 0 && self.queries_made <= MAX_PROOF_STEPS
329	}
330
331	/// Processes a query response from the recursive resolver, returning a list of new queries to
332	/// send to the resolver.
333	pub fn process_response(&mut self, resp: &QueryBuf) -> Result<Vec<QueryBuf>, ProofBuildingError> {
334		if self.pending_queries == 0 { return Err(ProofBuildingError::NoResponseExpected); }
335
336		let mut rrsig_key_names = Vec::new();
337		let min_ttl = match handle_response(resp, &mut self.proof, &mut rrsig_key_names) {
338			Ok(min_ttl) => min_ttl,
339			Err(err) => {
340				if self.proof.is_empty() && err == ProofBuildingError::MissingRecord {
341					return Err(ProofBuildingError::NoSuchName);
342				} else {
343					return Err(err);
344				}
345			},
346		};
347		self.min_ttl = cmp::min(self.min_ttl, min_ttl);
348		self.pending_queries -= 1;
349
350		rrsig_key_names.sort_unstable();
351		rrsig_key_names.dedup();
352
353		let mut new_queries = Vec::with_capacity(2);
354		for key_name in rrsig_key_names.drain(..) {
355			if !self.dnskeys_requested.contains(&key_name) {
356				new_queries.push(build_query(&key_name, DnsKey::TYPE));
357				self.pending_queries += 1;
358				self.queries_made += 1;
359				self.dnskeys_requested.push(key_name.clone());
360
361				if key_name.as_str() != "." {
362					new_queries.push(build_query(&key_name, DS::TYPE));
363					self.pending_queries += 1;
364					self.queries_made += 1;
365				}
366			}
367		}
368		if self.queries_made <= MAX_PROOF_STEPS {
369			Ok(new_queries)
370		} else {
371			Ok(Vec::new())
372		}
373	}
374
375	/// Finalizes the proof, if one is available, and returns it as well as the TTL that should be
376	/// used to cache the proof (i.e. the lowest TTL of all records which were used to build the
377	/// proof).
378	///
379	/// Only fails if too many queries have been made or there are still some pending queries.
380	pub fn finish_proof(self) -> Result<(Vec<u8>, u32), ()> {
381		if self.pending_queries > 0 || self.queries_made > MAX_PROOF_STEPS {
382			Err(())
383		} else {
384			Ok((self.proof, self.min_ttl))
385		}
386	}
387}
388
389#[cfg(feature = "std")]
390fn send_query(stream: &mut TcpStream, query: &[u8]) -> Result<(), Error> {
391	stream.write_all(&(query.len() as u16).to_be_bytes())?;
392	stream.write_all(&query)?;
393	Ok(())
394}
395
396#[cfg(feature = "tokio")]
397async fn send_query_async(stream: &mut TokioTcpStream, query: &[u8]) -> Result<(), Error> {
398	stream.write_all(&(query.len() as u16).to_be_bytes()).await?;
399	stream.write_all(&query).await?;
400	Ok(())
401}
402
403#[cfg(feature = "std")]
404fn read_response(stream: &mut TcpStream) -> Result<QueryBuf, Error> {
405	let mut len_bytes = [0; 2];
406	stream.read_exact(&mut len_bytes)?;
407	let mut buf = QueryBuf::new_zeroed(u16::from_be_bytes(len_bytes));
408	stream.read_exact(&mut buf)?;
409	Ok(buf)
410}
411
412#[cfg(feature = "tokio")]
413async fn read_response_async(stream: &mut TokioTcpStream) -> Result<QueryBuf, Error> {
414	let mut len_bytes = [0; 2];
415	stream.read_exact(&mut len_bytes).await?;
416	let mut buf = QueryBuf::new_zeroed(u16::from_be_bytes(len_bytes));
417	stream.read_exact(&mut buf).await?;
418	Ok(buf)
419}
420
421#[cfg(feature = "std")]
422macro_rules! build_proof_impl {
423	($stream: ident, $send_query: ident, $read_response: ident, $domain: expr, $ty: expr $(, $async_ok: tt)?) => { {
424		// We require the initial query to have already gone out, and assume our resolver will
425		// return any CNAMEs all the way to the final record in the response. From there, we just
426		// have to take any RRSIGs in the response and walk them up to the root. We do so
427		// iteratively, sending DNSKEY and DS lookups after every response, deduplicating requests
428		// using `dnskeys_requested`.
429		let (mut builder, initial_query) = ProofBuilder::new($domain, $ty);
430		$send_query(&mut $stream, &initial_query)
431			$(.await?; $async_ok)??; // Either await?; Ok(())?, or just ?
432		while builder.awaiting_responses() {
433			let response = $read_response(&mut $stream)
434				$(.await?; $async_ok)??; // Either await?; Ok(())?, or just ?
435			let new_queries = builder.process_response(&response)
436				.map_err(|err| Error::new(ErrorKind::Other, err))?;
437			for query in new_queries {
438				$send_query(&mut $stream, &query)
439					$(.await?; $async_ok)??; // Either await?; Ok(())?, or just ?
440			}
441		}
442
443		builder.finish_proof()
444			.map_err(|()| Error::new(ErrorKind::Other, "Too many requests required"))
445	} }
446}
447
448#[cfg(feature = "std")]
449fn build_proof(resolver: SocketAddr, domain: &Name, ty: u16) -> Result<(Vec<u8>, u32), Error> {
450	let mut stream = TcpStream::connect(resolver)?;
451	build_proof_impl!(stream, send_query, read_response, domain, ty)
452}
453
454#[cfg(feature = "tokio")]
455async fn build_proof_async(resolver: SocketAddr, domain: &Name, ty: u16) -> Result<(Vec<u8>, u32), Error> {
456	let mut stream = TokioTcpStream::connect(resolver).await?;
457	build_proof_impl!(stream, send_query_async, read_response_async, domain, ty, { Ok::<(), Error>(()) })
458}
459
460/// Builds a DNSSEC proof for an A record by querying a recursive resolver, returning the proof as
461/// well as the TTL for the proof provided by the recursive resolver.
462///
463/// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
464/// module to validate the records contained.
465#[cfg(feature = "std")]
466pub fn build_a_proof(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
467	build_proof(resolver, domain, A::TYPE)
468}
469
470/// Builds a DNSSEC proof for an AAAA record by querying a recursive resolver, returning the proof
471/// as well as the TTL for the proof provided by the recursive resolver.
472///
473/// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
474/// module to validate the records contained.
475#[cfg(feature = "std")]
476pub fn build_aaaa_proof(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
477	build_proof(resolver, domain, AAAA::TYPE)
478}
479
480/// Builds a DNSSEC proof for an TXT record by querying a recursive resolver, returning the proof
481/// as well as the TTL for the proof provided by the recursive resolver.
482///
483/// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
484/// module to validate the records contained.
485#[cfg(feature = "std")]
486pub fn build_txt_proof(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
487	build_proof(resolver, domain, Txt::TYPE)
488}
489
490/// Builds a DNSSEC proof for an TLSA record by querying a recursive resolver, returning the proof
491/// as well as the TTL for the proof provided by the recursive resolver.
492///
493/// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
494/// module to validate the records contained.
495#[cfg(feature = "std")]
496pub fn build_tlsa_proof(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
497	build_proof(resolver, domain, TLSA::TYPE)
498}
499
500
501/// Builds a DNSSEC proof for an A record by querying a recursive resolver, returning the proof as
502/// well as the TTL for the proof provided by the recursive resolver.
503///
504/// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
505/// module to validate the records contained.
506#[cfg(feature = "tokio")]
507pub async fn build_a_proof_async(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
508	build_proof_async(resolver, domain, A::TYPE).await
509}
510
511/// Builds a DNSSEC proof for an AAAA record by querying a recursive resolver, returning the proof
512/// as well as the TTL for the proof provided by the recursive resolver.
513///
514/// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
515/// module to validate the records contained.
516#[cfg(feature = "tokio")]
517pub async fn build_aaaa_proof_async(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
518	build_proof_async(resolver, domain, AAAA::TYPE).await
519}
520
521/// Builds a DNSSEC proof for an TXT record by querying a recursive resolver, returning the proof
522/// as well as the TTL for the proof provided by the recursive resolver.
523///
524/// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
525/// module to validate the records contained.
526#[cfg(feature = "tokio")]
527pub async fn build_txt_proof_async(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
528	build_proof_async(resolver, domain, Txt::TYPE).await
529}
530
531/// Builds a DNSSEC proof for an TLSA record by querying a recursive resolver, returning the proof
532/// as well as the TTL for the proof provided by the recursive resolver.
533///
534/// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
535/// module to validate the records contained.
536#[cfg(feature = "tokio")]
537pub async fn build_tlsa_proof_async(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
538	build_proof_async(resolver, domain, TLSA::TYPE).await
539}
540
541#[cfg(all(feature = "validation", feature = "std", test))]
542mod tests {
543	use super::*;
544	use crate::validation::*;
545
546	use rand::seq::SliceRandom;
547
548	use std::net::ToSocketAddrs;
549	use std::time::SystemTime;
550
551	#[test]
552	fn test_cloudflare_txt_query() {
553		let sockaddr = "8.8.8.8:53".to_socket_addrs().unwrap().next().unwrap();
554		let query_name = "cloudflare.com.".try_into().unwrap();
555		let (proof, _) = build_txt_proof(sockaddr, &query_name).unwrap();
556
557		let mut rrs = parse_rr_stream(&proof).unwrap();
558		rrs.shuffle(&mut rand::rngs::OsRng);
559		let verified_rrs = verify_rr_stream(&rrs).unwrap();
560		assert!(verified_rrs.verified_rrs.len() > 1);
561
562		let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
563		assert!(verified_rrs.valid_from < now);
564		assert!(verified_rrs.expires > now);
565	}
566
567	#[test]
568	fn test_sha1_query() {
569		let sockaddr = "8.8.8.8:53".to_socket_addrs().unwrap().next().unwrap();
570		let query_name = "benthecarman.com.".try_into().unwrap();
571		let (proof, _) = build_a_proof(sockaddr, &query_name).unwrap();
572
573		let mut rrs = parse_rr_stream(&proof).unwrap();
574		rrs.shuffle(&mut rand::rngs::OsRng);
575		let verified_rrs = verify_rr_stream(&rrs).unwrap();
576		assert!(verified_rrs.verified_rrs.len() >= 1);
577
578		let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
579		assert!(verified_rrs.valid_from < now);
580		assert!(verified_rrs.expires > now);
581	}
582
583	#[test]
584	fn test_txt_query() {
585		let sockaddr = "8.8.8.8:53".to_socket_addrs().unwrap().next().unwrap();
586		let query_name = "matt.user._bitcoin-payment.mattcorallo.com.".try_into().unwrap();
587		let (proof, _) = build_txt_proof(sockaddr, &query_name).unwrap();
588
589		let mut rrs = parse_rr_stream(&proof).unwrap();
590		rrs.shuffle(&mut rand::rngs::OsRng);
591		let verified_rrs = verify_rr_stream(&rrs).unwrap();
592		assert_eq!(verified_rrs.verified_rrs.len(), 1);
593
594		let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
595		assert!(verified_rrs.valid_from < now);
596		assert!(verified_rrs.expires > now);
597	}
598
599	#[test]
600	fn test_cname_query() {
601		for resolver in ["1.1.1.1:53", "8.8.8.8:53", "9.9.9.9:53"] {
602			let sockaddr = resolver.to_socket_addrs().unwrap().next().unwrap();
603			let query_name = "cname_test.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap();
604			let (proof, _) = build_txt_proof(sockaddr, &query_name).unwrap();
605
606			let mut rrs = parse_rr_stream(&proof).unwrap();
607			rrs.shuffle(&mut rand::rngs::OsRng);
608			let verified_rrs = verify_rr_stream(&rrs).unwrap();
609			assert_eq!(verified_rrs.verified_rrs.len(), 2);
610
611			let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
612			assert!(verified_rrs.valid_from < now);
613			assert!(verified_rrs.expires > now);
614
615			let resolved_rrs = verified_rrs.resolve_name(&query_name);
616			assert_eq!(resolved_rrs.len(), 1);
617			if let RR::Txt(txt) = &resolved_rrs[0] {
618				assert_eq!(txt.name.as_str(), "txt_test.dnssec_proof_tests.bitcoin.ninja.");
619				assert_eq!(txt.data.as_vec(), b"dnssec_prover_test");
620			} else { panic!(); }
621		}
622	}
623
624	#[cfg(feature = "tokio")]
625	use tokio_crate as tokio;
626
627	#[cfg(feature = "tokio")]
628	#[tokio::test]
629	async fn test_txt_query_async() {
630		let sockaddr = "8.8.8.8:53".to_socket_addrs().unwrap().next().unwrap();
631		let query_name = "matt.user._bitcoin-payment.mattcorallo.com.".try_into().unwrap();
632		let (proof, _) = build_txt_proof_async(sockaddr, &query_name).await.unwrap();
633
634		let mut rrs = parse_rr_stream(&proof).unwrap();
635		rrs.shuffle(&mut rand::rngs::OsRng);
636		let verified_rrs = verify_rr_stream(&rrs).unwrap();
637		assert_eq!(verified_rrs.verified_rrs.len(), 1);
638
639		let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
640		assert!(verified_rrs.valid_from < now);
641		assert!(verified_rrs.expires > now);
642	}
643
644	#[cfg(feature = "tokio")]
645	#[tokio::test]
646	async fn test_cross_domain_cname_query_async() {
647		for resolver in ["1.1.1.1:53", "8.8.8.8:53", "9.9.9.9:53"] {
648			let sockaddr = resolver.to_socket_addrs().unwrap().next().unwrap();
649			let query_name = "wildcard.x_domain_cname_wild.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap();
650			let (proof, _) = build_txt_proof_async(sockaddr, &query_name).await.unwrap();
651
652			let mut rrs = parse_rr_stream(&proof).unwrap();
653			rrs.shuffle(&mut rand::rngs::OsRng);
654			let verified_rrs = verify_rr_stream(&rrs).unwrap();
655			assert_eq!(verified_rrs.verified_rrs.len(), 2);
656
657			let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
658			assert!(verified_rrs.valid_from < now);
659			assert!(verified_rrs.expires > now);
660
661			let resolved_rrs = verified_rrs.resolve_name(&query_name);
662			assert_eq!(resolved_rrs.len(), 1);
663			if let RR::Txt(txt) = &resolved_rrs[0] {
664				assert_eq!(txt.name.as_str(), "matt.user._bitcoin-payment.mattcorallo.com.");
665				assert!(txt.data.as_vec().starts_with(b"bitcoin:"));
666			} else { panic!(); }
667		}
668	}
669
670	#[cfg(feature = "tokio")]
671	#[tokio::test]
672	async fn test_dname_wildcard_query_async() {
673		for resolver in ["1.1.1.1:53", "8.8.8.8:53", "9.9.9.9:53"] {
674			let sockaddr = resolver.to_socket_addrs().unwrap().next().unwrap();
675			let query_name = "wildcard_a.wildcard_b.dname_test.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap();
676			let (proof, _) = build_txt_proof_async(sockaddr, &query_name).await.unwrap();
677
678			let mut rrs = parse_rr_stream(&proof).unwrap();
679			rrs.shuffle(&mut rand::rngs::OsRng);
680			let verified_rrs = verify_rr_stream(&rrs).unwrap();
681			assert_eq!(verified_rrs.verified_rrs.len(), 3);
682
683			let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
684			assert!(verified_rrs.valid_from < now);
685			assert!(verified_rrs.expires > now);
686
687			let resolved_rrs = verified_rrs.resolve_name(&query_name);
688			assert_eq!(resolved_rrs.len(), 1);
689			if let RR::Txt(txt) = &resolved_rrs[0] {
690				assert_eq!(txt.name.as_str(), "cname.wildcard_test.dnssec_proof_tests.bitcoin.ninja.");
691				assert_eq!(txt.data.as_vec(), b"wildcard_test");
692			} else { panic!(); }
693		}
694	}
695
696	#[cfg(feature = "tokio")]
697	#[tokio::test]
698	async fn test_tbast_ovh_hosted() {
699		// OVH's DNS servers do all kinds of weird inefficient things, making for a good test.
700		for resolver in ["1.1.1.1:53", "8.8.8.8:53", "9.9.9.9:53"] {
701			let sockaddr = resolver.to_socket_addrs().unwrap().next().unwrap();
702			let query_name = "me.user._bitcoin-payment.t-bast.xyz.".try_into().unwrap();
703			let (proof, _) = build_txt_proof_async(sockaddr, &query_name).await.unwrap();
704
705			let mut rrs = parse_rr_stream(&proof).unwrap();
706			rrs.shuffle(&mut rand::rngs::OsRng);
707			let verified_rrs = verify_rr_stream(&rrs).unwrap();
708			assert_eq!(verified_rrs.verified_rrs.len(), 1);
709
710			let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
711			assert!(verified_rrs.valid_from < now);
712			assert!(verified_rrs.expires > now);
713
714			let resolved_rrs = verified_rrs.resolve_name(&query_name);
715			assert_eq!(resolved_rrs.len(), 1);
716			if let RR::Txt(txt) = &resolved_rrs[0] {
717				assert_eq!(txt.name.as_str(), "me.user._bitcoin-payment.t-bast.xyz.");
718				assert!(txt.data.as_vec().starts_with(b"bitcoin:"));
719			} else { panic!(); }
720		}
721	}
722
723	#[cfg(feature = "tokio")]
724	#[tokio::test]
725	async fn test_no_dnssec() {
726		// Google believes DNSSEC is a bad idea due to 10 year old information and a cargo cult
727		// within Mountain View. Thus we assume they'll never bother to use the security it
728		// provides.
729		for resolver in ["1.1.1.1:53", "8.8.8.8:53", "9.9.9.9:53"] {
730			let sockaddr = resolver.to_socket_addrs().unwrap().next().unwrap();
731			let query_name = "google.com.".try_into().unwrap();
732			let err = build_a_proof_async(sockaddr, &query_name).await.unwrap_err();
733			assert_eq!(err.into_inner().unwrap().downcast().unwrap(), Box::new(ProofBuildingError::Unauthenticated));
734		}
735	}
736}