1 //! ASN.1 `GeneralizedTime` support. 2 #![cfg_attr(feature = "arbitrary", allow(clippy::integer_arithmetic))] 3 4 use crate::{ 5 datetime::{self, DateTime}, 6 ord::OrdIsValueOrd, 7 DecodeValue, EncodeValue, ErrorKind, FixedTag, Header, Length, Reader, Result, Tag, Writer, 8 }; 9 use core::time::Duration; 10 11 #[cfg(feature = "std")] 12 use { 13 crate::{asn1::AnyRef, Error}, 14 std::time::SystemTime, 15 }; 16 17 #[cfg(feature = "time")] 18 use time::PrimitiveDateTime; 19 20 /// ASN.1 `GeneralizedTime` type. 21 /// 22 /// This type implements the validity requirements specified in 23 /// [RFC 5280 Section 4.1.2.5.2][1], namely: 24 /// 25 /// > For the purposes of this profile, GeneralizedTime values MUST be 26 /// > expressed in Greenwich Mean Time (Zulu) and MUST include seconds 27 /// > (i.e., times are `YYYYMMDDHHMMSSZ`), even where the number of seconds 28 /// > is zero. GeneralizedTime values MUST NOT include fractional seconds. 29 /// 30 /// [1]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5.2 31 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] 32 #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] 33 pub struct GeneralizedTime(DateTime); 34 35 impl GeneralizedTime { 36 /// Length of an RFC 5280-flavored ASN.1 DER-encoded [`GeneralizedTime`]. 37 const LENGTH: usize = 15; 38 39 /// Create a [`GeneralizedTime`] from a [`DateTime`]. from_date_time(datetime: DateTime) -> Self40 pub const fn from_date_time(datetime: DateTime) -> Self { 41 Self(datetime) 42 } 43 44 /// Convert this [`GeneralizedTime`] into a [`DateTime`]. to_date_time(&self) -> DateTime45 pub fn to_date_time(&self) -> DateTime { 46 self.0 47 } 48 49 /// Create a new [`GeneralizedTime`] given a [`Duration`] since `UNIX_EPOCH` 50 /// (a.k.a. "Unix time") from_unix_duration(unix_duration: Duration) -> Result<Self>51 pub fn from_unix_duration(unix_duration: Duration) -> Result<Self> { 52 DateTime::from_unix_duration(unix_duration) 53 .map(Into::into) 54 .map_err(|_| Self::TAG.value_error()) 55 } 56 57 /// Get the duration of this timestamp since `UNIX_EPOCH`. to_unix_duration(&self) -> Duration58 pub fn to_unix_duration(&self) -> Duration { 59 self.0.unix_duration() 60 } 61 62 /// Instantiate from [`SystemTime`]. 63 #[cfg(feature = "std")] from_system_time(time: SystemTime) -> Result<Self>64 pub fn from_system_time(time: SystemTime) -> Result<Self> { 65 DateTime::try_from(time) 66 .map(Into::into) 67 .map_err(|_| Self::TAG.value_error()) 68 } 69 70 /// Convert to [`SystemTime`]. 71 #[cfg(feature = "std")] to_system_time(&self) -> SystemTime72 pub fn to_system_time(&self) -> SystemTime { 73 self.0.to_system_time() 74 } 75 } 76 77 impl_any_conversions!(GeneralizedTime); 78 79 impl<'a> DecodeValue<'a> for GeneralizedTime { decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self>80 fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> { 81 if Self::LENGTH != usize::try_from(header.length)? { 82 return Err(Self::TAG.value_error()); 83 } 84 85 let mut bytes = [0u8; Self::LENGTH]; 86 reader.read_into(&mut bytes)?; 87 88 match bytes { 89 // RFC 5280 requires mandatory seconds and Z-normalized time zone 90 [y1, y2, y3, y4, mon1, mon2, day1, day2, hour1, hour2, min1, min2, sec1, sec2, b'Z'] => { 91 let year = u16::from(datetime::decode_decimal(Self::TAG, y1, y2)?) 92 .checked_mul(100) 93 .and_then(|y| { 94 y.checked_add(datetime::decode_decimal(Self::TAG, y3, y4).ok()?.into()) 95 }) 96 .ok_or(ErrorKind::DateTime)?; 97 let month = datetime::decode_decimal(Self::TAG, mon1, mon2)?; 98 let day = datetime::decode_decimal(Self::TAG, day1, day2)?; 99 let hour = datetime::decode_decimal(Self::TAG, hour1, hour2)?; 100 let minute = datetime::decode_decimal(Self::TAG, min1, min2)?; 101 let second = datetime::decode_decimal(Self::TAG, sec1, sec2)?; 102 103 DateTime::new(year, month, day, hour, minute, second) 104 .map_err(|_| Self::TAG.value_error()) 105 .and_then(|dt| Self::from_unix_duration(dt.unix_duration())) 106 } 107 _ => Err(Self::TAG.value_error()), 108 } 109 } 110 } 111 112 impl EncodeValue for GeneralizedTime { value_len(&self) -> Result<Length>113 fn value_len(&self) -> Result<Length> { 114 Self::LENGTH.try_into() 115 } 116 encode_value(&self, writer: &mut impl Writer) -> Result<()>117 fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { 118 let year_hi = u8::try_from(self.0.year() / 100)?; 119 let year_lo = u8::try_from(self.0.year() % 100)?; 120 121 datetime::encode_decimal(writer, Self::TAG, year_hi)?; 122 datetime::encode_decimal(writer, Self::TAG, year_lo)?; 123 datetime::encode_decimal(writer, Self::TAG, self.0.month())?; 124 datetime::encode_decimal(writer, Self::TAG, self.0.day())?; 125 datetime::encode_decimal(writer, Self::TAG, self.0.hour())?; 126 datetime::encode_decimal(writer, Self::TAG, self.0.minutes())?; 127 datetime::encode_decimal(writer, Self::TAG, self.0.seconds())?; 128 writer.write_byte(b'Z') 129 } 130 } 131 132 impl FixedTag for GeneralizedTime { 133 const TAG: Tag = Tag::GeneralizedTime; 134 } 135 136 impl OrdIsValueOrd for GeneralizedTime {} 137 138 impl From<&GeneralizedTime> for GeneralizedTime { from(value: &GeneralizedTime) -> GeneralizedTime139 fn from(value: &GeneralizedTime) -> GeneralizedTime { 140 *value 141 } 142 } 143 144 impl From<GeneralizedTime> for DateTime { from(utc_time: GeneralizedTime) -> DateTime145 fn from(utc_time: GeneralizedTime) -> DateTime { 146 utc_time.0 147 } 148 } 149 150 impl From<&GeneralizedTime> for DateTime { from(utc_time: &GeneralizedTime) -> DateTime151 fn from(utc_time: &GeneralizedTime) -> DateTime { 152 utc_time.0 153 } 154 } 155 156 impl From<DateTime> for GeneralizedTime { from(datetime: DateTime) -> Self157 fn from(datetime: DateTime) -> Self { 158 Self::from_date_time(datetime) 159 } 160 } 161 162 impl From<&DateTime> for GeneralizedTime { from(datetime: &DateTime) -> Self163 fn from(datetime: &DateTime) -> Self { 164 Self::from_date_time(*datetime) 165 } 166 } 167 168 impl<'a> DecodeValue<'a> for DateTime { decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self>169 fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> { 170 Ok(GeneralizedTime::decode_value(reader, header)?.into()) 171 } 172 } 173 174 impl EncodeValue for DateTime { value_len(&self) -> Result<Length>175 fn value_len(&self) -> Result<Length> { 176 GeneralizedTime::from(self).value_len() 177 } 178 encode_value(&self, writer: &mut impl Writer) -> Result<()>179 fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { 180 GeneralizedTime::from(self).encode_value(writer) 181 } 182 } 183 184 impl FixedTag for DateTime { 185 const TAG: Tag = Tag::GeneralizedTime; 186 } 187 188 impl OrdIsValueOrd for DateTime {} 189 190 #[cfg(feature = "std")] 191 impl<'a> DecodeValue<'a> for SystemTime { decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self>192 fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> { 193 Ok(GeneralizedTime::decode_value(reader, header)?.into()) 194 } 195 } 196 197 #[cfg(feature = "std")] 198 impl EncodeValue for SystemTime { value_len(&self) -> Result<Length>199 fn value_len(&self) -> Result<Length> { 200 GeneralizedTime::try_from(self)?.value_len() 201 } 202 encode_value(&self, writer: &mut impl Writer) -> Result<()>203 fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { 204 GeneralizedTime::try_from(self)?.encode_value(writer) 205 } 206 } 207 208 #[cfg(feature = "std")] 209 impl From<GeneralizedTime> for SystemTime { from(time: GeneralizedTime) -> SystemTime210 fn from(time: GeneralizedTime) -> SystemTime { 211 time.to_system_time() 212 } 213 } 214 215 #[cfg(feature = "std")] 216 impl From<&GeneralizedTime> for SystemTime { from(time: &GeneralizedTime) -> SystemTime217 fn from(time: &GeneralizedTime) -> SystemTime { 218 time.to_system_time() 219 } 220 } 221 222 #[cfg(feature = "std")] 223 impl TryFrom<SystemTime> for GeneralizedTime { 224 type Error = Error; 225 try_from(time: SystemTime) -> Result<GeneralizedTime>226 fn try_from(time: SystemTime) -> Result<GeneralizedTime> { 227 GeneralizedTime::from_system_time(time) 228 } 229 } 230 231 #[cfg(feature = "std")] 232 impl TryFrom<&SystemTime> for GeneralizedTime { 233 type Error = Error; 234 try_from(time: &SystemTime) -> Result<GeneralizedTime>235 fn try_from(time: &SystemTime) -> Result<GeneralizedTime> { 236 GeneralizedTime::from_system_time(*time) 237 } 238 } 239 240 #[cfg(feature = "std")] 241 impl<'a> TryFrom<AnyRef<'a>> for SystemTime { 242 type Error = Error; 243 try_from(any: AnyRef<'a>) -> Result<SystemTime>244 fn try_from(any: AnyRef<'a>) -> Result<SystemTime> { 245 GeneralizedTime::try_from(any).map(|s| s.to_system_time()) 246 } 247 } 248 249 #[cfg(feature = "std")] 250 impl FixedTag for SystemTime { 251 const TAG: Tag = Tag::GeneralizedTime; 252 } 253 254 #[cfg(feature = "std")] 255 impl OrdIsValueOrd for SystemTime {} 256 257 #[cfg(feature = "time")] 258 impl<'a> DecodeValue<'a> for PrimitiveDateTime { decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self>259 fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> { 260 GeneralizedTime::decode_value(reader, header)?.try_into() 261 } 262 } 263 264 #[cfg(feature = "time")] 265 impl EncodeValue for PrimitiveDateTime { value_len(&self) -> Result<Length>266 fn value_len(&self) -> Result<Length> { 267 GeneralizedTime::try_from(self)?.value_len() 268 } 269 encode_value(&self, writer: &mut impl Writer) -> Result<()>270 fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { 271 GeneralizedTime::try_from(self)?.encode_value(writer) 272 } 273 } 274 275 #[cfg(feature = "time")] 276 impl FixedTag for PrimitiveDateTime { 277 const TAG: Tag = Tag::GeneralizedTime; 278 } 279 280 #[cfg(feature = "time")] 281 impl OrdIsValueOrd for PrimitiveDateTime {} 282 283 #[cfg(feature = "time")] 284 impl TryFrom<PrimitiveDateTime> for GeneralizedTime { 285 type Error = Error; 286 try_from(time: PrimitiveDateTime) -> Result<GeneralizedTime>287 fn try_from(time: PrimitiveDateTime) -> Result<GeneralizedTime> { 288 Ok(GeneralizedTime::from_date_time(DateTime::try_from(time)?)) 289 } 290 } 291 292 #[cfg(feature = "time")] 293 impl TryFrom<&PrimitiveDateTime> for GeneralizedTime { 294 type Error = Error; 295 try_from(time: &PrimitiveDateTime) -> Result<GeneralizedTime>296 fn try_from(time: &PrimitiveDateTime) -> Result<GeneralizedTime> { 297 Self::try_from(*time) 298 } 299 } 300 301 #[cfg(feature = "time")] 302 impl TryFrom<GeneralizedTime> for PrimitiveDateTime { 303 type Error = Error; 304 try_from(time: GeneralizedTime) -> Result<PrimitiveDateTime>305 fn try_from(time: GeneralizedTime) -> Result<PrimitiveDateTime> { 306 time.to_date_time().try_into() 307 } 308 } 309 310 #[cfg(test)] 311 mod tests { 312 use super::GeneralizedTime; 313 use crate::{Decode, Encode, SliceWriter}; 314 use hex_literal::hex; 315 316 #[test] round_trip()317 fn round_trip() { 318 let example_bytes = hex!("18 0f 31 39 39 31 30 35 30 36 32 33 34 35 34 30 5a"); 319 let utc_time = GeneralizedTime::from_der(&example_bytes).unwrap(); 320 assert_eq!(utc_time.to_unix_duration().as_secs(), 673573540); 321 322 let mut buf = [0u8; 128]; 323 let mut encoder = SliceWriter::new(&mut buf); 324 utc_time.encode(&mut encoder).unwrap(); 325 assert_eq!(example_bytes, encoder.finish().unwrap()); 326 } 327 } 328