Skip to content

Commit 34e8f9e

Browse files
xyzzyzdjc
authored andcommitted
Select CRLs by cRLNumber
Choose the newest authoritative CRL within the same issuer/IDP scope before checking revocation status.
1 parent e0f73bb commit 34e8f9e

3 files changed

Lines changed: 276 additions & 14 deletions

File tree

src/crl/mod.rs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,31 @@ impl RevocationOptions<'_> {
130130
return Ok(None);
131131
}
132132

133-
let crl = self
134-
.crls
135-
.iter()
136-
.find(|candidate_crl| candidate_crl.authoritative(path));
133+
let mut best_crl: Option<&CertRevocationList<'_>> = None;
134+
135+
for crl in self.crls.iter().copied() {
136+
if !crl.authoritative(path) {
137+
continue;
138+
}
139+
140+
let best_so_far = best_crl.get_or_insert(crl);
141+
142+
// Now check if `crl` supersedes `best_so_far`.
143+
// First, check if the scope is the same
144+
if crl.issuer() != best_so_far.issuer()
145+
|| crl.issuing_distribution_point() != best_so_far.issuing_distribution_point()
146+
{
147+
continue;
148+
}
149+
150+
// Then, check if it has newer CRL number.
151+
if crl.crl_number() > best_so_far.crl_number() {
152+
best_crl = Some(crl);
153+
}
154+
}
137155

138156
use UnknownStatusPolicy::*;
139-
let crl = match (crl, self.status_policy) {
157+
let crl = match (best_crl, self.status_policy) {
140158
(Some(crl), _) => crl,
141159
// If the policy allows unknown, return Ok(None) to indicate that the certificate
142160
// was not confirmed as CertNotRevoked, but that this isn't an error condition.

src/crl/types.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
use alloc::collections::BTreeMap;
33
#[cfg(feature = "alloc")]
44
use alloc::vec::Vec;
5+
use core::cmp::Ordering;
56
use core::fmt::Debug;
67

78
use pki_types::{SignatureVerificationAlgorithm, UnixTime};
@@ -156,6 +157,36 @@ impl CertRevocationList<'_> {
156157

157158
Ok(())
158159
}
160+
161+
pub(crate) fn crl_number(&self) -> CrlNumber<'_> {
162+
match self {
163+
#[cfg(feature = "alloc")]
164+
CertRevocationList::Owned(crl) => CrlNumber {
165+
value: &crl.crl_number,
166+
},
167+
CertRevocationList::Borrowed(crl) => CrlNumber {
168+
value: crl.crl_number.as_slice_less_safe(),
169+
},
170+
}
171+
}
172+
}
173+
174+
#[derive(Debug, Eq, PartialEq)]
175+
pub(crate) struct CrlNumber<'a> {
176+
/// Parsed nonnegative INTEGER bytes.
177+
/// It has no DER sign padding, and no unnecessary leading zeroes.
178+
value: &'a [u8],
179+
}
180+
181+
impl PartialOrd for CrlNumber<'_> {
182+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
183+
Some(
184+
self.value
185+
.len()
186+
.cmp(&other.value.len())
187+
.then_with(|| self.value.cmp(other.value)),
188+
)
189+
}
159190
}
160191

161192
/// Owned representation of a RFC 5280[^1] profile Certificate Revocation List (CRL).

tests/client_auth_revocation.rs

