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