Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 36 additions & 5 deletions der/src/asn1/utc_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

use crate::{
DecodeValue, EncodeValue, Error, ErrorKind, FixedTag, Header, Length, Reader, Result, Tag,
Writer,
ValueOrd, Writer,
datetime::{self, DateTime},
ord::OrdIsValueOrd,
};
use core::time::Duration;
use core::{cmp::Ordering, time::Duration};

#[cfg(feature = "std")]
use std::time::SystemTime;
Expand Down Expand Up @@ -173,7 +172,27 @@ impl FixedTag for UtcTime {
const TAG: Tag = Tag::UtcTime;
}

impl OrdIsValueOrd for UtcTime {}
/// Compares the `YYMMDDHHMMSSZ` encodings (X.690 11.6 orders `SET OF` components by their
/// encodings), which differs from [`Ord`] (chronological) across 2000: `000101000000Z`
/// (2000) sorts before `991231235959Z` (1999).
impl ValueOrd for UtcTime {
fn value_cmp(&self, other: &Self) -> Result<Ordering> {
// Every field is encoded as two decimal digits, so comparing the fields in order is
// comparing the encodings.
let fields = |t: &Self| {
let dt = &t.0;
(
dt.year() % 100,
dt.month(),
dt.day(),
dt.hour(),
dt.minutes(),
dt.seconds(),
)
};
Ok(fields(self).cmp(&fields(other)))
Comment on lines +180 to +193

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a little bit weird way of implementing this (i.e. using an anonymous function) versus just writing out the comparisons, but the fix seems correct so I'll go ahead and merge it

}
}

impl From<&UtcTime> for UtcTime {
fn from(value: &UtcTime) -> UtcTime {
Expand Down Expand Up @@ -255,9 +274,21 @@ impl<'a> arbitrary::Arbitrary<'a> for UtcTime {
#[allow(clippy::unwrap_used)]
mod tests {
use super::UtcTime;
use crate::{Decode, Encode, SliceWriter};
use crate::{Decode, DerOrd, Encode, SliceWriter, ValueOrd};
use core::cmp::Ordering;
use hex_literal::hex;

#[test]
fn value_ord_is_encoding_order() {
// 1999-12-31 encodes as "99...", 2000-01-01 as "00...": X.690 11.6 puts 2000 first.
let y1999 = UtcTime::from_der(&hex!("170d 3939313233313233353935395a")).unwrap();
let y2000 = UtcTime::from_der(&hex!("170d 3030303130313030303030305a")).unwrap();
assert!(y1999 < y2000); // `Ord` stays chronological
assert_eq!(y2000.value_cmp(&y1999), Ok(Ordering::Less));
assert_eq!(y2000.der_cmp(&y1999), Ok(Ordering::Less));
assert_eq!(y1999.value_cmp(&y1999), Ok(Ordering::Equal));
}

#[test]
fn round_trip_vector() {
let example_bytes = hex!("17 0d 39 31 30 35 30 36 32 33 34 35 34 30 5a");
Expand Down
24 changes: 24 additions & 0 deletions der/tests/set_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,27 @@ mod ordering {
assert_eq!(ObjectIdentifier::new("2.5.4.3").unwrap(), attr1.oid);
}
}

/// X.690 11.6: `SET OF UTCTime` is ordered by the `YYMMDDHHMMSSZ` encodings, so a 20xx time
/// (`00..49`) sorts before a 19xx time (`50..99`).
mod utctime_order {
use der::{
Decode, Encode,
asn1::{SetOfVec, UtcTime},
};
use hex_literal::hex;

const Y1999: [u8; 15] = hex!("170d 3939313233313233353935395a");
const Y2000: [u8; 15] = hex!("170d 3030303130313030303030305a");

#[test]
fn setofvec_utctime_encodes_in_octet_order() {
let mut set = SetOfVec::new();
set.insert(UtcTime::from_der(&Y1999).unwrap()).unwrap();
set.insert(UtcTime::from_der(&Y2000).unwrap()).unwrap();
let mut want = hex!("311e").to_vec();
want.extend_from_slice(&Y2000);
want.extend_from_slice(&Y1999);
assert_eq!(set.to_der().unwrap(), want);
}
}
Loading