use core::{fmt, marker::PhantomData, str::FromStr, time::Duration};
use der::asn1::{GeneralizedTime, UtcTime};
use der::{Choice, DateTime, DecodeValue, Encode, Header, Length, Reader, Sequence, ValueOrd};
#[cfg(feature = "std")]
use std::time::SystemTime;
use crate::certificate::{Profile, Rfc5280};
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Choice, Copy, Clone, Debug, Eq, PartialEq, ValueOrd)]
pub enum Time {
#[asn1(type = "UTCTime")]
UtcTime(UtcTime),
#[asn1(type = "GeneralizedTime")]
GeneralTime(GeneralizedTime),
}
impl Time {
pub const INFINITY: Time =
Time::GeneralTime(GeneralizedTime::from_date_time(DateTime::INFINITY));
pub fn to_unix_duration(self) -> Duration {
match self {
Time::UtcTime(t) => t.to_unix_duration(),
Time::GeneralTime(t) => t.to_unix_duration(),
}
}
pub fn to_date_time(&self) -> DateTime {
match self {
Time::UtcTime(t) => t.to_date_time(),
Time::GeneralTime(t) => t.to_date_time(),
}
}
#[cfg(feature = "std")]
pub fn to_system_time(&self) -> SystemTime {
match self {
Time::UtcTime(t) => t.to_system_time(),
Time::GeneralTime(t) => t.to_system_time(),
}
}
pub(crate) fn rfc5280_adjust_utc_time(&mut self) -> der::Result<()> {
if let Time::GeneralTime(t) = self {
let date = t.to_date_time();
if date.year() <= UtcTime::MAX_YEAR {
*self = Time::UtcTime(UtcTime::from_date_time(date)?);
}
}
Ok(())
}
#[cfg(feature = "std")]
pub fn now() -> der::Result<Self> {
SystemTime::now().try_into()
}
}
impl fmt::Display for Time {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_date_time())
}
}
impl From<UtcTime> for Time {
fn from(time: UtcTime) -> Time {
Time::UtcTime(time)
}
}
impl From<GeneralizedTime> for Time {
fn from(time: GeneralizedTime) -> Time {
Time::GeneralTime(time)
}
}
impl From<DateTime> for Time {
fn from(time: DateTime) -> Time {
UtcTime::from_date_time(time)
.map(Self::UtcTime)
.unwrap_or_else(|_e| Self::GeneralTime(GeneralizedTime::from_date_time(time)))
}
}
impl FromStr for Time {
type Err = der::Error;
fn from_str(input: &str) -> der::Result<Self> {
let datetime = DateTime::from_str(input)?;
Ok(Self::from(datetime))
}
}
#[cfg(feature = "std")]
impl From<Time> for SystemTime {
fn from(time: Time) -> SystemTime {
time.to_system_time()
}
}
#[cfg(feature = "std")]
impl From<&Time> for SystemTime {
fn from(time: &Time) -> SystemTime {
time.to_system_time()
}
}
#[cfg(feature = "std")]
impl TryFrom<SystemTime> for Time {
type Error = der::Error;
fn try_from(time: SystemTime) -> der::Result<Time> {
let datetime = DateTime::from_system_time(time)?;
Ok(datetime.into())
}
}
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueOrd)]
pub struct Validity<P: Profile = Rfc5280> {
pub not_before: Time,
pub not_after: Time,
_profile: PhantomData<P>,
}
impl<P> Validity<P>
where
P: Profile,
{
pub const fn new(not_before: Time, not_after: Time) -> Self {
Self {
not_before,
not_after,
_profile: PhantomData,
}
}
#[cfg(feature = "std")]
pub fn from_now(duration: Duration) -> der::Result<Self> {
let now = SystemTime::now();
let then = now + duration;
Ok(Self {
not_before: Time::try_from(now)?,
not_after: Time::try_from(then)?,
_profile: PhantomData,
})
}
#[cfg(all(feature = "std", feature = "hazmat"))]
pub fn infinity() -> der::Result<Self> {
let now = SystemTime::now();
Ok(Self {
not_before: Time::try_from(now)?,
not_after: Time::INFINITY,
_profile: PhantomData,
})
}
}
impl<'a, P: Profile> DecodeValue<'a> for Validity<P> {
type Error = der::Error;
fn decode_value<R: Reader<'a>>(reader: &mut R, _header: Header) -> der::Result<Self> {
let not_before = reader.decode()?;
let not_after = reader.decode()?;
let out = Self {
not_before,
not_after,
_profile: PhantomData,
};
Ok(out)
}
}
impl<P: Profile> ::der::EncodeValue for Validity<P> {
fn value_len(&self) -> ::der::Result<::der::Length> {
[
P::time_encoding(self.not_before)?.encoded_len()?,
P::time_encoding(self.not_after)?.encoded_len()?,
]
.into_iter()
.try_fold(Length::ZERO, |acc, len| acc + len)
}
fn encode_value(&self, writer: &mut impl ::der::Writer) -> ::der::Result<()> {
P::time_encoding(self.not_before)?.encode(writer)?;
P::time_encoding(self.not_after)?.encode(writer)?;
Ok(())
}
}
impl<P: Profile> Sequence<'_> for Validity<P> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_time() {
let time = Time::from_str("1970-01-01T00:00:00Z").expect("parse date from string");
assert!(matches!(time, Time::UtcTime(_)));
assert_eq!(alloc::format!("{}", time), "1970-01-01T00:00:00Z");
let time = Time::from_str("2020-01-01T00:00:00Z").expect("parse date from string");
assert!(matches!(time, Time::UtcTime(_)));
assert_eq!(alloc::format!("{}", time), "2020-01-01T00:00:00Z");
let time = Time::from_str("2049-12-31T23:59:59Z").expect("parse date from string");
assert!(matches!(time, Time::UtcTime(_)));
assert_eq!(alloc::format!("{}", time), "2049-12-31T23:59:59Z");
let time = Time::from_str("2050-01-01T00:00:00Z").expect("parse date from string");
assert!(matches!(time, Time::GeneralTime(_)));
assert_eq!(alloc::format!("{}", time), "2050-01-01T00:00:00Z");
}
}