Skip to main content
This is unreleased documentation for the main (development) branch of crypto-glue.

x509_cert/
time.rs

1//! X.501 time types as defined in RFC 5280
2
3use core::{fmt, marker::PhantomData, str::FromStr, time::Duration};
4use der::asn1::{GeneralizedTime, UtcTime};
5use der::{Choice, DateTime, DecodeValue, Encode, Header, Length, Reader, Sequence, ValueOrd};
6
7#[cfg(feature = "std")]
8use std::time::SystemTime;
9
10use crate::certificate::{Profile, Rfc5280};
11
12/// X.501 `Time` as defined in [RFC 5280 Section 4.1.2.5].
13///
14/// Schema definition from [RFC 5280 Appendix A]:
15///
16/// ```text
17/// Time ::= CHOICE {
18///      utcTime        UTCTime,
19///      generalTime    GeneralizedTime
20/// }
21/// ```
22///
23/// [RFC 5280 Section 4.1.2.5]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5
24/// [RFC 5280 Appendix A]: https://tools.ietf.org/html/rfc5280#page-117
25#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
26#[derive(Choice, Copy, Clone, Debug, Eq, PartialEq, ValueOrd)]
27pub enum Time {
28    /// Legacy UTC time (has 2-digit year, valid from 1970 to 2049).
29    ///
30    /// Note: RFC 5280 specifies 1950-2049, however due to common operations working on
31    /// `UNIX_EPOCH` this implementation's lower bound is 1970.
32    #[asn1(type = "UTCTime")]
33    UtcTime(UtcTime),
34
35    /// Modern [`GeneralizedTime`] encoding with 4-digit year.
36    #[asn1(type = "GeneralizedTime")]
37    GeneralTime(GeneralizedTime),
38}
39
40impl Time {
41    /// Time used for Certificate who do not expire.
42    pub const INFINITY: Time =
43        Time::GeneralTime(GeneralizedTime::from_date_time(DateTime::INFINITY));
44
45    /// Get duration since `UNIX_EPOCH`.
46    pub fn to_unix_duration(self) -> Duration {
47        match self {
48            Time::UtcTime(t) => t.to_unix_duration(),
49            Time::GeneralTime(t) => t.to_unix_duration(),
50        }
51    }
52
53    /// Get Time as DateTime
54    pub fn to_date_time(&self) -> DateTime {
55        match self {
56            Time::UtcTime(t) => t.to_date_time(),
57            Time::GeneralTime(t) => t.to_date_time(),
58        }
59    }
60
61    /// Convert to [`SystemTime`].
62    #[cfg(feature = "std")]
63    pub fn to_system_time(&self) -> SystemTime {
64        match self {
65            Time::UtcTime(t) => t.to_system_time(),
66            Time::GeneralTime(t) => t.to_system_time(),
67        }
68    }
69
70    /// Convert time to UTCTime representation
71    /// As per RFC 5280: 4.1.2.5, date through 2049 should be expressed as UTC Time.
72    pub(crate) fn rfc5280_adjust_utc_time(&mut self) -> der::Result<()> {
73        if let Time::GeneralTime(t) = self {
74            let date = t.to_date_time();
75            if date.year() <= UtcTime::MAX_YEAR {
76                *self = Time::UtcTime(UtcTime::from_date_time(date)?);
77            }
78        }
79
80        Ok(())
81    }
82
83    /// Creates a `Time` from the current date.
84    #[cfg(feature = "std")]
85    pub fn now() -> der::Result<Self> {
86        SystemTime::now().try_into()
87    }
88}
89
90impl fmt::Display for Time {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        write!(f, "{}", self.to_date_time())
93    }
94}
95
96impl From<UtcTime> for Time {
97    fn from(time: UtcTime) -> Time {
98        Time::UtcTime(time)
99    }
100}
101
102impl From<GeneralizedTime> for Time {
103    fn from(time: GeneralizedTime) -> Time {
104        Time::GeneralTime(time)
105    }
106}
107
108impl From<DateTime> for Time {
109    fn from(time: DateTime) -> Time {
110        UtcTime::from_date_time(time)
111            .map(Self::UtcTime)
112            .unwrap_or_else(|_e| Self::GeneralTime(GeneralizedTime::from_date_time(time)))
113    }
114}
115
116impl FromStr for Time {
117    type Err = der::Error;
118
119    fn from_str(input: &str) -> der::Result<Self> {
120        let datetime = DateTime::from_str(input)?;
121
122        Ok(Self::from(datetime))
123    }
124}
125
126#[cfg(feature = "std")]
127impl From<Time> for SystemTime {
128    fn from(time: Time) -> SystemTime {
129        time.to_system_time()
130    }
131}
132
133#[cfg(feature = "std")]
134impl From<&Time> for SystemTime {
135    fn from(time: &Time) -> SystemTime {
136        time.to_system_time()
137    }
138}
139
140#[cfg(feature = "std")]
141impl TryFrom<SystemTime> for Time {
142    type Error = der::Error;
143
144    fn try_from(time: SystemTime) -> der::Result<Time> {
145        let datetime = DateTime::from_system_time(time)?;
146
147        Ok(datetime.into())
148    }
149}
150
151/// X.501 `Validity` as defined in [RFC 5280 Section 4.1.2.5]
152///
153/// ```text
154/// Validity ::= SEQUENCE {
155///     notBefore      Time,
156///     notAfter       Time
157/// }
158/// ```
159/// [RFC 5280 Section 4.1.2.5]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.5
160#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
161#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueOrd)]
162pub struct Validity<P: Profile = Rfc5280> {
163    /// notBefore value
164    pub not_before: Time,
165
166    /// notAfter value
167    pub not_after: Time,
168
169    _profile: PhantomData<P>,
170}
171
172impl<P> Validity<P>
173where
174    P: Profile,
175{
176    /// Creates a `Validity` with the provided bounds
177    pub const fn new(not_before: Time, not_after: Time) -> Self {
178        Self {
179            not_before,
180            not_after,
181            _profile: PhantomData,
182        }
183    }
184
185    /// Creates a `Validity` which starts now and lasts for `duration`.
186    #[cfg(feature = "std")]
187    pub fn from_now(duration: Duration) -> der::Result<Self> {
188        let now = SystemTime::now();
189        let then = now + duration;
190
191        Ok(Self {
192            not_before: Time::try_from(now)?,
193            not_after: Time::try_from(then)?,
194            _profile: PhantomData,
195        })
196    }
197
198    /// Creates a `Validity` which starts now and does not expire.
199    #[cfg(all(feature = "std", feature = "hazmat"))]
200    pub fn infinity() -> der::Result<Self> {
201        let now = SystemTime::now();
202
203        Ok(Self {
204            not_before: Time::try_from(now)?,
205            not_after: Time::INFINITY,
206            _profile: PhantomData,
207        })
208    }
209}
210
211impl<'a, P: Profile> DecodeValue<'a> for Validity<P> {
212    type Error = der::Error;
213
214    fn decode_value<R: Reader<'a>>(reader: &mut R, _header: Header) -> der::Result<Self> {
215        let not_before = reader.decode()?;
216        let not_after = reader.decode()?;
217        let out = Self {
218            not_before,
219            not_after,
220            _profile: PhantomData,
221        };
222
223        Ok(out)
224    }
225}
226
227impl<P: Profile> ::der::EncodeValue for Validity<P> {
228    fn value_len(&self) -> ::der::Result<::der::Length> {
229        [
230            P::time_encoding(self.not_before)?.encoded_len()?,
231            P::time_encoding(self.not_after)?.encoded_len()?,
232        ]
233        .into_iter()
234        .try_fold(Length::ZERO, |acc, len| acc + len)
235    }
236    fn encode_value(&self, writer: &mut impl ::der::Writer) -> ::der::Result<()> {
237        P::time_encoding(self.not_before)?.encode(writer)?;
238        P::time_encoding(self.not_after)?.encode(writer)?;
239        Ok(())
240    }
241}
242
243impl<P: Profile> Sequence<'_> for Validity<P> {}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn parse_time() {
251        let time = Time::from_str("1970-01-01T00:00:00Z").expect("parse date from string");
252        assert!(matches!(time, Time::UtcTime(_)));
253        assert_eq!(alloc::format!("{}", time), "1970-01-01T00:00:00Z");
254
255        let time = Time::from_str("2020-01-01T00:00:00Z").expect("parse date from string");
256        assert!(matches!(time, Time::UtcTime(_)));
257        assert_eq!(alloc::format!("{}", time), "2020-01-01T00:00:00Z");
258
259        let time = Time::from_str("2049-12-31T23:59:59Z").expect("parse date from string");
260        assert!(matches!(time, Time::UtcTime(_)));
261        assert_eq!(alloc::format!("{}", time), "2049-12-31T23:59:59Z");
262
263        let time = Time::from_str("2050-01-01T00:00:00Z").expect("parse date from string");
264        assert!(matches!(time, Time::GeneralTime(_)));
265        assert_eq!(alloc::format!("{}", time), "2050-01-01T00:00:00Z");
266    }
267}