//! ASN.1 `GeneralizedTime` support. #![cfg_attr(feature = "arbitrary", allow(clippy::integer_arithmetic))] use crate::{ datetime::{self, DateTime}, ord::OrdIsValueOrd, DecodeValue, EncodeValue, ErrorKind, FixedTag, Header, Length, Reader, Result, Tag, Writer, }; use core::time::Duration; #[cfg(any(feature = "std", feature = "time"))] use crate::Error; #[cfg(feature = "std")] use { crate::asn1::AnyRef, std::time::SystemTime, }; #[cfg(feature = "time")] use time::PrimitiveDateTime; /// ASN.1 `GeneralizedTime` type. /// /// This type implements the validity requirements specified in /// [RFC 5280 Section 4.1.2.5.2][1], namely: /// /// > For the purposes of this profile, GeneralizedTime values MUST be /// > expressed in Greenwich Mean Time (Zulu) and MUST include seconds /// > (i.e., times are `YYYYMMDDHHMMSSZ`), even where the number of seconds /// > is zero. GeneralizedTime values MUST NOT include fractional seconds. /// /// [1]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5.2 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub struct GeneralizedTime(DateTime); impl GeneralizedTime { /// Length of an RFC 5280-flavored ASN.1 DER-encoded [`GeneralizedTime`]. const LENGTH: usize = 15; /// Create a [`GeneralizedTime`] from a [`DateTime`]. pub const fn from_date_time(datetime: DateTime) -> Self { Self(datetime) } /// Convert this [`GeneralizedTime`] into a [`DateTime`]. pub fn to_date_time(&self) -> DateTime { self.0 } /// Create a new [`GeneralizedTime`] given a [`Duration`] since `UNIX_EPOCH` /// (a.k.a. "Unix time") pub fn from_unix_duration(unix_duration: Duration) -> Result { DateTime::from_unix_duration(unix_duration) .map(Into::into) .map_err(|_| Self::TAG.value_error()) } /// Get the duration of this timestamp since `UNIX_EPOCH`. pub fn to_unix_duration(&self) -> Duration { self.0.unix_duration() } /// Instantiate from [`SystemTime`]. #[cfg(feature = "std")] pub fn from_system_time(time: SystemTime) -> Result { DateTime::try_from(time) .map(Into::into) .map_err(|_| Self::TAG.value_error()) } /// Convert to [`SystemTime`]. #[cfg(feature = "std")] pub fn to_system_time(&self) -> SystemTime { self.0.to_system_time() } } impl_any_conversions!(GeneralizedTime); impl<'a> DecodeValue<'a> for GeneralizedTime { fn decode_value>(reader: &mut R, header: Header) -> Result { if Self::LENGTH != usize::try_from(header.length)? { return Err(Self::TAG.value_error()); } let mut bytes = [0u8; Self::LENGTH]; reader.read_into(&mut bytes)?; match bytes { // RFC 5280 requires mandatory seconds and Z-normalized time zone [y1, y2, y3, y4, mon1, mon2, day1, day2, hour1, hour2, min1, min2, sec1, sec2, b'Z'] => { let year = u16::from(datetime::decode_decimal(Self::TAG, y1, y2)?) .checked_mul(100) .and_then(|y| { y.checked_add(datetime::decode_decimal(Self::TAG, y3, y4).ok()?.into()) }) .ok_or(ErrorKind::DateTime)?; let month = datetime::decode_decimal(Self::TAG, mon1, mon2)?; let day = datetime::decode_decimal(Self::TAG, day1, day2)?; let hour = datetime::decode_decimal(Self::TAG, hour1, hour2)?; let minute = datetime::decode_decimal(Self::TAG, min1, min2)?; let second = datetime::decode_decimal(Self::TAG, sec1, sec2)?; DateTime::new(year, month, day, hour, minute, second) .map_err(|_| Self::TAG.value_error()) .and_then(|dt| Self::from_unix_duration(dt.unix_duration())) } _ => Err(Self::TAG.value_error()), } } } impl EncodeValue for GeneralizedTime { fn value_len(&self) -> Result { Self::LENGTH.try_into() } fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { let year_hi = u8::try_from(self.0.year() / 100)?; let year_lo = u8::try_from(self.0.year() % 100)?; datetime::encode_decimal(writer, Self::TAG, year_hi)?; datetime::encode_decimal(writer, Self::TAG, year_lo)?; datetime::encode_decimal(writer, Self::TAG, self.0.month())?; datetime::encode_decimal(writer, Self::TAG, self.0.day())?; datetime::encode_decimal(writer, Self::TAG, self.0.hour())?; datetime::encode_decimal(writer, Self::TAG, self.0.minutes())?; datetime::encode_decimal(writer, Self::TAG, self.0.seconds())?; writer.write_byte(b'Z') } } impl FixedTag for GeneralizedTime { const TAG: Tag = Tag::GeneralizedTime; } impl OrdIsValueOrd for GeneralizedTime {} impl From<&GeneralizedTime> for GeneralizedTime { fn from(value: &GeneralizedTime) -> GeneralizedTime { *value } } impl From for DateTime { fn from(utc_time: GeneralizedTime) -> DateTime { utc_time.0 } } impl From<&GeneralizedTime> for DateTime { fn from(utc_time: &GeneralizedTime) -> DateTime { utc_time.0 } } impl From for GeneralizedTime { fn from(datetime: DateTime) -> Self { Self::from_date_time(datetime) } } impl From<&DateTime> for GeneralizedTime { fn from(datetime: &DateTime) -> Self { Self::from_date_time(*datetime) } } impl<'a> DecodeValue<'a> for DateTime { fn decode_value>(reader: &mut R, header: Header) -> Result { Ok(GeneralizedTime::decode_value(reader, header)?.into()) } } impl EncodeValue for DateTime { fn value_len(&self) -> Result { GeneralizedTime::from(self).value_len() } fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { GeneralizedTime::from(self).encode_value(writer) } } impl FixedTag for DateTime { const TAG: Tag = Tag::GeneralizedTime; } impl OrdIsValueOrd for DateTime {} #[cfg(feature = "std")] impl<'a> DecodeValue<'a> for SystemTime { fn decode_value>(reader: &mut R, header: Header) -> Result { Ok(GeneralizedTime::decode_value(reader, header)?.into()) } } #[cfg(feature = "std")] impl EncodeValue for SystemTime { fn value_len(&self) -> Result { GeneralizedTime::try_from(self)?.value_len() } fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { GeneralizedTime::try_from(self)?.encode_value(writer) } } #[cfg(feature = "std")] impl From for SystemTime { fn from(time: GeneralizedTime) -> SystemTime { time.to_system_time() } } #[cfg(feature = "std")] impl From<&GeneralizedTime> for SystemTime { fn from(time: &GeneralizedTime) -> SystemTime { time.to_system_time() } } #[cfg(feature = "std")] impl TryFrom for GeneralizedTime { type Error = Error; fn try_from(time: SystemTime) -> Result { GeneralizedTime::from_system_time(time) } } #[cfg(feature = "std")] impl TryFrom<&SystemTime> for GeneralizedTime { type Error = Error; fn try_from(time: &SystemTime) -> Result { GeneralizedTime::from_system_time(*time) } } #[cfg(feature = "std")] impl<'a> TryFrom> for SystemTime { type Error = Error; fn try_from(any: AnyRef<'a>) -> Result { GeneralizedTime::try_from(any).map(|s| s.to_system_time()) } } #[cfg(feature = "std")] impl FixedTag for SystemTime { const TAG: Tag = Tag::GeneralizedTime; } #[cfg(feature = "std")] impl OrdIsValueOrd for SystemTime {} #[cfg(feature = "time")] impl<'a> DecodeValue<'a> for PrimitiveDateTime { fn decode_value>(reader: &mut R, header: Header) -> Result { GeneralizedTime::decode_value(reader, header)?.try_into() } } #[cfg(feature = "time")] impl EncodeValue for PrimitiveDateTime { fn value_len(&self) -> Result { GeneralizedTime::try_from(self)?.value_len() } fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { GeneralizedTime::try_from(self)?.encode_value(writer) } } #[cfg(feature = "time")] impl FixedTag for PrimitiveDateTime { const TAG: Tag = Tag::GeneralizedTime; } #[cfg(feature = "time")] impl OrdIsValueOrd for PrimitiveDateTime {} #[cfg(feature = "time")] impl TryFrom for GeneralizedTime { type Error = Error; fn try_from(time: PrimitiveDateTime) -> Result { Ok(GeneralizedTime::from_date_time(DateTime::try_from(time)?)) } } #[cfg(feature = "time")] impl TryFrom<&PrimitiveDateTime> for GeneralizedTime { type Error = Error; fn try_from(time: &PrimitiveDateTime) -> Result { Self::try_from(*time) } } #[cfg(feature = "time")] impl TryFrom for PrimitiveDateTime { type Error = Error; fn try_from(time: GeneralizedTime) -> Result { time.to_date_time().try_into() } } #[cfg(test)] mod tests { use super::GeneralizedTime; use crate::{Decode, Encode, SliceWriter}; use hex_literal::hex; #[test] fn round_trip() { let example_bytes = hex!("18 0f 31 39 39 31 30 35 30 36 32 33 34 35 34 30 5a"); let utc_time = GeneralizedTime::from_der(&example_bytes).unwrap(); assert_eq!(utc_time.to_unix_duration().as_secs(), 673573540); 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()); } }