• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! ASN.1 `GeneralizedTime` support.
2 
3 use 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 };
10 use core::time::Duration;
11 
12 #[cfg(feature = "std")]
13 use std::time::SystemTime;
14 
15 #[cfg(feature = "time")]
16 use 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)]
30 pub struct GeneralizedTime(DateTime);
31 
32 impl 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`].
from_date_time(datetime: DateTime) -> Self37     pub fn from_date_time(datetime: DateTime) -> Self {
38         Self(datetime)
39     }
40 
41     /// Convert this [`GeneralizedTime`] into a [`DateTime`].
to_date_time(&self) -> DateTime42     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")
from_unix_duration(unix_duration: Duration) -> Result<Self>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`.
to_unix_duration(&self) -> Duration55     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")))]
from_system_time(time: SystemTime) -> Result<Self>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")))]
to_system_time(&self) -> SystemTime71     pub fn to_system_time(&self) -> SystemTime {
72         self.0.to_system_time()
73     }
74 }
75 
76 impl<'a> DecodeValue<'a> for GeneralizedTime {
decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self>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 
109 impl EncodeValue for GeneralizedTime {
value_len(&self) -> Result<Length>110     fn value_len(&self) -> Result<Length> {
111         Self::LENGTH.try_into()
112     }
113 
encode_value(&self, writer: &mut dyn Writer) -> Result<()>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 
129 impl FixedTag for GeneralizedTime {
130     const TAG: Tag = Tag::GeneralizedTime;
131 }
132 
133 impl OrdIsValueOrd for GeneralizedTime {}
134 
135 impl From<&GeneralizedTime> for GeneralizedTime {
from(value: &GeneralizedTime) -> GeneralizedTime136     fn from(value: &GeneralizedTime) -> GeneralizedTime {
137         *value
138     }
139 }
140 
141 impl From<GeneralizedTime> for DateTime {
from(utc_time: GeneralizedTime) -> DateTime142     fn from(utc_time: GeneralizedTime) -> DateTime {
143         utc_time.0
144     }
145 }
146 
147 impl From<&GeneralizedTime> for DateTime {
from(utc_time: &GeneralizedTime) -> DateTime148     fn from(utc_time: &GeneralizedTime) -> DateTime {
149         utc_time.0
150     }
151 }
152 
153 impl From<DateTime> for GeneralizedTime {
from(datetime: DateTime) -> Self154     fn from(datetime: DateTime) -> Self {
155         Self::from_date_time(datetime)
156     }
157 }
158 
159 impl From<&DateTime> for GeneralizedTime {
from(datetime: &DateTime) -> Self160     fn from(datetime: &DateTime) -> Self {
161         Self::from_date_time(*datetime)
162     }
163 }
164 
165 impl TryFrom<AnyRef<'_>> for GeneralizedTime {
166     type Error = Error;
167 
try_from(any: AnyRef<'_>) -> Result<GeneralizedTime>168     fn try_from(any: AnyRef<'_>) -> Result<GeneralizedTime> {
169         any.decode_into()
170     }
171 }
172 
173 impl<'a> DecodeValue<'a> for DateTime {
decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self>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 
179 impl EncodeValue for DateTime {
value_len(&self) -> Result<Length>180     fn value_len(&self) -> Result<Length> {
181         GeneralizedTime::from(self).value_len()
182     }
183 
encode_value(&self, writer: &mut dyn Writer) -> Result<()>184     fn encode_value(&self, writer: &mut dyn Writer) -> Result<()> {
185         GeneralizedTime::from(self).encode_value(writer)
186     }
187 }
188 
189 impl FixedTag for DateTime {
190     const TAG: Tag = Tag::GeneralizedTime;
191 }
192 
193 impl OrdIsValueOrd for DateTime {}
194 
195 #[cfg(feature = "std")]
196 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
197 impl<'a> DecodeValue<'a> for SystemTime {
decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self>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")))]
205 impl EncodeValue for SystemTime {
value_len(&self) -> Result<Length>206     fn value_len(&self) -> Result<Length> {
207         GeneralizedTime::try_from(self)?.value_len()
208     }
209 
encode_value(&self, writer: &mut dyn Writer) -> Result<()>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")))]
217 impl From<GeneralizedTime> for SystemTime {
from(time: GeneralizedTime) -> SystemTime218     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")))]
225 impl From<&GeneralizedTime> for SystemTime {
from(time: &GeneralizedTime) -> SystemTime226     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")))]
233 impl TryFrom<SystemTime> for GeneralizedTime {
234     type Error = Error;
235 
try_from(time: SystemTime) -> Result<GeneralizedTime>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")))]
243 impl TryFrom<&SystemTime> for GeneralizedTime {
244     type Error = Error;
245 
try_from(time: &SystemTime) -> Result<GeneralizedTime>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")))]
253 impl<'a> TryFrom<AnyRef<'a>> for SystemTime {
254     type Error = Error;
255 
try_from(any: AnyRef<'a>) -> Result<SystemTime>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")))]
263 impl FixedTag for SystemTime {
264     const TAG: Tag = Tag::GeneralizedTime;
265 }
266 
267 #[cfg(feature = "std")]
268 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
269 impl OrdIsValueOrd for SystemTime {}
270 
271 #[cfg(feature = "time")]
272 #[cfg_attr(docsrs, doc(cfg(feature = "time")))]
273 impl<'a> DecodeValue<'a> for PrimitiveDateTime {
decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self>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")))]
281 impl EncodeValue for PrimitiveDateTime {
value_len(&self) -> Result<Length>282     fn value_len(&self) -> Result<Length> {
283         GeneralizedTime::try_from(self)?.value_len()
284     }
285 
encode_value(&self, writer: &mut dyn Writer) -> Result<()>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")))]
293 impl FixedTag for PrimitiveDateTime {
294     const TAG: Tag = Tag::GeneralizedTime;
295 }
296 
297 #[cfg(feature = "time")]
298 #[cfg_attr(docsrs, doc(cfg(feature = "time")))]
299 impl OrdIsValueOrd for PrimitiveDateTime {}
300 
301 #[cfg(feature = "time")]
302 #[cfg_attr(docsrs, doc(cfg(feature = "time")))]
303 impl TryFrom<PrimitiveDateTime> for GeneralizedTime {
304     type Error = Error;
305 
try_from(time: PrimitiveDateTime) -> Result<GeneralizedTime>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")))]
313 impl TryFrom<&PrimitiveDateTime> for GeneralizedTime {
314     type Error = Error;
315 
try_from(time: &PrimitiveDateTime) -> Result<GeneralizedTime>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")))]
323 impl TryFrom<GeneralizedTime> for PrimitiveDateTime {
324     type Error = Error;
325 
try_from(time: GeneralizedTime) -> Result<PrimitiveDateTime>326     fn try_from(time: GeneralizedTime) -> Result<PrimitiveDateTime> {
327         time.to_date_time().try_into()
328     }
329 }
330 
331 #[cfg(test)]
332 mod tests {
333     use super::GeneralizedTime;
334     use crate::{Decode, Encode, SliceWriter};
335     use hex_literal::hex;
336 
337     #[test]
round_trip()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 }
349