webpki/
crl.rs

1// Copyright 2023 Daniel McCarney.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
10// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15use crate::cert::lenient_certificate_serial_number;
16use crate::der::Tag;
17use crate::signed_data::{self, SignedData};
18use crate::verify_cert::Budget;
19use crate::x509::{remember_extension, set_extension_once, Extension};
20use crate::{der, public_values_eq, Error, SignatureAlgorithm, Time};
21
22#[cfg(feature = "alloc")]
23use std::collections::HashMap;
24
25use private::Sealed;
26
27/// Operations over a RFC 5280[^1] profile Certificate Revocation List (CRL) required
28/// for revocation checking. Implemented by [`OwnedCertRevocationList`] and
29/// [`BorrowedCertRevocationList`].
30///
31/// [^1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
32pub trait CertRevocationList: Sealed {
33    /// Return the DER encoded issuer of the CRL.
34    fn issuer(&self) -> &[u8];
35
36    /// Try to find a revoked certificate in the CRL by DER encoded serial number. This
37    /// may yield an error if the CRL has malformed revoked certificates.
38    fn find_serial(&self, serial: &[u8]) -> Result<Option<BorrowedRevokedCert>, Error>;
39
40    /// Verify the CRL signature using the issuer's subject public key information (SPKI)
41    /// and a list of supported signature algorithms.
42    fn verify_signature(
43        &self,
44        supported_sig_algs: &[&SignatureAlgorithm],
45        issuer_spki: &[u8],
46    ) -> Result<(), Error>;
47}
48
49/// Owned representation of a RFC 5280[^1] profile Certificate Revocation List (CRL).
50///
51/// [^1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
52#[cfg(feature = "alloc")]
53#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
54#[allow(dead_code)] // we parse some fields we don't expose now, but may choose to expose in the future.
55#[derive(Debug, Clone)]
56pub struct OwnedCertRevocationList {
57    /// A map of the revoked certificates contained in then CRL, keyed by the DER encoding
58    /// of the revoked cert's serial number.
59    revoked_certs: HashMap<Vec<u8>, OwnedRevokedCert>,
60
61    issuer: Vec<u8>,
62
63    signed_data: signed_data::OwnedSignedData,
64}
65
66#[cfg(feature = "alloc")]
67impl Sealed for OwnedCertRevocationList {}
68
69#[cfg(feature = "alloc")]
70impl CertRevocationList for OwnedCertRevocationList {
71    fn issuer(&self) -> &[u8] {
72        &self.issuer
73    }
74
75    fn find_serial(&self, serial: &[u8]) -> Result<Option<BorrowedRevokedCert>, Error> {
76        // note: this is infallible for the owned representation because we process all
77        // revoked certificates at the time of construction to build the `revoked_certs` map,
78        // returning any encountered errors at that time.
79        Ok(self
80            .revoked_certs
81            .get(serial)
82            .map(|owned_revoked_cert| owned_revoked_cert.borrow()))
83    }
84
85    fn verify_signature(
86        &self,
87        supported_sig_algs: &[&SignatureAlgorithm],
88        issuer_spki: &[u8],
89    ) -> Result<(), Error> {
90        signed_data::verify_signed_data(
91            supported_sig_algs,
92            untrusted::Input::from(issuer_spki),
93            &self.signed_data.borrow(),
94            &mut Budget::default(),
95        )
96    }
97}
98
99/// Borrowed representation of a RFC 5280[^1] profile Certificate Revocation List (CRL).
100///
101/// [^1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
102#[derive(Debug)]
103pub struct BorrowedCertRevocationList<'a> {
104    /// A `SignedData` structure that can be passed to `verify_signed_data`.
105    signed_data: SignedData<'a>,
106
107    /// Identifies the entity that has signed and issued this
108    /// CRL.
109    issuer: untrusted::Input<'a>,
110
111    /// List of certificates revoked by the issuer in this CRL.
112    revoked_certs: untrusted::Input<'a>,
113}
114
115impl<'a> BorrowedCertRevocationList<'a> {
116    /// Try to parse the given bytes as a RFC 5280[^1] profile Certificate Revocation List (CRL).
117    ///
118    /// Webpki does not support:
119    ///   * CRL versions other than version 2.
120    ///   * CRLs missing the next update field.
121    ///   * CRLs missing certificate revocation list extensions.
122    ///   * Delta CRLs.
123    ///   * CRLs larger than (2^32)-1 bytes in size.
124    ///
125    /// [^1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
126    pub fn from_der(crl_der: &'a [u8]) -> Result<Self, Error> {
127        // Try to parse the CRL.
128        let reader = untrusted::Input::from(crl_der);
129        let (tbs_cert_list, signed_data) = reader.read_all(Error::BadDer, |crl_der| {
130            der::nested_limited(
131                crl_der,
132                Tag::Sequence,
133                Error::BadDer,
134                |signed_der| SignedData::from_der(signed_der, der::MAX_DER_SIZE),
135                der::MAX_DER_SIZE,
136            )
137        })?;
138
139        let crl = tbs_cert_list.read_all(Error::BadDer, |tbs_cert_list| {
140            // RFC 5280 §5.1.2.1:
141            //   This optional field describes the version of the encoded CRL.  When
142            //   extensions are used, as required by this profile, this field MUST be
143            //   present and MUST specify version 2 (the integer value is 1).
144            // RFC 5280 §5.2:
145            //   Conforming CRL issuers are REQUIRED to include the authority key
146            //   identifier (Section 5.2.1) and the CRL number (Section 5.2.3)
147            //   extensions in all CRLs issued.
148            // As a result of the above we parse this as a required section, not OPTIONAL.
149            // NOTE: Encoded value of version 2 is 1.
150            if der::small_nonnegative_integer(tbs_cert_list)? != 1 {
151                return Err(Error::UnsupportedCrlVersion);
152            }
153
154            // RFC 5280 §5.1.2.2:
155            //   This field MUST contain the same algorithm identifier as the
156            //   signatureAlgorithm field in the sequence CertificateList
157            let signature = der::expect_tag_and_get_value(tbs_cert_list, Tag::Sequence)?;
158            if !public_values_eq(signature, signed_data.algorithm) {
159                return Err(Error::SignatureAlgorithmMismatch);
160            }
161
162            // RFC 5280 §5.1.2.3:
163            //   The issuer field MUST contain a non-empty X.500 distinguished name (DN).
164            let issuer = der::expect_tag_and_get_value(tbs_cert_list, Tag::Sequence)?;
165
166            // RFC 5280 §5.1.2.4:
167            //    This field indicates the issue date of this CRL.  thisUpdate may be
168            //    encoded as UTCTime or GeneralizedTime.
169            // We do not presently enforce the correct choice of UTCTime or GeneralizedTime based on
170            // whether the date is post 2050.
171            der::time_choice(tbs_cert_list)?;
172
173            // While OPTIONAL in the ASN.1 module, RFC 5280 §5.1.2.5 says:
174            //   Conforming CRL issuers MUST include the nextUpdate field in all CRLs.
175            // We do not presently enforce the correct choice of UTCTime or GeneralizedTime based on
176            // whether the date is post 2050.
177            der::time_choice(tbs_cert_list)?;
178
179            // RFC 5280 §5.1.2.6:
180            //   When there are no revoked certificates, the revoked certificates list
181            //   MUST be absent
182            // TODO(@cpu): Do we care to support empty CRLs if we don't support delta CRLs?
183            let revoked_certs = if tbs_cert_list.peek(Tag::Sequence.into()) {
184                der::expect_tag_and_get_value_limited(
185                    tbs_cert_list,
186                    Tag::Sequence,
187                    der::MAX_DER_SIZE,
188                )?
189            } else {
190                untrusted::Input::from(&[])
191            };
192
193            let mut crl = BorrowedCertRevocationList {
194                signed_data,
195                issuer,
196                revoked_certs,
197            };
198
199            // RFC 5280 §5.1.2.7:
200            //   This field may only appear if the version is 2 (Section 5.1.2.1).  If
201            //   present, this field is a sequence of one or more CRL extensions.
202            // RFC 5280 §5.2:
203            //   Conforming CRL issuers are REQUIRED to include the authority key
204            //   identifier (Section 5.2.1) and the CRL number (Section 5.2.3)
205            //   extensions in all CRLs issued.
206            // As a result of the above we parse this as a required section, not OPTIONAL.
207            der::nested(
208                tbs_cert_list,
209                Tag::ContextSpecificConstructed0,
210                Error::MalformedExtensions,
211                |tagged| {
212                    der::nested_of_mut(
213                        tagged,
214                        Tag::Sequence,
215                        Tag::Sequence,
216                        Error::BadDer,
217                        |extension| {
218                            // RFC 5280 §5.2:
219                            //   If a CRL contains a critical extension
220                            //   that the application cannot process, then the application MUST NOT
221                            //   use that CRL to determine the status of certificates.  However,
222                            //   applications may ignore unrecognized non-critical extensions.
223                            crl.remember_extension(&Extension::parse(extension)?)
224                        },
225                    )
226                },
227            )?;
228
229            Ok(crl)
230        })?;
231
232        Ok(crl)
233    }
234
235    /// Convert the CRL to an [`OwnedCertRevocationList`]. This may error if any of the revoked
236    /// certificates in the CRL are malformed or contain unsupported features.
237    #[cfg(feature = "alloc")]
238    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
239    pub fn to_owned(&self) -> Result<OwnedCertRevocationList, Error> {
240        // Parse and collect the CRL's revoked cert entries, ensuring there are no errors. With
241        // the full set in-hand, create a lookup map by serial number for fast revocation checking.
242        let revoked_certs = self
243            .into_iter()
244            .collect::<Result<Vec<_>, _>>()?
245            .iter()
246            .map(|revoked_cert| (revoked_cert.serial_number.to_vec(), revoked_cert.to_owned()))
247            .collect::<HashMap<_, _>>();
248
249        Ok(OwnedCertRevocationList {
250            signed_data: self.signed_data.to_owned(),
251            issuer: self.issuer.as_slice_less_safe().to_vec(),
252            revoked_certs,
253        })
254    }
255
256    fn remember_extension(&mut self, extension: &Extension<'a>) -> Result<(), Error> {
257        remember_extension(extension, |id| {
258            match id {
259                // id-ce-cRLNumber 2.5.29.20 - RFC 5280 §5.2.3
260                20 => {
261                    // RFC 5280 §5.2.3:
262                    //   CRL verifiers MUST be able to handle CRLNumber values
263                    //   up to 20 octets.  Conforming CRL issuers MUST NOT use CRLNumber
264                    //   values longer than 20 octets.
265                    //
266                    extension.value.read_all(Error::InvalidCrlNumber, |der| {
267                        let crl_number = ring::io::der::positive_integer(der)
268                            .map_err(|_| Error::InvalidCrlNumber)?
269                            .big_endian_without_leading_zero();
270                        if crl_number.len() <= 20 {
271                            Ok(crl_number)
272                        } else {
273                            Err(Error::InvalidCrlNumber)
274                        }
275                    })?;
276                    // We enforce the cRLNumber is sensible, but don't retain the value for use.
277                    Ok(())
278                }
279
280                // id-ce-deltaCRLIndicator 2.5.29.27 - RFC 5280 §5.2.4
281                // We explicitly do not support delta CRLs.
282                27 => Err(Error::UnsupportedDeltaCrl),
283
284                // id-ce-issuingDistributionPoint 2.5.29.28 - RFC 5280 §5.2.4
285                //    Although the extension is critical, conforming implementations are not
286                //    required to support this extension.  However, implementations that do not
287                //    support this extension MUST either treat the status of any certificate not listed
288                //    on this CRL as unknown or locate another CRL that does not contain any
289                //    unrecognized critical extensions.
290                // TODO(@cpu): We may want to parse this enough to be able to error on indirectCRL
291                //  bool == true, or to enforce validation based on onlyContainsUserCerts,
292                //  onlyContainsCACerts, and onlySomeReasons. For now we use the carve-out where
293                //  we'll treat it as understood without parsing and consider certificates not found
294                //  in the list as unknown.
295                28 => Ok(()),
296
297                // id-ce-authorityKeyIdentifier 2.5.29.35 - RFC 5280 §5.2.1, §4.2.1.1
298                // We recognize the extension but don't retain its value for use.
299                35 => Ok(()),
300
301                // Unsupported extension
302                _ => extension.unsupported(),
303            }
304        })
305    }
306}
307
308impl Sealed for BorrowedCertRevocationList<'_> {}
309
310impl CertRevocationList for BorrowedCertRevocationList<'_> {
311    fn issuer(&self) -> &[u8] {
312        self.issuer.as_slice_less_safe()
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    fn verify_signature(
331        &self,
332        supported_sig_algs: &[&SignatureAlgorithm],
333        issuer_spki: &[u8],
334    ) -> Result<(), Error> {
335        signed_data::verify_signed_data(
336            supported_sig_algs,
337            untrusted::Input::from(issuer_spki),
338            &self.signed_data,
339            &mut Budget::default(),
340        )
341    }
342}
343
344impl<'a> IntoIterator for &'a BorrowedCertRevocationList<'a> {
345    type Item = Result<BorrowedRevokedCert<'a>, Error>;
346    type IntoIter = RevokedCerts<'a>;
347
348    fn into_iter(self) -> Self::IntoIter {
349        RevokedCerts {
350            reader: untrusted::Reader::new(self.revoked_certs),
351        }
352    }
353}
354
355#[derive(Debug)]
356pub struct RevokedCerts<'a> {
357    reader: untrusted::Reader<'a>,
358}
359
360impl<'a> Iterator for RevokedCerts<'a> {
361    type Item = Result<BorrowedRevokedCert<'a>, Error>;
362
363    fn next(&mut self) -> Option<Self::Item> {
364        (!self.reader.at_end()).then(|| BorrowedRevokedCert::from_der(&mut self.reader))
365    }
366}
367
368/// Owned representation of a RFC 5280[^1] profile Certificate Revocation List (CRL) revoked
369/// certificate entry.
370///
371/// Only available when the "alloc" feature is enabled.
372///
373/// [^1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
374#[cfg(feature = "alloc")]
375#[derive(Clone, Debug)]
376pub struct OwnedRevokedCert {
377    /// Serial number of the revoked certificate.
378    pub serial_number: Vec<u8>,
379
380    /// The date at which the CA processed the revocation.
381    pub revocation_date: Time,
382
383    /// Identifies the reason for the certificate revocation. When absent, the revocation reason
384    /// is assumed to be RevocationReason::Unspecified. For consistency with other extensions
385    /// and to ensure only one revocation reason extension may be present we maintain this field
386    /// as optional instead of defaulting to unspecified.
387    pub reason_code: Option<RevocationReason>,
388
389    /// Provides the date on which it is known or suspected that the private key was compromised or
390    /// that the certificate otherwise became invalid. This date may be earlier than the revocation
391    /// date which is the date at which the CA processed the revocation.
392    pub invalidity_date: Option<Time>,
393}
394
395#[cfg(feature = "alloc")]
396impl OwnedRevokedCert {
397    /// Convert the owned representation of this revoked cert to a borrowed version.
398    pub fn borrow(&self) -> BorrowedRevokedCert {
399        BorrowedRevokedCert {
400            serial_number: &self.serial_number,
401            revocation_date: self.revocation_date,
402            reason_code: self.reason_code,
403            invalidity_date: self.invalidity_date,
404        }
405    }
406}
407
408/// Borrowed representation of a RFC 5280[^1] profile Certificate Revocation List (CRL) revoked
409/// certificate entry.
410///
411/// [^1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
412#[derive(Debug)]
413pub struct BorrowedRevokedCert<'a> {
414    /// Serial number of the revoked certificate.
415    pub serial_number: &'a [u8],
416
417    /// The date at which the CA processed the revocation.
418    pub revocation_date: Time,
419
420    /// Identifies the reason for the certificate revocation. When absent, the revocation reason
421    /// is assumed to be RevocationReason::Unspecified. For consistency with other extensions
422    /// and to ensure only one revocation reason extension may be present we maintain this field
423    /// as optional instead of defaulting to unspecified.
424    pub reason_code: Option<RevocationReason>,
425
426    /// Provides the date on which it is known or suspected that the private key was compromised or
427    /// that the certificate otherwise became invalid. This date may be earlier than the revocation
428    /// date which is the date at which the CA processed the revocation.
429    pub invalidity_date: Option<Time>,
430}
431
432impl<'a> BorrowedRevokedCert<'a> {
433    /// Construct an owned representation of the revoked certificate.
434    #[cfg(feature = "alloc")]
435    #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
436    pub fn to_owned(&self) -> OwnedRevokedCert {
437        OwnedRevokedCert {
438            serial_number: self.serial_number.to_vec(),
439            revocation_date: self.revocation_date,
440            reason_code: self.reason_code,
441            invalidity_date: self.invalidity_date,
442        }
443    }
444
445    fn from_der(der: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
446        der::nested(der, Tag::Sequence, Error::BadDer, |der| {
447            // RFC 5280 §4.1.2.2:
448            //    Certificate users MUST be able to handle serialNumber values up to 20 octets.
449            //    Conforming CAs MUST NOT use serialNumber values longer than 20 octets.
450            //
451            //    Note: Non-conforming CAs may issue certificates with serial numbers
452            //    that are negative or zero.  Certificate users SHOULD be prepared to
453            //    gracefully handle such certificates.
454            // Like the handling in cert.rs we choose to be lenient here, not enforcing the length
455            // of a CRL revoked certificate's serial number is less than 20 octets in encoded form.
456            let serial_number = lenient_certificate_serial_number(der)
457                .map_err(|_| Error::InvalidSerialNumber)?
458                .as_slice_less_safe();
459
460            let revocation_date = der::time_choice(der)?;
461
462            let mut revoked_cert = BorrowedRevokedCert {
463                serial_number,
464                revocation_date,
465                reason_code: None,
466                invalidity_date: None,
467            };
468
469            // RFC 5280 §5.3:
470            //   Support for the CRL entry extensions defined in this specification is
471            //   optional for conforming CRL issuers and applications.  However, CRL
472            //   issuers SHOULD include reason codes (Section 5.3.1) and invalidity
473            //   dates (Section 5.3.2) whenever this information is available.
474            if der.at_end() {
475                return Ok(revoked_cert);
476            }
477
478            // It would be convenient to use der::nested_of_mut here to unpack a SEQUENCE of one or
479            // more SEQUENCEs, however CAs have been mis-encoding the absence of extensions as an
480            // empty SEQUENCE so we must be tolerant of that.
481            let ext_seq = der::expect_tag_and_get_value(der, Tag::Sequence)?;
482            if ext_seq.is_empty() {
483                return Ok(revoked_cert);
484            }
485
486            let mut reader = untrusted::Reader::new(ext_seq);
487            loop {
488                der::nested(&mut reader, Tag::Sequence, Error::BadDer, |ext_der| {
489                    // RFC 5280 §5.3:
490                    //   If a CRL contains a critical CRL entry extension that the application cannot
491                    //   process, then the application MUST NOT use that CRL to determine the
492                    //   status of any certificates.  However, applications may ignore
493                    //   unrecognized non-critical CRL entry extensions.
494                    revoked_cert.remember_extension(&Extension::parse(ext_der)?)
495                })?;
496                if reader.at_end() {
497                    break;
498                }
499            }
500
501            Ok(revoked_cert)
502        })
503    }
504
505    fn remember_extension(&mut self, extension: &Extension<'a>) -> Result<(), Error> {
506        remember_extension(extension, |id| {
507            match id {
508                // id-ce-cRLReasons 2.5.29.21 - RFC 5280 §5.3.1.
509                21 => set_extension_once(&mut self.reason_code, || {
510                    RevocationReason::from_der(extension.value)
511                }),
512
513                // id-ce-invalidityDate 2.5.29.24 - RFC 5280 §5.3.2.
514                24 => set_extension_once(&mut self.invalidity_date, || {
515                    extension.value.read_all(Error::BadDer, der::time_choice)
516                }),
517
518                // id-ce-certificateIssuer 2.5.29.29 - RFC 5280 §5.3.3.
519                //   This CRL entry extension identifies the certificate issuer associated
520                //   with an entry in an indirect CRL, that is, a CRL that has the
521                //   indirectCRL indicator set in its issuing distribution point
522                //   extension.
523                // We choose not to support indirect CRLs and so turn this into a more specific
524                // error rather than simply letting it fail as an unsupported critical extension.
525                29 => Err(Error::UnsupportedIndirectCrl),
526
527                // Unsupported extension
528                _ => extension.unsupported(),
529            }
530        })
531    }
532}
533
534/// Identifies the reason a certificate was revoked.
535/// See RFC 5280 §5.3.1[^1]
536///
537/// [^1] <https://www.rfc-editor.org/rfc/rfc5280#section-5.3.1>
538#[derive(Debug, Clone, Copy, Eq, PartialEq)]
539#[allow(missing_docs)] // Not much to add above the code name.
540pub enum RevocationReason {
541    /// Unspecified should not be used, and is instead assumed by the absence of a RevocationReason
542    /// extension.
543    Unspecified = 0,
544    KeyCompromise = 1,
545    CaCompromise = 2,
546    AffiliationChanged = 3,
547    Superseded = 4,
548    CessationOfOperation = 5,
549    CertificateHold = 6,
550    // 7 is not used.
551    /// RemoveFromCrl only appears in delta CRLs that are unsupported.
552    RemoveFromCrl = 8,
553    PrivilegeWithdrawn = 9,
554    AaCompromise = 10,
555}
556
557impl RevocationReason {
558    // RFC 5280 §5.3.1.
559    fn from_der(value: untrusted::Input<'_>) -> Result<Self, Error> {
560        value.read_all(Error::BadDer, |enumerated_reason| {
561            let value = der::expect_tag(enumerated_reason, Tag::Enum)?;
562            Self::try_from(value.value().read_all(Error::BadDer, |reason| {
563                reason.read_byte().map_err(|_| Error::BadDer)
564            })?)
565        })
566    }
567}
568
569impl TryFrom<u8> for RevocationReason {
570    type Error = Error;
571
572    fn try_from(value: u8) -> Result<Self, Self::Error> {
573        // See https://www.rfc-editor.org/rfc/rfc5280#section-5.3.1
574        match value {
575            0 => Ok(RevocationReason::Unspecified),
576            1 => Ok(RevocationReason::KeyCompromise),
577            2 => Ok(RevocationReason::CaCompromise),
578            3 => Ok(RevocationReason::AffiliationChanged),
579            4 => Ok(RevocationReason::Superseded),
580            5 => Ok(RevocationReason::CessationOfOperation),
581            6 => Ok(RevocationReason::CertificateHold),
582            // 7 is not used.
583            8 => Ok(RevocationReason::RemoveFromCrl),
584            9 => Ok(RevocationReason::PrivilegeWithdrawn),
585            10 => Ok(RevocationReason::AaCompromise),
586            _ => Err(Error::UnsupportedRevocationReason),
587        }
588    }
589}
590
591mod private {
592    pub trait Sealed {}
593}
594
595#[cfg(test)]
596mod tests {
597    use alloc::vec::Vec;
598
599    use crate::{Error, RevocationReason};
600
601    #[test]
602    fn revocation_reasons() {
603        // Test that we can convert the allowed u8 revocation reason code values into the expected
604        // revocation reason variant.
605        let testcases: Vec<(u8, RevocationReason)> = vec![
606            (0, RevocationReason::Unspecified),
607            (1, RevocationReason::KeyCompromise),
608            (2, RevocationReason::CaCompromise),
609            (3, RevocationReason::AffiliationChanged),
610            (4, RevocationReason::Superseded),
611            (5, RevocationReason::CessationOfOperation),
612            (6, RevocationReason::CertificateHold),
613            // Note: 7 is unused.
614            (8, RevocationReason::RemoveFromCrl),
615            (9, RevocationReason::PrivilegeWithdrawn),
616            (10, RevocationReason::AaCompromise),
617        ];
618        for tc in testcases.iter() {
619            let (id, expected) = tc;
620            let actual = <u8 as TryInto<RevocationReason>>::try_into(*id)
621                .expect("unexpected reason code conversion error");
622            assert_eq!(actual, *expected);
623            #[cfg(feature = "alloc")]
624            {
625                // revocation reasons should be Debug.
626                println!("{:?}", actual);
627            }
628        }
629
630        // Unsupported/unknown revocation reason codes should produce an error.
631        let res = <u8 as TryInto<RevocationReason>>::try_into(7);
632        assert!(matches!(res, Err(Error::UnsupportedRevocationReason)));
633    }
634
635    #[test]
636    #[cfg(feature = "alloc")]
637    // redundant clone, clone_on_copy allowed to verify derived traits.
638    #[allow(clippy::redundant_clone, clippy::clone_on_copy)]
639    fn test_derived_traits() {
640        let crl = crate::crl::BorrowedCertRevocationList::from_der(include_bytes!(
641            "../tests/crls/crl.valid.der"
642        ))
643        .unwrap();
644        println!("{:?}", crl); // BorrowedCertRevocationList should be debug.
645
646        let owned_crl = crl.to_owned().unwrap();
647        println!("{:?}", owned_crl); // OwnedCertRevocationList should be debug.
648        let _ = owned_crl.clone(); // OwnedCertRevocationList should be clone.
649
650        let mut revoked_certs = crl.into_iter();
651        println!("{:?}", revoked_certs); // RevokedCert should be debug.
652
653        let revoked_cert = revoked_certs.next().unwrap().unwrap();
654        println!("{:?}", revoked_cert); // BorrowedRevokedCert should be debug.
655
656        let owned_revoked_cert = revoked_cert.to_owned();
657        println!("{:?}", owned_revoked_cert); // OwnedRevokedCert should be debug.
658        let _ = owned_revoked_cert.clone(); // OwnedRevokedCert should be clone.
659    }
660}