Skip to main content

der/asn1/
generalized_time.rs

1//! ASN.1 `GeneralizedTime` support.
2#![cfg_attr(feature = "arbitrary", allow(clippy::arithmetic_side_effects))]
3
4use crate::{
5    DecodeValue, EncodeValue, Error, ErrorKind, FixedTag, Header, Length, Reader, Result, Tag,
6    Writer,
7    datetime::{self, DateTime},
8    ord::OrdIsValueOrd,
9};
10use core::time::Duration;
11
12#[cfg(feature = "std")]
13use {crate::asn1::AnyRef, std::time::SystemTime};
14
15#[cfg(feature = "time")]
16use time::PrimitiveDateTime;
17
18/// ASN.1 `GeneralizedTime` type.
19///
20/// This type implements the validity requirements specified in
21/// [RFC 5280 Section 4.1.2.5.2][1], namely:
22///
23/// > For the purposes of this profile, GeneralizedTime values MUST be
24/// > expressed in Greenwich Mean Time (Zulu) and MUST include seconds
25/// > (i.e., times are `YYYYMMDDHHMMSSZ`), even where the number of seconds
26/// > is zero.  GeneralizedTime values MUST NOT include fractional seconds.
27///
28/// [1]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
29#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
30#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
31pub struct GeneralizedTime(DateTime);
32
33impl GeneralizedTime {
34    /// Length of an RFC 5280-flavored ASN.1 DER-encoded [`GeneralizedTime`].
35    const LENGTH: usize = 15;
36
37    /// Create a [`GeneralizedTime`] from a [`DateTime`].
38    #[must_use]
39    pub const fn from_date_time(datetime: DateTime) -> Self {
40        Self(datetime)
41    }
42
43    /// Convert this [`GeneralizedTime`] into a [`DateTime`].
44    #[must_use]
45    pub const fn to_date_time(&self) -> DateTime {
46        self.0
47    }
48
49    /// Create a new [`GeneralizedTime`] given a [`Duration`] since `UNIX_EPOCH`
50    /// (a.k.a. "Unix time").
51    ///
52    /// # Errors
53    /// Returns [`Error`] with a value error kind in the event `unix_duration` could not be parsed.
54    pub fn from_unix_duration(unix_duration: Duration) -> Result<Self> {
55        DateTime::from_unix_duration(unix_duration)
56            .map(Into::into)
57            .map_err(|_| Self::TAG.value_error().into())
58    }
59
60    /// Get the duration of this timestamp since `UNIX_EPOCH`.
61    #[must_use]
62    pub fn to_unix_duration(&self) -> Duration {
63        self.0.unix_duration()
64    }
65
66    /// Instantiate from [`SystemTime`].
67    ///
68    /// # Errors
69    /// If the time conversion failed.
70    #[cfg(feature = "std")]
71    pub fn from_system_time(time: SystemTime) -> Result<Self> {
72        DateTime::try_from(time)
73            .map(Into::into)
74            .map_err(|_| Self::TAG.value_error().into())
75    }
76
77    /// Convert to [`SystemTime`].
78    #[cfg(feature = "std")]
79    #[must_use]
80    pub fn to_system_time(&self) -> SystemTime {
81        self.0.to_system_time()
82    }
83}
84
85impl_any_conversions!(GeneralizedTime);
86
87/// Creates a [`GeneralizedTime`] from its individual, ascii
88/// encoded components.
89fn decode_from_values(
90    year: (u8, u8, u8, u8),
91    month: (u8, u8),
92    day: (u8, u8),
93    hour: (u8, u8),
94    min: (u8, u8),
95    sec: (u8, u8),
96) -> Result<GeneralizedTime> {
97    let year = u16::from(datetime::decode_decimal(
98        GeneralizedTime::TAG,
99        year.0,
100        year.1,
101    )?)
102    .checked_mul(100)
103    .and_then(|y| {
104        y.checked_add(
105            datetime::decode_decimal(GeneralizedTime::TAG, year.2, year.3)
106                .ok()?
107                .into(),
108        )
109    })
110    .ok_or(ErrorKind::DateTime)?;
111    let month = datetime::decode_decimal(GeneralizedTime::TAG, month.0, month.1)?;
112    let day = datetime::decode_decimal(GeneralizedTime::TAG, day.0, day.1)?;
113    let hour = datetime::decode_decimal(GeneralizedTime::TAG, hour.0, hour.1)?;
114    let minute = datetime::decode_decimal(GeneralizedTime::TAG, min.0, min.1)?;
115    let second = datetime::decode_decimal(GeneralizedTime::TAG, sec.0, sec.1)?;
116
117    let dt = DateTime::new(year, month, day, hour, minute, second)
118        .map_err(|_| GeneralizedTime::TAG.value_error())?;
119
120    GeneralizedTime::from_unix_duration(dt.unix_duration())
121}
122
123impl<'a> DecodeValue<'a> for GeneralizedTime {
124    type Error = Error;
125
126    fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
127        if Self::LENGTH != usize::try_from(header.length())? {
128            return Err(reader.error(Self::TAG.value_error()));
129        }
130
131        let mut bytes = [0u8; Self::LENGTH];
132        reader.read_into(&mut bytes)?;
133
134        match bytes {
135            // RFC 5280 requires mandatory seconds and Z-normalized time zone
136            [
137                y1,
138                y2,
139                y3,
140                y4,
141                mon1,
142                mon2,
143                day1,
144                day2,
145                hour1,
146                hour2,
147                min1,
148                min2,
149                sec1,
150                sec2,
151                b'Z',
152            ] => decode_from_values(
153                (y1, y2, y3, y4),
154                (mon1, mon2),
155                (day1, day2),
156                (hour1, hour2),
157                (min1, min2),
158                (sec1, sec2),
159            )
160            .map_err(|err| reader.error(err.kind())),
161            _ => Err(reader.error(Self::TAG.value_error())),
162        }
163    }
164}
165
166impl EncodeValue for GeneralizedTime {
167    fn value_len(&self) -> Result<Length> {
168        Self::LENGTH.try_into()
169    }
170
171    fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
172        let year_hi = u8::try_from(self.0.year() / 100)?;
173        let year_lo = u8::try_from(self.0.year() % 100)?;
174
175        datetime::encode_decimal(writer, Self::TAG, year_hi)?;
176        datetime::encode_decimal(writer, Self::TAG, year_lo)?;
177        datetime::encode_decimal(writer, Self::TAG, self.0.month())?;
178        datetime::encode_decimal(writer, Self::TAG, self.0.day())?;
179        datetime::encode_decimal(writer, Self::TAG, self.0.hour())?;
180        datetime::encode_decimal(writer, Self::TAG, self.0.minutes())?;
181        datetime::encode_decimal(writer, Self::TAG, self.0.seconds())?;
182        writer.write_byte(b'Z')
183    }
184}
185
186impl FixedTag for GeneralizedTime {
187    const TAG: Tag = Tag::GeneralizedTime;
188}
189
190impl OrdIsValueOrd for GeneralizedTime {}
191
192impl From<&GeneralizedTime> for GeneralizedTime {
193    fn from(value: &GeneralizedTime) -> GeneralizedTime {
194        *value
195    }
196}
197
198impl From<GeneralizedTime> for DateTime {
199    fn from(utc_time: GeneralizedTime) -> DateTime {
200        utc_time.0
201    }
202}
203
204impl From<&GeneralizedTime> for DateTime {
205    fn from(utc_time: &GeneralizedTime) -> DateTime {
206        utc_time.0
207    }
208}
209
210impl From<DateTime> for GeneralizedTime {
211    fn from(datetime: DateTime) -> Self {
212        Self::from_date_time(datetime)
213    }
214}
215
216impl From<&DateTime> for GeneralizedTime {
217    fn from(datetime: &DateTime) -> Self {
218        Self::from_date_time(*datetime)
219    }
220}
221
222impl<'a> DecodeValue<'a> for DateTime {
223    type Error = Error;
224
225    fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
226        Ok(GeneralizedTime::decode_value(reader, header)?.into())
227    }
228}
229
230impl EncodeValue for DateTime {
231    fn value_len(&self) -> Result<Length> {
232        GeneralizedTime::from(self).value_len()
233    }
234
235    fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
236        GeneralizedTime::from(self).encode_value(writer)
237    }
238}
239
240impl FixedTag for DateTime {
241    const TAG: Tag = Tag::GeneralizedTime;
242}
243
244impl OrdIsValueOrd for DateTime {}
245
246#[cfg(feature = "std")]
247impl<'a> DecodeValue<'a> for SystemTime {
248    type Error = Error;
249
250    fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
251        Ok(GeneralizedTime::decode_value(reader, header)?.into())
252    }
253}
254
255#[cfg(feature = "std")]
256impl EncodeValue for SystemTime {
257    fn value_len(&self) -> Result<Length> {
258        GeneralizedTime::try_from(self)?.value_len()
259    }
260
261    fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
262        GeneralizedTime::try_from(self)?.encode_value(writer)
263    }
264}
265
266#[cfg(feature = "std")]
267impl From<GeneralizedTime> for SystemTime {
268    fn from(time: GeneralizedTime) -> SystemTime {
269        time.to_system_time()
270    }
271}
272
273#[cfg(feature = "std")]
274impl From<&GeneralizedTime> for SystemTime {
275    fn from(time: &GeneralizedTime) -> SystemTime {
276        time.to_system_time()
277    }
278}
279
280#[cfg(feature = "std")]
281impl TryFrom<SystemTime> for GeneralizedTime {
282    type Error = Error;
283
284    fn try_from(time: SystemTime) -> Result<GeneralizedTime> {
285        GeneralizedTime::from_system_time(time)
286    }
287}
288
289#[cfg(feature = "std")]
290impl TryFrom<&SystemTime> for GeneralizedTime {
291    type Error = Error;
292
293    fn try_from(time: &SystemTime) -> Result<GeneralizedTime> {
294        GeneralizedTime::from_system_time(*time)
295    }
296}
297
298#[cfg(feature = "std")]
299impl<'a> TryFrom<AnyRef<'a>> for SystemTime {
300    type Error = Error;
301
302    fn try_from(any: AnyRef<'a>) -> Result<SystemTime> {
303        GeneralizedTime::try_from(any).map(|s| s.to_system_time())
304    }
305}
306
307#[cfg(feature = "std")]
308impl FixedTag for SystemTime {
309    const TAG: Tag = Tag::GeneralizedTime;
310}
311
312#[cfg(feature = "std")]
313impl OrdIsValueOrd for SystemTime {}
314
315#[cfg(feature = "time")]
316impl<'a> DecodeValue<'a> for PrimitiveDateTime {
317    type Error = Error;
318
319    fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
320        GeneralizedTime::decode_value(reader, header)?.try_into()
321    }
322}
323
324#[cfg(feature = "time")]
325impl EncodeValue for PrimitiveDateTime {
326    fn value_len(&self) -> Result<Length> {
327        GeneralizedTime::try_from(self)?.value_len()
328    }
329
330    fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
331        GeneralizedTime::try_from(self)?.encode_value(writer)
332    }
333}
334
335#[cfg(feature = "time")]
336impl FixedTag for PrimitiveDateTime {
337    const TAG: Tag = Tag::GeneralizedTime;
338}
339
340#[cfg(feature = "time")]
341impl OrdIsValueOrd for PrimitiveDateTime {}
342
343#[cfg(feature = "time")]
344impl TryFrom<PrimitiveDateTime> for GeneralizedTime {
345    type Error = Error;
346
347    fn try_from(time: PrimitiveDateTime) -> Result<GeneralizedTime> {
348        Ok(GeneralizedTime::from_date_time(DateTime::try_from(time)?))
349    }
350}
351
352#[cfg(feature = "time")]
353impl TryFrom<&PrimitiveDateTime> for GeneralizedTime {
354    type Error = Error;
355
356    fn try_from(time: &PrimitiveDateTime) -> Result<GeneralizedTime> {
357        Self::try_from(*time)
358    }
359}
360
361#[cfg(feature = "time")]
362impl TryFrom<GeneralizedTime> for PrimitiveDateTime {
363    type Error = Error;
364
365    fn try_from(time: GeneralizedTime) -> Result<PrimitiveDateTime> {
366        time.to_date_time().try_into()
367    }
368}
369
370#[cfg(test)]
371#[allow(clippy::unwrap_used)]
372mod tests {
373    use super::GeneralizedTime;
374    use crate::{Decode, Encode, SliceWriter};
375    use hex_literal::hex;
376
377    #[test]
378    fn round_trip() {
379        let example_bytes = hex!("18 0f 31 39 39 31 30 35 30 36 32 33 34 35 34 30 5a");
380        let utc_time = GeneralizedTime::from_der(&example_bytes).unwrap();
381        assert_eq!(utc_time.to_unix_duration().as_secs(), 673573540);
382
383        let mut buf = [0u8; 128];
384        let mut writer = SliceWriter::new(&mut buf);
385        utc_time.encode(&mut writer).unwrap();
386        assert_eq!(example_bytes, writer.finish().unwrap());
387    }
388
389    #[test]
390    fn max_valid_generalized_time() {
391        let example_bytes = "\x18\x0f99991231235959Z".as_bytes();
392        let utc_time = GeneralizedTime::from_der(example_bytes).unwrap();
393        assert_eq!(utc_time.to_unix_duration().as_secs(), 253402300799);
394
395        let mut buf = [0u8; 128];
396        let mut writer = SliceWriter::new(&mut buf);
397        utc_time.encode(&mut writer).unwrap();
398        assert_eq!(example_bytes, writer.finish().unwrap());
399    }
400
401    #[test]
402    fn invalid_year_generalized_time() {
403        let example_bytes = "\x18\x0f999@1231235959Z".as_bytes();
404        assert!(GeneralizedTime::from_der(example_bytes).is_err());
405    }
406
407    #[test]
408    fn invalid_month_generalized_time() {
409        let example_bytes = "\x18\x0f99991331235959Z".as_bytes();
410        assert!(GeneralizedTime::from_der(example_bytes).is_err());
411    }
412
413    #[test]
414    fn invalid_day_generalized_time() {
415        let example_bytes = "\x18\x0f99991232235959Z".as_bytes();
416        assert!(GeneralizedTime::from_der(example_bytes).is_err());
417    }
418
419    #[test]
420    fn invalid_hour_generalized_time() {
421        let example_bytes = "\x18\x0f99991231245959Z".as_bytes();
422        assert!(GeneralizedTime::from_der(example_bytes).is_err());
423    }
424
425    #[test]
426    fn invalid_minute_generalized_time() {
427        let example_bytes = "\x18\x0f99991231236059Z".as_bytes();
428        assert!(GeneralizedTime::from_der(example_bytes).is_err());
429    }
430
431    #[test]
432    fn invalid_second_generalized_time() {
433        let example_bytes = "\x18\x0f99991231235960Z".as_bytes();
434        assert!(GeneralizedTime::from_der(example_bytes).is_err());
435    }
436}