webpki/crl/
types.rs

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