Skip to content

der: order UTCTime in SET OF by its encoding - #2448

Merged
tarcieri merged 2 commits into
RustCrypto:masterfrom
yuxi-liu-wired:fix/der-utctime-value-ord
Oct 5, 2026
Merged

tarcieri merged 2 commits into
RustCrypto:masterfrom
yuxi-liu-wired:fix/der-utctime-value-ord

Conversation

@yuxi-liu-wired

Copy link
Copy Markdown
Contributor

UtcTime uses its chronological Ord as ValueOrd (impl OrdIsValueOrd for UtcTime). X.690 11.6 orders the components of a DER SET OF by their encodings. A UTCTime encodes the year as two digits: under RFC 5280, 50..99 is 19xx and 00..49 is 20xx. So the two orders disagree across 2000:

1999-12-31 23:59:59  ->  17 0d "991231235959Z"
2000-01-01 00:00:00  ->  17 0d "000101000000Z"   <- first in DER

SetOfVec<UtcTime> therefore encodes such a set out of DER order, and SetOf/SetOfRef reject the correctly ordered encoding.

This PR implements ValueOrd for UtcTime by comparing the encoded YYMMDDHHMMSSZ value. Ord stays chronological. No other OrdIsValueOrd type has this mismatch: GeneralizedTime's four-digit year sorts the same in both orders, and every pair of ObjectIdentifier, Int/Uint, string, BitString and OctetString values checked through der_cmp agrees with octet order.

Tests:

  • utc_time::tests::value_ord_is_encoding_order: value_cmp/der_cmp put 2000 before 1999, and Ord still puts 1999 first.
  • der/tests/set_of.rs utctime_order: a SetOfVec<UtcTime> holding both dates encodes 2000 first. It fails on master.

This is independent of the SET OF tag-order fix (der: order SET OF components by their encoded octets): the two touch different code and merge without conflict.

This PR was produced by AI agents (Claude) during differential fuzzing of der/x509-cert. It was found by checking every OrdIsValueOrd type's der_cmp against the byte order of its encodings.

`UtcTime` used its chronological `Ord` as `ValueOrd` (`OrdIsValueOrd`).
X.690 11.6 orders `SET OF` components by their encodings, and a UTCTime
encodes the year as two digits (RFC 5280: 50..99 = 19xx, 00..49 = 20xx),
so the two orders disagree across 2000:

    1999-12-31 23:59:59  ->  "991231235959Z"
    2000-01-01 00:00:00  ->  "000101000000Z"   (sorts first in DER)

`SetOfVec<UtcTime>` therefore encodes non-DER output for such sets, and
`SetOf`/`SetOfRef` reject the DER encoding.

Implement `ValueOrd` for `UtcTime` by comparing the encoded value. `Ord`
stays chronological.
Comment thread der/src/asn1/utc_time.rs Outdated
Comment thread der/src/asn1/utc_time.rs
Comment on lines +180 to +193
// 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)))

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

@tarcieri
tarcieri merged commit 7be020f into RustCrypto:master Oct 5, 2026
117 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants