diff --git a/der/src/asn1/generalized_time.rs b/der/src/asn1/generalized_time.rs index c3a0be004..63305315b 100644 --- a/der/src/asn1/generalized_time.rs +++ b/der/src/asn1/generalized_time.rs @@ -40,7 +40,7 @@ impl GeneralizedTime { } /// Convert this [`GeneralizedTime`] into a [`DateTime`]. - pub fn to_date_time(&self) -> DateTime { + pub const fn to_date_time(&self) -> DateTime { self.0 } @@ -347,4 +347,52 @@ mod tests { utc_time.encode(&mut encoder).unwrap(); assert_eq!(example_bytes, encoder.finish().unwrap()); } + + #[test] + fn max_valid_generalized_time() { + let example_bytes = "\x18\x0f99991231235959Z".as_bytes(); + let utc_time = GeneralizedTime::from_der(example_bytes).unwrap(); + assert_eq!(utc_time.to_unix_duration().as_secs(), 253402300799); + + let mut buf = [0u8; 128]; + let mut encoder = SliceWriter::new(&mut buf); + utc_time.encode(&mut encoder).unwrap(); + assert_eq!(example_bytes, encoder.finish().unwrap()); + } + + #[test] + fn invalid_year_generalized_time() { + let example_bytes = "\x18\x0f999@1231235959Z".as_bytes(); + assert!(GeneralizedTime::from_der(example_bytes).is_err()); + } + + #[test] + fn invalid_month_generalized_time() { + let example_bytes = "\x18\x0f99991331235959Z".as_bytes(); + assert!(GeneralizedTime::from_der(example_bytes).is_err()); + } + + #[test] + fn invalid_day_generalized_time() { + let example_bytes = "\x18\x0f99991232235959Z".as_bytes(); + assert!(GeneralizedTime::from_der(example_bytes).is_err()); + } + + #[test] + fn invalid_hour_generalized_time() { + let example_bytes = "\x18\x0f99991231245959Z".as_bytes(); + assert!(GeneralizedTime::from_der(example_bytes).is_err()); + } + + #[test] + fn invalid_minute_generalized_time() { + let example_bytes = "\x18\x0f99991231236059Z".as_bytes(); + assert!(GeneralizedTime::from_der(example_bytes).is_err()); + } + + #[test] + fn invalid_second_generalized_time() { + let example_bytes = "\x18\x0f99991231235960Z".as_bytes(); + assert!(GeneralizedTime::from_der(example_bytes).is_err()); + } } diff --git a/der/src/datetime.rs b/der/src/datetime.rs index 39dbd11ef..95785bb4d 100644 --- a/der/src/datetime.rs +++ b/der/src/datetime.rs @@ -11,6 +11,7 @@ use core::{fmt, str::FromStr, time::Duration}; #[cfg(feature = "std")] use std::time::{SystemTime, UNIX_EPOCH}; +use const_range::const_contains_u8; #[cfg(feature = "time")] use time::PrimitiveDateTime; @@ -69,18 +70,42 @@ impl DateTime { }; /// Create a new [`DateTime`] from the given UTC time components. + pub const fn new( + year: u16, + month: u8, + day: u8, + hour: u8, + minutes: u8, + seconds: u8, + ) -> Result { + match Self::from_ymd_hms(year, month, day, hour, minutes, seconds) { + Some(date) => Ok(date), + None => Err(Error::from_kind(ErrorKind::DateTime)), + } + } + + /// Create a new [`DateTime`] from the given UTC time components. + /// + /// Returns `None` if the value is outside the supported date range. // TODO(tarcieri): checked arithmetic #[allow(clippy::arithmetic_side_effects)] - pub fn new(year: u16, month: u8, day: u8, hour: u8, minutes: u8, seconds: u8) -> Result { + pub(crate) const fn from_ymd_hms( + year: u16, + month: u8, + day: u8, + hour: u8, + minutes: u8, + seconds: u8, + ) -> Option { // Basic validation of the components. if year < MIN_YEAR - || !(1..=12).contains(&month) - || !(1..=31).contains(&day) - || !(0..=23).contains(&hour) - || !(0..=59).contains(&minutes) - || !(0..=59).contains(&seconds) + || !const_contains_u8(1..=12, month) + || !const_contains_u8(1..=31, day) + || !const_contains_u8(0..=23, hour) + || !const_contains_u8(0..=59, minutes) + || !const_contains_u8(0..=59, seconds) { - return Err(ErrorKind::DateTime.into()); + return None; } let leap_years = @@ -102,28 +127,28 @@ impl DateTime { 10 => (273, 31), 11 => (304, 30), 12 => (334, 31), - _ => return Err(ErrorKind::DateTime.into()), + _ => return None, }; if day > mdays || day == 0 { - return Err(ErrorKind::DateTime.into()); + return None; } - ydays += u16::from(day) - 1; + ydays += day as u16 - 1; if is_leap_year && month > 2 { ydays += 1; } - let days = u64::from(year - 1970) * 365 + u64::from(leap_years) + u64::from(ydays); - let time = u64::from(seconds) + (u64::from(minutes) * 60) + (u64::from(hour) * 3600); + let days = ((year - 1970) as u64) * 365 + leap_years as u64 + ydays as u64; + let time = seconds as u64 + (minutes as u64 * 60) + (hour as u64 * 3600); let unix_duration = Duration::from_secs(time + days * 86400); - if unix_duration > MAX_UNIX_DURATION { - return Err(ErrorKind::DateTime.into()); + if unix_duration.as_secs() > MAX_UNIX_DURATION.as_secs() { + return None; } - Ok(Self { + Some(Self { year, month, day, @@ -136,7 +161,7 @@ impl DateTime { /// Compute a [`DateTime`] from the given [`Duration`] since the `UNIX_EPOCH`. /// - /// Returns `None` if the value is outside the supported date range. + /// Returns `Err` if the value is outside the supported date range. // TODO(tarcieri): checked arithmetic #[allow(clippy::arithmetic_side_effects)] pub fn from_unix_duration(unix_duration: Duration) -> Result { @@ -430,6 +455,16 @@ fn decode_year(year: &[u8; 4]) -> Result { Ok(u16::from(hi) * 100 + u16::from(lo)) } +mod const_range { + use core::ops::RangeInclusive; + + /// const [`RangeInclusive::contains`] + #[inline] + pub const fn const_contains_u8(range: RangeInclusive, item: u8) -> bool { + item >= *range.start() && item <= *range.end() + } +} + #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { @@ -447,6 +482,29 @@ mod tests { assert!(!is_date_valid(2100, 2, 29, 0, 0, 0)); } + #[test] + fn invalid_dates() { + assert!(!is_date_valid(2, 3, 25, 0, 0, 0)); + + assert!(is_date_valid(1970, 1, 26, 0, 0, 0)); + assert!(!is_date_valid(1969, 1, 26, 0, 0, 0)); + assert!(!is_date_valid(1968, 1, 26, 0, 0, 0)); + assert!(!is_date_valid(1600, 1, 26, 0, 0, 0)); + + assert!(is_date_valid(2039, 2, 27, 0, 0, 0)); + assert!(!is_date_valid(2039, 2, 27, 255, 0, 0)); + assert!(!is_date_valid(2039, 2, 27, 0, 255, 0)); + assert!(!is_date_valid(2039, 2, 27, 0, 0, 255)); + + assert!(is_date_valid(2055, 12, 31, 0, 0, 0)); + assert!(is_date_valid(2055, 12, 31, 23, 0, 0)); + assert!(!is_date_valid(2055, 12, 31, 24, 0, 0)); + assert!(is_date_valid(2055, 12, 31, 0, 59, 0)); + assert!(!is_date_valid(2055, 12, 31, 0, 60, 0)); + assert!(is_date_valid(2055, 12, 31, 0, 0, 59)); + assert!(!is_date_valid(2055, 12, 31, 0, 0, 60)); + } + #[test] fn from_str() { let datetime = "2001-01-02T12:13:14Z".parse::().unwrap();