Coverage Report

Created: 2025-11-16 07:09

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