der/asn1/
generalized_time.rs

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