} else { panic!(); }
}
}
+
+ #[cfg(feature = "tokio")]
+ #[tokio::test]
+ async fn test_dname_wildcard_query_async() {
+ 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 = "wildcard_a.wildcard_b.dname_test.dnssec_proof_tests.bitcoin.ninja.".try_into().unwrap();
+ let (proof, _) = build_txt_proof_async(sockaddr, &query_name).await.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(), 3);
+
+ 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(), "cname.wildcard_test.dnssec_proof_tests.bitcoin.ninja.");
+ assert_eq!(txt.data, b"wildcard_test");
+ } else { panic!(); }
+ }
+ }
+
}
TLSA(TLSA),
/// A Canonical Name record
CName(CName),
+ /// A Delegation Name record
+ DName(DName),
/// A DNS (Public) Key resource record
DnsKey(DnsKey),
/// A Delegated Signer resource record
RR::NS(rr) => &rr.name,
RR::Txt(rr) => &rr.name,
RR::CName(rr) => &rr.name,
+ RR::DName(rr) => &rr.name,
RR::TLSA(rr) => &rr.name,
RR::DnsKey(rr) => &rr.name,
RR::DS(rr) => &rr.name,
RR::NS(rr) => StaticRecord::json(rr),
RR::Txt(rr) => StaticRecord::json(rr),
RR::CName(rr) => StaticRecord::json(rr),
+ RR::DName(rr) => StaticRecord::json(rr),
RR::TLSA(rr) => StaticRecord::json(rr),
RR::DnsKey(rr) => StaticRecord::json(rr),
RR::DS(rr) => StaticRecord::json(rr),
RR::NS(_) => NS::TYPE,
RR::Txt(_) => Txt::TYPE,
RR::CName(_) => CName::TYPE,
+ RR::DName(_) => DName::TYPE,
RR::TLSA(_) => TLSA::TYPE,
RR::DnsKey(_) => DnsKey::TYPE,
RR::DS(_) => DS::TYPE,
RR::NS(rr) => StaticRecord::write_u16_len_prefixed_data(rr, out),
RR::Txt(rr) => StaticRecord::write_u16_len_prefixed_data(rr, out),
RR::CName(rr) => StaticRecord::write_u16_len_prefixed_data(rr, out),
+ RR::DName(rr) => StaticRecord::write_u16_len_prefixed_data(rr, out),
RR::TLSA(rr) => StaticRecord::write_u16_len_prefixed_data(rr, out),
RR::DnsKey(rr) => StaticRecord::write_u16_len_prefixed_data(rr, out),
RR::DS(rr) => StaticRecord::write_u16_len_prefixed_data(rr, out),
impl From<NS> for RR { fn from(ns: NS) -> RR { RR::NS(ns) } }
impl From<Txt> for RR { fn from(txt: Txt) -> RR { RR::Txt(txt) } }
impl From<CName> for RR { fn from(cname: CName) -> RR { RR::CName(cname) } }
+impl From<DName> for RR { fn from(cname: DName) -> RR { RR::DName(cname) } }
impl From<TLSA> for RR { fn from(tlsa: TLSA) -> RR { RR::TLSA(tlsa) } }
impl From<DnsKey> for RR { fn from(dnskey: DnsKey) -> RR { RR::DnsKey(dnskey) } }
impl From<DS> for RR { fn from(ds: DS) -> RR { RR::DS(ds) } }
}
}
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+/// A Delegation Name resource record, referring all queries for subdomains of this name to another
+/// subtree of the DNS.
+pub struct DName {
+ /// The name this record is at.
+ pub name: Name,
+ /// The delegation name.
+ ///
+ /// A resolver should use this domain name tree when looking up any further records for
+ /// subdomains of [`self.name`].
+ pub delegation_name: Name,
+}
+impl StaticRecord for DName {
+ const TYPE: u16 = 39;
+ fn name(&self) -> &Name { &self.name }
+ fn json(&self) -> String {
+ format!("{{\"type\":\"dname\",\"name\":\"{}\",\"delegation_name\":\"{}\"}}",
+ self.name.0, self.delegation_name.0)
+ }
+ fn read_from_data(name: Name, mut data: &[u8], wire_packet: &[u8]) -> Result<Self, ()> {
+ Ok(DName { name, delegation_name: read_wire_packet_name(&mut data, wire_packet)? })
+ }
+ fn write_u16_len_prefixed_data(&self, out: &mut Vec<u8>) {
+ let len: u16 = name_len(&self.delegation_name);
+ out.extend_from_slice(&len.to_be_bytes());
+ write_name(out, &self.delegation_name);
+ }
+}
+
+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
/// A public key resource record which can be used to validate [`RRSig`]s.
pub struct DnsKey {
NS::TYPE => RR::NS(NS::read_from_data(name, data, wire_packet)?),
Txt::TYPE => RR::Txt(Txt::read_from_data(name, data, wire_packet)?),
CName::TYPE => RR::CName(CName::read_from_data(name, data, wire_packet)?),
+ DName::TYPE => RR::DName(DName::read_from_data(name, data, wire_packet)?),
TLSA::TYPE => RR::TLSA(TLSA::read_from_data(name, data, wire_packet)?),
DnsKey::TYPE => RR::DnsKey(DnsKey::read_from_data(name, data, wire_packet)?),
DS::TYPE => RR::DS(DS::read_from_data(name, data, wire_packet)?),
//! Utilities to deserialize and validate RFC 9102 proofs
+use alloc::borrow::ToOwned;
use alloc::vec::Vec;
use alloc::vec;
use core::cmp;
/// 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 {
+ pub fn resolve_name<'b>(&self, name_param: &'b Name) -> Vec<&'a RR> where 'a: 'b {
+ let mut dname_name;
+ let mut name = name_param;
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;
+ continue;
}
+
+ let mut dname_search = self.verified_rrs.iter()
+ .filter(|rr| name.ends_with(&**rr.name()))
+ .filter_map(|rr| if let RR::DName(dn) = rr { Some(dn) } else { None });
+ if let Some(dname) = dname_search.next() {
+ let prefix = name.strip_suffix(&*dname.name).expect("We just filtered for this");
+ let resolved_name = prefix.to_owned() + &dname.delegation_name;
+ dname_name = if let Ok(name) = resolved_name.try_into() {
+ name
+ } else {
+ // This should only happen if the combined name ended up being too long
+ return Vec::new();
+ };
+ name = &dname_name;
+ continue;
+ }
+
return self.verified_rrs.iter().filter(|rr| rr.name() == name).map(|rr| *rr).collect();
}
}