+ min_ttl = cmp::min(min_ttl, ttl);
+ if let RR::RRSig(rrsig) = rr { rrsig_key_names.push(rrsig.key_name); }
+ }
+
+ Ok(min_ttl)
+}
+
+#[cfg(fuzzing)]
+/// Read a stream of responses and handle them it as if they came from a server, for fuzzing.
+pub fn fuzz_proof_builder(mut response_stream: &[u8]) {
+ let (mut builder, _) = ProofBuilder::new(&"example.com.".try_into().unwrap(), Txt::TYPE);
+ while builder.awaiting_responses() {
+ let len = if let Ok(len) = read_u16(&mut response_stream) { len } else { return };
+ let mut buf = QueryBuf::new_zeroed(len);
+ if response_stream.len() < len as usize { return; }
+ buf.copy_from_slice(&response_stream[..len as usize]);
+ response_stream = &response_stream[len as usize..];
+ let _ = builder.process_response(&buf);
+ }
+ let _ = builder.finish_proof();
+}
+
+const MAX_REQUESTS: usize = 10;
+/// A simple state machine which will generate a series of queries and process the responses until
+/// it has built a DNSSEC proof.
+///
+/// A [`ProofBuilder`] driver starts with [`ProofBuilder::new`], fetching the state machine and
+/// initial query. As long as [`ProofBuilder::awaiting_responses`] returns true, responses should
+/// be read from the resolver. For each query response read from the DNS resolver,
+/// [`ProofBuilder::process_response`] should be called, and each fresh query returned should be
+/// sent to the resolver. Once [`ProofBuilder::awaiting_responses`] returns false,
+/// [`ProofBuilder::finish_proof`] should be called to fetch the resulting proof.
+///
+/// To build a DNSSEC proof using a DoH server, take each [`QueryBuf`], encode it as base64url, and
+/// make a query to `https://doh-server/endpoint?dns=base64url_encoded_query` with an `Accept`
+/// header of `application/dns-message`. Each response, in raw binary, can be fed directly into
+/// [`ProofBuilder::process_response`].
+pub struct ProofBuilder {
+ proof: Vec<u8>,
+ min_ttl: u32,
+ dnskeys_requested: Vec<Name>,
+ pending_queries: usize,
+ queries_made: usize,
+}
+
+impl ProofBuilder {
+ /// Constructs a new [`ProofBuilder`] and an initial query to send to the recursive resolver to
+ /// begin the proof building process.
+ ///
+ /// Given a correctly-functioning resolver the proof will ultimately be able to prove the
+ /// contents of any records with the given `ty`pe at the given `name` (as long as the given
+ /// `ty`pe is supported by this library).
+ ///
+ /// You can find constants for supported standard types in the [`crate::rr`] module.
+ pub fn new(name: &Name, ty: u16) -> (ProofBuilder, QueryBuf) {
+ let initial_query = build_query(name, ty);
+ (ProofBuilder {
+ proof: Vec::new(),
+ min_ttl: u32::MAX,
+ dnskeys_requested: Vec::with_capacity(MAX_REQUESTS),
+ pending_queries: 1,
+ queries_made: 1,
+ }, initial_query)
+ }
+
+ /// Returns true as long as further responses are expected from the resolver.
+ ///
+ /// As long as this returns true, responses should be read from the resolver and passed to
+ /// [`Self::process_response`]. Once this returns false, [`Self::finish_proof`] should be used
+ /// to (possibly) get the final proof.
+ pub fn awaiting_responses(&self) -> bool {
+ self.pending_queries > 0 && self.queries_made <= MAX_REQUESTS
+ }
+
+ /// Processes a query response from the recursive resolver, returning a list of new queries to
+ /// send to the resolver.
+ pub fn process_response(&mut self, resp: &QueryBuf) -> Result<Vec<QueryBuf>, ()> {
+ if self.pending_queries == 0 { return Err(()); }
+
+ let mut rrsig_key_names = Vec::new();
+ let min_ttl = handle_response(&resp, &mut self.proof, &mut rrsig_key_names)?;
+ self.min_ttl = cmp::min(self.min_ttl, min_ttl);
+ self.pending_queries -= 1;
+
+ rrsig_key_names.sort_unstable();
+ rrsig_key_names.dedup();
+
+ let mut new_queries = Vec::with_capacity(2);
+ for key_name in rrsig_key_names.drain(..) {
+ if !self.dnskeys_requested.contains(&key_name) {
+ new_queries.push(build_query(&key_name, DnsKey::TYPE));
+ self.pending_queries += 1;
+ self.queries_made += 1;
+ self.dnskeys_requested.push(key_name.clone());
+
+ if key_name.as_str() != "." {
+ new_queries.push(build_query(&key_name, DS::TYPE));
+ self.pending_queries += 1;
+ self.queries_made += 1;
+ }
+ }
+ }
+ if self.queries_made <= MAX_REQUESTS {
+ Ok(new_queries)
+ } else {
+ Ok(Vec::new())
+ }
+ }
+
+ /// Finalizes the proof, if one is available, and returns it as well as the TTL that should be
+ /// used to cache the proof (i.e. the lowest TTL of all records which were used to build the
+ /// proof).
+ pub fn finish_proof(self) -> Result<(Vec<u8>, u32), ()> {
+ if self.pending_queries > 0 || self.queries_made > MAX_REQUESTS {
+ Err(())
+ } else {
+ Ok((self.proof, self.min_ttl))
+ }