x509_parser/
time.rs

1use asn1_rs::nom::Err;
2use asn1_rs::{Error, FromDer, GeneralizedTime, Header, ParseResult, UtcTime};
3use der_parser::ber::{Tag, MAX_OBJECT_SIZE};
4use std::fmt;
5use std::ops::{Add, Sub};
6use time::macros::format_description;
7use time::{Duration, OffsetDateTime};
8
9use crate::error::{X509Error, X509Result};
10
11/// An ASN.1 timestamp.
12#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
13pub struct ASN1Time {
14    time: OffsetDateTime,
15    generalized: bool,
16}
17
18impl ASN1Time {
19    pub(crate) fn from_der_opt(i: &[u8]) -> X509Result<Option<Self>> {
20        if i.is_empty() {
21            return Ok((i, None));
22        }
23        match parse_choice_of_time(i) {
24            Ok((rem, time)) => Ok((rem, Some(time))),
25            Err(Err::Error(Error::InvalidTag)) | Err(Err::Error(Error::UnexpectedTag { .. })) => {
26                Ok((i, None))
27            }
28            Err(_) => Err(Err::Error(X509Error::InvalidDate)),
29        }
30    }
31
32    #[inline]
33    pub const fn new(dt: OffsetDateTime) -> Self {
34        let generalized = dt.year() > 2049;
35        Self {
36            time: dt,
37            generalized,
38        }
39    }
40
41    #[inline]
42    pub const fn new_generalized(dt: OffsetDateTime) -> Self {
43        Self {
44            time: dt,
45            generalized: true,
46        }
47    }
48
49    #[inline]
50    pub const fn new_utc(dt: OffsetDateTime) -> Self {
51        Self {
52            time: dt,
53            generalized: false,
54        }
55    }
56
57    #[inline]
58    pub const fn to_datetime(&self) -> OffsetDateTime {
59        self.time
60    }
61
62    /// Makes a new `ASN1Time` from the number of non-leap seconds since Epoch
63    pub fn from_timestamp(secs: i64) -> Result<Self, X509Error> {
64        let dt = OffsetDateTime::from_unix_timestamp(secs).map_err(|_| X509Error::InvalidDate)?;
65        Ok(ASN1Time::new(dt))
66    }
67
68    /// Returns the number of non-leap seconds since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp").
69    #[inline]
70    pub fn timestamp(&self) -> i64 {
71        self.time.unix_timestamp()
72    }
73
74    /// Returns a `ASN1Time` which corresponds to the current date.
75    #[inline]
76    pub fn now() -> Self {
77        ASN1Time::new(OffsetDateTime::now_utc())
78    }
79
80    /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`.
81    ///
82    /// Conversion to RFC2822 date can fail if date cannot be represented in this format,
83    /// for example if year < 1900.
84    ///
85    /// For an infallible conversion to string, use `.to_string()`.
86    #[inline]
87    pub fn to_rfc2822(self) -> Result<String, String> {
88        self.time
89            .format(&time::format_description::well_known::Rfc2822)
90            .map_err(|e| e.to_string())
91    }
92
93    /// Return `true` if date is encoded as UTCTime
94    ///
95    /// According to RFC 5280, dates though year 2049 should be encoded as UTCTime, and
96    /// GeneralizedTime after 2029.
97    #[inline]
98    pub const fn is_utctime(&self) -> bool {
99        !self.generalized
100    }
101
102    /// Return `true` if date is encoded as GeneralizedTime
103    ///
104    /// According to RFC 5280, dates though year 2049 should be encoded as UTCTime, and
105    /// GeneralizedTime after 2029.
106    #[inline]
107    pub const fn is_generalizedtime(&self) -> bool {
108        self.generalized
109    }
110}
111
112impl FromDer<'_, X509Error> for ASN1Time {
113    fn from_der(i: &[u8]) -> X509Result<Self> {
114        let (rem, time) = parse_choice_of_time(i).map_err(|_| X509Error::InvalidDate)?;
115        Ok((rem, time))
116    }
117}
118
119pub(crate) fn parse_choice_of_time(i: &[u8]) -> ParseResult<ASN1Time> {
120    if let Ok((rem, t)) = UtcTime::from_der(i) {
121        let dt = t.utc_adjusted_datetime()?;
122        return Ok((rem, ASN1Time::new_utc(dt)));
123    }
124    if let Ok((rem, t)) = GeneralizedTime::from_der(i) {
125        let dt = t.utc_datetime()?;
126        return Ok((rem, ASN1Time::new_generalized(dt)));
127    }
128    parse_malformed_date(i)
129}
130
131// allow relaxed parsing of UTCTime (ex: 370116130016+0000)
132fn parse_malformed_date(i: &[u8]) -> ParseResult<ASN1Time> {
133    #[allow(clippy::trivially_copy_pass_by_ref)]
134    // fn check_char(b: &u8) -> bool {
135    //     (0x20 <= *b && *b <= 0x7f) || (*b == b'+')
136    // }
137    let (_rem, hdr) = Header::from_der(i)?;
138    let len = hdr.length().definite()?;
139    if len > MAX_OBJECT_SIZE {
140        return Err(Err::Error(Error::InvalidLength));
141    }
142    match hdr.tag() {
143        Tag::UtcTime => {
144            // // if we are in this function, the PrintableString could not be validated.
145            // // Accept it without validating charset, because some tools do not respect the charset
146            // // restrictions (for ex. they use '*' while explicingly disallowed)
147            // let (rem, data) = take(len as usize)(rem)?;
148            // if !data.iter().all(check_char) {
149            //     return Err(nom::Err::Error(BerError::BerValueError));
150            // }
151            // let s = std::str::from_utf8(data).map_err(|_| BerError::BerValueError)?;
152            // let content = BerObjectContent::UTCTime(s);
153            // let obj = DerObject::from_header_and_content(hdr, content);
154            // Ok((rem, obj))
155            Err(Err::Error(Error::BerValueError))
156        }
157        _ => Err(Err::Error(Error::unexpected_tag(None, hdr.tag()))),
158    }
159}
160
161impl fmt::Display for ASN1Time {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        let format = format_description!("[month repr:short] [day padding:space] [hour]:[minute]:[second] [year padding:none] [offset_hour sign:mandatory]:[offset_minute]");
164        let s = self
165            .time
166            .format(format)
167            .unwrap_or_else(|e| format!("Invalid date: {}", e));
168        f.write_str(&s)
169    }
170}
171
172impl Add<Duration> for ASN1Time {
173    type Output = Option<ASN1Time>;
174
175    #[inline]
176    fn add(self, rhs: Duration) -> Option<ASN1Time> {
177        Some(ASN1Time::new(self.time + rhs))
178    }
179}
180
181impl Sub<ASN1Time> for ASN1Time {
182    type Output = Option<Duration>;
183
184    #[inline]
185    fn sub(self, rhs: ASN1Time) -> Option<Duration> {
186        if self.time > rhs.time {
187            Some(self.time - rhs.time)
188        } else {
189            None
190        }
191    }
192}
193
194impl From<OffsetDateTime> for ASN1Time {
195    fn from(dt: OffsetDateTime) -> Self {
196        ASN1Time::new(dt)
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use time::macros::datetime;
203
204    use super::ASN1Time;
205
206    #[test]
207    fn test_time_to_string() {
208        let d = datetime!(1 - 1 - 1 12:34:56 UTC);
209        let t = ASN1Time::from(d);
210        assert_eq!(t.to_string(), "Jan  1 12:34:56 1 +00:00".to_string());
211    }
212
213    #[test]
214    fn test_nonrfc2822_date() {
215        // test year < 1900
216        let d = datetime!(1 - 1 - 1 00:00:00 UTC);
217        let t = ASN1Time::from(d);
218        assert!(t.to_rfc2822().is_err());
219    }
220}