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}