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