diff --git a/src/calendar.rs b/src/calendar.rs deleted file mode 100644 index 187eb80d..00000000 --- a/src/calendar.rs +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright 2015-2016 Brian Smith. -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES -// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR -// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -use super::{time::Time, Error}; - -pub(crate) fn time_from_ymdhms_utc( - year: u64, - month: u64, - day_of_month: u64, - hours: u64, - minutes: u64, - seconds: u64, -) -> Result { - let days_before_year_since_unix_epoch = days_before_year_since_unix_epoch(year)?; - - const JAN: u64 = 31; - let feb = days_in_feb(year); - const MAR: u64 = 31; - const APR: u64 = 30; - const MAY: u64 = 31; - const JUN: u64 = 30; - const JUL: u64 = 31; - const AUG: u64 = 31; - const SEP: u64 = 30; - const OCT: u64 = 31; - const NOV: u64 = 30; - let days_before_month_in_year = match month { - 1 => 0, - 2 => JAN, - 3 => JAN + feb, - 4 => JAN + feb + MAR, - 5 => JAN + feb + MAR + APR, - 6 => JAN + feb + MAR + APR + MAY, - 7 => JAN + feb + MAR + APR + MAY + JUN, - 8 => JAN + feb + MAR + APR + MAY + JUN + JUL, - 9 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG, - 10 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP, - 11 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT, - 12 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT + NOV, - _ => unreachable!(), // `read_two_digits` already bounds-checked it. - }; - - let days_before = - days_before_year_since_unix_epoch + days_before_month_in_year + day_of_month - 1; - - let seconds_since_unix_epoch = - (days_before * 24 * 60 * 60) + (hours * 60 * 60) + (minutes * 60) + seconds; - - Ok(Time::from_seconds_since_unix_epoch( - seconds_since_unix_epoch, - )) -} - -fn days_before_year_since_unix_epoch(year: u64) -> Result { - // We don't support dates before January 1, 1970 because that is the - // Unix epoch. It is likely that other software won't deal well with - // certificates that have dates before the epoch. - if year < UNIX_EPOCH_YEAR { - return Err(Error::BadDerTime); - } - let days_before_year_ad = days_before_year_ad(year); - debug_assert!(days_before_year_ad >= DAYS_BEFORE_UNIX_EPOCH_AD); - Ok(days_before_year_ad - DAYS_BEFORE_UNIX_EPOCH_AD) -} - -const UNIX_EPOCH_YEAR: u64 = 1970; - -fn days_before_year_ad(year: u64) -> u64 { - ((year - 1) * 365) - + ((year - 1) / 4) // leap years are every 4 years, - - ((year - 1) / 100) // except years divisible by 100, - + ((year - 1) / 400) // except years divisible by 400. -} - -pub(crate) fn days_in_month(year: u64, month: u64) -> u64 { - match month { - 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, - 4 | 6 | 9 | 11 => 30, - 2 => days_in_feb(year), - _ => unreachable!(), // `read_two_digits` already bounds-checked it. - } -} - -fn days_in_feb(year: u64) -> u64 { - if (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)) { - 29 - } else { - 28 - } -} - -/// All the days up to and including 1969, plus the 477 leap days since AD began -/// (calculated in Gregorian rules). -const DAYS_BEFORE_UNIX_EPOCH_AD: u64 = 1969 * 365 + 477; - -#[cfg(test)] -mod tests { - #[test] - fn test_days_before_unix_epoch() { - use super::{days_before_year_ad, DAYS_BEFORE_UNIX_EPOCH_AD, UNIX_EPOCH_YEAR}; - assert_eq!( - DAYS_BEFORE_UNIX_EPOCH_AD, - days_before_year_ad(UNIX_EPOCH_YEAR) - ); - } - - #[test] - fn test_days_before_year_since_unix_epoch() { - use super::{days_before_year_since_unix_epoch, Error, UNIX_EPOCH_YEAR}; - assert_eq!(Ok(0), days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR)); - assert_eq!( - Ok(365), - days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR + 1) - ); - assert_eq!( - Err(Error::BadDerTime), - days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR - 1) - ); - } - - #[test] - fn test_days_in_month() { - use super::days_in_month; - assert_eq!(days_in_month(2017, 1), 31); - assert_eq!(days_in_month(2017, 2), 28); - assert_eq!(days_in_month(2017, 3), 31); - assert_eq!(days_in_month(2017, 4), 30); - assert_eq!(days_in_month(2017, 5), 31); - assert_eq!(days_in_month(2017, 6), 30); - assert_eq!(days_in_month(2017, 7), 31); - assert_eq!(days_in_month(2017, 8), 31); - assert_eq!(days_in_month(2017, 9), 30); - assert_eq!(days_in_month(2017, 10), 31); - assert_eq!(days_in_month(2017, 11), 30); - assert_eq!(days_in_month(2017, 12), 31); - - // leap cases - assert_eq!(days_in_month(2000, 2), 29); - assert_eq!(days_in_month(2004, 2), 29); - assert_eq!(days_in_month(2016, 2), 29); - assert_eq!(days_in_month(2100, 2), 28); - } - - #[test] - fn test_time_from_ymdhms_utc() { - use super::{time_from_ymdhms_utc, Error, Time, UNIX_EPOCH_YEAR}; - - // 1969-12-31 00:00:00 - assert_eq!( - Err(Error::BadDerTime), - time_from_ymdhms_utc(UNIX_EPOCH_YEAR - 1, 1, 1, 0, 0, 0) - ); - - // 1969-12-31 23:59:59 - assert_eq!( - Err(Error::BadDerTime), - time_from_ymdhms_utc(UNIX_EPOCH_YEAR - 1, 12, 31, 23, 59, 59) - ); - - // 1970-01-01 00:00:00 - assert_eq!( - Time::from_seconds_since_unix_epoch(0), - time_from_ymdhms_utc(UNIX_EPOCH_YEAR, 1, 1, 0, 0, 0).unwrap() - ); - - // 1970-01-01 00:00:01 - assert_eq!( - Time::from_seconds_since_unix_epoch(1), - time_from_ymdhms_utc(UNIX_EPOCH_YEAR, 1, 1, 0, 0, 1).unwrap() - ); - - // 1971-01-01 00:00:00 - assert_eq!( - Time::from_seconds_since_unix_epoch(365 * 86400), - time_from_ymdhms_utc(UNIX_EPOCH_YEAR + 1, 1, 1, 0, 0, 0).unwrap() - ); - - // year boundary - assert_eq!( - Time::from_seconds_since_unix_epoch(1_483_228_799), - time_from_ymdhms_utc(2016, 12, 31, 23, 59, 59).unwrap() - ); - assert_eq!( - Time::from_seconds_since_unix_epoch(1_483_228_800), - time_from_ymdhms_utc(2017, 1, 1, 0, 0, 0).unwrap() - ); - - // not a leap year - assert_eq!( - Time::from_seconds_since_unix_epoch(1_492_449_162), - time_from_ymdhms_utc(2017, 4, 17, 17, 12, 42).unwrap() - ); - - // leap year, post-feb - assert_eq!( - Time::from_seconds_since_unix_epoch(1_460_913_162), - time_from_ymdhms_utc(2016, 4, 17, 17, 12, 42).unwrap() - ); - } -} diff --git a/src/cert.rs b/src/cert.rs index dae388e8..3450c894 100644 --- a/src/cert.rs +++ b/src/cert.rs @@ -116,7 +116,7 @@ impl<'a> Cert<'a> { der::Tag::Sequence, Error::BadDer, |extension| { - remember_cert_extension(&mut cert, &Extension::parse(extension)?) + remember_cert_extension(&mut cert, &Extension::from_der(extension)?) }, ) }, @@ -165,7 +165,7 @@ fn version3(input: &mut untrusted::Reader) -> Result<(), Error> { der::Tag::ContextSpecificConstructed0, Error::UnsupportedCertVersion, |input| { - let version = der::small_nonnegative_integer(input)?; + let version = u8::from_der(input)?; if version != 2 { // v3 return Err(Error::UnsupportedCertVersion); diff --git a/src/crl.rs b/src/crl.rs index aaa15494..1d4c2948 100644 --- a/src/crl.rs +++ b/src/crl.rs @@ -277,7 +277,7 @@ impl<'a> FromDer<'a> for BorrowedCertRevocationList<'a> { // extensions in all CRLs issued. // As a result of the above we parse this as a required section, not OPTIONAL. // NOTE: Encoded value of version 2 is 1. - if der::small_nonnegative_integer(tbs_cert_list)? != 1 { + if u8::from_der(tbs_cert_list)? != 1 { return Err(Error::UnsupportedCrlVersion); } @@ -298,13 +298,13 @@ impl<'a> FromDer<'a> for BorrowedCertRevocationList<'a> { // encoded as UTCTime or GeneralizedTime. // We do not presently enforce the correct choice of UTCTime or GeneralizedTime based on // whether the date is post 2050. - der::time_choice(tbs_cert_list)?; + Time::from_der(tbs_cert_list)?; // While OPTIONAL in the ASN.1 module, RFC 5280 §5.1.2.5 says: // Conforming CRL issuers MUST include the nextUpdate field in all CRLs. // We do not presently enforce the correct choice of UTCTime or GeneralizedTime based on // whether the date is post 2050. - der::time_choice(tbs_cert_list)?; + Time::from_der(tbs_cert_list)?; // RFC 5280 §5.1.2.6: // When there are no revoked certificates, the revoked certificates list @@ -351,7 +351,7 @@ impl<'a> FromDer<'a> for BorrowedCertRevocationList<'a> { // that the application cannot process, then the application MUST NOT // use that CRL to determine the status of certificates. However, // applications may ignore unrecognized non-critical extensions. - crl.remember_extension(&Extension::parse(extension)?) + crl.remember_extension(&Extension::from_der(extension)?) }, ) }, @@ -468,7 +468,7 @@ impl<'a> BorrowedRevokedCert<'a> { // id-ce-invalidityDate 2.5.29.24 - RFC 5280 §5.3.2. 24 => set_extension_once(&mut self.invalidity_date, || { - extension.value.read_all(Error::BadDer, der::time_choice) + extension.value.read_all(Error::BadDer, Time::from_der) }), // id-ce-certificateIssuer 2.5.29.29 - RFC 5280 §5.3.3. @@ -503,7 +503,7 @@ impl<'a> FromDer<'a> for BorrowedRevokedCert<'a> { .map_err(|_| Error::InvalidSerialNumber)? .as_slice_less_safe(); - let revocation_date = der::time_choice(der)?; + let revocation_date = Time::from_der(der)?; let mut revoked_cert = BorrowedRevokedCert { serial_number, @@ -537,7 +537,7 @@ impl<'a> FromDer<'a> for BorrowedRevokedCert<'a> { // process, then the application MUST NOT use that CRL to determine the // status of any certificates. However, applications may ignore // unrecognized non-critical CRL entry extensions. - revoked_cert.remember_extension(&Extension::parse(ext_der)?) + revoked_cert.remember_extension(&Extension::from_der(ext_der)?) })?; if reader.at_end() { break; diff --git a/src/der.rs b/src/der.rs index ca94d97b..4f987902 100644 --- a/src/der.rs +++ b/src/der.rs @@ -14,7 +14,7 @@ use core::marker::PhantomData; -use crate::{calendar, time, Error}; +use crate::Error; #[derive(Debug)] pub struct DerIterator<'a, T> { @@ -364,19 +364,13 @@ pub(crate) fn bit_string_flags(input: untrusted::Input) -> Result Result { - if !input.peek(Tag::Boolean.into()) { - return Ok(false); - } - nested(input, Tag::Boolean, Error::BadDer, |input| { - match input.read_byte() { - Ok(0xff) => Ok(true), - Ok(0x00) => Ok(false), +impl<'a> FromDer<'a> for u8 { + fn from_der(reader: &mut untrusted::Reader<'a>) -> Result { + match *nonnegative_integer(reader)?.as_slice_less_safe() { + [b] => Ok(b), _ => Err(Error::BadDer), } - }) + } } pub(crate) fn nonnegative_integer<'a>( @@ -406,67 +400,22 @@ pub(crate) fn nonnegative_integer<'a>( } } -// Parse an integer in the range 0..=255. -pub(crate) fn small_nonnegative_integer(input: &mut untrusted::Reader) -> Result { - match *nonnegative_integer(input)?.as_slice_less_safe() { - [b] => Ok(b), - _ => Err(Error::BadDer), - } -} - -pub(crate) fn time_choice(input: &mut untrusted::Reader) -> Result { - let is_utc_time = input.peek(Tag::UTCTime.into()); - let expected_tag = if is_utc_time { - Tag::UTCTime - } else { - Tag::GeneralizedTime - }; - - fn read_digit(inner: &mut untrusted::Reader) -> Result { - const DIGIT: core::ops::RangeInclusive = b'0'..=b'9'; - let b = inner.read_byte().map_err(|_| Error::BadDerTime)?; - if DIGIT.contains(&b) { - return Ok(u64::from(b - DIGIT.start())); +// Like mozilla::pkix, we accept the nonconformant explicit encoding of +// the default value (false) for compatibility with real-world certificates. +impl<'a> FromDer<'a> for bool { + fn from_der(reader: &mut untrusted::Reader<'a>) -> Result { + if !reader.peek(Tag::Boolean.into()) { + return Ok(false); } - Err(Error::BadDerTime) - } - fn read_two_digits(inner: &mut untrusted::Reader, min: u64, max: u64) -> Result { - let hi = read_digit(inner)?; - let lo = read_digit(inner)?; - let value = (hi * 10) + lo; - if value < min || value > max { - return Err(Error::BadDerTime); - } - Ok(value) + nested(reader, Tag::Boolean, Error::BadDer, |input| { + match input.read_byte() { + Ok(0xff) => Ok(true), + Ok(0x00) => Ok(false), + _ => Err(Error::BadDer), + } + }) } - - nested(input, expected_tag, Error::BadDer, |value| { - let (year_hi, year_lo) = if is_utc_time { - let lo = read_two_digits(value, 0, 99)?; - let hi = if lo >= 50 { 19 } else { 20 }; - (hi, lo) - } else { - let hi = read_two_digits(value, 0, 99)?; - let lo = read_two_digits(value, 0, 99)?; - (hi, lo) - }; - - let year = (year_hi * 100) + year_lo; - let month = read_two_digits(value, 1, 12)?; - let days_in_month = calendar::days_in_month(year, month); - let day_of_month = read_two_digits(value, 1, days_in_month)?; - let hours = read_two_digits(value, 0, 23)?; - let minutes = read_two_digits(value, 0, 59)?; - let seconds = read_two_digits(value, 0, 59)?; - - let time_zone = value.read_byte().map_err(|_| Error::BadDerTime)?; - if time_zone != b'Z' { - return Err(Error::BadDerTime); - } - - calendar::time_from_ymdhms_utc(year, month, day_of_month, hours, minutes, seconds) - }) } macro_rules! oid { @@ -480,25 +429,25 @@ macro_rules! oid { mod tests { #[test] fn test_optional_boolean() { - use super::{optional_boolean, Error}; + use super::{Error, FromDer}; // Empty input results in false - assert!(!optional_boolean(&mut bytes_reader(&[])).unwrap()); + assert!(!bool::from_der(&mut bytes_reader(&[])).unwrap()); // Optional, so another data type results in false - assert!(!optional_boolean(&mut bytes_reader(&[0x05, 0x00])).unwrap()); + assert!(!bool::from_der(&mut bytes_reader(&[0x05, 0x00])).unwrap()); // Only 0x00 and 0xff are accepted values assert_eq!( Err(Error::BadDer), - optional_boolean(&mut bytes_reader(&[0x01, 0x01, 0x42])) + bool::from_der(&mut bytes_reader(&[0x01, 0x01, 0x42])) ); // True - assert!(optional_boolean(&mut bytes_reader(&[0x01, 0x01, 0xff])).unwrap()); + assert!(bool::from_der(&mut bytes_reader(&[0x01, 0x01, 0xff])).unwrap()); // False - assert!(!optional_boolean(&mut bytes_reader(&[0x01, 0x01, 0x00])).unwrap()); + assert!(!bool::from_der(&mut bytes_reader(&[0x01, 0x01, 0x00])).unwrap()); } #[test] @@ -738,23 +687,23 @@ mod tests { #[test] fn test_small_nonnegative_integer() { - use super::{small_nonnegative_integer, Error, Tag}; + use super::{Error, FromDer, Tag}; for value in 0..=127 { let data = [Tag::Integer.into(), 1, value]; let mut rd = untrusted::Reader::new(untrusted::Input::from(&data)); - assert_eq!(small_nonnegative_integer(&mut rd), Ok(value),); + assert_eq!(u8::from_der(&mut rd), Ok(value),); } for value in 128..=255 { let data = [Tag::Integer.into(), 2, 0x00, value]; let mut rd = untrusted::Reader::new(untrusted::Input::from(&data)); - assert_eq!(small_nonnegative_integer(&mut rd), Ok(value),); + assert_eq!(u8::from_der(&mut rd), Ok(value),); } // not an integer assert_eq!( - small_nonnegative_integer(&mut untrusted::Reader::new(untrusted::Input::from(&[ + u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[ Tag::Sequence.into(), 1, 1 @@ -764,7 +713,7 @@ mod tests { // negative assert_eq!( - small_nonnegative_integer(&mut untrusted::Reader::new(untrusted::Input::from(&[ + u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[ Tag::Integer.into(), 1, 0xff @@ -774,7 +723,7 @@ mod tests { // positive but too large assert_eq!( - small_nonnegative_integer(&mut untrusted::Reader::new(untrusted::Input::from(&[ + u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[ Tag::Integer.into(), 2, 0x01, @@ -785,7 +734,7 @@ mod tests { // unnecessary leading zero assert_eq!( - small_nonnegative_integer(&mut untrusted::Reader::new(untrusted::Input::from(&[ + u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[ Tag::Integer.into(), 2, 0x00, @@ -796,19 +745,19 @@ mod tests { // truncations assert_eq!( - small_nonnegative_integer(&mut untrusted::Reader::new(untrusted::Input::from(&[]))), + u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[]))), Err(Error::BadDer) ); assert_eq!( - small_nonnegative_integer(&mut untrusted::Reader::new(untrusted::Input::from(&[ + u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[ Tag::Integer.into(), ]))), Err(Error::BadDer) ); assert_eq!( - small_nonnegative_integer(&mut untrusted::Reader::new(untrusted::Input::from(&[ + u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[ Tag::Integer.into(), 1, ]))), @@ -816,7 +765,7 @@ mod tests { ); assert_eq!( - small_nonnegative_integer(&mut untrusted::Reader::new(untrusted::Input::from(&[ + u8::from_der(&mut untrusted::Reader::new(untrusted::Input::from(&[ Tag::Integer.into(), 2, 0 diff --git a/src/lib.rs b/src/lib.rs index ec498375..c267c747 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,6 @@ extern crate alloc; #[macro_use] mod der; -mod calendar; mod cert; mod end_entity; mod error; diff --git a/src/time.rs b/src/time.rs index 1519801c..e22a2ac9 100644 --- a/src/time.rs +++ b/src/time.rs @@ -14,6 +14,9 @@ //! Conversions into the library's time type. +use crate::der::{self, FromDer, Tag}; +use crate::error::Error; + /// The time type. /// /// Internally this is merely a UNIX timestamp: a count of non-leap @@ -35,6 +38,67 @@ impl Time { } } +impl<'a> FromDer<'a> for Time { + fn from_der(input: &mut untrusted::Reader<'a>) -> Result { + let is_utc_time = input.peek(Tag::UTCTime.into()); + let expected_tag = if is_utc_time { + Tag::UTCTime + } else { + Tag::GeneralizedTime + }; + + fn read_digit(inner: &mut untrusted::Reader) -> Result { + const DIGIT: core::ops::RangeInclusive = b'0'..=b'9'; + let b = inner.read_byte().map_err(|_| Error::BadDerTime)?; + if DIGIT.contains(&b) { + return Ok(u64::from(b - DIGIT.start())); + } + Err(Error::BadDerTime) + } + + fn read_two_digits( + inner: &mut untrusted::Reader, + min: u64, + max: u64, + ) -> Result { + let hi = read_digit(inner)?; + let lo = read_digit(inner)?; + let value = (hi * 10) + lo; + if value < min || value > max { + return Err(Error::BadDerTime); + } + Ok(value) + } + + der::nested(input, expected_tag, Error::BadDer, |value| { + let (year_hi, year_lo) = if is_utc_time { + let lo = read_two_digits(value, 0, 99)?; + let hi = if lo >= 50 { 19 } else { 20 }; + (hi, lo) + } else { + let hi = read_two_digits(value, 0, 99)?; + let lo = read_two_digits(value, 0, 99)?; + (hi, lo) + }; + + let year = (year_hi * 100) + year_lo; + let month = read_two_digits(value, 1, 12)?; + let days_in_month = days_in_month(year, month); + let day_of_month = read_two_digits(value, 1, days_in_month)?; + let hours = read_two_digits(value, 0, 23)?; + let minutes = read_two_digits(value, 0, 59)?; + let seconds = read_two_digits(value, 0, 59)?; + + let time_zone = value.read_byte().map_err(|_| Error::BadDerTime)?; + if time_zone != b'Z' { + return Err(Error::BadDerTime); + } + + time_from_ymdhms_utc(year, month, day_of_month, hours, minutes, seconds) + }) + } +} + #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl TryFrom for Time { @@ -64,3 +128,199 @@ impl TryFrom for Time { .map(|d| Self::from_seconds_since_unix_epoch(d.as_secs())) } } + +pub(crate) fn time_from_ymdhms_utc( + year: u64, + month: u64, + day_of_month: u64, + hours: u64, + minutes: u64, + seconds: u64, +) -> Result { + let days_before_year_since_unix_epoch = days_before_year_since_unix_epoch(year)?; + + const JAN: u64 = 31; + let feb = days_in_feb(year); + const MAR: u64 = 31; + const APR: u64 = 30; + const MAY: u64 = 31; + const JUN: u64 = 30; + const JUL: u64 = 31; + const AUG: u64 = 31; + const SEP: u64 = 30; + const OCT: u64 = 31; + const NOV: u64 = 30; + let days_before_month_in_year = match month { + 1 => 0, + 2 => JAN, + 3 => JAN + feb, + 4 => JAN + feb + MAR, + 5 => JAN + feb + MAR + APR, + 6 => JAN + feb + MAR + APR + MAY, + 7 => JAN + feb + MAR + APR + MAY + JUN, + 8 => JAN + feb + MAR + APR + MAY + JUN + JUL, + 9 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG, + 10 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP, + 11 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT, + 12 => JAN + feb + MAR + APR + MAY + JUN + JUL + AUG + SEP + OCT + NOV, + _ => unreachable!(), // `read_two_digits` already bounds-checked it. + }; + + let days_before = + days_before_year_since_unix_epoch + days_before_month_in_year + day_of_month - 1; + + let seconds_since_unix_epoch = + (days_before * 24 * 60 * 60) + (hours * 60 * 60) + (minutes * 60) + seconds; + + Ok(Time::from_seconds_since_unix_epoch( + seconds_since_unix_epoch, + )) +} + +fn days_before_year_since_unix_epoch(year: u64) -> Result { + // We don't support dates before January 1, 1970 because that is the + // Unix epoch. It is likely that other software won't deal well with + // certificates that have dates before the epoch. + if year < UNIX_EPOCH_YEAR { + return Err(Error::BadDerTime); + } + let days_before_year_ad = days_before_year_ad(year); + debug_assert!(days_before_year_ad >= DAYS_BEFORE_UNIX_EPOCH_AD); + Ok(days_before_year_ad - DAYS_BEFORE_UNIX_EPOCH_AD) +} + +const UNIX_EPOCH_YEAR: u64 = 1970; + +fn days_before_year_ad(year: u64) -> u64 { + ((year - 1) * 365) + + ((year - 1) / 4) // leap years are every 4 years, + - ((year - 1) / 100) // except years divisible by 100, + + ((year - 1) / 400) // except years divisible by 400. +} + +pub(crate) fn days_in_month(year: u64, month: u64) -> u64 { + match month { + 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31, + 4 | 6 | 9 | 11 => 30, + 2 => days_in_feb(year), + _ => unreachable!(), // `read_two_digits` already bounds-checked it. + } +} + +fn days_in_feb(year: u64) -> u64 { + if (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)) { + 29 + } else { + 28 + } +} + +/// All the days up to and including 1969, plus the 477 leap days since AD began +/// (calculated in Gregorian rules). +const DAYS_BEFORE_UNIX_EPOCH_AD: u64 = 1969 * 365 + 477; + +#[cfg(test)] +mod tests { + #[test] + fn test_days_before_unix_epoch() { + use super::{days_before_year_ad, DAYS_BEFORE_UNIX_EPOCH_AD, UNIX_EPOCH_YEAR}; + assert_eq!( + DAYS_BEFORE_UNIX_EPOCH_AD, + days_before_year_ad(UNIX_EPOCH_YEAR) + ); + } + + #[test] + fn test_days_before_year_since_unix_epoch() { + use super::{days_before_year_since_unix_epoch, Error, UNIX_EPOCH_YEAR}; + assert_eq!(Ok(0), days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR)); + assert_eq!( + Ok(365), + days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR + 1) + ); + assert_eq!( + Err(Error::BadDerTime), + days_before_year_since_unix_epoch(UNIX_EPOCH_YEAR - 1) + ); + } + + #[test] + fn test_days_in_month() { + use super::days_in_month; + assert_eq!(days_in_month(2017, 1), 31); + assert_eq!(days_in_month(2017, 2), 28); + assert_eq!(days_in_month(2017, 3), 31); + assert_eq!(days_in_month(2017, 4), 30); + assert_eq!(days_in_month(2017, 5), 31); + assert_eq!(days_in_month(2017, 6), 30); + assert_eq!(days_in_month(2017, 7), 31); + assert_eq!(days_in_month(2017, 8), 31); + assert_eq!(days_in_month(2017, 9), 30); + assert_eq!(days_in_month(2017, 10), 31); + assert_eq!(days_in_month(2017, 11), 30); + assert_eq!(days_in_month(2017, 12), 31); + + // leap cases + assert_eq!(days_in_month(2000, 2), 29); + assert_eq!(days_in_month(2004, 2), 29); + assert_eq!(days_in_month(2016, 2), 29); + assert_eq!(days_in_month(2100, 2), 28); + } + + #[test] + fn test_time_from_ymdhms_utc() { + use super::{time_from_ymdhms_utc, Error, Time, UNIX_EPOCH_YEAR}; + + // 1969-12-31 00:00:00 + assert_eq!( + Err(Error::BadDerTime), + time_from_ymdhms_utc(UNIX_EPOCH_YEAR - 1, 1, 1, 0, 0, 0) + ); + + // 1969-12-31 23:59:59 + assert_eq!( + Err(Error::BadDerTime), + time_from_ymdhms_utc(UNIX_EPOCH_YEAR - 1, 12, 31, 23, 59, 59) + ); + + // 1970-01-01 00:00:00 + assert_eq!( + Time::from_seconds_since_unix_epoch(0), + time_from_ymdhms_utc(UNIX_EPOCH_YEAR, 1, 1, 0, 0, 0).unwrap() + ); + + // 1970-01-01 00:00:01 + assert_eq!( + Time::from_seconds_since_unix_epoch(1), + time_from_ymdhms_utc(UNIX_EPOCH_YEAR, 1, 1, 0, 0, 1).unwrap() + ); + + // 1971-01-01 00:00:00 + assert_eq!( + Time::from_seconds_since_unix_epoch(365 * 86400), + time_from_ymdhms_utc(UNIX_EPOCH_YEAR + 1, 1, 1, 0, 0, 0).unwrap() + ); + + // year boundary + assert_eq!( + Time::from_seconds_since_unix_epoch(1_483_228_799), + time_from_ymdhms_utc(2016, 12, 31, 23, 59, 59).unwrap() + ); + assert_eq!( + Time::from_seconds_since_unix_epoch(1_483_228_800), + time_from_ymdhms_utc(2017, 1, 1, 0, 0, 0).unwrap() + ); + + // not a leap year + assert_eq!( + Time::from_seconds_since_unix_epoch(1_492_449_162), + time_from_ymdhms_utc(2017, 4, 17, 17, 12, 42).unwrap() + ); + + // leap year, post-feb + assert_eq!( + Time::from_seconds_since_unix_epoch(1_460_913_162), + time_from_ymdhms_utc(2016, 4, 17, 17, 12, 42).unwrap() + ); + } +} diff --git a/src/verify_cert.rs b/src/verify_cert.rs index 76101b0c..863c2c0a 100644 --- a/src/verify_cert.rs +++ b/src/verify_cert.rs @@ -13,9 +13,10 @@ // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. use crate::cert::{Cert, EndEntityOrCa}; +use crate::der::{self, FromDer}; use crate::{ - der, signed_data, subject_name, time, CertRevocationList, Error, - SignatureVerificationAlgorithm, TrustAnchor, + signed_data, subject_name, time, CertRevocationList, Error, SignatureVerificationAlgorithm, + TrustAnchor, }; pub(crate) struct ChainOptions<'a> { @@ -268,8 +269,8 @@ fn check_issuer_independent_properties( // https://tools.ietf.org/html/rfc5280#section-4.1.2.5 fn check_validity(input: &mut untrusted::Reader, time: time::Time) -> Result<(), Error> { - let not_before = der::time_choice(input)?; - let not_after = der::time_choice(input)?; + let not_before = time::Time::from_der(input)?; + let not_after = time::Time::from_der(input)?; if not_before > not_after { return Err(Error::InvalidCertValidity); @@ -309,15 +310,14 @@ fn check_basic_constraints( ) -> Result<(), Error> { let (is_ca, path_len_constraint) = match input { Some(input) => { - let is_ca = der::optional_boolean(input)?; + let is_ca = bool::from_der(input)?; // https://bugzilla.mozilla.org/show_bug.cgi?id=985025: RFC 5280 // says that a certificate must not have pathLenConstraint unless // it is a CA certificate, but some real-world end-entity // certificates have pathLenConstraint. let path_len_constraint = if !input.at_end() { - let value = der::small_nonnegative_integer(input)?; - Some(usize::from(value)) + Some(usize::from(u8::from_der(input)?)) } else { None }; diff --git a/src/x509.rs b/src/x509.rs index 863f0ae0..7944dce9 100644 --- a/src/x509.rs +++ b/src/x509.rs @@ -23,17 +23,6 @@ pub(crate) struct Extension<'a> { } impl<'a> Extension<'a> { - pub(crate) fn parse(der: &mut untrusted::Reader<'a>) -> Result, Error> { - let id = der::expect_tag_and_get_value(der, der::Tag::OID)?; - let critical = der::optional_boolean(der)?; - let value = der::expect_tag_and_get_value(der, der::Tag::OctetString)?; - Ok(Extension { - id, - critical, - value, - }) - } - pub(crate) fn unsupported(&self) -> Result<(), Error> { match self.critical { true => Err(Error::UnsupportedCriticalExtension), @@ -42,6 +31,19 @@ impl<'a> Extension<'a> { } } +impl<'a> FromDer<'a> for Extension<'a> { + fn from_der(reader: &mut untrusted::Reader<'a>) -> Result { + let id = der::expect_tag_and_get_value(reader, der::Tag::OID)?; + let critical = bool::from_der(reader)?; + let value = der::expect_tag_and_get_value(reader, der::Tag::OctetString)?; + Ok(Extension { + id, + critical, + value, + }) + } +} + pub(crate) fn set_extension_once( destination: &mut Option, parser: impl Fn() -> Result,