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