Skip to content

Commit

Permalink
ber: Split and improve UTCTime type (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicceboy authored Aug 28, 2023
1 parent db2ab2f commit 9dc64bd
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 23 deletions.
46 changes: 45 additions & 1 deletion src/ber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ mod tests {
use alloc::borrow::ToOwned;
use alloc::vec;
use alloc::vec::Vec;
use chrono::{DateTime, FixedOffset, NaiveDate, Utc};

use crate::types::*;

Expand Down Expand Up @@ -215,7 +216,6 @@ mod tests {
}
#[test]
fn test_generalized_time() {
use chrono::{DateTime, FixedOffset, NaiveDate, Utc};
// "20801009130005.342Z"
let offset = chrono::FixedOffset::east_opt(0).unwrap();
let dt = NaiveDate::from_ymd_opt(2080, 10, 9)
Expand Down Expand Up @@ -300,4 +300,48 @@ mod tests {
assert!(result.is_ok());
assert_eq!(dt1, result.unwrap());
}
#[test]
fn test_utc_time() {
// "180122132900Z"
round_trip!(
ber,
UtcTime,
UtcTime::from(
NaiveDate::from_ymd_opt(2018, 1, 22)
.unwrap()
.and_hms_opt(13, 29, 0)
.unwrap()
.and_utc()
),
&[
0x17, 0x0d, 0x31, 0x38, 0x30, 0x31, 0x32, 0x32, 0x31, 0x33, 0x32, 0x39, 0x30, 0x30,
0x5a
]
);
// "230122130000-0500" - converts to canonical form "230122180000Z"
let offset = FixedOffset::east_opt(-3600 * 5).unwrap();
let dt1 = UtcTime::from(DateTime::<FixedOffset>::from_local(
NaiveDate::from_ymd_opt(2023, 1, 22)
.unwrap()
.and_hms_opt(13, 0, 0)
.unwrap(),
offset,
));
round_trip!(
ber,
UtcTime,
dt1,
&[
0x17, 0x0d, 0x32, 0x33, 0x30, 0x31, 0x32, 0x32, 0x31, 0x38, 0x30, 0x30, 0x30, 0x30,
0x5a
]
);
// "230122130000-0500" as bytes
let data = [
23, 17, 50, 51, 48, 49, 50, 50, 49, 51, 48, 48, 48, 48, 45, 48, 53, 48, 48,
];
let result = crate::ber::decode::<crate::types::UtcTime>(&data);
assert!(result.is_ok());
assert_eq!(dt1, result.unwrap());
}
}
82 changes: 65 additions & 17 deletions src/ber/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,57 @@ impl<'input> Decoder<'input> {
Err(Error::InvalidDate)
};
}
/// Parse any UTCTime string, can be any from ASN.1 definition
/// TODO, move to type itself?
pub fn parse_any_utc_time_string(
string: alloc::string::String,
) -> Result<types::UtcTime, Error> {
// When compared to GeneralizedTime, UTC time has no fractions.
let len = string.len();
// Largest string, e.g. "820102070000-0500".len() == 17
if len > 17 {
return Err(Error::InvalidDate);
}
let format = if string.contains('Z') {
if len == 11 {
"%y%m%d%H%MZ"
} else {
"%y%m%d%H%M%SZ"
}
} else if len == 15 {
"%y%m%d%H%M%z"
} else {
"%y%m%d%H%M%S%z"
};
match len {
11 | 13 => {
let naive = NaiveDateTime::parse_from_str(&string, format)
.map_err(|_| Error::InvalidDate)?;
Ok(DateTime::<Utc>::from_utc(naive, Utc).into())
}
15 | 17 => Ok(DateTime::parse_from_str(&string, format)
.map_err(|_| Error::InvalidDate)?
.into()),
_ => Err(Error::InvalidDate),
}
}

/// Enforce CER/DER restrictions defined in Section 11.8, strictly raise error on non-compliant
pub fn parse_canonical_utc_time_string(
string: alloc::string::String,
) -> Result<types::UtcTime, Error> {
let len = string.len();
return if string.ends_with('Z') {
let naive = match len {
13 => NaiveDateTime::parse_from_str(&string, "%y%m%d%H%M%SZ")
.map_err(|_| Error::InvalidDate)?,
_ => Err(Error::InvalidDate)?,
};
Ok(DateTime::<Utc>::from_utc(naive, Utc).into())
} else {
Err(Error::InvalidDate)
};
}
}

impl<'input> crate::Decoder for Decoder<'input> {
Expand Down Expand Up @@ -481,24 +532,12 @@ impl<'input> crate::Decoder for Decoder<'input> {

fn decode_utc_time(&mut self, tag: Tag) -> Result<types::UtcTime> {
// Reference https://obj-sys.com/asn1tutorial/node15.html
// FIXME - should this be DateTime<UTC> rather than NaiveDateTime ?
let string = self.decode_utf8_string(tag, <_>::default())?;
let format = if string.contains('Z') {
if string.len() == 11 {
"%y%m%d%H%MZ"
} else {
"%y%m%d%H%M%SZ"
}
} else if string.len() == 15 {
"%y%m%d%H%M%z"
if self.config.encoding_rules.is_ber() {
Self::parse_any_utc_time_string(string)
} else {
"%y%m%d%H%M%S%z"
};

chrono::NaiveDateTime::parse_from_str(&string, format)
.ok()
.context(error::InvalidDateSnafu)
.map(|date| types::UtcTime::from_utc(date, chrono::Utc))
Self::parse_canonical_utc_time_string(string)
}
}

fn decode_sequence_of<D: Decode>(
Expand Down Expand Up @@ -767,22 +806,31 @@ mod tests {
let time =
crate::types::GeneralizedTime::parse_from_str("991231235959+0000", "%y%m%d%H%M%S%z")
.unwrap();
// 991231235959Z
let has_z = &[
0x17, 0x0D, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39,
0x5A,
];
// 991231235959+0000
let has_noz = &[
0x17, 0x11, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39,
0x2B, 0x30, 0x32, 0x30, 0x30,
0x2B, 0x30, 0x30, 0x30, 0x30,
];
assert_eq!(
time,
decode::<chrono::DateTime::<chrono::Utc>>(has_z).unwrap()
);

assert_eq!(
time,
crate::der::decode::<crate::types::UtcTime>(has_z).unwrap()
);

assert_eq!(
time,
decode::<chrono::DateTime::<chrono::Utc>>(has_noz).unwrap()
);
assert!(crate::der::decode::<crate::types::UtcTime>(has_noz).is_err());
}

#[test]
Expand Down
17 changes: 12 additions & 5 deletions src/ber/enc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,17 @@ impl Encoder {
string.push('Z');
string.into_bytes()
}

#[must_use]
/// Canonical byte presentation for CER/DER UTCTime as defined in X.690 section 11.8.
/// Also used for BER on this crate.
pub fn datetime_to_canonical_utc_time_bytes(value: &chrono::DateTime<chrono::Utc>) -> Vec<u8> {
value
.naive_utc()
.format("%y%m%d%H%M%SZ")
.to_string()
.into_bytes()
}
}

impl crate::Encoder for Encoder {
Expand Down Expand Up @@ -456,11 +467,7 @@ impl crate::Encoder for Encoder {
) -> Result<Self::Ok, Self::Error> {
self.encode_primitive(
tag,
value
.naive_utc()
.format("%y%m%d%H%M%SZ")
.to_string()
.as_bytes(),
Self::datetime_to_canonical_utc_time_bytes(value).as_slice(),
);

Ok(())
Expand Down

0 comments on commit 9dc64bd

Please sign in to comment.