Skip to main content

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}