webpki/crl/
types.rs

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