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