Introduce a Timestamp type for Rrsig. (#294)

This PR splits the RRSIG timestamps from Serial into their own type
rdata::dnssec::Timestamp.

This PR is a breaking change.
This commit is contained in:
Philip-NLnetLabs
2024-04-11 17:22:03 +02:00
committed by GitHub
parent cf5288a57a
commit 084c842eae
4 changed files with 273 additions and 156 deletions
+1 -122
View File
@@ -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<S: Scanner>(scanner: &mut S) -> Result<Self, S::Error> {
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<S: Scanner>(scanner: &mut S) -> Result<Self, S::Error> {
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<Self, IllegalSignatureTime> {
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)]
+264 -25
View File
@@ -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<Name> {
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<Name> ProtoRrsig<Name> {
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<Name: ToDname> ProtoRrsig<Name> {
+ 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 isnt 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<S: Scanner>(scanner: &mut S) -> Result<Self, S::Error> {
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<Octs: AsRef<[u8]> + ?Sized>(
parser: &mut Parser<Octs>,
) -> Result<Self, ParseError> {
Serial::parse(parser).map(Self)
}
pub fn compose<Target: Composer + ?Sized>(
&self,
target: &mut Target,
) -> Result<(), Target::AppendError> {
self.0.compose(target)
}
}
//--- From and FromStr
impl From<u32> 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<Self, Self::Err> {
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<cmp::Ordering> {
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<Octs, Name> {
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<Octs, Name> Rrsig<Octs, Name> {
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<Octs, Name> Rrsig<Octs, Name> {
+ 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<Octs, Name> Rrsig<Octs, Name> {
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<Octs, Name> Rrsig<Octs, Name> {
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<Octs, Name> Rrsig<Octs, Name> {
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<Octs> Rrsig<Octs, ParsedDname<Octs>> {
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::<Vec<u8>>::from_str("example.com.").unwrap(),
b"key",
+3 -4
View File
@@ -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<N, D> SortedRecords<N, D> {
pub fn sign<Octets, Key, ApexName>(
&self,
apex: &FamilyName<ApexName>,
expiration: Serial,
inception: Serial,
expiration: Timestamp,
inception: Timestamp,
key: Key,
) -> Result<Vec<Record<N, Rrsig<Octets, ApexName>>>, Key::Error>
where
+5 -5
View File
@@ -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::<Dname>().unwrap(),
base64::decode::<Vec<u8>>(
@@ -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::<Vec<u8>>(