Lines changed: 222 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ use rcgen::{
2626
#[cfg(feature = "alloc")]
2727
use webpki::OwnedCertRevocationList;
2828
use webpki::{
29-
BorrowedCertRevocationList, CertRevocationList, ExtendedKeyUsage, PathBuilder,
30-
RevocationCheckDepth, RevocationOptions, RevocationOptionsBuilder, UnknownStatusPolicy,
31-
anchor_from_trusted_cert,
29+
BorrowedCertRevocationList, CertRevocationList, ExpirationPolicy, ExtendedKeyUsage,
30+
PathBuilder, RevocationCheckDepth, RevocationOptions, RevocationOptionsBuilder,
31+
UnknownStatusPolicy, anchor_from_trusted_cert,
3232
};
3333
use x509_parser::oid_registry;
3434

@@ -990,7 +990,7 @@ fn ee_revoked_expired_crl() {
990990
.unwrap()
991991
.with_depth(RevocationCheckDepth::EndEntity)
992992
.with_status_policy(UnknownStatusPolicy::Allow)
993-
.with_expiration_policy(webpki::ExpirationPolicy::Enforce)
993+
.with_expiration_policy(ExpirationPolicy::Enforce)
994994
.build();
995995

996996
assert!(matches!(
@@ -1004,6 +1004,180 @@ fn ee_revoked_expired_crl() {
10041004
));
10051005
}
10061006

1007+
#[test]
1008+
fn expired_crl_does_not_shadow_current_crl_when_enforcing_expiration() {
1009+
let chain = CertChain::no_key_usages("expired_first_enforce");
1010+
let expired_not_revoked = chain.int_a.generate_crl_with_updates_and_number(
1011+
SerialNumber::from(0xFFFF),
1012+
None,
1013+
0x1FED_F00D - 120,
1014+
0x1FED_F00D - 60,
1015+
1,
1016+
);
1017+
let current_not_revoked = chain.int_a.generate_crl_with_updates_and_number(
1018+
SerialNumber::from(0xFFFF),
1019+
None,
1020+
0x1FED_F00D - 60,
1021+
0x1FED_F00D + 60,
1022+
2,
1023+
);
1024+
1025+
let expired_not_revoked = CertRevocationList::Borrowed(
1026+
BorrowedCertRevocationList::from_der(&expired_not_revoked).unwrap(),
1027+
);
1028+
let current_not_revoked = CertRevocationList::Borrowed(
1029+
BorrowedCertRevocationList::from_der(&current_not_revoked).unwrap(),
1030+
);
1031+
let crls = &[&expired_not_revoked, &current_not_revoked];
1032+
1033+
let revocation = RevocationOptionsBuilder::new(crls)
1034+
.unwrap()
1035+
.with_depth(RevocationCheckDepth::EndEntity)
1036+
.with_status_policy(UnknownStatusPolicy::Allow)
1037+
.with_expiration_policy(ExpirationPolicy::Enforce)
1038+
.build();
1039+
1040+
assert_eq!(
1041+
check_cert(
1042+
chain.ee_cert.der(),
1043+
&chain.intermediates(),
1044+
chain.root.der(),
1045+
Some(revocation),
1046+
),
1047+
Ok(())
1048+
);
1049+
}
1050+
1051+
#[test]
1052+
fn expired_crl_does_not_shadow_newer_revocation_when_ignoring_expiration() {
1053+
let chain = CertChain::no_key_usages("expired_first_ignore");
1054+
let expired_not_revoked = chain.int_a.generate_crl_with_updates_and_number(
1055+
SerialNumber::from(0xFFFF),
1056+
None,
1057+
0x1FED_F00D - 120,
1058+
0x1FED_F00D - 60,
1059+
1,
1060+
);
1061+
let current_revoked = chain.int_a.generate_crl_with_updates_and_number(
1062+
chain.ee_serial.clone(),
1063+
None,
1064+
0x1FED_F00D - 60,
1065+
0x1FED_F00D + 60,
1066+
2,
1067+
);
1068+
1069+
let expired_not_revoked = CertRevocationList::Borrowed(
1070+
BorrowedCertRevocationList::from_der(&expired_not_revoked).unwrap(),
1071+
);
1072+
let current_revoked = CertRevocationList::Borrowed(
1073+
BorrowedCertRevocationList::from_der(&current_revoked).unwrap(),
1074+
);
1075+
let crls = &[&expired_not_revoked, &current_revoked];
1076+
1077+
let revocation = RevocationOptionsBuilder::new(crls)
1078+
.unwrap()
1079+
.with_depth(RevocationCheckDepth::EndEntity)
1080+
.with_status_policy(UnknownStatusPolicy::Allow)
1081+
.with_expiration_policy(ExpirationPolicy::Ignore)
1082+
.build();
1083+
1084+
assert_eq!(
1085+
check_cert(
1086+
chain.ee_cert.der(),
1087+
&chain.intermediates(),
1088+
chain.root.der(),
1089+
Some(revocation),
1090+
),
1091+
Err(webpki::Error::CertRevoked)
1092+
);
1093+
}
1094+
1095+
#[test]
1096+
fn crl_number_in_other_partition_does_not_shadow_revoked_partition() {
1097+
let chain = CertChain::with_crl_dps("partitioned_crl_order", vec![MATCHING_URI.to_string()]);
1098+
let other_partition_not_revoked = chain.int_a.generate_crl_with_updates_and_number(
1099+
SerialNumber::from(0xFFFF),
1100+
Some(idp(NON_MATCHING_URI)),
1101+
NOT_BEFORE_SECS,
1102+
NOT_AFTER_SECS,
1103+
100,
1104+
);
1105+
let revoked_partition = chain.int_a.generate_crl_with_updates_and_number(
1106+
chain.ee_serial.clone(),
1107+
Some(idp(MATCHING_URI)),
1108+
NOT_BEFORE_SECS,
1109+
NOT_AFTER_SECS,
1110+
1,
1111+
);
1112+
1113+
let other_partition_not_revoked = CertRevocationList::Borrowed(
1114+
BorrowedCertRevocationList::from_der(&other_partition_not_revoked).unwrap(),
1115+
);
1116+
let revoked_partition = CertRevocationList::Borrowed(
1117+
BorrowedCertRevocationList::from_der(&revoked_partition).unwrap(),
1118+
);
1119+
let crls = &[&other_partition_not_revoked, &revoked_partition];
1120+
1121+
let revocation = RevocationOptionsBuilder::new(crls)
1122+
.unwrap()
1123+
.with_depth(RevocationCheckDepth::EndEntity)
1124+
.with_status_policy(UnknownStatusPolicy::Allow)
1125+
.build();
1126+
1127+
assert_eq!(
1128+
check_cert(
1129+
chain.ee_cert.der(),
1130+
&chain.intermediates(),
1131+
chain.root.der(),
1132+
Some(revocation),
1133+
),
1134+
Err(webpki::Error::CertRevoked)
1135+
);
1136+
}
1137+
1138+
#[test]
1139+
fn crl_number_order_uses_integer_value_not_lexicographic_bytes() {
1140+
let chain = CertChain::no_key_usages("crl_number_order");
1141+
let crl_255_not_revoked = chain.int_a.generate_crl_with_updates_and_number(
1142+
SerialNumber::from(0xFFFF),
1143+
None,
1144+
NOT_BEFORE_SECS,
1145+
NOT_AFTER_SECS,
1146+
0xFF,
1147+
);
1148+
let crl_256_revoked = chain.int_a.generate_crl_with_updates_and_number(
1149+
chain.ee_serial.clone(),
1150+
None,
1151+
NOT_BEFORE_SECS,
1152+
NOT_AFTER_SECS,
1153+
0x0100,
1154+
);
1155+
1156+
let crl_255_not_revoked = CertRevocationList::Borrowed(
1157+
BorrowedCertRevocationList::from_der(&crl_255_not_revoked).unwrap(),
1158+
);
1159+
let crl_256_revoked = CertRevocationList::Borrowed(
1160+
BorrowedCertRevocationList::from_der(&crl_256_revoked).unwrap(),
1161+
);
1162+
let crls = &[&crl_255_not_revoked, &crl_256_revoked];
1163+
1164+
let revocation = RevocationOptionsBuilder::new(crls)
1165+
.unwrap()
1166+
.with_depth(RevocationCheckDepth::EndEntity)
1167+
.with_status_policy(UnknownStatusPolicy::Allow)
1168+
.build();
1169+
1170+
assert_eq!(
1171+
check_cert(
1172+
chain.ee_cert.der(),
1173+
&chain.intermediates(),
1174+
chain.root.der(),
1175+
Some(revocation),
1176+
),
1177+
Err(webpki::Error::CertRevoked)
1178+
);
1179+
}
1180+
10071181
// Cert has two normal DPs; first doesn't match IDP, second does.
10081182
// Proves the outer cert_dp loop continues to the next DP when URIs don't match.
10091183
#[test]
@@ -1312,6 +1486,26 @@ impl Intermediate {
13121486
.into()
13131487
}
13141488

1489+
fn generate_crl_with_updates_and_number(
1490+
&self,
1491+
serial: SerialNumber,
1492+
issuing_dp: Option<CrlIssuingDistributionPoint>,
1493+
this_update_secs: u64,
1494+
next_update_secs: u64,
1495+
crl_number: u64,
1496+
) -> CertificateRevocationListDer<'static> {
1497+
crl_params_with_updates_and_number(
1498+
serial,
1499+
issuing_dp,
1500+
this_update_secs,
1501+
next_update_secs,
1502+
crl_number,
1503+
)
1504+
.signed_by(&self.issuer)
1505+
.unwrap()
1506+
.into()
1507+
}
1508+
13151509
fn generate_crl_with_crl_sign(
13161510
&self,
13171511
serial: SerialNumber,
@@ -1336,12 +1530,27 @@ fn crl_params(
13361530
serial_number: SerialNumber,
13371531
issuing_distribution_point: Option<CrlIssuingDistributionPoint>,
13381532
not_after_secs: Option<u64>,
1533+
) -> CertificateRevocationListParams {
1534+
crl_params_with_updates_and_number(
1535+
serial_number,
1536+
issuing_distribution_point,
1537+
NOT_BEFORE_SECS,
1538+
not_after_secs.unwrap_or(NOT_AFTER_SECS),
1539+
1234,
1540+
)
1541+
}
1542+
1543+
fn crl_params_with_updates_and_number(
1544+
serial_number: SerialNumber,
1545+
issuing_distribution_point: Option<CrlIssuingDistributionPoint>,
1546+
this_update_secs: u64,
1547+
next_update_secs: u64,
1548+
crl_number: u64,
13391549
) -> CertificateRevocationListParams {
13401550
CertificateRevocationListParams {
1341-
this_update: date_time_ymd(1970, 1, 1) + Duration::from_secs(NOT_BEFORE_SECS),
1342-
next_update: date_time_ymd(1970, 1, 1)
1343-
+ Duration::from_secs(not_after_secs.unwrap_or(NOT_AFTER_SECS)),
1344-
crl_number: SerialNumber::from(1234),
1551+
this_update: date_time_ymd(1970, 1, 1) + Duration::from_secs(this_update_secs),
1552+
next_update: date_time_ymd(1970, 1, 1) + Duration::from_secs(next_update_secs),
1553+
crl_number: SerialNumber::from(crl_number),
13451554
issuing_distribution_point,
13461555
key_identifier_method: KeyIdMethod::Sha256,
13471556
revoked_certs: vec![RevokedCertParams {
@@ -1516,9 +1725,13 @@ fn build_crl_dps_extension(dps: &[Vec<u8>]) -> Vec<u8> {
15161725
}
15171726

15181727
fn matching_idp() -> CrlIssuingDistributionPoint {
1728+
idp(MATCHING_URI)
1729+
}
1730+
1731+
fn idp(uri: &str) -> CrlIssuingDistributionPoint {
15191732
CrlIssuingDistributionPoint {
15201733
distribution_point: CrlDistributionPoint {
1521-
uris: vec![MATCHING_URI.to_string()],
1734+
uris: vec![uri.to_string()],
15221735
},
15231736
scope: None,
15241737
}

0 commit comments

Comments
 (0)