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