1 //! This module exposes utilities for building DNSSEC proofs by directly querying a recursive
8 #[cfg(feature = "std")]
9 use std::net::{SocketAddr, TcpStream};
10 #[cfg(feature = "std")]
11 use std::io::{Read, Write, Error, ErrorKind};
13 #[cfg(feature = "tokio")]
14 use tokio_crate::net::TcpStream as TokioTcpStream;
15 #[cfg(feature = "tokio")]
16 use tokio_crate::io::{AsyncReadExt, AsyncWriteExt};
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.
25 const STACK_BUF_LIMIT: u16 = 32;
27 const STACK_BUF_LIMIT: u16 = 2048;
29 /// A buffer for storing queries and responses.
30 #[derive(Clone, PartialEq, Eq)]
32 buf: [u8; STACK_BUF_LIMIT as usize],
37 fn new_zeroed(len: u16) -> Self {
38 let heap_buf = if len > STACK_BUF_LIMIT { vec![0; len as usize] } else { Vec::new() };
40 buf: [0; STACK_BUF_LIMIT as usize],
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]);
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..]
57 &mut self.buf[self.len as usize..new_len as usize]
59 target.copy_from_slice(sl);
63 impl ops::Deref for QueryBuf {
65 fn deref(&self) -> &[u8] {
66 if self.len > STACK_BUF_LIMIT {
69 &self.buf[..self.len as usize]
73 impl ops::DerefMut for QueryBuf {
74 fn deref_mut(&mut self) -> &mut [u8] {
75 if self.len > STACK_BUF_LIMIT {
78 &mut self.buf[..self.len as usize]
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;
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
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);
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 {
120 if flags & 0b0111_1010_0000_0111 != 0 {
123 if flags & 0b10_0000 == 0 {
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)?;
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
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); }
150 const MAX_REQUESTS: usize = 10;
151 /// A simple state machine which will generate a series of queries and process the responses until
152 /// it has built a DNSSEC proof.
154 /// A [`ProofBuilder`] driver starts with [`ProofBuilder::new`], fetching the state machine and
155 /// initial query. As long as [`ProofBuilder::awaiting_responses`] returns true, responses should
156 /// be read from the resolver. For each query response read from the DNS resolver,
157 /// [`ProofBuilder::process_response`] should be called, and each fresh query returned should be
158 /// sent to the resolver. Once [`ProofBuilder::awaiting_responses`] returns false,
159 /// [`ProofBuilder::finish_proof`] should be called to fetch the resulting proof.
160 pub struct ProofBuilder {
163 dnskeys_requested: Vec<Name>,
164 pending_queries: usize,
169 /// Constructs a new [`ProofBuilder`] and an initial query to send to the recursive resolver to
170 /// begin the proof building process.
172 /// Given a correctly-functioning resolver the proof will ultimately be able to prove the
173 /// contents of any records with the given `ty`pe at the given `name` (as long as the given
174 /// `ty`pe is supported by this library).
176 /// You can find constants for supported standard types in the [`crate::rr`] module.
177 pub fn new(name: &Name, ty: u16) -> (ProofBuilder, QueryBuf) {
178 let initial_query = build_query(name, ty);
182 dnskeys_requested: Vec::with_capacity(MAX_REQUESTS),
188 /// Returns true as long as further responses are expected from the resolver.
190 /// As long as this returns true, responses should be read from the resolver and passed to
191 /// [`Self::process_response`]. Once this returns false, [`Self::finish_proof`] should be used
192 /// to (possibly) get the final proof.
193 pub fn awaiting_responses(&self) -> bool {
194 self.pending_queries > 0 && self.queries_made <= MAX_REQUESTS
197 /// Processes a query response from the recursive resolver, returning a list of new queries to
198 /// send to the resolver.
199 pub fn process_response(&mut self, resp: &QueryBuf) -> Result<Vec<QueryBuf>, ()> {
200 if self.pending_queries == 0 { return Err(()); }
202 let mut rrsig_key_names = Vec::new();
203 let min_ttl = handle_response(&resp, &mut self.proof, &mut rrsig_key_names)?;
204 self.min_ttl = cmp::min(self.min_ttl, min_ttl);
205 self.pending_queries -= 1;
207 rrsig_key_names.sort_unstable();
208 rrsig_key_names.dedup();
210 let mut new_queries = Vec::with_capacity(2);
211 for key_name in rrsig_key_names.drain(..) {
212 if !self.dnskeys_requested.contains(&key_name) {
213 new_queries.push(build_query(&key_name, DnsKey::TYPE));
214 self.pending_queries += 1;
215 self.queries_made += 1;
216 self.dnskeys_requested.push(key_name.clone());
218 if key_name.as_str() != "." {
219 new_queries.push(build_query(&key_name, DS::TYPE));
220 self.pending_queries += 1;
221 self.queries_made += 1;
225 if self.queries_made <= MAX_REQUESTS {
232 /// Finalizes the proof, if one is available, and returns it as well as the TTL that should be
233 /// used to cache the proof (i.e. the lowest TTL of all records which were used to build the
235 pub fn finish_proof(self) -> Result<(Vec<u8>, u32), ()> {
236 if self.pending_queries > 0 || self.queries_made > MAX_REQUESTS {
239 Ok((self.proof, self.min_ttl))
244 #[cfg(feature = "std")]
245 fn send_query(stream: &mut TcpStream, query: &[u8]) -> Result<(), Error> {
246 stream.write_all(&query)?;
250 #[cfg(feature = "tokio")]
251 async fn send_query_async(stream: &mut TokioTcpStream, query: &[u8]) -> Result<(), Error> {
252 stream.write_all(&query).await?;
256 #[cfg(feature = "std")]
257 fn read_response(stream: &mut TcpStream) -> Result<QueryBuf, Error> {
258 let mut len_bytes = [0; 2];
259 stream.read_exact(&mut len_bytes)?;
260 let mut buf = QueryBuf::new_zeroed(u16::from_be_bytes(len_bytes));
261 stream.read_exact(&mut buf)?;
265 #[cfg(feature = "tokio")]
266 async fn read_response_async(stream: &mut TokioTcpStream) -> Result<QueryBuf, Error> {
267 let mut len_bytes = [0; 2];
268 stream.read_exact(&mut len_bytes).await?;
269 let mut buf = QueryBuf::new_zeroed(u16::from_be_bytes(len_bytes));
270 stream.read_exact(&mut buf).await?;
274 #[cfg(feature = "std")]
275 macro_rules! build_proof_impl {
276 ($stream: ident, $send_query: ident, $read_response: ident, $domain: expr, $ty: expr $(, $async_ok: tt)?) => { {
277 // We require the initial query to have already gone out, and assume our resolver will
278 // return any CNAMEs all the way to the final record in the response. From there, we just
279 // have to take any RRSIGs in the response and walk them up to the root. We do so
280 // iteratively, sending DNSKEY and DS lookups after every response, deduplicating requests
281 // using `dnskeys_requested`.
282 let (mut builder, initial_query) = ProofBuilder::new($domain, $ty);
283 $send_query(&mut $stream, &initial_query)
284 $(.await?; $async_ok)??; // Either await?; Ok(())?, or just ?
285 while builder.awaiting_responses() {
286 let response = $read_response(&mut $stream)
287 $(.await?; $async_ok)??; // Either await?; Ok(())?, or just ?
288 let new_queries = builder.process_response(&response)
289 .map_err(|()| Error::new(ErrorKind::Other, "Bad response"))?;
290 for query in new_queries {
291 $send_query(&mut $stream, &query)
292 $(.await?; $async_ok)??; // Either await?; Ok(())?, or just ?
296 builder.finish_proof()
297 .map_err(|()| Error::new(ErrorKind::Other, "Too many requests required"))
301 #[cfg(feature = "std")]
302 fn build_proof(resolver: SocketAddr, domain: &Name, ty: u16) -> Result<(Vec<u8>, u32), Error> {
303 let mut stream = TcpStream::connect(resolver)?;
304 build_proof_impl!(stream, send_query, read_response, domain, ty)
307 #[cfg(feature = "tokio")]
308 async fn build_proof_async(resolver: SocketAddr, domain: &Name, ty: u16) -> Result<(Vec<u8>, u32), Error> {
309 let mut stream = TokioTcpStream::connect(resolver).await?;
310 build_proof_impl!(stream, send_query_async, read_response_async, domain, ty, { Ok::<(), Error>(()) })
313 /// Builds a DNSSEC proof for an A record by querying a recursive resolver, returning the proof as
314 /// well as the TTL for the proof provided by the recursive resolver.
316 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
317 /// module to validate the records contained.
318 #[cfg(feature = "std")]
319 pub fn build_a_proof(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
320 build_proof(resolver, domain, A::TYPE)
323 /// Builds a DNSSEC proof for an AAAA record by querying a recursive resolver, returning the proof
324 /// as well as the TTL for the proof provided by the recursive resolver.
326 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
327 /// module to validate the records contained.
328 #[cfg(feature = "std")]
329 pub fn build_aaaa_proof(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
330 build_proof(resolver, domain, AAAA::TYPE)
333 /// Builds a DNSSEC proof for an TXT record by querying a recursive resolver, returning the proof
334 /// as well as the TTL for the proof provided by the recursive resolver.
336 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
337 /// module to validate the records contained.
338 #[cfg(feature = "std")]
339 pub fn build_txt_proof(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
340 build_proof(resolver, domain, Txt::TYPE)
343 /// Builds a DNSSEC proof for an TLSA record by querying a recursive resolver, returning the proof
344 /// as well as the TTL for the proof provided by the recursive resolver.
346 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
347 /// module to validate the records contained.
348 #[cfg(feature = "std")]
349 pub fn build_tlsa_proof(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
350 build_proof(resolver, domain, TLSA::TYPE)
354 /// Builds a DNSSEC proof for an A record by querying a recursive resolver, returning the proof as
355 /// well as the TTL for the proof provided by the recursive resolver.
357 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
358 /// module to validate the records contained.
359 #[cfg(feature = "tokio")]
360 pub async fn build_a_proof_async(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
361 build_proof_async(resolver, domain, A::TYPE).await
364 /// Builds a DNSSEC proof for an AAAA record by querying a recursive resolver, returning the proof
365 /// as well as the TTL for the proof provided by the recursive resolver.
367 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
368 /// module to validate the records contained.
369 #[cfg(feature = "tokio")]
370 pub async fn build_aaaa_proof_async(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
371 build_proof_async(resolver, domain, AAAA::TYPE).await
374 /// Builds a DNSSEC proof for an TXT record by querying a recursive resolver, returning the proof
375 /// as well as the TTL for the proof provided by the recursive resolver.
377 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
378 /// module to validate the records contained.
379 #[cfg(feature = "tokio")]
380 pub async fn build_txt_proof_async(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
381 build_proof_async(resolver, domain, Txt::TYPE).await
384 /// Builds a DNSSEC proof for an TLSA record by querying a recursive resolver, returning the proof
385 /// as well as the TTL for the proof provided by the recursive resolver.
387 /// Note that this proof is NOT verified in any way, you need to use the [`crate::validation`]
388 /// module to validate the records contained.
389 #[cfg(feature = "tokio")]
390 pub async fn build_tlsa_proof_async(resolver: SocketAddr, domain: &Name) -> Result<(Vec<u8>, u32), Error> {
391 build_proof_async(resolver, domain, TLSA::TYPE).await
394 #[cfg(all(feature = "validation", feature = "std", test))]
397 use crate::validation::*;
399 use rand::seq::SliceRandom;
401 use std::net::ToSocketAddrs;
402 use std::time::SystemTime;
405 fn test_cloudflare_txt_query() {
406 let sockaddr = "8.8.8.8:53".to_socket_addrs().unwrap().next().unwrap();
407 let query_name = "cloudflare.com.".try_into().unwrap();
408 let (proof, _) = build_txt_proof(sockaddr, &query_name).unwrap();
410 let mut rrs = parse_rr_stream(&proof).unwrap();
411 rrs.shuffle(&mut rand::rngs::OsRng);
412 let verified_rrs = verify_rr_stream(&rrs).unwrap();
413 assert!(verified_rrs.verified_rrs.len() > 1);
415 let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
416 assert!(verified_rrs.valid_from < now);
417 assert!(verified_rrs.expires > now);
421 fn test_sha1_query() {
422 let sockaddr = "8.8.8.8:53".to_socket_addrs().unwrap().next().unwrap();
423 let query_name = "benthecarman.com.".try_into().unwrap();
424 let (proof, _) = build_a_proof(sockaddr, &query_name).unwrap();
426 let mut rrs = parse_rr_stream(&proof).unwrap();
427 rrs.shuffle(&mut rand::rngs::OsRng);
428 let verified_rrs = verify_rr_stream(&rrs).unwrap();
429 assert!(verified_rrs.verified_rrs.len() >= 1);
431 let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
432 assert!(verified_rrs.valid_from < now);
433 assert!(verified_rrs.expires > now);
437 fn test_txt_query() {
438 let sockaddr = "8.8.8.8:53".to_socket_addrs().unwrap().next().unwrap();
439 let query_name = "matt.user._bitcoin-payment.mattcorallo.com.".try_into().unwrap();
440 let (proof, _) = build_txt_proof(sockaddr, &query_name).unwrap();
442 let mut rrs = parse_rr_stream(&proof).unwrap();
443 rrs.shuffle(&mut rand::rngs::OsRng);
444 let verified_rrs = verify_rr_stream(&rrs).unwrap();
445 assert_eq!(verified_rrs.verified_rrs.len(), 1);
447 let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
448 assert!(verified_rrs.valid_from < now);
449 assert!(verified_rrs.expires > now);
453 fn test_cname_query() {
454 for resolver in ["1.1.1.1:53", "8.8.8.8:53", "9.9.9.9:53"] {
455 let sockaddr = resolver.to_socket_addrs().unwrap().next().unwrap();
456 let query_name = "cname_test.matcorallo.com.".try_into().unwrap();
457 let (proof, _) = build_txt_proof(sockaddr, &query_name).unwrap();
459 let mut rrs = parse_rr_stream(&proof).unwrap();
460 rrs.shuffle(&mut rand::rngs::OsRng);
461 let verified_rrs = verify_rr_stream(&rrs).unwrap();
462 assert_eq!(verified_rrs.verified_rrs.len(), 2);
464 let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
465 assert!(verified_rrs.valid_from < now);
466 assert!(verified_rrs.expires > now);
468 let resolved_rrs = verified_rrs.resolve_name(&query_name);
469 assert_eq!(resolved_rrs.len(), 1);
470 if let RR::Txt(txt) = &resolved_rrs[0] {
471 assert_eq!(txt.name.as_str(), "txt_test.matcorallo.com.");
472 assert_eq!(txt.data, b"dnssec_prover_test");
477 #[cfg(feature = "tokio")]
478 use tokio_crate as tokio;
480 #[cfg(feature = "tokio")]
482 async fn test_txt_query_async() {
483 let sockaddr = "8.8.8.8:53".to_socket_addrs().unwrap().next().unwrap();
484 let query_name = "matt.user._bitcoin-payment.mattcorallo.com.".try_into().unwrap();
485 let (proof, _) = build_txt_proof_async(sockaddr, &query_name).await.unwrap();
487 let mut rrs = parse_rr_stream(&proof).unwrap();
488 rrs.shuffle(&mut rand::rngs::OsRng);
489 let verified_rrs = verify_rr_stream(&rrs).unwrap();
490 assert_eq!(verified_rrs.verified_rrs.len(), 1);
492 let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
493 assert!(verified_rrs.valid_from < now);
494 assert!(verified_rrs.expires > now);
497 #[cfg(feature = "tokio")]
499 async fn test_cross_domain_cname_query_async() {
500 for resolver in ["1.1.1.1:53", "8.8.8.8:53", "9.9.9.9:53"] {
501 let sockaddr = resolver.to_socket_addrs().unwrap().next().unwrap();
502 let query_name = "wildcard.x_domain_cname_wild.matcorallo.com.".try_into().unwrap();
503 let (proof, _) = build_txt_proof_async(sockaddr, &query_name).await.unwrap();
505 let mut rrs = parse_rr_stream(&proof).unwrap();
506 rrs.shuffle(&mut rand::rngs::OsRng);
507 let verified_rrs = verify_rr_stream(&rrs).unwrap();
508 assert_eq!(verified_rrs.verified_rrs.len(), 2);
510 let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
511 assert!(verified_rrs.valid_from < now);
512 assert!(verified_rrs.expires > now);
514 let resolved_rrs = verified_rrs.resolve_name(&query_name);
515 assert_eq!(resolved_rrs.len(), 1);
516 if let RR::Txt(txt) = &resolved_rrs[0] {
517 assert_eq!(txt.name.as_str(), "matt.user._bitcoin-payment.mattcorallo.com.");
518 assert!(txt.data.starts_with(b"bitcoin:"));