diff --git a/src/base/serial.rs b/src/base/serial.rs index 898e3ea0..5d6a7626 100644 --- a/src/base/serial.rs +++ b/src/base/serial.rs @@ -8,19 +8,17 @@ //! [`Serial`]: struct.Serial.html use super::cmp::CanonicalOrd; -use super::scan::{Scan, Scanner, ScannerError}; +use super::scan::{Scan, Scanner}; use super::wire::{Compose, Composer, Parse, ParseError}; #[cfg(feature = "chrono")] use chrono::{DateTime, TimeZone}; use core::cmp::Ordering; -use core::str::FromStr; use core::{cmp, fmt, str}; #[cfg(all(feature = "std", feature = "mock-time"))] use mock_instant::{SystemTime, UNIX_EPOCH}; use octseq::parse::Parser; #[cfg(all(feature = "std", not(feature = "mock-time")))] use std::time::{SystemTime, UNIX_EPOCH}; -use time::{Date, Month, PrimitiveDateTime, Time}; //------------ Serial -------------------------------------------------------- @@ -96,107 +94,6 @@ impl Serial { pub fn scan(scanner: &mut S) -> Result { u32::scan(scanner).map(Into::into) } - - /// Scan a serial represention signature time value. - /// - /// In [RRSIG] records, the expiration and inception times are given as - /// serial values. Their representation format can either be the - /// value or a specific date in `YYYYMMDDHHmmSS` format. - /// - /// [RRSIG]: ../../rdata/rfc4034/struct.Rrsig.html - pub fn scan_rrsig(scanner: &mut S) -> Result { - let mut pos = 0; - let mut buf = [0u8; 14]; - scanner.scan_symbols(|symbol| { - if pos >= 14 { - return Err(S::Error::custom("illegal signature time")); - } - buf[pos] = symbol - .into_digit(10) - .map_err(|_| S::Error::custom("illegal signature time"))? - as u8; - pos += 1; - Ok(()) - })?; - if pos <= 10 { - // We have an integer. We generate it into a u64 to deal - // with possible overflows. - let mut res = 0u64; - for ch in &buf[..pos] { - res = res * 10 + (u64::from(*ch)); - } - if res > u64::from(u32::MAX) { - Err(S::Error::custom("illegal signature time")) - } else { - Ok(Serial(res as u32)) - } - } else if pos == 14 { - let year = u32_from_buf(&buf[0..4]) as i32; - let month = Month::try_from(u8_from_buf(&buf[4..6])) - .map_err(|_| S::Error::custom("illegal signature time"))?; - let day = u8_from_buf(&buf[6..8]); - let hour = u8_from_buf(&buf[8..10]); - let minute = u8_from_buf(&buf[10..12]); - let second = u8_from_buf(&buf[12..14]); - Ok(Serial( - PrimitiveDateTime::new( - Date::from_calendar_date(year, month, day).map_err( - |_| S::Error::custom("illegal signature time"), - )?, - Time::from_hms(hour, minute, second).map_err(|_| { - S::Error::custom("illegal signature time") - })?, - ) - .assume_utc() - .unix_timestamp() as u32, - )) - } else { - Err(S::Error::custom("illegal signature time")) - } - } - - /// Parses a serial representing a time value from a string. - /// - /// In [RRSIG] records, the expiration and inception times are given as - /// serial values. Their representation format can either be the - /// value or a specific date in `YYYYMMDDHHmmSS` format. - /// - /// [RRSIG]: ../../rdata/rfc4034/struct.Rrsig.html - pub fn rrsig_from_str(src: &str) -> Result { - if !src.is_ascii() { - return Err(IllegalSignatureTime(())); - } - if src.len() == 14 { - let year = u32::from_str(&src[0..4]) - .map_err(|_| IllegalSignatureTime(()))? - as i32; - let month = Month::try_from( - u8::from_str(&src[4..6]) - .map_err(|_| IllegalSignatureTime(()))?, - ) - .map_err(|_| IllegalSignatureTime(()))?; - let day = u8::from_str(&src[6..8]) - .map_err(|_| IllegalSignatureTime(()))?; - let hour = u8::from_str(&src[8..10]) - .map_err(|_| IllegalSignatureTime(()))?; - let minute = u8::from_str(&src[10..12]) - .map_err(|_| IllegalSignatureTime(()))?; - let second = u8::from_str(&src[12..14]) - .map_err(|_| IllegalSignatureTime(()))?; - Ok(Serial( - PrimitiveDateTime::new( - Date::from_calendar_date(year, month, day) - .map_err(|_| IllegalSignatureTime(()))?, - Time::from_hms(hour, minute, second) - .map_err(|_| IllegalSignatureTime(()))?, - ) - .assume_utc() - .unix_timestamp() as u32, - )) - } else { - Serial::from_str(src).map_err(|_| IllegalSignatureTime(())) - } - } } /// # Parsing and Composing @@ -288,24 +185,6 @@ impl CanonicalOrd for Serial { } } -//------------ Helper Functions ---------------------------------------------- - -fn u8_from_buf(buf: &[u8]) -> u8 { - let mut res = 0; - for ch in buf { - res = res * 10 + *ch; - } - res -} - -fn u32_from_buf(buf: &[u8]) -> u32 { - let mut res = 0; - for ch in buf { - res = res * 10 + (u32::from(*ch)); - } - res -} - //============ Errors ======================================================== #[derive(Clone, Copy, Debug)] diff --git a/src/rdata/dnssec.rs b/src/rdata/dnssec.rs index 782bc418..cfdb932c 100644 --- a/src/rdata/dnssec.rs +++ b/src/rdata/dnssec.rs @@ -17,7 +17,7 @@ use crate::base::wire::{Compose, Composer, FormError, Parse, ParseError}; use crate::utils::{base16, base64}; use core::cmp::Ordering; use core::convert::TryInto; -use core::{fmt, hash, ptr}; +use core::{cmp, fmt, hash, ptr, str}; use octseq::builder::{ EmptyBuilder, FreezeBuilder, FromBuilder, OctetsBuilder, Truncate, }; @@ -27,6 +27,7 @@ use octseq::parse::Parser; use octseq::serde::{DeserializeOctets, SerializeOctets}; #[cfg(feature = "std")] use std::vec::Vec; +use time::{Date, Month, PrimitiveDateTime, Time}; //------------ Dnskey -------------------------------------------------------- @@ -436,8 +437,8 @@ pub struct ProtoRrsig { algorithm: SecAlg, labels: u8, original_ttl: Ttl, - expiration: Serial, - inception: Serial, + expiration: Timestamp, + inception: Timestamp, key_tag: u16, signer_name: Name, } @@ -449,8 +450,8 @@ impl ProtoRrsig { algorithm: SecAlg, labels: u8, original_ttl: Ttl, - expiration: Serial, - inception: Serial, + expiration: Timestamp, + inception: Timestamp, key_tag: u16, signer_name: Name, ) -> Self { @@ -509,8 +510,8 @@ impl ProtoRrsig { + SecAlg::COMPOSE_LEN + u8::COMPOSE_LEN + u32::COMPOSE_LEN - + Serial::COMPOSE_LEN - + Serial::COMPOSE_LEN + + Timestamp::COMPOSE_LEN + + Timestamp::COMPOSE_LEN + u16::COMPOSE_LEN + self.signer_name.compose_len() } @@ -576,6 +577,230 @@ where } } +//------------ Timestamp ------------------------------------------------------ + +/// A Timestamp for RRSIG Records. +/// +/// DNS uses 32 bit timestamps that are conceptionally +/// viewed as the 32 bit modulus of a larger number space. Because of that, +/// special rules apply when processing these values. + +/// [RFC 4034] defines Timestamps as the number of seconds elepased since +/// since 1 January 1970 00:00:00 UTC, ignoring leap seconds. Timestamps +/// are compared using so-called "Serial number arithmetic", as defined in +/// [RFC 1982]. + +/// The RFC defines the semantics for doing arithmetics in the +/// face of these wrap-arounds. This type implements these semantics atop a +/// native `u32`. The RFC defines two operations: addition and comparison. +/// +/// For addition, the amount added can only be a positive number of up to +/// `2^31 - 1`. Because of this, we decided to not implement the +/// `Add` trait but rather have a dedicated method `add` so as to not cause +/// surprise panics. +/// +/// Timestamps only implement a partial ordering. That is, there are +/// pairs of values that are not equal but there still isn’t one value larger +/// than the other. Since this is neatly implemented by the `PartialOrd` +/// trait, the type implements that. + +/// +/// [RFC 1982]: https://tools.ietf.org/html/rfc1982 +/// [RFC 4034]: https://tools.ietf.org/html/rfc4034 + +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Timestamp(Serial); + +impl Timestamp { + /// Returns a serial number for the current Unix time. + #[cfg(feature = "std")] + #[must_use] + pub fn now() -> Self { + Self(Serial::now()) + } + + /// Scan a serial represention signature time value. + /// + /// In [RRSIG] records, the expiration and inception times are given as + /// serial values. Their representation format can either be the + /// value or a specific date in `YYYYMMDDHHmmSS` format. + /// + /// [RRSIG]: ../../rdata/rfc4034/struct.Rrsig.html + pub fn scan(scanner: &mut S) -> Result { + let mut pos = 0; + let mut buf = [0u8; 14]; + scanner.scan_symbols(|symbol| { + if pos >= 14 { + return Err(S::Error::custom("illegal signature time")); + } + buf[pos] = symbol + .into_digit(10) + .map_err(|_| S::Error::custom("illegal signature time"))? + as u8; + pos += 1; + Ok(()) + })?; + if pos <= 10 { + // We have an integer. We generate it into a u64 to deal + // with possible overflows. + let mut res = 0u64; + for ch in &buf[..pos] { + res = res * 10 + (u64::from(*ch)); + } + if res > u64::from(u32::MAX) { + Err(S::Error::custom("illegal signature time")) + } else { + Ok(Self(Serial(res as u32))) + } + } else if pos == 14 { + let year = u32_from_buf(&buf[0..4]) as i32; + let month = Month::try_from(u8_from_buf(&buf[4..6])) + .map_err(|_| S::Error::custom("illegal signature time"))?; + let day = u8_from_buf(&buf[6..8]); + let hour = u8_from_buf(&buf[8..10]); + let minute = u8_from_buf(&buf[10..12]); + let second = u8_from_buf(&buf[12..14]); + Ok(Self(Serial( + PrimitiveDateTime::new( + Date::from_calendar_date(year, month, day).map_err( + |_| S::Error::custom("illegal signature time"), + )?, + Time::from_hms(hour, minute, second).map_err(|_| { + S::Error::custom("illegal signature time") + })?, + ) + .assume_utc() + .unix_timestamp() as u32, + ))) + } else { + Err(S::Error::custom("illegal signature time")) + } + } + + /// Returns the timestamp as a raw integer. + #[must_use] + pub fn into_int(self) -> u32 { + self.0.into_int() + } + +} + +/// # Parsing and Composing +/// +impl Timestamp { + pub const COMPOSE_LEN: u16 = Serial::COMPOSE_LEN; + + pub fn parse + ?Sized>( + parser: &mut Parser, + ) -> Result { + Serial::parse(parser).map(Self) + } + + pub fn compose( + &self, + target: &mut Target, + ) -> Result<(), Target::AppendError> { + self.0.compose(target) + } +} + + +//--- From and FromStr + +impl From for Timestamp { + fn from(item: u32) -> Self { + Self(Serial::from(item)) + } +} + +impl str::FromStr for Timestamp { + type Err = IllegalSignatureTime; + + /// Parses a timestamp value from a string. + /// + /// The presentation format can either be their integer value or a + /// specific date in `YYYYMMDDHHmmSS` format. + fn from_str(src: &str) -> Result { + if !src.is_ascii() { + return Err(IllegalSignatureTime(())); + } + if src.len() == 14 { + let year = u32::from_str(&src[0..4]) + .map_err(|_| IllegalSignatureTime(()))? + as i32; + let month = Month::try_from( + u8::from_str(&src[4..6]) + .map_err(|_| IllegalSignatureTime(()))?, + ) + .map_err(|_| IllegalSignatureTime(()))?; + let day = u8::from_str(&src[6..8]) + .map_err(|_| IllegalSignatureTime(()))?; + let hour = u8::from_str(&src[8..10]) + .map_err(|_| IllegalSignatureTime(()))?; + let minute = u8::from_str(&src[10..12]) + .map_err(|_| IllegalSignatureTime(()))?; + let second = u8::from_str(&src[12..14]) + .map_err(|_| IllegalSignatureTime(()))?; + Ok(Timestamp(Serial( + PrimitiveDateTime::new( + Date::from_calendar_date(year, month, day) + .map_err(|_| IllegalSignatureTime(()))?, + Time::from_hms(hour, minute, second) + .map_err(|_| IllegalSignatureTime(()))?, + ) + .assume_utc() + .unix_timestamp() as u32, + ))) + } else { + Serial::from_str(src).map(Timestamp).map_err(|_| { + IllegalSignatureTime(()) + }) + } + } +} + +//--- Display + +impl fmt::Display for Timestamp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +//--- PartialOrd and CanonicalOrd + +impl cmp::PartialOrd for Timestamp { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl CanonicalOrd for Timestamp { + fn canonical_cmp(&self, other: &Self) -> cmp::Ordering { + self.0.canonical_cmp(&other.0) + } +} + + +//------------ Helper Functions ---------------------------------------------- + +fn u8_from_buf(buf: &[u8]) -> u8 { + let mut res = 0; + for ch in buf { + res = res * 10 + *ch; + } + res +} + +fn u32_from_buf(buf: &[u8]) -> u32 { + let mut res = 0; + for ch in buf { + res = res * 10 + (u32::from(*ch)); + } + res +} + //------------ Rrsig --------------------------------------------------------- #[derive(Clone)] @@ -600,8 +825,8 @@ pub struct Rrsig { algorithm: SecAlg, labels: u8, original_ttl: Ttl, - expiration: Serial, - inception: Serial, + expiration: Timestamp, + inception: Timestamp, key_tag: u16, signer_name: Name, #[cfg_attr( @@ -623,8 +848,8 @@ impl Rrsig { algorithm: SecAlg, labels: u8, original_ttl: Ttl, - expiration: Serial, - inception: Serial, + expiration: Timestamp, + inception: Timestamp, key_tag: u16, signer_name: Name, signature: Octs, @@ -639,8 +864,8 @@ impl Rrsig { + SecAlg::COMPOSE_LEN + u8::COMPOSE_LEN + u32::COMPOSE_LEN - + Serial::COMPOSE_LEN - + Serial::COMPOSE_LEN + + Timestamp::COMPOSE_LEN + + Timestamp::COMPOSE_LEN + u16::COMPOSE_LEN + signer_name.compose_len(), ) @@ -674,8 +899,8 @@ impl Rrsig { algorithm: SecAlg, labels: u8, original_ttl: Ttl, - expiration: Serial, - inception: Serial, + expiration: Timestamp, + inception: Timestamp, key_tag: u16, signer_name: Name, signature: Octs, @@ -709,11 +934,11 @@ impl Rrsig { self.original_ttl } - pub fn expiration(&self) -> Serial { + pub fn expiration(&self) -> Timestamp { self.expiration } - pub fn inception(&self) -> Serial { + pub fn inception(&self) -> Timestamp { self.inception } @@ -789,8 +1014,8 @@ impl Rrsig { SecAlg::scan(scanner)?, u8::scan(scanner)?, Ttl::scan(scanner)?, - Serial::scan_rrsig(scanner)?, - Serial::scan_rrsig(scanner)?, + Timestamp::scan(scanner)?, + Timestamp::scan(scanner)?, u16::scan(scanner)?, scanner.scan_dname()?, scanner.convert_entry(base64::SymbolConverter::new())?, @@ -807,8 +1032,8 @@ impl Rrsig> { let algorithm = SecAlg::parse(parser)?; let labels = u8::parse(parser)?; let original_ttl = Ttl::parse(parser)?; - let expiration = Serial::parse(parser)?; - let inception = Serial::parse(parser)?; + let expiration = Timestamp::parse(parser)?; + let inception = Timestamp::parse(parser)?; let key_tag = u16::parse(parser)?; let signer_name = ParsedDname::parse(parser)?; let len = parser.remaining(); @@ -1049,8 +1274,8 @@ where + SecAlg::COMPOSE_LEN + u8::COMPOSE_LEN + u32::COMPOSE_LEN - + Serial::COMPOSE_LEN - + Serial::COMPOSE_LEN + + Timestamp::COMPOSE_LEN + + Timestamp::COMPOSE_LEN + u16::COMPOSE_LEN + self.signer_name.compose_len()) .checked_add( @@ -2413,6 +2638,20 @@ fn read_window(data: &[u8]) -> Option<((u8, &[u8]), &[u8])> { }) } +//============ Errors ======================================================== + +#[derive(Clone, Copy, Debug)] +pub struct IllegalSignatureTime(()); + +impl fmt::Display for IllegalSignatureTime { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("illegal signature time") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for IllegalSignatureTime {} + //============ Test ========================================================== #[cfg(test)] @@ -2448,8 +2687,8 @@ mod test { SecAlg::RSASHA1, 3, Ttl::from_secs(12), - Serial::from(13), - Serial::from(14), + Timestamp::from(13), + Timestamp::from(14), 15, Dname::>::from_str("example.com.").unwrap(), b"key", diff --git a/src/sign/records.rs b/src/sign/records.rs index 55d019d3..7d0c3a3d 100644 --- a/src/sign/records.rs +++ b/src/sign/records.rs @@ -6,9 +6,8 @@ use crate::base::iana::{Class, Rtype}; use crate::base::name::ToDname; use crate::base::rdata::{ComposeRecordData, RecordData}; use crate::base::record::Record; -use crate::base::serial::Serial; use crate::base::Ttl; -use crate::rdata::dnssec::{ProtoRrsig, RtypeBitmap}; +use crate::rdata::dnssec::{ProtoRrsig, RtypeBitmap, Timestamp}; use crate::rdata::{Dnskey, Ds, Nsec, Rrsig}; use octseq::builder::{EmptyBuilder, FromBuilder, OctetsBuilder, Truncate}; use std::vec::Vec; @@ -66,8 +65,8 @@ impl SortedRecords { pub fn sign( &self, apex: &FamilyName, - expiration: Serial, - inception: Serial, + expiration: Timestamp, + inception: Timestamp, key: Key, ) -> Result>>, Key::Error> where diff --git a/src/validate.rs b/src/validate.rs index 5a76dd75..eb883178 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -349,8 +349,8 @@ impl error::Error for AlgorithmError {} mod test { use super::*; use crate::base::iana::{Class, Rtype}; - use crate::base::serial::Serial; use crate::base::Ttl; + use crate::rdata::dnssec::Timestamp; use crate::rdata::{Mx, ZoneRecordData}; use crate::utils::base64; use bytes::Bytes; @@ -487,8 +487,8 @@ mod test { SecAlg::RSASHA256, 1, Ttl::from_secs(86400), - Serial::rrsig_from_str("20210921162830").unwrap(), - Serial::rrsig_from_str("20210906162330").unwrap(), + Timestamp::from_str("20210921162830").unwrap(), + Timestamp::from_str("20210906162330").unwrap(), 35886, "net.".parse::().unwrap(), base64::decode::>( @@ -673,8 +673,8 @@ mod test { SecAlg::RSASHA1, 2, Ttl::from_secs(3600), - Serial::rrsig_from_str("20040509183619").unwrap(), - Serial::rrsig_from_str("20040409183619").unwrap(), + Timestamp::from_str("20040509183619").unwrap(), + Timestamp::from_str("20040409183619").unwrap(), 38519, Dname::from_str("example.").unwrap(), base64::decode::>(