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: u64 = 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}
284