Marginally resolve time rollovers, giving us until 2133
authorMatt Corallo <git@bluematt.me>
Tue, 6 Feb 2024 01:46:21 +0000 (01:46 +0000)
committerMatt Corallo <git@bluematt.me>
Tue, 6 Feb 2024 01:46:21 +0000 (01:46 +0000)
src/lib.rs
src/query.rs

index 667624aa46eca30801aaae5f11854320b7ee81ed..c60f704925fc637f3c2ea81e2ee625aefd95f5c1 100644 (file)
@@ -240,13 +240,17 @@ pub struct VerifiedRRStream<'a> {
        ///
        /// Any records in [`Self::verified_rrs`] should not be considered valid unless this is before
        /// the current UNIX time.
-       pub valid_from: u32,
+       ///
+       /// While the field here is a u64, the algorithm used to identify rollovers will fail in 2133.
+       pub valid_from: u64,
        /// The earliest [`RRSig::expiration`] of all the [`RRSig`]s validated to verify
        /// [`Self::verified_rrs`].
        ///
        /// Any records in [`Self::verified_rrs`] should not be considered valid unless this is after
        /// the current UNIX time.
-       pub expires: u32,
+       ///
+       /// While the field here is a u64, the algorithm used to identify rollovers will fail in 2133.
+       pub expires: u64,
        /// The minimum [`RRSig::orig_ttl`] of all the [`RRSig`]s validated to verify
        /// [`Self::verified_rrs`].
        ///
@@ -254,6 +258,17 @@ pub struct VerifiedRRStream<'a> {
        pub max_cache_ttl: u32,
 }
 
+fn resolve_time(time: u32) -> u64 {
+       // RFC 2065 was published in January 1997, so we arbitrarily use that as a cutoff and assume
+       // any timestamps before then are actually past 2106 instead.
+       // We ignore leap years for simplicity.
+       if time < 60*60*24*365*27 {
+               (time as u64) + (u32::MAX as u64)
+       } else {
+               time.into()
+       }
+}
+
 /// Verifies the given set of resource records.
 ///
 /// Given a set of arbitrary records, this attempts to validate DNSSEC data from the [`root_hints`]
@@ -269,7 +284,7 @@ pub fn verify_rr_stream<'a>(inp: &'a [RR]) -> Result<VerifiedRRStream<'a>, Valid
        let mut res = Vec::new();
        let mut pending_ds_sets = Vec::with_capacity(1);
        let mut latest_inception = 0;
-       let mut earliest_expiry = u32::MAX;
+       let mut earliest_expiry = u64::MAX;
        let mut min_ttl = u32::MAX;
        'next_zone: while zone == "." || !pending_ds_sets.is_empty() {
                let mut found_unsupported_alg = false;
@@ -297,8 +312,8 @@ pub fn verify_rr_stream<'a>(inp: &'a [RR]) -> Result<VerifiedRRStream<'a>, Valid
                                verify_dnskey_rrsig(rrsig, next_ds_set.clone().unwrap(), dnskeys.clone().collect())
                        };
                        if dnskeys_verified.is_ok() {
-                               latest_inception = cmp::max(latest_inception, rrsig.inception);
-                               earliest_expiry = cmp::min(earliest_expiry, rrsig.expiration);
+                               latest_inception = cmp::max(latest_inception, resolve_time(rrsig.inception));
+                               earliest_expiry = cmp::min(earliest_expiry, resolve_time(rrsig.expiration));
                                min_ttl = cmp::min(min_ttl, rrsig.orig_ttl);
                                for rrsig in inp.iter()
                                        .filter_map(|rr| if let RR::RRSig(sig) = rr { Some(sig) } else { None })
@@ -308,8 +323,8 @@ pub fn verify_rr_stream<'a>(inp: &'a [RR]) -> Result<VerifiedRRStream<'a>, Valid
                                        let signed_records = inp.iter()
                                                .filter(|rr| rr.name() == &rrsig.name && rr.ty() == rrsig.ty);
                                        verify_rrsig(rrsig, dnskeys.clone(), signed_records.clone().collect())?;
-                                       latest_inception = cmp::max(latest_inception, rrsig.inception);
-                                       earliest_expiry = cmp::min(earliest_expiry, rrsig.expiration);
+                                       latest_inception = cmp::max(latest_inception, resolve_time(rrsig.inception));
+                                       earliest_expiry = cmp::min(earliest_expiry, resolve_time(rrsig.expiration));
                                        min_ttl = cmp::min(min_ttl, rrsig.orig_ttl);
                                        match rrsig.ty {
                                                // RRSigs shouldn't cover child `DnsKey`s or other `RRSig`s
index 7599afca9ae35955b9d1ae29123d42c3018227fa..b82f0a6d62007ded38822b30b80ce69de582fbb3 100644 (file)
@@ -211,6 +211,7 @@ mod tests {
        use rand::seq::SliceRandom;
 
        use std::net::ToSocketAddrs;
+       use std::time::SystemTime;
 
        #[test]
        fn test_txt_query() {
@@ -222,6 +223,10 @@ mod tests {
                rrs.shuffle(&mut rand::rngs::OsRng);
                let verified_rrs = verify_rr_stream(&rrs).unwrap();
                assert_eq!(verified_rrs.verified_rrs.len(), 1);
+
+               let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
+               assert!(verified_rrs.valid_from < now);
+               assert!(verified_rrs.expires > now);
        }
 
        #[cfg(feature = "tokio")]
@@ -238,5 +243,9 @@ mod tests {
                rrs.shuffle(&mut rand::rngs::OsRng);
                let verified_rrs = verify_rr_stream(&rrs).unwrap();
                assert_eq!(verified_rrs.verified_rrs.len(), 1);
+
+               let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
+               assert!(verified_rrs.valid_from < now);
+               assert!(verified_rrs.expires > now);
        }
 }