der/asn1/
utc_time.rs

1//! ASN.1 `UTCTime` support.
2
3use crate::{
4    asn1::AnyRef,
5    datetime::{self, DateTime},
6    ord::OrdIsValueOrd,
7    DecodeValue, EncodeValue, Error, ErrorKind, FixedTag, Header, Length, Reader, Result, Tag,
8    Writer,
9};
10use core::time::Duration;
11
12#[cfg(feature = "std")]
13use std::time::SystemTime;
14
15/// Maximum year that can be represented as a `UTCTime`.
16pub const MAX_YEAR: u16 = 2049;
17
18/// ASN.1 `UTCTime` type.
19///
20/// This type implements the validity requirements specified in
21/// [RFC 5280 Section 4.1.2.5.1][1], namely:
22///
23/// > For the purposes of this profile, UTCTime values MUST be expressed in
24/// > Greenwich Mean Time (Zulu) and MUST include seconds (i.e., times are
25/// > `YYMMDDHHMMSSZ`), even where the number of seconds is zero.  Conforming
26/// > systems MUST interpret the year field (`YY`) as follows:
27/// >
28/// > - Where `YY` is greater than or equal to 50, the year SHALL be
29/// >   interpreted as `19YY`; and
30/// > - Where `YY` is less than 50, the year SHALL be interpreted as `20YY`.
31///
32/// [1]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
33#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
34pub struct UtcTime(DateTime);
35
36impl UtcTime {
37    /// Length of an RFC 5280-flavored ASN.1 DER-encoded [`UtcTime`].
38    pub const LENGTH: usize = 13;
39
40    /// Create a [`UtcTime`] from a [`DateTime`].
41    pub fn from_date_time(datetime: DateTime) -> Result<Self> {
42        if datetime.year() <= MAX_YEAR {
43            Ok(Self(datetime))
44        } else {
45            Err(Self::TAG.value_error())
46        }
47    }
48
49    /// Convert this [`UtcTime`] into a [`DateTime`].
50    pub fn to_date_time(&self) -> DateTime {
51        self.0
52    }
53
54    /// Create a new [`UtcTime`] given a [`Duration`] since `UNIX_EPOCH`
55    /// (a.k.a. "Unix time")
56    pub fn from_unix_duration(unix_duration: Duration) -> Result<Self> {
57        DateTime::from_unix_duration(unix_duration)?.try_into()
58    }
59
60    /// Get the duration of this timestamp since `UNIX_EPOCH`.
61    pub fn to_unix_duration(&self) -> Duration {
62        self.0.unix_duration()
63    }
64
65    /// Instantiate from [`SystemTime`].
66    #[cfg(feature = "std")]
67    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
68    pub fn from_system_time(time: SystemTime) -> Result<Self> {
69        DateTime::try_from(time)
70            .map_err(|_| Self::TAG.value_error())?
71            .try_into()
72    }
73
74    /// Convert to [`SystemTime`].
75    #[cfg(feature = "std")]
76    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
77    pub fn to_system_time(&self) -> SystemTime {
78        self.0.to_system_time()
79    }
80}
81
82impl<'a> DecodeValue<'a> for UtcTime {
83    fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
84        if Self::LENGTH != usize::try_from(header.length)? {
85            return Err(Self::TAG.value_error());
86        }
87
88        let mut bytes = [0u8; Self::LENGTH];
89        reader.read_into(&mut bytes)?;
90
91        match bytes {
92            // RFC 5280 requires mandatory seconds and Z-normalized time zone
93            [year1, year2, mon1, mon2, day1, day2, hour1, hour2, min1, min2, sec1, sec2, b'Z'] => {
94                let year = u16::from(datetime::decode_decimal(Self::TAG, year1, year2)?);
95                let month = datetime::decode_decimal(Self::TAG, mon1, mon2)?;
96                let day = datetime::decode_decimal(Self::TAG, day1, day2)?;
97                let hour = datetime::decode_decimal(Self::TAG, hour1, hour2)?;
98                let minute = datetime::decode_decimal(Self::TAG, min1, min2)?;
99                let second = datetime::decode_decimal(Self::TAG, sec1, sec2)?;
100
101                // RFC 5280 rules for interpreting the year
102                let year = if year >= 50 {
103                    year.checked_add(1900)
104                } else {
105                    year.checked_add(2000)
106                }
107                .ok_or(ErrorKind::DateTime)?;
108
109                DateTime::new(year, month, day, hour, minute, second)
110                    .map_err(|_| Self::TAG.value_error())
111                    .and_then(|dt| Self::from_unix_duration(dt.unix_duration()))
112            }
113            _ => Err(Self::TAG.value_error()),
114        }
115    }
116}
117
118impl EncodeValue for UtcTime {
119    fn value_len(&self) -> Result<Length> {
120        Self::LENGTH.try_into()
121    }
122
123    fn encode_value(&self, writer: &mut dyn Writer) -> Result<()> {
124        let year = match self.0.year() {
125            y @ 1950..=1999 => y.checked_sub(1900),
126            y @ 2000..=2049 => y.checked_sub(2000),
127            _ => return Err(Self::TAG.value_error()),
128        }
129        .and_then(|y| u8::try_from(y).ok())
130        .ok_or(ErrorKind::DateTime)?;
131
132        datetime::encode_decimal(writer, Self::TAG, year)?;
133        datetime::encode_decimal(writer, Self::TAG, self.0.month())?;
134        datetime::encode_decimal(writer, Self::TAG, self.0.day())?;
135        datetime::encode_decimal(writer, Self::TAG, self.0.hour())?;
136        datetime::encode_decimal(writer, Self::TAG, self.0.minutes())?;
137        datetime::encode_decimal(writer, Self::TAG, self.0.seconds())?;
138        writer.write_byte(b'Z')
139    }
140}
141
142impl FixedTag for UtcTime {
143    const TAG: Tag = Tag::UtcTime;
144}
145
146impl OrdIsValueOrd for UtcTime {}
147
148impl From<&UtcTime> for UtcTime {
149    fn from(value: &UtcTime) -> UtcTime {
150        *value
151    }
152}
153
154impl From<UtcTime> for DateTime {
155    fn from(utc_time: UtcTime) -> DateTime {
156        utc_time.0
157    }
158}
159
160impl From<&UtcTime> for DateTime {
161    fn from(utc_time: &UtcTime) -> DateTime {
162        utc_time.0
163    }
164}
165
166impl TryFrom<DateTime> for UtcTime {
167    type Error = Error;
168
169    fn try_from(datetime: DateTime) -> Result<Self> {
170        Self::from_date_time(datetime)
171    }
172}
173
174impl TryFrom<&DateTime> for UtcTime {
175    type Error = Error;
176
177    fn try_from(datetime: &DateTime) -> Result<Self> {
178        Self::from_date_time(*datetime)
179    }
180}
181
182#[cfg(feature = "std")]
183#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
184impl From<UtcTime> for SystemTime {
185    fn from(utc_time: UtcTime) -> SystemTime {
186        utc_time.to_system_time()
187    }
188}
189
190impl TryFrom<AnyRef<'_>> for UtcTime {
191    type Error = Error;
192
193    fn try_from(any: AnyRef<'_>) -> Result<UtcTime> {
194        any.decode_into()
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::UtcTime;
201    use crate::{Decode, Encode, SliceWriter};
202    use hex_literal::hex;
203
204    #[test]
205    fn round_trip_vector() {
206        let example_bytes = hex!("17 0d 39 31 30 35 30 36 32 33 34 35 34 30 5a");
207        let utc_time = UtcTime::from_der(&example_bytes).unwrap();
208        assert_eq!(utc_time.to_unix_duration().as_secs(), 673573540);
209
210        let mut buf = [0u8; 128];
211        let mut encoder = SliceWriter::new(&mut buf);
212        utc_time.encode(&mut encoder).unwrap();
213        assert_eq!(example_bytes, encoder.finish().unwrap());
214    }
215}