der/asn1/
utc_time.rs

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