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