blob: ca37217b51c09e96088d85480257bf69bd4ee6c0 [file] [log] [blame]
use std::ops::{Add, Sub};
use std::time::Duration;
use std::time::SystemTime;
use chrono::offset::{TimeZone, Utc};
use chrono::{DateTime, Datelike};
use der_parser::ber::{ber_read_element_header, BerObjectContent, BerTag, MAX_OBJECT_SIZE};
use der_parser::der::{parse_der_generalizedtime, parse_der_utctime, DerObject};
use der_parser::error::{BerError, DerResult};
use nom::branch::alt;
use nom::bytes::complete::take;
use nom::combinator::{complete, map_res, opt};
use crate::error::{X509Error, X509Result};
use crate::traits::FromDer;
/// An ASN.1 timestamp.
#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub struct ASN1Time(DateTime<Utc>);
impl ASN1Time {
pub(crate) fn from_der_opt(i: &[u8]) -> X509Result<Option<Self>> {
opt(map_res(parse_choice_of_time, der_to_utctime))(i)
.map_err(|_| X509Error::InvalidDate.into())
}
#[inline]
pub(crate) fn from_datetime_utc(dt: DateTime<Utc>) -> Self {
ASN1Time(dt)
}
/// Makes a new `ASN1Time` from the number of non-leap seconds since Epoch
pub fn from_timestamp(secs: i64) -> Self {
ASN1Time(Utc.timestamp(secs, 0))
}
/// Returns the number of non-leap seconds since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp").
#[inline]
pub fn timestamp(&self) -> i64 {
self.0.timestamp()
}
/// Returns a `ASN1Time` which corresponds to the current date.
#[inline]
pub fn now() -> Self {
ASN1Time(SystemTime::now().into())
}
/// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`.
#[inline]
pub fn to_rfc2822(self) -> String {
self.0.to_rfc2822()
}
}
impl<'a> FromDer<'a> for ASN1Time {
fn from_der(i: &[u8]) -> X509Result<Self> {
map_res(parse_choice_of_time, der_to_utctime)(i).map_err(|_| X509Error::InvalidDate.into())
}
}
fn parse_choice_of_time(i: &[u8]) -> DerResult {
alt((
complete(parse_der_utctime),
complete(parse_der_generalizedtime),
complete(parse_malformed_date),
))(i)
}
// allow relaxed parsing of UTCTime (ex: 370116130016+0000)
fn parse_malformed_date(i: &[u8]) -> DerResult {
#[allow(clippy::trivially_copy_pass_by_ref)]
fn check_char(b: &u8) -> bool {
(0x20 <= *b && *b <= 0x7f) || (*b == b'+')
}
let (rem, hdr) = ber_read_element_header(i)?;
let len = hdr.len.primitive()?;
if len > MAX_OBJECT_SIZE {
return Err(nom::Err::Error(BerError::InvalidLength));
}
match hdr.tag {
BerTag::UtcTime => {
// if we are in this function, the PrintableString could not be validated.
// Accept it without validating charset, because some tools do not respect the charset
// restrictions (for ex. they use '*' while explicingly disallowed)
let (rem, data) = take(len as usize)(rem)?;
if !data.iter().all(check_char) {
return Err(nom::Err::Error(BerError::BerValueError));
}
let s = std::str::from_utf8(data).map_err(|_| BerError::BerValueError)?;
let content = BerObjectContent::UTCTime(s);
let obj = DerObject::from_header_and_content(hdr, content);
Ok((rem, obj))
}
_ => Err(nom::Err::Error(BerError::InvalidTag)),
}
}
pub(crate) fn der_to_utctime(obj: DerObject) -> Result<ASN1Time, X509Error> {
if let BerObjectContent::UTCTime(s) = obj.content {
let dt = if s.ends_with('Z') {
// UTC
if s.len() == 11 {
// some implementations do not encode the number of seconds
// accept certificate even if date is not correct
Utc.datetime_from_str(s, "%y%m%d%H%MZ")
} else {
Utc.datetime_from_str(s, "%y%m%d%H%M%SZ")
}
} else {
DateTime::parse_from_str(s, "%y%m%d%H%M%S%z").map(|dt| dt.with_timezone(&Utc))
};
match dt {
Ok(mut tm) => {
if tm.year() < 50 {
tm = tm
.with_year(tm.year() + 100)
.ok_or(X509Error::InvalidDate)?;
}
// tm = tm.with_year(tm.year() + 1900).ok_or(X509Error::InvalidDate)?;
// eprintln!("date: {}", tm.rfc822());
Ok(ASN1Time::from_datetime_utc(tm))
}
Err(_e) => Err(X509Error::InvalidDate),
}
} else if let BerObjectContent::GeneralizedTime(s) = obj.content {
let dt = if s.ends_with('Z') {
// UTC
if s.len() == 11 {
// some implementations do not encode the number of seconds
// accept certificate even if date is not correct
Utc.datetime_from_str(s, "%Y%m%d%H%MZ")
} else {
Utc.datetime_from_str(s, "%Y%m%d%H%M%SZ")
}
} else {
DateTime::parse_from_str(s, "%Y%m%d%H%M%S%z").map(|dt| dt.with_timezone(&Utc))
};
dt.map(ASN1Time::from_datetime_utc)
.or(Err(X509Error::InvalidDate))
} else {
Err(X509Error::InvalidDate)
}
}
impl Add<Duration> for ASN1Time {
type Output = Option<ASN1Time>;
#[inline]
fn add(self, rhs: Duration) -> Option<ASN1Time> {
let secs = rhs.as_secs();
// u32::MAX is not supported in rust 1.34
const MAX_U32: u64 = 4_294_967_295;
if secs > MAX_U32 {
return None;
}
let duration = chrono::Duration::seconds(secs as i64);
let dt = self.0.checked_add_signed(duration)?;
Some(ASN1Time(dt))
}
}
impl Sub<ASN1Time> for ASN1Time {
type Output = Option<Duration>;
#[inline]
fn sub(self, rhs: ASN1Time) -> Option<Duration> {
self.0.signed_duration_since(rhs.0).to_std().ok()
}
}