der/asn1/
generalized_time.rs

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