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(OffsetDateTime);
14
15impl ASN1Time {
16 pub(crate) fn from_der_opt(i: &[u8]) -> X509Result<Option<Self>> {
17 if i.is_empty() {
18 return Ok((i, None));
19 }
20 match parse_choice_of_time(i) {
21 Ok((rem, dt)) => Ok((rem, Some(ASN1Time(dt)))),
22 Err(Err::Error(Error::InvalidTag)) | Err(Err::Error(Error::UnexpectedTag { .. })) => {
23 Ok((i, None))
24 }
25 Err(_) => Err(Err::Error(X509Error::InvalidDate)),
26 }
27 }
28
29 #[inline]
30 pub const fn new(dt: OffsetDateTime) -> Self {
31 Self(dt)
32 }
33
34 #[inline]
35 pub const fn to_datetime(&self) -> OffsetDateTime {
36 self.0
37 }
38
39 pub fn from_timestamp(secs: i64) -> Result<Self, X509Error> {
41 let dt = OffsetDateTime::from_unix_timestamp(secs).map_err(|_| X509Error::InvalidDate)?;
42 Ok(ASN1Time(dt))
43 }
44
45 #[inline]
47 pub fn timestamp(&self) -> i64 {
48 self.0.unix_timestamp()
49 }
50
51 #[inline]
53 pub fn now() -> Self {
54 ASN1Time(OffsetDateTime::now_utc())
55 }
56
57 #[inline]
64 pub fn to_rfc2822(self) -> Result<String, String> {
65 self.0
66 .format(&time::format_description::well_known::Rfc2822)
67 .map_err(|e| e.to_string())
68 }
69}
70
71impl<'a> FromDer<'a, X509Error> for ASN1Time {
72 fn from_der(i: &[u8]) -> X509Result<Self> {
73 let (rem, dt) = parse_choice_of_time(i).map_err(|_| X509Error::InvalidDate)?;
74 Ok((rem, ASN1Time(dt)))
75 }
76}
77
78pub(crate) fn parse_choice_of_time(i: &[u8]) -> ParseResult<OffsetDateTime> {
79 if let Ok((rem, t)) = UtcTime::from_der(i) {
80 let dt = t.utc_adjusted_datetime()?;
81 return Ok((rem, dt));
82 }
83 if let Ok((rem, t)) = GeneralizedTime::from_der(i) {
84 let dt = t.utc_datetime()?;
85 return Ok((rem, dt));
86 }
87 parse_malformed_date(i)
88}
89
90fn parse_malformed_date(i: &[u8]) -> ParseResult<OffsetDateTime> {
92 #[allow(clippy::trivially_copy_pass_by_ref)]
93 let (_rem, hdr) = Header::from_der(i)?;
97 let len = hdr.length().definite()?;
98 if len > MAX_OBJECT_SIZE {
99 return Err(nom::Err::Error(Error::InvalidLength));
100 }
101 match hdr.tag() {
102 Tag::UtcTime => {
103 Err(nom::Err::Error(Error::BerValueError))
115 }
116 _ => Err(nom::Err::Error(Error::unexpected_tag(None, hdr.tag()))),
117 }
118}
119
120impl fmt::Display for ASN1Time {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 let format = format_description!("[month repr:short] [day padding:space] [hour]:[minute]:[second] [year padding:none] [offset_hour sign:mandatory]:[offset_minute]");
123 let s = self
124 .0
125 .format(format)
126 .unwrap_or_else(|e| format!("Invalid date: {}", e));
127 f.write_str(&s)
128 }
129}
130
131impl Add<Duration> for ASN1Time {
132 type Output = Option<ASN1Time>;
133
134 #[inline]
135 fn add(self, rhs: Duration) -> Option<ASN1Time> {
136 Some(ASN1Time(self.0 + rhs))
137 }
138}
139
140impl Sub<ASN1Time> for ASN1Time {
141 type Output = Option<Duration>;
142
143 #[inline]
144 fn sub(self, rhs: ASN1Time) -> Option<Duration> {
145 if self.0 > rhs.0 {
146 Some(self.0 - rhs.0)
147 } else {
148 None
149 }
150 }
151}
152
153impl From<OffsetDateTime> for ASN1Time {
154 fn from(dt: OffsetDateTime) -> Self {
155 ASN1Time(dt)
156 }
157}
158
159#[cfg(test)]
160mod 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 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}