Add a utility function to resolve CNAMEs in verified RRs
authorMatt Corallo <git@bluematt.me>
Fri, 9 Feb 2024 23:06:04 +0000 (23:06 +0000)
committerMatt Corallo <git@bluematt.me>
Fri, 9 Feb 2024 23:25:01 +0000 (23:25 +0000)
src/query.rs
src/validation.rs

index b73b5b1d4d643f775a2d3ee0a5926405efd4c4fe..315c0a7f118268bd4411ae3fea3d98151e1e2a5b 100644 (file)
@@ -262,6 +262,31 @@ mod tests {
                assert!(verified_rrs.expires > now);
        }
 
+       #[test]
+       fn test_cname_query() {
+               for resolver in ["1.1.1.1:53", "8.8.8.8:53", "9.9.9.9:53"] {
+                       let sockaddr = resolver.to_socket_addrs().unwrap().next().unwrap();
+                       let query_name = "cname_test.matcorallo.com.".try_into().unwrap();
+                       let (proof, _) = build_txt_proof(sockaddr, &query_name).unwrap();
+
+                       let mut rrs = parse_rr_stream(&proof).unwrap();
+                       rrs.shuffle(&mut rand::rngs::OsRng);
+                       let verified_rrs = verify_rr_stream(&rrs).unwrap();
+                       assert_eq!(verified_rrs.verified_rrs.len(), 2);
+
+                       let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
+                       assert!(verified_rrs.valid_from < now);
+                       assert!(verified_rrs.expires > now);
+
+                       let resolved_rrs = verified_rrs.resolve_name(&query_name);
+                       assert_eq!(resolved_rrs.len(), 1);
+                       if let RR::Txt(txt) = &resolved_rrs[0] {
+                               assert_eq!(txt.name.as_str(), "txt_test.matcorallo.com.");
+                               assert_eq!(txt.data, b"dnssec_prover_test");
+                       } else { panic!(); }
+               }
+       }
+
        #[cfg(feature = "tokio")]
        use tokio_crate as tokio;
 
index 615146eb399648812fd7edf1809cceb96389a086..7b2fab98a71b54cf92b125aaf0405caa050ded29 100644 (file)
@@ -350,6 +350,29 @@ pub fn verify_rr_stream<'a>(inp: &'a [RR]) -> Result<VerifiedRRStream<'a>, Valid
        }
 }
 
+impl<'a> VerifiedRRStream<'a> {
+       /// Given a name, resolve any [`CName`] records and return any verified records which were
+       /// pointed to by the original name.
+       ///
+       /// Note that because of [`CName`]s, the [`RR::name`] in the returned records may or may not be
+       /// equal to `name`.
+       ///
+       /// You MUST still check that the current UNIX time is between
+       /// [`VerifiedRRStream::valid_from`] and [`VerifiedRRStream::expires`] before
+       /// using any records returned here.
+       pub fn resolve_name<'b>(&self, mut name: &'b Name) -> Vec<&'a RR> where 'a: 'b {
+               loop {
+                       let mut cname_search = self.verified_rrs.iter()
+                               .filter(|rr| rr.name() == name)
+                               .filter_map(|rr| if let RR::CName(cn) = rr { Some(cn) } else { None });
+                       if let Some(cname) = cname_search.next() {
+                               name = &cname.canonical_name;
+                       }
+                       return self.verified_rrs.iter().filter(|rr| rr.name() == name).map(|rr| *rr).collect();
+               }
+       }
+}
+
 #[cfg(test)]
 mod tests {
        #![allow(deprecated)]
@@ -664,6 +687,14 @@ mod tests {
                        assert_eq!(cname.name.as_str(), "cname_test.matcorallo.com.");
                        assert_eq!(cname.canonical_name.as_str(), "txt_test.matcorallo.com.");
                } else { panic!(); }
+
+               let filtered_rrs =
+                       verified_rrs.resolve_name(&"cname_test.matcorallo.com.".try_into().unwrap());
+               assert_eq!(filtered_rrs.len(), 1);
+               if let RR::Txt(txt) = &filtered_rrs[0] {
+                       assert_eq!(txt.name.as_str(), "txt_test.matcorallo.com.");
+                       assert_eq!(txt.data, b"dnssec_prover_test");
+               } else { panic!(); }
        }
 
        #[test]
@@ -697,6 +728,14 @@ mod tests {
                        assert_eq!(cname.name.as_str(), "test.cname_wildcard_test.matcorallo.com.");
                        assert_eq!(cname.canonical_name.as_str(), "cname.wildcard_test.matcorallo.com.");
                } else { panic!(); }
+
+               let filtered_rrs =
+                       verified_rrs.resolve_name(&"test.cname_wildcard_test.matcorallo.com.".try_into().unwrap());
+               assert_eq!(filtered_rrs.len(), 1);
+               if let RR::Txt(txt) = &filtered_rrs[0] {
+                       assert_eq!(txt.name.as_str(), "cname.wildcard_test.matcorallo.com.");
+                       assert_eq!(txt.data, b"wildcard_test");
+               } else { panic!(); }
        }
 
        #[test]