webpki/crl/types.rs
1#[cfg(feature = "alloc")]
2use alloc::collections::BTreeMap;
3#[cfg(feature = "alloc")]
4use alloc::vec::Vec;
5use core::fmt::Debug;
6
7use pki_types::{SignatureVerificationAlgorithm, UnixTime};
8
9use crate::cert::lenient_certificate_serial_number;
10use crate::crl::crl_signature_err;
11use crate::der::{self, CONSTRUCTED, CONTEXT_SPECIFIC, DerIterator, FromDer, Tag};
12use crate::error::{DerTypeId, Error};
13use crate::public_values_eq;
14use crate::signed_data::{self, SignedData};
15use crate::subject_name::GeneralName;
16use crate::verify_cert::{Budget, PathNode, Role};
17use crate::x509::{
18 DistributionPointName, Extension, UnknownExtensionPolicy, remember_extension,
19 set_extension_once,
20};
21
22/// A RFC 5280[^1] profile Certificate Revocation List (CRL).
23///
24/// May be either an owned, or a borrowed representation.
25///
26/// [^1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
27#[derive(Debug)]
28pub enum CertRevocationList<'a> {
29 /// An owned representation of a CRL.
30 #[cfg(feature = "alloc")]
31 Owned(OwnedCertRevocationList),
32 /// A borrowed representation of a CRL.
33 Borrowed(BorrowedCertRevocationList<'a>),
34}
35
36#[cfg(feature = "alloc")]
37impl From<OwnedCertRevocationList> for CertRevocationList<'_> {
38 fn from(crl: OwnedCertRevocationList) -> Self {
39 Self::Owned(crl)
40 }
41}
42
43impl<'a> From<BorrowedCertRevocationList<'a>> for CertRevocationList<'a> {
44 fn from(crl: BorrowedCertRevocationList<'a>) -> Self {
45 Self::Borrowed(crl)
46 }
47}
48
49impl CertRevocationList<'_> {
50 /// Return the DER encoded issuer of the CRL.
51 pub fn issuer(&self) -> &[u8] {
52 match self {
53 #[cfg(feature = "alloc")]
54 CertRevocationList::Owned(crl) => crl.issuer.as_ref(),
55 CertRevocationList::Borrowed(crl) => crl.issuer.as_slice_less_safe(),
56 }
57 }
58
59 /// Return the DER encoded issuing distribution point of the CRL, if any.
60 pub fn issuing_distribution_point(&self) -> Option<&[u8]> {
61 match self {
62 #[cfg(feature = "alloc")]
63 CertRevocationList::Owned(crl) => crl.issuing_distribution_point.as_deref(),
64 CertRevocationList::Borrowed(crl) => crl
65 .issuing_distribution_point
66 .map(|idp| idp.as_slice_less_safe()),
67 }
68 }
69
70 /// Try to find a revoked certificate in the CRL by DER encoded serial number. This
71 /// may yield an error if the CRL has malformed revoked certificates.
72 pub fn find_serial(&self, serial: &[u8]) -> Result<Option<BorrowedRevokedCert<'_>>, Error> {
73 match self {
74 #[cfg(feature = "alloc")]
75 CertRevocationList::Owned(crl) => crl.find_serial(serial),
76 CertRevocationList::Borrowed(crl) => crl.find_serial(serial),
77 }
78 }
79
80 /// Returns true if the CRL can be considered authoritative for the given certificate.
81 ///
82 /// A CRL is considered authoritative for a certificate when:
83 /// * The certificate issuer matches the CRL issuer and,
84 /// * The certificate has no CRL distribution points, and the CRL has no issuing distribution
85 /// point extension.
86 /// * Or, the certificate has no CRL distribution points, but the the CRL has an issuing
87 /// distribution point extension with a scope that includes the certificate.
88 /// * Or, the certificate has CRL distribution points, and the CRL has an issuing
89 /// distribution point extension with a scope that includes the certificate, and at least
90 /// one distribution point full name is a URI type general name that can also be found in
91 /// the CRL issuing distribution point full name general name sequence.
92 /// * Or, the certificate has CRL distribution points, and the CRL has no issuing
93 /// distribution point extension.
94 ///
95 /// In all other circumstances the CRL is not considered authoritative.
96 pub(crate) fn authoritative(&self, path: &PathNode<'_>) -> bool {
97 // In all cases we require that the authoritative CRL have the same issuer
98 // as the certificate. Recall we do not support indirect CRLs.
99 if self.issuer() != path.cert.issuer() {
100 return false;
101 }
102
103 let crl_idp = match self.issuing_distribution_point() {
104 // If the CRL has an issuing distribution point, parse it so we can consider its scope
105 // and compare against the cert CRL distribution points, if present.
106 Some(crl_idp) => {
107 match IssuingDistributionPoint::from_der(untrusted::Input::from(crl_idp)) {
108 Ok(crl_idp) => crl_idp,
109 Err(_) => return false, // Note: shouldn't happen - we verify IDP at CRL-load.
110 }
111 }
112 // If the CRL has no issuing distribution point we assume the CRL scope
113 // to be "everything" and consider the CRL authoritative for the cert based on the
114 // issuer matching. We do not need to consider the certificate's CRL distribution point
115 // extension (see also https://github.com/rustls/webpki/issues/228).
116 None => return true,
117 };
118
119 crl_idp.authoritative_for(path)
120 }
121
122 /// Verify the CRL signature using the issuer certificate and a list of supported signature
123 /// verification algorithms, consuming signature operations from the [`Budget`].
124 pub(crate) fn verify_signature(
125 &self,
126 supported_sig_algs: &[&dyn SignatureVerificationAlgorithm],
127 issuer_spki: untrusted::Input<'_>,
128 budget: &mut Budget,
129 ) -> Result<(), Error> {
130 signed_data::verify_signed_data(
131 supported_sig_algs,
132 issuer_spki,
133 &match self {
134 #[cfg(feature = "alloc")]
135 CertRevocationList::Owned(crl) => crl.signed_data.borrow(),
136 CertRevocationList::Borrowed(crl) => SignedData {
137 data: crl.signed_data.data,
138 algorithm: crl.signed_data.algorithm,
139 signature: crl.signed_data.signature,
140 },
141 },
142 budget,
143 )
144 .map_err(crl_signature_err)
145 }
146
147 /// Checks the verification time is before the time in the CRL nextUpdate field.
148 pub(crate) fn check_expiration(&self, time: UnixTime) -> Result<(), Error> {
149 let next_update = match self {
150 #[cfg(feature = "alloc")]
151 CertRevocationList::Owned(crl) => crl.next_update,
152 CertRevocationList::Borrowed(crl) => crl.next_update,
153 };
154
155 if time >= next_update {
156 return Err(Error::CrlExpired { time, next_update });
157 }
158
159 Ok(())
160 }
161}
162
163/// Owned representation of a RFC 5280[^1] profile Certificate Revocation List (CRL).
164///
165/// [^1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
166#[cfg(feature = "alloc")]
167#[derive(Debug, Clone)]
168pub struct OwnedCertRevocationList {
169 /// A map of the revoked certificates contained in then CRL, keyed by the DER encoding
170 /// of the revoked cert's serial number.
171 revoked_certs: BTreeMap<Vec<u8>, OwnedRevokedCert>,
172
173 issuer: Vec<u8>,
174
175 issuing_distribution_point: Option<Vec<u8>>,
176
177 signed_data: signed_data::OwnedSignedData,
178
179 next_update: UnixTime,
180}
181
182#[cfg(feature = "alloc")]
183impl OwnedCertRevocationList {
184 /// Try to parse the given bytes as a RFC 5280[^1] profile Certificate Revocation List (CRL).
185 ///
186 /// Webpki does not support:
187 /// * CRL versions other than version 2.
188 /// * CRLs missing the next update field.
189 /// * CRLs missing certificate revocation list extensions.
190 /// * Delta CRLs.
191 /// * CRLs larger than (2^32)-1 bytes in size.
192 ///
193 /// See [BorrowedCertRevocationList::from_der] for more details.
194 ///
195 /// [^1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
196 pub fn from_der(crl_der: &[u8]) -> Result<Self, Error> {
197 BorrowedCertRevocationList::from_der(crl_der)?.to_owned()
198 }
199
200 fn find_serial(&self, serial: &[u8]) -> Result<Option<BorrowedRevokedCert<'_>>, Error> {
201 // note: this is infallible for the owned representation because we process all
202 // revoked certificates at the time of construction to build the `revoked_certs` map,
203 // returning any encountered errors at that time.
204 Ok(self
205 .revoked_certs
206 .get(serial)
207 .map(|owned_revoked_cert| owned_revoked_cert.borrow()))
208 }
209}
210
211/// Borrowed representation of a RFC 5280[^1] profile Certificate Revocation List (CRL).
212///
213/// [^1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
214#[derive(Debug)]
215pub struct BorrowedCertRevocationList<'a> {
216 /// A `SignedData` structure that can be passed to `verify_signed_data`.
217 signed_data: SignedData<'a>,
218
219 /// Identifies the entity that has signed and issued this
220 /// CRL.
221 issuer: untrusted::Input<'a>,
222
223 /// An optional CRL extension that identifies the CRL distribution point and scope for the CRL.
224 issuing_distribution_point: Option<untrusted::Input<'a>>,
225
226 /// List of certificates revoked by the issuer in this CRL.
227 revoked_certs: untrusted::Input<'a>,
228
229 next_update: UnixTime,
230}
231
232impl<'a> BorrowedCertRevocationList<'a> {
233 /// Try to parse the given bytes as a RFC 5280[^1] profile Certificate Revocation List (CRL).
234 ///
235 /// Webpki does not support:
236 /// * CRL versions other than version 2.
237 /// * CRLs missing the next update field.
238 /// * CRLs missing certificate revocation list extensions.
239 /// * Delta CRLs.
240 /// * CRLs larger than (2^32)-1 bytes in size.
241 ///
242 /// [^1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
243 pub fn from_der(crl_der: &'a [u8]) -> Result<Self, Error> {
244 der::read_all(untrusted::Input::from(crl_der))
245 }
246
247 /// Convert the CRL to an [`OwnedCertRevocationList`]. This may error if any of the revoked
248 /// certificates in the CRL are malformed or contain unsupported features.
249 #[cfg(feature = "alloc")]
250 pub fn to_owned(&self) -> Result<OwnedCertRevocationList, Error> {
251 // Parse and collect the CRL's revoked cert entries, ensuring there are no errors. With
252 // the full set in-hand, create a lookup map by serial number for fast revocation checking.
253 let revoked_certs = self
254 .into_iter()
255 .collect::<Result<Vec<_>, _>>()?
256 .iter()
257 .map(|revoked_cert| (revoked_cert.serial_number.to_vec(), revoked_cert.to_owned()))
258 .collect::<BTreeMap<_, _>>();
259
260 Ok(OwnedCertRevocationList {
261 signed_data: self.signed_data.to_owned(),
262 issuer: self.issuer.as_slice_less_safe().to_vec(),
263 issuing_distribution_point: self
264 .issuing_distribution_point
265 .map(|idp| idp.as_slice_less_safe().to_vec()),
266 revoked_certs,
267 next_update: self.next_update,
268 })
269 }
270
271 fn remember_extension(&mut self, extension: &Extension<'a>) -> Result<(), Error> {
272 remember_extension(extension, UnknownExtensionPolicy::default(), |id| {
273 match id {
274 // id-ce-cRLNumber 2.5.29.20 - RFC 5280 §5.2.3
275 20 => {
276 // RFC 5280 §5.2.3:
277 // CRL verifiers MUST be able to handle CRLNumber values
278 // up to 20 octets. Conforming CRL issuers MUST NOT use CRLNumber
279 // values longer than 20 octets.
280 //
281 extension.value.read_all(Error::InvalidCrlNumber, |der| {
282 let crl_number = der::nonnegative_integer(der)
283 .map_err(|_| Error::InvalidCrlNumber)?
284 .as_slice_less_safe();
285 if crl_number.len() <= 20 {
286 Ok(crl_number)
287 } else {
288 Err(Error::InvalidCrlNumber)
289 }
290 })?;
291 // We enforce the cRLNumber is sensible, but don't retain the value for use.
292 Ok(())
293 }
294
295 // id-ce-deltaCRLIndicator 2.5.29.27 - RFC 5280 §5.2.4
296 // We explicitly do not support delta CRLs.
297 27 => Err(Error::UnsupportedDeltaCrl),
298
299 // id-ce-issuingDistributionPoint 2.5.29.28 - RFC 5280 §5.2.4
300 // We recognize the extension and retain its value for use.
301 28 => {
302 set_extension_once(&mut self.issuing_distribution_point, || Ok(extension.value))
303 }
304
305 // id-ce-authorityKeyIdentifier 2.5.29.35 - RFC 5280 §5.2.1, §4.2.1.1
306 // We recognize the extension but don't retain its value for use.
307 35 => Ok(()),
308
309 // Unsupported extension
310 _ => extension.unsupported(UnknownExtensionPolicy::default()),
311 }
312 })
313 }
314
315 fn find_serial(&self, serial: &[u8]) -> Result<Option<BorrowedRevokedCert<'_>>, Error> {
316 for revoked_cert_result in self {
317 match revoked_cert_result {
318 Err(e) => return Err(e),
319 Ok(revoked_cert) => {
320 if revoked_cert.serial_number.eq(serial) {
321 return Ok(Some(revoked_cert));
322 }
323 }
324 }
325 }
326
327 Ok(None)
328 }
329}
330
331impl<'a> FromDer<'a> for BorrowedCertRevocationList<'a> {
332 /// Try to parse the given bytes as a RFC 5280[^1] profile Certificate Revocation List (CRL).
333 ///
334 /// Webpki does not support:
335 /// * CRL versions other than version 2.
336 /// * CRLs missing the next update field.
337 /// * CRLs missing certificate revocation list extensions.
338 /// * Delta CRLs.
339 /// * CRLs larger than (2^32)-1 bytes in size.
340 ///
341 /// [^1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
342 fn from_der(reader: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
343 let (tbs_cert_list, signed_data) = der::nested_limited(
344 reader,
345 Tag::Sequence,
346 Error::TrailingData(Self::TYPE_ID),
347 |signed_der| SignedData::from_der(signed_der, der::MAX_DER_SIZE),
348 der::MAX_DER_SIZE,
349 )?;
350
351 let crl = tbs_cert_list.read_all(Error::BadDer, |tbs_cert_list| {
352 // RFC 5280 §5.1.2.1:
353 // This optional field describes the version of the encoded CRL. When
354 // extensions are used, as required by this profile, this field MUST be
355 // present and MUST specify version 2 (the integer value is 1).
356 // RFC 5280 §5.2:
357 // Conforming CRL issuers are REQUIRED to include the authority key
358 // identifier (Section 5.2.1) and the CRL number (Section 5.2.3)
359 // extensions in all CRLs issued.
360 // As a result of the above we parse this as a required section, not OPTIONAL.
361 // NOTE: Encoded value of version 2 is 1.
362 if u8::from_der(tbs_cert_list)? != 1 {
363 return Err(Error::UnsupportedCrlVersion);
364 }
365
366 // RFC 5280 §5.1.2.2:
367 // This field MUST contain the same algorithm identifier as the
368 // signatureAlgorithm field in the sequence CertificateList
369 let signature = der::expect_tag(tbs_cert_list, Tag::Sequence)?;
370 if !public_values_eq(signature, signed_data.algorithm) {
371 return Err(Error::SignatureAlgorithmMismatch);
372 }
373
374 // RFC 5280 §5.1.2.3:
375 // The issuer field MUST contain a non-empty X.500 distinguished name (DN).
376 let issuer = der::expect_tag(tbs_cert_list, Tag::Sequence)?;
377
378 // RFC 5280 §5.1.2.4:
379 // This field indicates the issue date of this CRL. thisUpdate may be
380 // encoded as UTCTime or GeneralizedTime.
381 // We do not presently enforce the correct choice of UTCTime or GeneralizedTime based on
382 // whether the date is post 2050.
383 UnixTime::from_der(tbs_cert_list)?;
384
385 // While OPTIONAL in the ASN.1 module, RFC 5280 §5.1.2.5 says:
386 // Conforming CRL issuers MUST include the nextUpdate field in all CRLs.
387 // We do not presently enforce the correct choice of UTCTime or GeneralizedTime based on
388 // whether the date is post 2050.
389 let next_update = UnixTime::from_der(tbs_cert_list)?;
390
391 // RFC 5280 §5.1.2.6:
392 // When there are no revoked certificates, the revoked certificates list
393 // MUST be absent
394 // TODO(@cpu): Do we care to support empty CRLs if we don't support delta CRLs?
395 let revoked_certs = if tbs_cert_list.peek(Tag::Sequence.into()) {
396 der::expect_tag_and_get_value_limited(
397 tbs_cert_list,
398 Tag::Sequence,
399 der::MAX_DER_SIZE,
400 )?
401 } else {
402 untrusted::Input::from(&[])
403 };
404
405 let mut crl = BorrowedCertRevocationList {
406 signed_data,
407 issuer,
408 revoked_certs,
409 issuing_distribution_point: None,
410 next_update,
411 };
412
413 // RFC 5280 §5.1.2.7:
414 // This field may only appear if the version is 2 (Section 5.1.2.1). If
415 // present, this field is a sequence of one or more CRL extensions.
416 // RFC 5280 §5.2:
417 // Conforming CRL issuers are REQUIRED to include the authority key
418 // identifier (Section 5.2.1) and the CRL number (Section 5.2.3)
419 // extensions in all CRLs issued.
420 // As a result of the above we parse this as a required section, not OPTIONAL.
421 der::nested(
422 tbs_cert_list,
423 Tag::ContextSpecificConstructed0,
424 Error::MalformedExtensions,
425 |tagged| {
426 der::nested_of_mut(
427 tagged,
428 Tag::Sequence,
429 Tag::Sequence,
430 Error::TrailingData(DerTypeId::CertRevocationListExtension),
431 false,
432 |extension| {
433 // RFC 5280 §5.2:
434 // If a CRL contains a critical extension
435 // that the application cannot process, then the application MUST NOT
436 // use that CRL to determine the status of certificates. However,
437 // applications may ignore unrecognized non-critical extensions.
438 crl.remember_extension(&Extension::from_der(extension)?)
439 },
440 )
441 },
442 )?;
443
444 Ok(crl)
445 })?;
446
447 // If an issuing distribution point extension is present, parse it up-front to validate
448 // that it only uses well-formed and supported features.
449 if let Some(der) = crl.issuing_distribution_point {
450 IssuingDistributionPoint::from_der(der)?;
451 }
452
453 Ok(crl)
454 }
455
456 const TYPE_ID: DerTypeId = DerTypeId::CertRevocationList;
457}
458
459impl<'a> IntoIterator for &'a BorrowedCertRevocationList<'a> {
460 type Item = Result<BorrowedRevokedCert<'a>, Error>;
461 type IntoIter = DerIterator<'a, BorrowedRevokedCert<'a>>;
462
463 fn into_iter(self) -> Self::IntoIter {
464 DerIterator::new(self.revoked_certs)
465 }
466}
467
468pub(crate) struct IssuingDistributionPoint<'a> {
469 distribution_point: Option<untrusted::Input<'a>>,
470 pub(crate) only_contains_user_certs: bool,
471 pub(crate) only_contains_ca_certs: bool,
472 pub(crate) only_some_reasons: Option<der::BitStringFlags<'a>>,
473 pub(crate) indirect_crl: bool,
474 pub(crate) only_contains_attribute_certs: bool,
475}
476
477impl<'a> IssuingDistributionPoint<'a> {
478 pub(crate) fn from_der(der: untrusted::Input<'a>) -> Result<Self, Error> {
479 const DISTRIBUTION_POINT_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED;
480 const ONLY_CONTAINS_USER_CERTS_TAG: u8 = CONTEXT_SPECIFIC | 1;
481 const ONLY_CONTAINS_CA_CERTS_TAG: u8 = CONTEXT_SPECIFIC | 2;
482 const ONLY_CONTAINS_SOME_REASONS_TAG: u8 = CONTEXT_SPECIFIC | 3;
483 const INDIRECT_CRL_TAG: u8 = CONTEXT_SPECIFIC | 4;
484 const ONLY_CONTAINS_ATTRIBUTE_CERTS_TAG: u8 = CONTEXT_SPECIFIC | 5;
485
486 let mut result = IssuingDistributionPoint {
487 distribution_point: None,
488 only_contains_user_certs: false,
489 only_contains_ca_certs: false,
490 only_some_reasons: None,
491 indirect_crl: false,
492 only_contains_attribute_certs: false,
493 };
494
495 // Note: we can't use der::optional_boolean here because the distribution point
496 // booleans are context specific primitives and der::optional_boolean expects
497 // to unwrap a Tag::Boolean constructed value.
498 fn decode_bool(value: untrusted::Input<'_>) -> Result<bool, Error> {
499 let mut reader = untrusted::Reader::new(value);
500 let value = reader.read_byte().map_err(der::end_of_input_err)?;
501 if !reader.at_end() {
502 return Err(Error::BadDer);
503 }
504 match value {
505 0xFF => Ok(true),
506 0x00 => Ok(false), // non-conformant explicit encoding allowed for compat.
507 _ => Err(Error::BadDer),
508 }
509 }
510
511 // RFC 5280 section §4.2.1.13:
512 der::nested(
513 &mut untrusted::Reader::new(der),
514 Tag::Sequence,
515 Error::TrailingData(DerTypeId::IssuingDistributionPoint),
516 |der| {
517 while !der.at_end() {
518 let (tag, value) = der::read_tag_and_get_value(der)?;
519 match tag {
520 DISTRIBUTION_POINT_TAG => {
521 set_extension_once(&mut result.distribution_point, || Ok(value))?
522 }
523 ONLY_CONTAINS_USER_CERTS_TAG => {
524 result.only_contains_user_certs = decode_bool(value)?
525 }
526 ONLY_CONTAINS_CA_CERTS_TAG => {
527 result.only_contains_ca_certs = decode_bool(value)?
528 }
529 ONLY_CONTAINS_SOME_REASONS_TAG => {
530 set_extension_once(&mut result.only_some_reasons, || {
531 der::bit_string_flags(value)
532 })?
533 }
534 INDIRECT_CRL_TAG => result.indirect_crl = decode_bool(value)?,
535 ONLY_CONTAINS_ATTRIBUTE_CERTS_TAG => {
536 result.only_contains_attribute_certs = decode_bool(value)?
537 }
538 _ => return Err(Error::BadDer),
539 }
540 }
541
542 Ok(())
543 },
544 )?;
545
546 // RFC 5280 4.2.1.10:
547 // Conforming CRLs issuers MUST set the onlyContainsAttributeCerts boolean to FALSE.
548 if result.only_contains_attribute_certs {
549 return Err(Error::MalformedExtensions);
550 }
551
552 // We don't support indirect CRLs.
553 if result.indirect_crl {
554 return Err(Error::UnsupportedIndirectCrl);
555 }
556
557 // We don't support CRLs partitioned by revocation reason.
558 if result.only_some_reasons.is_some() {
559 return Err(Error::UnsupportedRevocationReasonsPartitioning);
560 }
561
562 // We require a distribution point, and it must be a full name.
563 use DistributionPointName::*;
564 match result.names() {
565 Ok(Some(FullName(_))) => Ok(result),
566 Ok(Some(NameRelativeToCrlIssuer)) | Ok(None) => {
567 Err(Error::UnsupportedCrlIssuingDistributionPoint)
568 }
569 Err(_) => Err(Error::MalformedExtensions),
570 }
571 }
572
573 /// Return the distribution point names (if any).
574 pub(crate) fn names(&self) -> Result<Option<DistributionPointName<'a>>, Error> {
575 self.distribution_point
576 .map(|input| DistributionPointName::from_der(&mut untrusted::Reader::new(input)))
577 .transpose()
578 }
579
580 /// Returns true if the CRL can be considered authoritative for the given certificate. We make
581 /// this determination using the certificate and CRL issuers, and the distribution point names
582 /// that may be present in extensions found on both.
583 ///
584 /// We consider the CRL authoritative for the certificate if the CRL issuing distribution point
585 /// has a scope that could include the cert and if the cert has CRL distribution points, that
586 /// at least one CRL DP has a valid distribution point full name where one of the general names
587 /// is a Uniform Resource Identifier (URI) general name that can also be found in the CRL
588 /// issuing distribution point.
589 ///
590 /// We do not consider:
591 /// * Distribution point names relative to an issuer.
592 /// * General names of a type other than URI.
593 /// * Malformed names or invalid IDP or CRL DP extensions.
594 pub(crate) fn authoritative_for(&self, node: &PathNode<'a>) -> bool {
595 assert!(!self.only_contains_attribute_certs); // We check this at time of parse.
596
597 // Check that the scope of the CRL issuing distribution point could include the cert.
598 if self.only_contains_ca_certs && node.role() != Role::Issuer
599 || self.only_contains_user_certs && node.role() != Role::EndEntity
600 {
601 return false; // CRL scope excludes this cert's role.
602 }
603
604 let cert_dps = match node.cert.crl_distribution_points() {
605 // If the certificate has no distribution points, then the CRL can be authoritative
606 // based on the issuer matching and the scope including the cert.
607 None => return true,
608 Some(cert_dps) => cert_dps,
609 };
610
611 for cert_dp in cert_dps {
612 let Ok(cert_dp) = cert_dp else {
613 continue; // Malformed DP, try next cert DP.
614 };
615
616 // If the certificate CRL DP was for an indirect CRL, or a CRL
617 // sharded by revocation reason, it can't match.
618 if cert_dp.crl_issuer.is_some() || cert_dp.reasons.is_some() {
619 continue; // Indirect CRL or reason-partitioned DP, try next cert DP.
620 }
621
622 let Ok(Some(DistributionPointName::FullName(dp_general_names))) = cert_dp.names()
623 else {
624 continue; // No full names or malformed, try next cert DP.
625 };
626
627 // At least one URI type name in the IDP full names must match a URI type name in the
628 // DP full names.
629 for dp_name in dp_general_names {
630 let dp_uri = match dp_name {
631 Ok(GeneralName::UniformResourceIdentifier(dp_uri)) => dp_uri,
632 Ok(_) => continue, // Not a URI type name, skip.
633 Err(_) => continue, // Malformed general name, try next name.
634 };
635
636 let Ok(Some(DistributionPointName::FullName(idp_general_names))) = self.names()
637 else {
638 return false; // IDP has no full names or is malformed.
639 };
640
641 for idp_name in idp_general_names.flatten() {
642 match idp_name {
643 GeneralName::UniformResourceIdentifier(idp_uri)
644 if dp_uri.as_slice_less_safe() == idp_uri.as_slice_less_safe() =>
645 {
646 return true; // DP URI matches IDP URI.
647 }
648 _ => continue, // Not a matching URI, try next IDP name.
649 }
650 }
651 }
652 }
653
654 false
655 }
656}
657
658/// Owned representation of a RFC 5280[^1] profile Certificate Revocation List (CRL) revoked
659/// certificate entry.
660///
661/// Only available when the "alloc" feature is enabled.
662///
663/// [^1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
664#[cfg(feature = "alloc")]
665#[derive(Clone, Debug)]
666pub struct OwnedRevokedCert {
667 /// Serial number of the revoked certificate.
668 pub serial_number: Vec<u8>,
669
670 /// The date at which the CA processed the revocation.
671 pub revocation_date: UnixTime,
672
673 /// Identifies the reason for the certificate revocation. When absent, the revocation reason
674 /// is assumed to be RevocationReason::Unspecified. For consistency with other extensions
675 /// and to ensure only one revocation reason extension may be present we maintain this field
676 /// as optional instead of defaulting to unspecified.
677 pub reason_code: Option<RevocationReason>,
678
679 /// Provides the date on which it is known or suspected that the private key was compromised or
680 /// that the certificate otherwise became invalid. This date may be earlier than the revocation
681 /// date which is the date at which the CA processed the revocation.
682 pub invalidity_date: Option<UnixTime>,
683}
684
685#[cfg(feature = "alloc")]
686impl OwnedRevokedCert {
687 /// Convert the owned representation of this revoked cert to a borrowed version.
688 pub fn borrow(&self) -> BorrowedRevokedCert<'_> {
689 BorrowedRevokedCert {
690 serial_number: &self.serial_number,
691 revocation_date: self.revocation_date,
692 reason_code: self.reason_code,
693 invalidity_date: self.invalidity_date,
694 }
695 }
696}
697
698/// Borrowed representation of a RFC 5280[^1] profile Certificate Revocation List (CRL) revoked
699/// certificate entry.
700///
701/// [^1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
702#[derive(Debug)]
703pub struct BorrowedRevokedCert<'a> {
704 /// Serial number of the revoked certificate.
705 pub serial_number: &'a [u8],
706
707 /// The date at which the CA processed the revocation.
708 pub revocation_date: UnixTime,
709
710 /// Identifies the reason for the certificate revocation. When absent, the revocation reason
711 /// is assumed to be RevocationReason::Unspecified. For consistency with other extensions
712 /// and to ensure only one revocation reason extension may be present we maintain this field
713 /// as optional instead of defaulting to unspecified.
714 pub reason_code: Option<RevocationReason>,
715
716 /// Provides the date on which it is known or suspected that the private key was compromised or
717 /// that the certificate otherwise became invalid. This date may be earlier than the revocation
718 /// date which is the date at which the CA processed the revocation.
719 pub invalidity_date: Option<UnixTime>,
720}
721
722impl<'a> BorrowedRevokedCert<'a> {
723 /// Construct an owned representation of the revoked certificate.
724 #[cfg(feature = "alloc")]
725 pub fn to_owned(&self) -> OwnedRevokedCert {
726 OwnedRevokedCert {
727 serial_number: self.serial_number.to_vec(),
728 revocation_date: self.revocation_date,
729 reason_code: self.reason_code,
730 invalidity_date: self.invalidity_date,
731 }
732 }
733
734 fn remember_extension(&mut self, extension: &Extension<'a>) -> Result<(), Error> {
735 remember_extension(extension, UnknownExtensionPolicy::default(), |id| {
736 match id {
737 // id-ce-cRLReasons 2.5.29.21 - RFC 5280 §5.3.1.
738 21 => set_extension_once(&mut self.reason_code, || der::read_all(extension.value)),
739
740 // id-ce-invalidityDate 2.5.29.24 - RFC 5280 §5.3.2.
741 24 => set_extension_once(&mut self.invalidity_date, || {
742 extension.value.read_all(Error::BadDer, UnixTime::from_der)
743 }),
744
745 // id-ce-certificateIssuer 2.5.29.29 - RFC 5280 §5.3.3.
746 // This CRL entry extension identifies the certificate issuer associated
747 // with an entry in an indirect CRL, that is, a CRL that has the
748 // indirectCRL indicator set in its issuing distribution point
749 // extension.
750 // We choose not to support indirect CRLs and so turn this into a more specific
751 // error rather than simply letting it fail as an unsupported critical extension.
752 29 => Err(Error::UnsupportedIndirectCrl),
753
754 // Unsupported extension
755 _ => extension.unsupported(UnknownExtensionPolicy::default()),
756 }
757 })
758 }
759}
760
761impl<'a> FromDer<'a> for BorrowedRevokedCert<'a> {
762 fn from_der(reader: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
763 der::nested(
764 reader,
765 Tag::Sequence,
766 Error::TrailingData(DerTypeId::RevokedCertEntry),
767 |der| {
768 // RFC 5280 §4.1.2.2:
769 // Certificate users MUST be able to handle serialNumber values up to 20 octets.
770 // Conforming CAs MUST NOT use serialNumber values longer than 20 octets.
771 //
772 // Note: Non-conforming CAs may issue certificates with serial numbers
773 // that are negative or zero. Certificate users SHOULD be prepared to
774 // gracefully handle such certificates.
775 // Like the handling in cert.rs we choose to be lenient here, not enforcing the length
776 // of a CRL revoked certificate's serial number is less than 20 octets in encoded form.
777 let serial_number = lenient_certificate_serial_number(der)
778 .map_err(|_| Error::InvalidSerialNumber)?
779 .as_slice_less_safe();
780
781 let revocation_date = UnixTime::from_der(der)?;
782
783 let mut revoked_cert = BorrowedRevokedCert {
784 serial_number,
785 revocation_date,
786 reason_code: None,
787 invalidity_date: None,
788 };
789
790 // RFC 5280 §5.3:
791 // Support for the CRL entry extensions defined in this specification is
792 // optional for conforming CRL issuers and applications. However, CRL
793 // issuers SHOULD include reason codes (Section 5.3.1) and invalidity
794 // dates (Section 5.3.2) whenever this information is available.
795 if der.at_end() {
796 return Ok(revoked_cert);
797 }
798
799 // It would be convenient to use der::nested_of_mut here to unpack a SEQUENCE of one or
800 // more SEQUENCEs, however CAs have been mis-encoding the absence of extensions as an
801 // empty SEQUENCE so we must be tolerant of that.
802 let ext_seq = der::expect_tag(der, Tag::Sequence)?;
803 if ext_seq.is_empty() {
804 return Ok(revoked_cert);
805 }
806
807 let mut reader = untrusted::Reader::new(ext_seq);
808 loop {
809 der::nested(
810 &mut reader,
811 Tag::Sequence,
812 Error::TrailingData(DerTypeId::RevokedCertificateExtension),
813 |ext_der| {
814 // RFC 5280 §5.3:
815 // If a CRL contains a critical CRL entry extension that the application cannot
816 // process, then the application MUST NOT use that CRL to determine the
817 // status of any certificates. However, applications may ignore
818 // unrecognized non-critical CRL entry extensions.
819 revoked_cert.remember_extension(&Extension::from_der(ext_der)?)
820 },
821 )?;
822 if reader.at_end() {
823 break;
824 }
825 }
826
827 Ok(revoked_cert)
828 },
829 )
830 }
831
832 const TYPE_ID: DerTypeId = DerTypeId::RevokedCertificate;
833}
834
835/// Identifies the reason a certificate was revoked.
836/// See [RFC 5280 §5.3.1][1]
837///
838/// [1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5.3.1>
839#[derive(Debug, Clone, Copy, Eq, PartialEq)]
840#[allow(missing_docs)] // Not much to add above the code name.
841pub enum RevocationReason {
842 /// Unspecified should not be used, and is instead assumed by the absence of a RevocationReason
843 /// extension.
844 Unspecified = 0,
845 KeyCompromise = 1,
846 CaCompromise = 2,
847 AffiliationChanged = 3,
848 Superseded = 4,
849 CessationOfOperation = 5,
850 CertificateHold = 6,
851 // 7 is not used.
852 /// RemoveFromCrl only appears in delta CRLs that are unsupported.
853 RemoveFromCrl = 8,
854 PrivilegeWithdrawn = 9,
855 AaCompromise = 10,
856}
857
858impl RevocationReason {
859 /// Return an iterator over all possible [RevocationReason] variants.
860 pub fn iter() -> impl Iterator<Item = Self> {
861 use RevocationReason::*;
862 [
863 Unspecified,
864 KeyCompromise,
865 CaCompromise,
866 AffiliationChanged,
867 Superseded,
868 CessationOfOperation,
869 CertificateHold,
870 RemoveFromCrl,
871 PrivilegeWithdrawn,
872 AaCompromise,
873 ]
874 .into_iter()
875 }
876}
877
878impl<'a> FromDer<'a> for RevocationReason {
879 // RFC 5280 §5.3.1.
880 fn from_der(reader: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
881 let input = der::expect_tag(reader, Tag::Enum)?;
882 Self::try_from(input.read_all(Error::BadDer, |reason| {
883 reason.read_byte().map_err(|_| Error::BadDer)
884 })?)
885 }
886
887 const TYPE_ID: DerTypeId = DerTypeId::RevocationReason;
888}
889
890impl TryFrom<u8> for RevocationReason {
891 type Error = Error;
892
893 fn try_from(value: u8) -> Result<Self, Self::Error> {
894 // See https://www.rfc-editor.org/rfc/rfc5280#section-5.3.1
895 match value {
896 0 => Ok(Self::Unspecified),
897 1 => Ok(Self::KeyCompromise),
898 2 => Ok(Self::CaCompromise),
899 3 => Ok(Self::AffiliationChanged),
900 4 => Ok(Self::Superseded),
901 5 => Ok(Self::CessationOfOperation),
902 6 => Ok(Self::CertificateHold),
903 // 7 is not used.
904 8 => Ok(Self::RemoveFromCrl),
905 9 => Ok(Self::PrivilegeWithdrawn),
906 10 => Ok(Self::AaCompromise),
907 _ => Err(Error::UnsupportedRevocationReason),
908 }
909 }
910}
911
912#[cfg(feature = "alloc")]
913#[cfg(test)]
914mod tests {
915 use std::time::Duration;
916
917 use pki_types::CertificateDer;
918 use std::println;
919
920 use super::*;
921 use crate::cert::Cert;
922 use crate::end_entity::EndEntityCert;
923 use crate::verify_cert::PartialPath;
924
925 #[test]
926 fn parse_issuing_distribution_point_ext() {
927 let crl = include_bytes!("../../tests/crls/crl.idp.valid.der");
928 let crl = BorrowedCertRevocationList::from_der(&crl[..]).unwrap();
929
930 // We should be able to parse the issuing distribution point extension.
931 let crl_issuing_dp = crl
932 .issuing_distribution_point
933 .expect("missing crl distribution point DER");
934
935 #[cfg(feature = "alloc")]
936 {
937 // We should also be able to find the distribution point extensions bytes from
938 // an owned representation of the CRL.
939 let owned_crl = crl.to_owned().unwrap();
940 assert!(owned_crl.issuing_distribution_point.is_some());
941 }
942
943 let crl_issuing_dp = IssuingDistributionPoint::from_der(untrusted::Input::from(
944 crl_issuing_dp.as_slice_less_safe(),
945 ))
946 .expect("failed to parse issuing distribution point DER");
947
948 // We don't expect any of the bool fields to have been set true.
949 assert!(!crl_issuing_dp.only_contains_user_certs);
950 assert!(!crl_issuing_dp.only_contains_ca_certs);
951 assert!(!crl_issuing_dp.indirect_crl);
952
953 // Since the issuing distribution point doesn't specify the optional onlySomeReasons field,
954 // we shouldn't find that it was parsed.
955 assert!(crl_issuing_dp.only_some_reasons.is_none());
956
957 // We should find the expected URI distribution point name.
958 let dp_name = crl_issuing_dp
959 .names()
960 .expect("failed to parse distribution point names")
961 .expect("missing distribution point name");
962 let uri = match dp_name {
963 DistributionPointName::NameRelativeToCrlIssuer => {
964 panic!("unexpected relative dp name")
965 }
966 DistributionPointName::FullName(general_names) => {
967 general_names.map(|general_name| match general_name {
968 Ok(GeneralName::UniformResourceIdentifier(uri)) => uri.as_slice_less_safe(),
969 _ => panic!("unexpected general name type"),
970 })
971 }
972 }
973 .collect::<Vec<_>>();
974 let expected = &["http://crl.trustcor.ca/sub/dv-ssl-rsa-s-0.crl".as_bytes()];
975 assert_eq!(uri, expected);
976 }
977
978 #[test]
979 fn test_issuing_distribution_point_only_user_certs() {
980 let crl = include_bytes!("../../tests/crls/crl.idp.only_user_certs.der");
981 let crl = BorrowedCertRevocationList::from_der(&crl[..]).unwrap();
982
983 // We should be able to parse the issuing distribution point extension.
984 let crl_issuing_dp = crl
985 .issuing_distribution_point
986 .expect("missing crl distribution point DER");
987 let crl_issuing_dp = IssuingDistributionPoint::from_der(crl_issuing_dp)
988 .expect("failed to parse issuing distribution point DER");
989
990 // We should find the expected bool state.
991 assert!(crl_issuing_dp.only_contains_user_certs);
992
993 // The IDP shouldn't be considered authoritative for a CA Cert.
994 let ee = CertificateDer::from(
995 &include_bytes!("../../tests/client_auth_revocation/no_crl_ku_chain.ee.der")[..],
996 );
997 let ee = EndEntityCert::try_from(&ee).unwrap();
998 let ca = include_bytes!("../../tests/client_auth_revocation/no_crl_ku_chain.int.a.ca.der");
999 let ca = Cert::from_der(untrusted::Input::from(&ca[..])).unwrap();
1000
1001 let mut path = PartialPath::new(&ee);
1002 path.push(ca).unwrap();
1003
1004 assert!(!crl_issuing_dp.authoritative_for(&path.node()));
1005 }
1006
1007 #[test]
1008 fn test_issuing_distribution_point_only_ca_certs() {
1009 let crl = include_bytes!("../../tests/crls/crl.idp.only_ca_certs.der");
1010 let crl = BorrowedCertRevocationList::from_der(&crl[..]).unwrap();
1011
1012 // We should be able to parse the issuing distribution point extension.
1013 let crl_issuing_dp = crl
1014 .issuing_distribution_point
1015 .expect("missing crl distribution point DER");
1016 let crl_issuing_dp = IssuingDistributionPoint::from_der(crl_issuing_dp)
1017 .expect("failed to parse issuing distribution point DER");
1018
1019 // We should find the expected bool state.
1020 assert!(crl_issuing_dp.only_contains_ca_certs);
1021
1022 // The IDP shouldn't be considered authoritative for an EE Cert.
1023 let ee = CertificateDer::from(
1024 &include_bytes!("../../tests/client_auth_revocation/no_crl_ku_chain.ee.der")[..],
1025 );
1026 let ee = EndEntityCert::try_from(&ee).unwrap();
1027 let path = PartialPath::new(&ee);
1028
1029 assert!(!crl_issuing_dp.authoritative_for(&path.node()));
1030 }
1031
1032 #[test]
1033 fn test_issuing_distribution_point_indirect() {
1034 let crl = include_bytes!("../../tests/crls/crl.idp.indirect_crl.der");
1035 // We should encounter an error parsing a CRL with an IDP extension that indicates it's an
1036 // indirect CRL.
1037 let result = BorrowedCertRevocationList::from_der(&crl[..]);
1038 assert!(matches!(result, Err(Error::UnsupportedIndirectCrl)));
1039 }
1040
1041 #[test]
1042 fn test_issuing_distribution_only_attribute_certs() {
1043 let crl = include_bytes!("../../tests/crls/crl.idp.only_attribute_certs.der");
1044 // We should find an error when we parse a CRL with an IDP extension that indicates it only
1045 // contains attribute certs.
1046 let result = BorrowedCertRevocationList::from_der(&crl[..]);
1047 assert!(matches!(result, Err(Error::MalformedExtensions)));
1048 }
1049
1050 #[test]
1051 fn test_issuing_distribution_only_some_reasons() {
1052 let crl = include_bytes!("../../tests/crls/crl.idp.only_some_reasons.der");
1053 // We should encounter an error parsing a CRL with an IDP extension that indicates it's
1054 // partitioned by revocation reason.
1055 let result = BorrowedCertRevocationList::from_der(&crl[..]);
1056 assert!(matches!(
1057 result,
1058 Err(Error::UnsupportedRevocationReasonsPartitioning)
1059 ));
1060 }
1061
1062 #[test]
1063 fn test_issuing_distribution_invalid_bool() {
1064 // Created w/
1065 // ascii2der -i tests/crls/crl.idp.invalid.bool.der.txt -o tests/crls/crl.idp.invalid.bool.der
1066 let crl = include_bytes!("../../tests/crls/crl.idp.invalid.bool.der");
1067 // We should encounter an error parsing a CRL with an IDP extension with an invalid encoded boolean.
1068 let result = BorrowedCertRevocationList::from_der(&crl[..]);
1069 assert!(matches!(result, Err(Error::BadDer)))
1070 }
1071
1072 #[test]
1073 fn test_issuing_distribution_explicit_false_bool() {
1074 // Created w/
1075 // ascii2der -i tests/crls/crl.idp.explicit.false.bool.der.txt -o tests/crls/crl.idp.explicit.false.bool.der
1076 let crl = include_bytes!("../../tests/crls/crl.idp.explicit.false.bool.der");
1077 let crl = BorrowedCertRevocationList::from_der(&crl[..]).unwrap();
1078
1079 // We should be able to parse the issuing distribution point extension.
1080 let crl_issuing_dp = crl
1081 .issuing_distribution_point
1082 .expect("missing crl distribution point DER");
1083 assert!(IssuingDistributionPoint::from_der(crl_issuing_dp).is_ok());
1084 }
1085
1086 #[test]
1087 fn test_issuing_distribution_unknown_tag() {
1088 // Created w/
1089 // ascii2der -i tests/crls/crl.idp.unknown.tag.der.txt -o tests/crls/crl.idp.unknown.tag.der
1090 let crl = include_bytes!("../../tests/crls/crl.idp.unknown.tag.der");
1091 // We should encounter an error parsing a CRL with an invalid IDP extension.
1092 let result = BorrowedCertRevocationList::from_der(&crl[..]);
1093 assert!(matches!(result, Err(Error::BadDer)));
1094 }
1095
1096 #[test]
1097 fn test_issuing_distribution_invalid_name() {
1098 // Created w/
1099 // ascii2der -i tests/crls/crl.idp.invalid.name.der.txt -o tests/crls/crl.idp.invalid.name.der
1100 let crl = include_bytes!("../../tests/crls/crl.idp.invalid.name.der");
1101
1102 // We should encounter an error parsing a CRL with an invalid issuing distribution point name.
1103 let result = BorrowedCertRevocationList::from_der(&crl[..]);
1104 assert!(matches!(result, Err(Error::MalformedExtensions)))
1105 }
1106
1107 #[test]
1108 fn test_issuing_distribution_relative_name() {
1109 let crl = include_bytes!("../../tests/crls/crl.idp.name_relative_to_issuer.der");
1110 // We should encounter an error parsing a CRL with an issuing distribution point extension
1111 // that has a distribution point name relative to an issuer.
1112 let result = BorrowedCertRevocationList::from_der(&crl[..]);
1113 assert!(matches!(
1114 result,
1115 Err(Error::UnsupportedCrlIssuingDistributionPoint)
1116 ))
1117 }
1118
1119 #[test]
1120 fn test_issuing_distribution_no_name() {
1121 let crl = include_bytes!("../../tests/crls/crl.idp.no_distribution_point_name.der");
1122 // We should encounter an error parsing a CRL with an issuing distribution point extension
1123 // that has no distribution point name.
1124 let result = BorrowedCertRevocationList::from_der(&crl[..]);
1125 assert!(matches!(
1126 result,
1127 Err(Error::UnsupportedCrlIssuingDistributionPoint)
1128 ))
1129 }
1130
1131 #[test]
1132 fn revocation_reasons() {
1133 // Test that we can convert the allowed u8 revocation reason code values into the expected
1134 // revocation reason variant.
1135 let testcases: Vec<(u8, RevocationReason)> = vec![
1136 (0, RevocationReason::Unspecified),
1137 (1, RevocationReason::KeyCompromise),
1138 (2, RevocationReason::CaCompromise),
1139 (3, RevocationReason::AffiliationChanged),
1140 (4, RevocationReason::Superseded),
1141 (5, RevocationReason::CessationOfOperation),
1142 (6, RevocationReason::CertificateHold),
1143 // Note: 7 is unused.
1144 (8, RevocationReason::RemoveFromCrl),
1145 (9, RevocationReason::PrivilegeWithdrawn),
1146 (10, RevocationReason::AaCompromise),
1147 ];
1148 for tc in testcases.iter() {
1149 let (id, expected) = tc;
1150 let actual = <u8 as TryInto<RevocationReason>>::try_into(*id)
1151 .expect("unexpected reason code conversion error");
1152 assert_eq!(actual, *expected);
1153 #[cfg(feature = "alloc")]
1154 {
1155 // revocation reasons should be Debug.
1156 println!("{actual:?}");
1157 }
1158 }
1159
1160 // Unsupported/unknown revocation reason codes should produce an error.
1161 let res = <u8 as TryInto<RevocationReason>>::try_into(7);
1162 assert!(matches!(res, Err(Error::UnsupportedRevocationReason)));
1163
1164 // The iterator should produce all possible revocation reason variants.
1165 let expected = testcases
1166 .iter()
1167 .map(|(_, reason)| *reason)
1168 .collect::<Vec<_>>();
1169 let actual = RevocationReason::iter().collect::<Vec<_>>();
1170 assert_eq!(actual, expected);
1171 }
1172
1173 #[test]
1174 // redundant clone, clone_on_copy allowed to verify derived traits.
1175 #[allow(clippy::redundant_clone, clippy::clone_on_copy)]
1176 fn test_derived_traits() {
1177 let crl =
1178 BorrowedCertRevocationList::from_der(include_bytes!("../../tests/crls/crl.valid.der"))
1179 .unwrap();
1180 println!("{crl:?}"); // BorrowedCertRevocationList should be debug.
1181
1182 let owned_crl = crl.to_owned().unwrap();
1183 println!("{owned_crl:?}"); // OwnedCertRevocationList should be debug.
1184 let _ = owned_crl.clone(); // OwnedCertRevocationList should be clone.
1185
1186 let mut revoked_certs = crl.into_iter();
1187 println!("{revoked_certs:?}"); // RevokedCert should be debug.
1188
1189 let revoked_cert = revoked_certs.next().unwrap().unwrap();
1190 println!("{revoked_cert:?}"); // BorrowedRevokedCert should be debug.
1191
1192 let owned_revoked_cert = revoked_cert.to_owned();
1193 println!("{owned_revoked_cert:?}"); // OwnedRevokedCert should be debug.
1194 let _ = owned_revoked_cert.clone(); // OwnedRevokedCert should be clone.
1195 }
1196
1197 #[test]
1198 fn test_enum_conversions() {
1199 let crl =
1200 include_bytes!("../../tests/client_auth_revocation/ee_revoked_crl_ku_ee_depth.crl.der");
1201 let borrowed_crl = BorrowedCertRevocationList::from_der(&crl[..]).unwrap();
1202 let owned_crl = borrowed_crl.to_owned().unwrap();
1203
1204 // It should be possible to convert a BorrowedCertRevocationList to a CertRevocationList.
1205 let _crl = CertRevocationList::from(borrowed_crl);
1206 // And similar for an OwnedCertRevocationList.
1207 let _crl = CertRevocationList::from(owned_crl);
1208 }
1209
1210 #[test]
1211 fn test_crl_authoritative_issuer_mismatch() {
1212 let crl = include_bytes!("../../tests/crls/crl.valid.der");
1213 let crl = CertRevocationList::from(BorrowedCertRevocationList::from_der(&crl[..]).unwrap());
1214
1215 let ee = CertificateDer::from(
1216 &include_bytes!("../../tests/client_auth_revocation/no_ku_chain.ee.der")[..],
1217 );
1218 let ee = EndEntityCert::try_from(&ee).unwrap();
1219 let path = PartialPath::new(&ee);
1220
1221 // The CRL should not be authoritative for an EE issued by a different issuer.
1222 assert!(!crl.authoritative(&path.node()));
1223 }
1224
1225 #[test]
1226 fn test_crl_authoritative_no_idp_no_cert_dp() {
1227 let crl =
1228 include_bytes!("../../tests/client_auth_revocation/ee_revoked_crl_ku_ee_depth.crl.der");
1229 let crl = CertRevocationList::from(BorrowedCertRevocationList::from_der(&crl[..]).unwrap());
1230
1231 let ee = CertificateDer::from(
1232 &include_bytes!("../../tests/client_auth_revocation/ku_chain.ee.der")[..],
1233 );
1234 let ee = EndEntityCert::try_from(&ee).unwrap();
1235 let path = PartialPath::new(&ee);
1236
1237 // The CRL should be considered authoritative, the issuers match, the CRL has no IDP and the
1238 // cert has no CRL DPs.
1239 assert!(crl.authoritative(&path.node()));
1240 }
1241
1242 #[test]
1243 fn test_crl_expired() {
1244 let crl = include_bytes!("../../tests/crls/crl.valid.der");
1245 let crl = CertRevocationList::from(BorrowedCertRevocationList::from_der(&crl[..]).unwrap());
1246 // Friday, February 2, 2024 8:26:19 PM GMT
1247 let time = UnixTime::since_unix_epoch(Duration::from_secs(1_706_905_579));
1248 assert!(matches!(
1249 crl.check_expiration(time),
1250 Err(Error::CrlExpired { .. })
1251 ));
1252 }
1253
1254 #[test]
1255 fn test_crl_not_expired() {
1256 let crl = include_bytes!("../../tests/crls/crl.valid.der");
1257 let crl = CertRevocationList::from(BorrowedCertRevocationList::from_der(&crl[..]).unwrap());
1258 // Wednesday, October 19, 2022 8:12:06 PM GMT
1259 let expiration_time = 1_666_210_326;
1260 let time = UnixTime::since_unix_epoch(Duration::from_secs(expiration_time - 1000));
1261
1262 assert!(matches!(crl.check_expiration(time), Ok(())));
1263 }
1264
1265 #[test]
1266 fn test_construct_owned_crl() {
1267 // It should be possible to construct an owned CRL directly from DER without needing
1268 // to build a borrowed representation first.
1269 let crl =
1270 include_bytes!("../../tests/client_auth_revocation/ee_revoked_crl_ku_ee_depth.crl.der");
1271 assert!(OwnedCertRevocationList::from_der(crl).is_ok())
1272 }
1273}