1use super::Error;
2use ring::io::der;
3
4#[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 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
44pub const MAX_ASN1_TIMESTAMP: i64 = 253_402_300_799;
46
47pub 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
98pub 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
108pub 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 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 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}