webpki/
time.rs

1// Copyright 2015-2016 Brian Smith.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
10// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15//! Conversions into the library's time type.
16
17use core::time::Duration;
18
19use pki_types::UnixTime;
20
21use crate::der::{self, FromDer, Tag};
22use crate::error::{DerTypeId, Error};
23
24impl<'a> FromDer<'a> for UnixTime {
25    fn from_der(input: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
26        let is_utc_time = input.peek(Tag::UTCTime.into());
27        let expected_tag = if is_utc_time {
28            Tag::UTCTime
29        } else {
30            Tag::GeneralizedTime
31        };
32
33        fn read_digit(inner: &mut untrusted::Reader<'_>) -> Result<u64, Error> {
34            const DIGIT: core::ops::RangeInclusive<u8> = b'0'..=b'9';
35            let b = inner.read_byte().map_err(|_| Error::BadDerTime)?;
36            if DIGIT.contains(&b) {
37                return Ok(u64::from(b - DIGIT.start()));
38            }
39            Err(Error::BadDerTime)
40        }
41
42        fn read_two_digits(
43            inner: &mut untrusted::Reader<'_>,
44            min: u64,
45            max: u64,
46        ) -> Result<u64, Error> {
47            let hi = read_digit(inner)?;
48            let lo = read_digit(inner)?;
49            let value = (hi * 10) + lo;
50            if value < min || value > max {
51                return Err(Error::BadDerTime);
52            }
53            Ok(value)
54        }
55
56        der::nested(
57            input,
58            expected_tag,
59            Error::TrailingData(Self::TYPE_ID),
60            |value| {
61                let (year_hi, year_lo) = if is_utc_time {
62                    let lo = read_two_digits(value, 0, 99)?;
63                    let hi = if lo >= 50 { 19 } else { 20 };
64                    (hi, lo)
65                } else {
66                    let hi = read_two_digits(value, 0, 99)?;
67                    let lo = read_two_digits(value, 0, 99)?;
68                    (hi, lo)
69                };
70
71                let year = (year_hi * 100) + year_lo;
72                let month = read_two_digits(value, 1, 12)?;
73                let days_in_month = days_in_month(year, month);
74                let day_of_month = read_two_digits(value, 1, days_in_month)?;
75                let hours = read_two_digits(value, 0, 23)?;
76                let minutes = read_two_digits(value, 0, 59)?;
77                let seconds = read_two_digits(value, 0, 59)?;
78
79                let time_zone = value.read_byte().map_err(|_| Error::BadDerTime)?;
80                if time_zone != b'Z' {
81                    return Err(Error::BadDerTime);
82                }
83
84                time_from_ymdhms_utc(year, month, day_of_month, hours, minutes, seconds)
85            },
86        )
87    }
88
89    const TYPE_ID: DerTypeId = DerTypeId::Time;
90}
91
92pub(crate) fn time_from_ymdhms_utc(
93    year: u64,
94    month: u64,
95    day_of_month: u64,
96    hours: u64,
97    minutes: u64,
98    seconds: u64,
99) -> Result<UnixTime, Error> {
100    let days_before_year_since_unix_epoch = days_before_year_since_unix_epoch(year)?;
101
102    const JAN: u64 = 31;
103    let feb = days_in_feb(year);
104    const MAR: u64 = 31;
105    const APR: u64 = 30;
106    const MAY: u64 = 31;
107    const JUN: u64 = 30;
108    const JUL: u64 = 31;
109    const AUG: u64 = 31;
110    const SEP: u64 = 30;
111    const OCT: u64 = 31;
112    const NOV: u64 = 30;
113    let days_before_month_in_year = match month {
114        1 => 0,
115        2 => JAN,
116        3 => JAN + feb,
117        4 => JAN + feb + MAR,
118        5 => JAN + feb + MAR + APR,
119        6 => JAN + feb + MAR + APR + MAY,
120        7 => JAN + feb + MAR + APR + MAY + JUN,
121        8 => JAN + feb + MAR + APR + MAY + JUN + JUL,
122        9 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG,
123        10 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP,
124        11 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT,
125        12 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT + NOV,
126        _ => unreachable!(), // `read_two_digits` already bounds-checked it.
127    };
128
129    let days_before =
130        days_before_year_since_unix_epoch + days_before_month_in_year + day_of_month - 1;
131
132    let seconds_since_unix_epoch =
133        (days_before * 24 * 60 * 60) + (hours * 60 * 60) + (minutes * 60) + seconds;
134
135    Ok(UnixTime::since_unix_epoch(Duration::from_secs(
136        seconds_since_unix_epoch,
137    )))
138}
139
140fn days_before_year_since_unix_epoch(year: u64) -> Result<u64, Error> {
141    // We don't support dates before January 1, 1970 because that is the
142    // Unix epoch. It is likely that other software won't deal well with
143    // certificates that have dates before the epoch.
144    if year < UNIX_EPOCH_YEAR {
145        return Err(Error::BadDerTime);
146    }
147    let days_before_year_ad = days_before_year_ad(year);
148    debug_assert!(days_before_year_ad >= DAYS_BEFORE_UNIX_EPOCH_AD);
149    Ok(days_before_year_ad - DAYS_BEFORE_UNIX_EPOCH_AD)
150}
151
152const UNIX_EPOCH_YEAR: u64 = 1970;
153
154fn days_before_year_ad(year: u64) -> u64 {
155    ((year - 1) * 365)
156        + ((year - 1) / 4)    // leap years are every 4 years,
157        - ((year - 1) / 100)  // except years divisible by 100,
158        + ((year - 1) / 400) // except years divisible by 400.
159}
160
161pub(crate) fn days_in_month(year: u64, month: u64) -> u64 {
162    match month {
163        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
164        4 | 6 | 9 | 11 => 30,
165        2 => days_in_feb(year),
166        _ => unreachable!(), // `read_two_digits` already bounds-checked it.
167    }
168}
169
170fn days_in_feb(year: u64) -> u64 {
171    if (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)) {
172        29
173    } else {
174        28
175    }
176}
177
178/// All the days up to and including 1969, plus the 477 leap days since AD began
179/// (calculated in Gregorian rules).
180const DAYS_BEFORE_UNIX_EPOCH_AD: u64 = 1969 * 365 + 477;
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_days_before_unix_epoch() {
188        assert_eq!(
189            DAYS_BEFORE_UNIX_EPOCH_AD,
190            days_before_year_ad(UNIX_EPOCH_YEAR)
191        );
192    }
193
194    #[test]
195    fn test_days_before_year_since_unix_epoch() {
196        assert_eq!(Ok(0), days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR));
197        assert_eq!(
198            Ok(365),
199            days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR + 1)
200        );
201        assert_eq!(
202            Err(Error::BadDerTime),
203            days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR - 1)
204        );
205    }
206
207    #[test]
208    fn test_days_in_month() {
209        assert_eq!(days_in_month(2017, 1), 31);
210        assert_eq!(days_in_month(2017, 2), 28);
211        assert_eq!(days_in_month(2017, 3), 31);
212        assert_eq!(days_in_month(2017, 4), 30);
213        assert_eq!(days_in_month(2017, 5), 31);
214        assert_eq!(days_in_month(2017, 6), 30);
215        assert_eq!(days_in_month(2017, 7), 31);
216        assert_eq!(days_in_month(2017, 8), 31);
217        assert_eq!(days_in_month(2017, 9), 30);
218        assert_eq!(days_in_month(2017, 10), 31);
219        assert_eq!(days_in_month(2017, 11), 30);
220        assert_eq!(days_in_month(2017, 12), 31);
221
222        // leap cases
223        assert_eq!(days_in_month(2000, 2), 29);
224        assert_eq!(days_in_month(2004, 2), 29);
225        assert_eq!(days_in_month(2016, 2), 29);
226        assert_eq!(days_in_month(2100, 2), 28);
227    }
228
229    #[test]
230    fn test_time_from_ymdhms_utc() {
231        // 1969-12-31 00:00:00
232        assert_eq!(
233            Err(Error::BadDerTime),
234            time_from_ymdhms_utc(UNIX_EPOCH_YEAR - 1, 1, 1, 0, 0, 0)
235        );
236
237        // 1969-12-31 23:59:59
238        assert_eq!(
239            Err(Error::BadDerTime),
240            time_from_ymdhms_utc(UNIX_EPOCH_YEAR - 1, 12, 31, 23, 59, 59)
241        );
242
243        // 1970-01-01 00:00:00
244        assert_eq!(
245            UnixTime::since_unix_epoch(Duration::from_secs(0)),
246            time_from_ymdhms_utc(UNIX_EPOCH_YEAR, 1, 1, 0, 0, 0).unwrap()
247        );
248
249        // 1970-01-01 00:00:01
250        assert_eq!(
251            UnixTime::since_unix_epoch(Duration::from_secs(1)),
252            time_from_ymdhms_utc(UNIX_EPOCH_YEAR, 1, 1, 0, 0, 1).unwrap()
253        );
254
255        // 1971-01-01 00:00:00
256        assert_eq!(
257            UnixTime::since_unix_epoch(Duration::from_secs(365 * 86400)),
258            time_from_ymdhms_utc(UNIX_EPOCH_YEAR + 1, 1, 1, 0, 0, 0).unwrap()
259        );
260
261        // year boundary
262        assert_eq!(
263            UnixTime::since_unix_epoch(Duration::from_secs(1_483_228_799)),
264            time_from_ymdhms_utc(2016, 12, 31, 23, 59, 59).unwrap()
265        );
266        assert_eq!(
267            UnixTime::since_unix_epoch(Duration::from_secs(1_483_228_800)),
268            time_from_ymdhms_utc(2017, 1, 1, 0, 0, 0).unwrap()
269        );
270
271        // not a leap year
272        assert_eq!(
273            UnixTime::since_unix_epoch(Duration::from_secs(1_492_449_162)),
274            time_from_ymdhms_utc(2017, 4, 17, 17, 12, 42).unwrap()
275        );
276
277        // leap year, post-feb
278        assert_eq!(
279            UnixTime::since_unix_epoch(Duration::from_secs(1_460_913_162)),
280            time_from_ymdhms_utc(2016, 4, 17, 17, 12, 42).unwrap()
281        );
282    }
283}