barebones_x509/
time.rs

1use super::Error;
2use ring::io::der;
3
4/// An ASN.1 timestamp.
5#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
6pub struct ASN1Time(i64);
7
8impl From<ASN1Time> for i64 {
9    fn from(s: ASN1Time) -> i64 { s.0 }
10}
11
12#[cfg(feature = "std")]
13impl ASN1Time {
14    /// Gets the current time as an ASN1Time.
15    ///
16    /// Returns `Err` if the system clock is too far in the future to represent
17    /// as an ASN.1 time, or if it is too far before this library was
18    /// written.
19    pub fn now() -> Result<Self, Error> {
20        use std::time::{SystemTime, UNIX_EPOCH};
21        let now = SystemTime::now()
22            .duration_since(UNIX_EPOCH)
23            .map_err(|_| Error::BadDERTime)?
24            .as_secs();
25        if 1588297438 >= now || now > MAX_ASN1_TIMESTAMP as u64 {
26            Err(Error::BadDERTime)
27        } else {
28            Ok(Self(now as i64))
29        }
30    }
31}
32
33impl core::convert::TryFrom<i64> for ASN1Time {
34    type Error = Error;
35    fn try_from(s: i64) -> Result<Self, Error> {
36        if MIN_ASN1_TIMESTAMP <= s && s <= MAX_ASN1_TIMESTAMP {
37            Ok(Self(s))
38        } else {
39            Err(Error::BadDERTime)
40        }
41    }
42}
43
44/// The largest timestamp that an ASN.1 GeneralizedTime can represent.
45pub const MAX_ASN1_TIMESTAMP: i64 = 253_402_300_799;
46
47/// The smallest timestamp that an ASN.1 GeneralizedTime can represent.
48pub const MIN_ASN1_TIMESTAMP: i64 = -62_167_219_200;
49
50macro_rules! convert_integers {
51    ($($i: ident),*) => {
52        $(let $i: u8 = $i.wrapping_sub(b'0'); { if $i > 9 { return Err( Error::BadDERTime) } })*
53    }
54}
55
56macro_rules! collect {
57    ($a: ident, $b: ident, $c: ident, $d: ident) => {{
58        convert_integers!($a, $b, $c, $d);
59        ((u16::from($a) * 10 + u16::from($b)) * 10 + u16::from($c)) * 10 + u16::from($d)
60    }};
61    ($a: ident, $b: ident) => {{
62        convert_integers!($a, $b);
63        10 * $a + $b
64    }};
65}
66
67const UTC_TIME: u8 = der::Tag::UTCTime as _;
68const GENERALIZED_TIME: u8 = der::Tag::GeneralizedTime as _;
69
70pub(super) fn read_time(reader: &mut untrusted::Reader<'_>) -> Result<ASN1Time, Error> {
71    let (tag, value) = der::read_tag_and_get_value(reader).map_err(|_| Error::BadDER)?;
72    let (slice, month, day, hour, minute, second) = match *value.as_slice_less_safe() {
73        [ref slice @ .., month1, month2, d1, d2, h1, h2, m1, m2, s1, s2, b'Z'] => {
74            let month: u8 = collect!(month1, month2);
75            let day: u8 = collect!(d1, d2);
76            let hour: u8 = collect!(h1, h2);
77            let minute: u8 = collect!(m1, m2);
78            let second: u8 = collect!(s1, s2);
79            (slice, month, day, hour, minute, second)
80        },
81        _ => return Err(Error::BadDERTime),
82    };
83
84    let year = match (tag, slice) {
85        (UTC_TIME, &[y1, y2]) => {
86            let year = collect!(y1, y2);
87            (if year > 49 { 1900 } else { 2000u16 }) + u16::from(year)
88        },
89        (GENERALIZED_TIME, &[y1, y2, y3, y4]) => collect!(y1, y2, y3, y4),
90        _ => return Err(Error::BadDER),
91    };
92    Ok(ASN1Time(
93        86400 * i64::from(days_from_ymd(year, month, day)?)
94            + i64::from(seconds_from_hms(hour, minute, second)?),
95    ))
96}
97
98/// Convert an (hour, minute, second) tuple to a number of seconds since
99/// midnight or an error.
100pub fn seconds_from_hms(hour: u8, minute: u8, second: u8) -> Result<u32, Error> {
101    if hour > 23 || minute > 59 || second > 59 {
102        Err(Error::BadDERTime)
103    } else {
104        Ok((u32::from(hour) * 60 + u32::from(minute)) * 60 + u32::from(second))
105    }
106}
107
108/// We use our own version, instead of chrono, because:
109///
110/// * We can (and do) perform exhaustive testing of every possible input. The
111///   only possible inputs are (0, 0, 0) to (9999, 99, 99) inclusive, and we can
112///   (and do) test every single one of them in a reasonable amount of time.
113/// * It avoids an unnecessary dependency, and thus prevents bloat.
114pub fn days_from_ymd(year: u16, month: u8, day: u8) -> Result<i32, Error> {
115    const DAYS_IN_MONTH: [u8; 12] = [31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
116    if month < 1 || month > 12 || day < 1 {
117        return Err(Error::BadDERTime);
118    }
119    if if month == 2 {
120        let not_leap = year % 4 != 0 || (year % 100 == 0 && year % 400 != 0);
121        day > 29u8 - u8::from(not_leap)
122    } else {
123        day > DAYS_IN_MONTH[month as usize - 1]
124    } {
125        return Err(Error::BadDERTime);
126    }
127
128    // Taken from https://howardhinnant.github.io/date_algorithms.html
129    // Public domain
130    let year: i32 = i32::from(year) - i32::from(month <= 2);
131    let era: i32 = if year >= 0 { year } else { year - 399 } / 400;
132    let yoe: i32 = year - era * 400;
133    let months_since_feb = if month > 2 { month - 3 } else { month + 9 };
134    // This is magic, but the unit-tests prove that it is correct.
135    let doy: i32 = (153 * months_since_feb as i32 + 2) / 5 + i32::from(day) - 1;
136    let doe: i32 = yoe * 365 + yoe / 4 - yoe / 100 + doy;
137    Ok(era * 146097 + doe - 719468)
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use chrono::{offset::LocalResult, prelude::*};
144
145    #[test]
146    fn seconds_from_hms_works() {
147        let mut last_second = u32::max_value();
148        let date = Utc.ymd(1970, 1, 1);
149        for hour in 0..100 {
150            for minute in 0..100 {
151                for second in 0..100 {
152                    let seconds_since_midnight = seconds_from_hms(hour, minute, second);
153                    if hour >= 24 || minute >= 60 || second >= 60 {
154                        assert!(seconds_since_midnight.is_err());
155                        assert!(date
156                            .and_hms_opt(hour.into(), minute.into(), second.into())
157                            .is_none());
158                        continue;
159                    }
160                    let seconds_since_midnight = seconds_since_midnight.unwrap();
161                    let chronos_version = date.and_hms(hour.into(), minute.into(), second.into());
162                    assert_eq!(
163                        chronos_version.timestamp(),
164                        i64::from(seconds_since_midnight)
165                    );
166                    assert_eq!(last_second.wrapping_add(1), seconds_since_midnight);
167                    assert!(seconds_since_midnight < 86400);
168                    last_second = seconds_since_midnight;
169                }
170            }
171        }
172    }
173
174    #[test]
175    fn days_from_ymd_works() {
176        let mut last_day = -719529i32;
177        for year in 0u16..10000 {
178            for month in 0u8..100 {
179                for day in 0u8..100 {
180                    let days_since_epoch = days_from_ymd(year, month, day);
181                    match Utc.ymd_opt(year.into(), month.into(), day.into()) {
182                        LocalResult::None => assert!(days_since_epoch.is_err()),
183                        LocalResult::Single(e) => {
184                            let this_day = days_since_epoch.unwrap();
185                            assert_eq!(this_day, last_day.wrapping_add(1));
186                            assert!(this_day < i32::max_value());
187                            last_day = this_day;
188                            assert_eq!(
189                                e.and_hms(0, 0, 0).timestamp(),
190                                i64::from(this_day) * 86400,
191                                "mismatch for {:04}-{:02}-{:02}",
192                                year,
193                                month,
194                                day,
195                            )
196                        },
197                        LocalResult::Ambiguous(_, _) => unreachable!(),
198                    }
199                }
200            }
201        }
202    }
203
204    macro_rules! input_test {
205        ($b: expr, $cmp: expr) => {
206            assert_eq!(
207                untrusted::Input::from($b)
208                    .read_all(Error::CertExpired, read_time)
209                    .map(i64::from),
210                $cmp
211            )
212        };
213    }
214
215    #[test]
216    fn wrong_length_rejected() {
217        let too_long_utc = untrusted::Input::from(b"\x17\x0f99991231235959Z")
218            .read_all(Error::CertExpired, read_time);
219        assert_eq!(too_long_utc, Err(Error::BadDER));
220        let too_short_generalized = untrusted::Input::from(b"\x18\x0d991231235959Z")
221            .read_all(Error::CertExpired, read_time);
222        assert_eq!(too_short_generalized, Err(Error::BadDER));
223        assert!(253402300799u64.leading_zeros() > 25);
224        input_test!(b"\x18\x0f99991231235959Z", Ok(MAX_ASN1_TIMESTAMP));
225        input_test!(b"\x18\x0f:9991231235959Z", Err(Error::BadDERTime));
226        input_test!(b"\x18\x0f9:991231235959Z", Err(Error::BadDERTime));
227        input_test!(b"\x18\x0f99:91231235959Z", Err(Error::BadDERTime));
228        input_test!(b"\x18\x0f999:1231235959Z", Err(Error::BadDERTime));
229        input_test!(b"\x18\x0f9999 331235959Z", Err(Error::BadDERTime));
230        input_test!(b"\x18\x0f99991 31235959Z", Err(Error::BadDERTime));
231        input_test!(b"\x18\x0f999912 1235959Z", Err(Error::BadDERTime));
232        input_test!(b"\x18\x0f9999123 235959Z", Err(Error::BadDERTime));
233        input_test!(b"\x18\x0f99991231 35959Z", Err(Error::BadDERTime));
234        input_test!(b"\x18\x0f999912312 5959Z", Err(Error::BadDERTime));
235        input_test!(b"\x18\x0f9999123123 959Z", Err(Error::BadDERTime));
236        input_test!(b"\x18\x0f99991231235 59Z", Err(Error::BadDERTime));
237        input_test!(b"\x18\x0f999912312359 9Z", Err(Error::BadDERTime));
238        input_test!(b"\x18\x0f9999123123595 Z", Err(Error::BadDERTime));
239        input_test!(b"\x18\x0f99991231235959 ", Err(Error::BadDERTime));
240        input_test!(b"\x18\x0f99991231245959Z", Err(Error::BadDERTime));
241        input_test!(b"\x18\x0f99991331235959Z", Err(Error::BadDERTime));
242        input_test!(b"\x18\x0f99990001235959Z", Err(Error::BadDERTime));
243        input_test!(b"\x18\x0f99990431235959Z", Err(Error::BadDERTime));
244        input_test!(b"\x18\x0f99990431235959Z", Err(Error::BadDERTime));
245        input_test!(b"\x18\x0f99990229235959Z", Err(Error::BadDERTime));
246        input_test!(b"\x18\x0d960229235959Z", Err(Error::BadDER));
247        input_test!(b"\x18\x0f19600229235959Z", Ok(-310435201));
248        input_test!(b"\x17\x0d490229235959Z", Err(Error::BadDERTime));
249        input_test!(b"\x17\x0d490228235959Z", Ok(2498169599));
250        input_test!(b"\x17\x0d500228235959Z", Ok(-626054401));
251        input_test!(b"\x18\x0f19960229235959Z", Ok(825638399));
252        input_test!(b"\x18\x0f00000101000000Z", Ok(MIN_ASN1_TIMESTAMP));
253        input_test!(b"\x17\x0d960229235959Z", Ok(825638399));
254        input_test!(b"\x18\x0f99960229235959Z", Ok(253281254399));
255        input_test!(b"\x18\x0e99960229235959Z", Err(Error::BadDERTime));
256        input_test!(b"\x18\x1099960229235959Z", Err(Error::BadDER));
257        input_test!(b"\x18\xFF99960229235959Z", Err(Error::BadDER));
258        input_test!(b"\x18\x0f99000229235959Z", Err(Error::BadDERTime));
259        input_test!(b"\x18\x0f96000229235959Z", Ok(240784703999));
260    }
261}