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#[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 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 #[inline]
70 pub fn timestamp(&self) -> i64 {
71 self.time.unix_timestamp()
72 }
73
74 #[inline]
76 pub fn now() -> Self {
77 ASN1Time::new(OffsetDateTime::now_utc())
78 }
79
80 #[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 #[inline]
98 pub const fn is_utctime(&self) -> bool {
99 !self.generalized
100 }
101
102 #[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
131fn parse_malformed_date(i: &[u8]) -> ParseResult<ASN1Time> {
133 #[allow(clippy::trivially_copy_pass_by_ref)]
134 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 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 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}