/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 | | } |