der/asn1/
generalized_time.rs

1//! ASN.1 `GeneralizedTime` 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#[cfg(feature = "time")]
15use time::PrimitiveDateTime;
16
17/// ASN.1 `GeneralizedTime` type.
18///
19/// This type implements the validity requirements specified in
20/// [RFC 5280 Section 4.1.2.5.2][1], namely:
21///
22/// > For the purposes of this profile, GeneralizedTime values MUST be
23/// > expressed in Greenwich Mean Time (Zulu) and MUST include seconds
24/// > (i.e., times are `YYYYMMDDHHMMSSZ`), even where the number of seconds
25/// > is zero.  GeneralizedTime values MUST NOT include fractional seconds.
26///
27/// [1]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
28#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
29pub struct GeneralizedTime(DateTime);
30
31impl GeneralizedTime {
32    /// Length of an RFC 5280-flavored ASN.1 DER-encoded [`GeneralizedTime`].
33    pub const LENGTH: Length = Length::new(15);
34
35    /// Create a [`GeneralizedTime`] from a [`DateTime`].
36    pub fn from_date_time(datetime: DateTime) -> Self {
37        Self(datetime)
38    }
39
40    /// Convert this [`GeneralizedTime`] into a [`DateTime`].
41    pub fn to_date_time(&self) -> DateTime {
42        self.0
43    }
44
45    /// Create a new [`GeneralizedTime`] given a [`Duration`] since `UNIX_EPOCH`
46    /// (a.k.a. "Unix time")
47    pub fn from_unix_duration(unix_duration: Duration) -> Result<Self> {
48        DateTime::from_unix_duration(unix_duration)
49            .map(Into::into)
50            .map_err(|_| Self::TAG.value_error())
51    }
52
53    /// Get the duration of this timestamp since `UNIX_EPOCH`.
54    pub fn to_unix_duration(&self) -> Duration {
55        self.0.unix_duration()
56    }
57
58    /// Instantiate from [`SystemTime`].
59    #[cfg(feature = "std")]
60    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
61    pub fn from_system_time(time: SystemTime) -> Result<Self> {
62        DateTime::try_from(time)
63            .map(Into::into)
64            .map_err(|_| Self::TAG.value_error())
65    }
66
67    /// Convert to [`SystemTime`].
68    #[cfg(feature = "std")]
69    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
70    pub fn to_system_time(&self) -> SystemTime {
71        self.0.to_system_time()
72    }
73}
74
75impl DecodeValue<'_> for GeneralizedTime {
76    fn decode_value(decoder: &mut Decoder<'_>, length: Length) -> Result<Self> {
77        match *ByteSlice::decode_value(decoder, length)?.as_bytes() {
78            // RFC 5280 requires mandatory seconds and Z-normalized time zone
79            [y1, y2, y3, y4, mon1, mon2, day1, day2, hour1, hour2, min1, min2, sec1, sec2, b'Z'] => {
80                let year = datetime::decode_decimal(Self::TAG, y1, y2)? as u16 * 100
81                    + datetime::decode_decimal(Self::TAG, y3, y4)? as u16;
82                let month = datetime::decode_decimal(Self::TAG, mon1, mon2)?;
83                let day = datetime::decode_decimal(Self::TAG, day1, day2)?;
84                let hour = datetime::decode_decimal(Self::TAG, hour1, hour2)?;
85                let minute = datetime::decode_decimal(Self::TAG, min1, min2)?;
86                let second = datetime::decode_decimal(Self::TAG, sec1, sec2)?;
87
88                DateTime::new(year, month, day, hour, minute, second)
89                    .map_err(|_| Self::TAG.value_error())
90                    .and_then(|dt| Self::from_unix_duration(dt.unix_duration()))
91            }
92            _ => Err(Self::TAG.value_error()),
93        }
94    }
95}
96
97impl EncodeValue for GeneralizedTime {
98    fn value_len(&self) -> Result<Length> {
99        Ok(Self::LENGTH)
100    }
101
102    fn encode_value(&self, encoder: &mut Encoder<'_>) -> Result<()> {
103        let year_hi = (self.0.year() / 100) as u8;
104        let year_lo = (self.0.year() % 100) as u8;
105
106        datetime::encode_decimal(encoder, Self::TAG, year_hi)?;
107        datetime::encode_decimal(encoder, Self::TAG, year_lo)?;
108        datetime::encode_decimal(encoder, Self::TAG, self.0.month())?;
109        datetime::encode_decimal(encoder, Self::TAG, self.0.day())?;
110        datetime::encode_decimal(encoder, Self::TAG, self.0.hour())?;
111        datetime::encode_decimal(encoder, Self::TAG, self.0.minutes())?;
112        datetime::encode_decimal(encoder, Self::TAG, self.0.seconds())?;
113        encoder.byte(b'Z')
114    }
115}
116
117impl FixedTag for GeneralizedTime {
118    const TAG: Tag = Tag::GeneralizedTime;
119}
120
121impl OrdIsValueOrd for GeneralizedTime {}
122
123impl From<&GeneralizedTime> for GeneralizedTime {
124    fn from(value: &GeneralizedTime) -> GeneralizedTime {
125        *value
126    }
127}
128
129impl From<GeneralizedTime> for DateTime {
130    fn from(utc_time: GeneralizedTime) -> DateTime {
131        utc_time.0
132    }
133}
134
135impl From<&GeneralizedTime> for DateTime {
136    fn from(utc_time: &GeneralizedTime) -> DateTime {
137        utc_time.0
138    }
139}
140
141impl From<DateTime> for GeneralizedTime {
142    fn from(datetime: DateTime) -> Self {
143        Self::from_date_time(datetime)
144    }
145}
146
147impl From<&DateTime> for GeneralizedTime {
148    fn from(datetime: &DateTime) -> Self {
149        Self::from_date_time(*datetime)
150    }
151}
152
153impl TryFrom<Any<'_>> for GeneralizedTime {
154    type Error = Error;
155
156    fn try_from(any: Any<'_>) -> Result<GeneralizedTime> {
157        any.decode_into()
158    }
159}
160
161impl DecodeValue<'_> for DateTime {
162    fn decode_value(decoder: &mut Decoder<'_>, length: Length) -> Result<Self> {
163        Ok(GeneralizedTime::decode_value(decoder, length)?.into())
164    }
165}
166
167impl EncodeValue for DateTime {
168    fn value_len(&self) -> Result<Length> {
169        GeneralizedTime::from(self).value_len()
170    }
171
172    fn encode_value(&self, encoder: &mut Encoder<'_>) -> Result<()> {
173        GeneralizedTime::from(self).encode_value(encoder)
174    }
175}
176
177impl FixedTag for DateTime {
178    const TAG: Tag = Tag::GeneralizedTime;
179}
180
181impl OrdIsValueOrd for DateTime {}
182
183#[cfg(feature = "std")]
184#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
185impl DecodeValue<'_> for SystemTime {
186    fn decode_value(decoder: &mut Decoder<'_>, length: Length) -> Result<Self> {
187        Ok(GeneralizedTime::decode_value(decoder, length)?.into())
188    }
189}
190
191#[cfg(feature = "std")]
192#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
193impl EncodeValue for SystemTime {
194    fn value_len(&self) -> Result<Length> {
195        GeneralizedTime::try_from(self)?.value_len()
196    }
197
198    fn encode_value(&self, encoder: &mut Encoder<'_>) -> Result<()> {
199        GeneralizedTime::try_from(self)?.encode_value(encoder)
200    }
201}
202
203#[cfg(feature = "std")]
204#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
205impl From<GeneralizedTime> for SystemTime {
206    fn from(time: GeneralizedTime) -> SystemTime {
207        time.to_system_time()
208    }
209}
210
211#[cfg(feature = "std")]
212#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
213impl From<&GeneralizedTime> for SystemTime {
214    fn from(time: &GeneralizedTime) -> SystemTime {
215        time.to_system_time()
216    }
217}
218
219#[cfg(feature = "std")]
220#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
221impl TryFrom<SystemTime> for GeneralizedTime {
222    type Error = Error;
223
224    fn try_from(time: SystemTime) -> Result<GeneralizedTime> {
225        GeneralizedTime::from_system_time(time)
226    }
227}
228
229#[cfg(feature = "std")]
230#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
231impl TryFrom<&SystemTime> for GeneralizedTime {
232    type Error = Error;
233
234    fn try_from(time: &SystemTime) -> Result<GeneralizedTime> {
235        GeneralizedTime::from_system_time(*time)
236    }
237}
238
239#[cfg(feature = "std")]
240#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
241impl<'a> TryFrom<Any<'a>> for SystemTime {
242    type Error = Error;
243
244    fn try_from(any: Any<'a>) -> Result<SystemTime> {
245        GeneralizedTime::try_from(any).map(|s| s.to_system_time())
246    }
247}
248
249#[cfg(feature = "std")]
250#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
251impl FixedTag for SystemTime {
252    const TAG: Tag = Tag::GeneralizedTime;
253}
254
255#[cfg(feature = "std")]
256#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
257impl OrdIsValueOrd for SystemTime {}
258
259#[cfg(feature = "time")]
260#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
261impl DecodeValue<'_> for PrimitiveDateTime {
262    fn decode_value(decoder: &mut Decoder<'_>, length: Length) -> Result<Self> {
263        GeneralizedTime::decode_value(decoder, length)?.try_into()
264    }
265}
266
267#[cfg(feature = "time")]
268#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
269impl EncodeValue for PrimitiveDateTime {
270    fn value_len(&self) -> Result<Length> {
271        GeneralizedTime::try_from(self)?.value_len()
272    }
273
274    fn encode_value(&self, encoder: &mut Encoder<'_>) -> Result<()> {
275        GeneralizedTime::try_from(self)?.encode_value(encoder)
276    }
277}
278
279#[cfg(feature = "time")]
280#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
281impl FixedTag for PrimitiveDateTime {
282    const TAG: Tag = Tag::GeneralizedTime;
283}
284
285#[cfg(feature = "time")]
286#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
287impl OrdIsValueOrd for PrimitiveDateTime {}
288
289#[cfg(feature = "time")]
290#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
291impl TryFrom<PrimitiveDateTime> for GeneralizedTime {
292    type Error = Error;
293
294    fn try_from(time: PrimitiveDateTime) -> Result<GeneralizedTime> {
295        Ok(GeneralizedTime::from_date_time(DateTime::try_from(time)?))
296    }
297}
298
299#[cfg(feature = "time")]
300#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
301impl TryFrom<&PrimitiveDateTime> for GeneralizedTime {
302    type Error = Error;
303
304    fn try_from(time: &PrimitiveDateTime) -> Result<GeneralizedTime> {
305        Self::try_from(*time)
306    }
307}
308
309#[cfg(feature = "time")]
310#[cfg_attr(docsrs, doc(cfg(feature = "time")))]
311impl TryFrom<GeneralizedTime> for PrimitiveDateTime {
312    type Error = Error;
313
314    fn try_from(time: GeneralizedTime) -> Result<PrimitiveDateTime> {
315        time.to_date_time().try_into()
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use super::GeneralizedTime;
322    use crate::{Decodable, Encodable, Encoder};
323    use hex_literal::hex;
324
325    #[test]
326    fn round_trip() {
327        let example_bytes = hex!("18 0f 31 39 39 31 30 35 30 36 32 33 34 35 34 30 5a");
328        let utc_time = GeneralizedTime::from_der(&example_bytes).unwrap();
329        assert_eq!(utc_time.to_unix_duration().as_secs(), 673573540);
330
331        let mut buf = [0u8; 128];
332        let mut encoder = Encoder::new(&mut buf);
333        utc_time.encode(&mut encoder).unwrap();
334        assert_eq!(example_bytes, encoder.finish().unwrap());
335    }
336}