1// Copyright 2015 Brian Smith.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
10// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15use pki_types::{CertificateDer, DnsName};
16
17use crate::der::{self, DerIterator, FromDer, Tag, CONSTRUCTED, CONTEXT_SPECIFIC};
18use crate::error::{DerTypeId, Error};
19use crate::public_values_eq;
20use crate::signed_data::SignedData;
21use crate::subject_name::{GeneralName, NameIterator, WildcardDnsNameRef};
22use crate::x509::{remember_extension, set_extension_once, DistributionPointName, Extension};
23
24/// A parsed X509 certificate.
25pub struct Cert<'a> {
26 pub(crate) serial: untrusted::Input<'a>,
27 pub(crate) signed_data: SignedData<'a>,
28 pub(crate) issuer: untrusted::Input<'a>,
29 pub(crate) validity: untrusted::Input<'a>,
30 pub(crate) subject: untrusted::Input<'a>,
31 pub(crate) spki: untrusted::Input<'a>,
32
33 pub(crate) basic_constraints: Option<untrusted::Input<'a>>,
34 // key usage (KU) extension (if any). When validating certificate revocation lists (CRLs) this
35 // field will be consulted to determine if the cert is allowed to sign CRLs. For cert validation
36 // this field is ignored (for more detail see in `verify_cert.rs` and
37 // `check_issuer_independent_properties`).
38 pub(crate) key_usage: Option<untrusted::Input<'a>>,
39 pub(crate) eku: Option<untrusted::Input<'a>>,
40 pub(crate) name_constraints: Option<untrusted::Input<'a>>,
41 pub(crate) subject_alt_name: Option<untrusted::Input<'a>>,
42 pub(crate) crl_distribution_points: Option<untrusted::Input<'a>>,
43
44 der: CertificateDer<'a>,
45}
46
47impl<'a> Cert<'a> {
48 pub(crate) fn from_der(cert_der: untrusted::Input<'a>) -> Result<Self, Error> {
49 let (tbs, signed_data) =
50 cert_der.read_all(Error::TrailingData(DerTypeId::Certificate), |cert_der| {
51 der::nested(
52 cert_der,
53 der::Tag::Sequence,
54 Error::TrailingData(DerTypeId::SignedData),
55 |der| {
56 // limited to SEQUENCEs of size 2^16 or less.
57 SignedData::from_der(der, der::TWO_BYTE_DER_SIZE)
58 },
59 )
60 })?;
61
62 tbs.read_all(
63 Error::TrailingData(DerTypeId::CertificateTbsCertificate),
64 |tbs| {
65 version3(tbs)?;
66
67 let serial = lenient_certificate_serial_number(tbs)?;
68
69 let signature = der::expect_tag(tbs, der::Tag::Sequence)?;
70 // TODO: In mozilla::pkix, the comparison is done based on the
71 // normalized value (ignoring whether or not there is an optional NULL
72 // parameter for RSA-based algorithms), so this may be too strict.
73 if !public_values_eq(signature, signed_data.algorithm) {
74 return Err(Error::SignatureAlgorithmMismatch);
75 }
76
77 let issuer = der::expect_tag(tbs, der::Tag::Sequence)?;
78 let validity = der::expect_tag(tbs, der::Tag::Sequence)?;
79 let subject = der::expect_tag(tbs, der::Tag::Sequence)?;
80 let spki = der::expect_tag(tbs, der::Tag::Sequence)?;
81
82 // In theory there could be fields [1] issuerUniqueID and [2]
83 // subjectUniqueID, but in practice there never are, and to keep the
84 // code small and simple we don't accept any certificates that do
85 // contain them.
86
87 let mut cert = Cert {
88 signed_data,
89 serial,
90 issuer,
91 validity,
92 subject,
93 spki,
94
95 basic_constraints: None,
96 key_usage: None,
97 eku: None,
98 name_constraints: None,
99 subject_alt_name: None,
100 crl_distribution_points: None,
101
102 der: CertificateDer::from(cert_der.as_slice_less_safe()),
103 };
104
105 if !tbs.at_end() {
106 der::nested(
107 tbs,
108 der::Tag::ContextSpecificConstructed3,
109 Error::TrailingData(DerTypeId::CertificateExtensions),
110 |tagged| {
111 der::nested_of_mut(
112 tagged,
113 der::Tag::Sequence,
114 der::Tag::Sequence,
115 Error::TrailingData(DerTypeId::Extension),
116 |extension| {
117 remember_cert_extension(
118 &mut cert,
119 &Extension::from_der(extension)?,
120 )
121 },
122 )
123 },
124 )?;
125 }
126
127 Ok(cert)
128 },
129 )
130 }
131
132 /// Returns a list of valid DNS names provided in the subject alternative names extension
133 ///
134 /// This function must not be used to implement custom DNS name verification.
135 /// Checking that a certificate is valid for a given subject name should always be done with
136 /// [EndEntityCert::verify_is_valid_for_subject_name].
137 ///
138 /// [EndEntityCert::verify_is_valid_for_subject_name]: crate::EndEntityCert::verify_is_valid_for_subject_name
139 pub fn valid_dns_names(&self) -> impl Iterator<Item = &str> {
140 NameIterator::new(Some(self.subject), self.subject_alt_name).filter_map(|result| {
141 let presented_id = match result.ok()? {
142 GeneralName::DnsName(presented) => presented,
143 _ => return None,
144 };
145
146 // if the name could be converted to a DNS name, return it; otherwise,
147 // keep going.
148 let dns_str = core::str::from_utf8(presented_id.as_slice_less_safe()).ok()?;
149 match DnsName::try_from(dns_str) {
150 Ok(_) => Some(dns_str),
151 Err(_) => {
152 match WildcardDnsNameRef::try_from_ascii(presented_id.as_slice_less_safe()) {
153 Ok(wildcard_dns_name) => Some(wildcard_dns_name.as_str()),
154 Err(_) => None,
155 }
156 }
157 }
158 })
159 }
160
161 /// Raw DER encoded certificate serial number.
162 pub fn serial(&self) -> &[u8] {
163 self.serial.as_slice_less_safe()
164 }
165
166 /// Raw DER encoded certificate issuer.
167 pub fn issuer(&self) -> &[u8] {
168 self.issuer.as_slice_less_safe()
169 }
170
171 /// Raw DER encoded certificate subject.
172 pub fn subject(&self) -> &[u8] {
173 self.subject.as_slice_less_safe()
174 }
175
176 /// Returns an iterator over the certificate's cRLDistributionPoints extension values, if any.
177 pub(crate) fn crl_distribution_points(
178 &self,
179 ) -> Option<impl Iterator<Item = Result<CrlDistributionPoint<'a>, Error>>> {
180 self.crl_distribution_points.map(DerIterator::new)
181 }
182
183 /// Raw DER encoded representation of the certificate.
184 pub fn der(&self) -> CertificateDer<'a> {
185 self.der.clone() // This is cheap, just cloning a reference.
186 }
187}
188
189// mozilla::pkix supports v1, v2, v3, and v4, including both the implicit
190// (correct) and explicit (incorrect) encoding of v1. We allow only v3.
191fn version3(input: &mut untrusted::Reader) -> Result<(), Error> {
192 der::nested(
193 input,
194 der::Tag::ContextSpecificConstructed0,
195 Error::UnsupportedCertVersion,
196 |input: &mut Reader<'_>| {
197 let version: u8 = u8::from_der(reader:input)?;
198 if version != 2 {
199 // v3
200 return Err(Error::UnsupportedCertVersion);
201 }
202 Ok(())
203 },
204 )
205}
206
207pub(crate) fn lenient_certificate_serial_number<'a>(
208 input: &mut untrusted::Reader<'a>,
209) -> Result<untrusted::Input<'a>, Error> {
210 // https://tools.ietf.org/html/rfc5280#section-4.1.2.2:
211 // * Conforming CAs MUST NOT use serialNumber values longer than 20 octets."
212 // * "The serial number MUST be a positive integer [...]"
213 //
214 // However, we don't enforce these constraints, as there are widely-deployed trust anchors
215 // and many X.509 implementations in common use that violate these constraints. This is called
216 // out by the same section of RFC 5280 as cited above:
217 // Note: Non-conforming CAs may issue certificates with serial numbers
218 // that are negative or zero. Certificate users SHOULD be prepared to
219 // gracefully handle such certificates.
220 der::expect_tag(input, Tag::Integer)
221}
222
223fn remember_cert_extension<'a>(
224 cert: &mut Cert<'a>,
225 extension: &Extension<'a>,
226) -> Result<(), Error> {
227 // We don't do anything with certificate policies so we can safely ignore
228 // all policy-related stuff. We assume that the policy-related extensions
229 // are not marked critical.
230
231 remember_extension(extension, |id| {
232 let out = match id {
233 // id-ce-keyUsage 2.5.29.15.
234 15 => &mut cert.key_usage,
235
236 // id-ce-subjectAltName 2.5.29.17
237 17 => &mut cert.subject_alt_name,
238
239 // id-ce-basicConstraints 2.5.29.19
240 19 => &mut cert.basic_constraints,
241
242 // id-ce-nameConstraints 2.5.29.30
243 30 => &mut cert.name_constraints,
244
245 // id-ce-cRLDistributionPoints 2.5.29.31
246 31 => &mut cert.crl_distribution_points,
247
248 // id-ce-extKeyUsage 2.5.29.37
249 37 => &mut cert.eku,
250
251 // Unsupported extension
252 _ => return extension.unsupported(),
253 };
254
255 set_extension_once(out, || {
256 extension.value.read_all(Error::BadDer, |value| match id {
257 // Unlike the other extensions we remember KU is a BitString and not a Sequence. We
258 // read the raw bytes here and parse at the time of use.
259 15 => Ok(value.read_bytes_to_end()),
260 // All other remembered certificate extensions are wrapped in a Sequence.
261 _ => der::expect_tag(value, Tag::Sequence),
262 })
263 })
264 })
265}
266
267/// A certificate revocation list (CRL) distribution point, describing a source of
268/// CRL information for a given certificate as described in RFC 5280 section 4.2.3.13[^1].
269///
270/// [^1]: <https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.13>
271pub(crate) struct CrlDistributionPoint<'a> {
272 /// distributionPoint describes the location of CRL information.
273 distribution_point: Option<untrusted::Input<'a>>,
274
275 /// reasons holds a bit flag set of certificate revocation reasons associated with the
276 /// CRL distribution point.
277 pub(crate) reasons: Option<der::BitStringFlags<'a>>,
278
279 /// when the CRL issuer is not the certificate issuer, crl_issuer identifies the issuer of the
280 /// CRL.
281 pub(crate) crl_issuer: Option<untrusted::Input<'a>>,
282}
283
284impl<'a> CrlDistributionPoint<'a> {
285 /// Return the distribution point names (if any).
286 pub(crate) fn names(&self) -> Result<Option<DistributionPointName<'a>>, Error> {
287 self.distribution_point
288 .map(|input: Input<'_>| DistributionPointName::from_der(&mut untrusted::Reader::new(input)))
289 .transpose()
290 }
291}
292
293impl<'a> FromDer<'a> for CrlDistributionPoint<'a> {
294 fn from_der(reader: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
295 // RFC 5280 section §4.2.1.13:
296 // A DistributionPoint consists of three fields, each of which is optional:
297 // distributionPoint, reasons, and cRLIssuer.
298 let mut result = CrlDistributionPoint {
299 distribution_point: None,
300 reasons: None,
301 crl_issuer: None,
302 };
303
304 der::nested(
305 reader,
306 Tag::Sequence,
307 Error::TrailingData(Self::TYPE_ID),
308 |der| {
309 const DISTRIBUTION_POINT_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED;
310 const REASONS_TAG: u8 = CONTEXT_SPECIFIC | 1;
311 const CRL_ISSUER_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 2;
312
313 while !der.at_end() {
314 let (tag, value) = der::read_tag_and_get_value(der)?;
315 match tag {
316 DISTRIBUTION_POINT_TAG => {
317 set_extension_once(&mut result.distribution_point, || Ok(value))?
318 }
319 REASONS_TAG => set_extension_once(&mut result.reasons, || {
320 der::bit_string_flags(value)
321 })?,
322 CRL_ISSUER_TAG => set_extension_once(&mut result.crl_issuer, || Ok(value))?,
323 _ => return Err(Error::BadDer),
324 }
325 }
326
327 // RFC 5280 section §4.2.1.13:
328 // a DistributionPoint MUST NOT consist of only the reasons field; either distributionPoint or
329 // cRLIssuer MUST be present.
330 match (result.distribution_point, result.crl_issuer) {
331 (None, None) => Err(Error::MalformedExtensions),
332 _ => Ok(result),
333 }
334 },
335 )
336 }
337
338 const TYPE_ID: DerTypeId = DerTypeId::CrlDistributionPoint;
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344 #[cfg(feature = "alloc")]
345 use crate::crl::RevocationReason;
346 #[cfg(feature = "alloc")]
347 use crate::error::Error;
348 #[cfg(feature = "alloc")]
349 use crate::subject_name::GeneralName;
350
351 #[test]
352 // Note: cert::parse_cert is crate-local visibility, and EndEntityCert doesn't expose the
353 // inner Cert, or the serial number. As a result we test that the raw serial value
354 // is read correctly here instead of in tests/integration.rs.
355 fn test_serial_read() {
356 let ee = include_bytes!("../tests/misc/serial_neg_ee.der");
357 let cert = Cert::from_der(untrusted::Input::from(ee)).expect("failed to parse certificate");
358 assert_eq!(cert.serial.as_slice_less_safe(), &[255, 33, 82, 65, 17]);
359
360 let ee = include_bytes!("../tests/misc/serial_large_positive.der");
361 let cert = Cert::from_der(untrusted::Input::from(ee)).expect("failed to parse certificate");
362 assert_eq!(
363 cert.serial.as_slice_less_safe(),
364 &[
365 0, 230, 9, 254, 122, 234, 0, 104, 140, 224, 36, 180, 237, 32, 27, 31, 239, 82, 180,
366 68, 209
367 ]
368 )
369 }
370
371 #[test]
372 #[cfg(feature = "alloc")]
373 fn test_crl_distribution_point_netflix() {
374 let ee = include_bytes!("../tests/netflix/ee.der");
375 let inter = include_bytes!("../tests/netflix/inter.der");
376 let ee_cert = Cert::from_der(untrusted::Input::from(ee)).expect("failed to parse EE cert");
377 let cert =
378 Cert::from_der(untrusted::Input::from(inter)).expect("failed to parse certificate");
379
380 // The end entity certificate shouldn't have a distribution point.
381 assert!(ee_cert.crl_distribution_points.is_none());
382
383 // We expect to be able to parse the intermediate certificate's CRL distribution points.
384 let crl_distribution_points = cert
385 .crl_distribution_points()
386 .expect("missing distribution points extension")
387 .collect::<Result<Vec<_>, Error>>()
388 .expect("failed to parse distribution points");
389
390 // There should be one distribution point present.
391 assert_eq!(crl_distribution_points.len(), 1);
392 let crl_distribution_point: &CrlDistributionPoint = crl_distribution_points
393 .first()
394 .expect("missing distribution point");
395
396 // The distribution point shouldn't have revocation reasons listed.
397 assert!(crl_distribution_point.reasons.is_none());
398
399 // The distribution point shouldn't have a CRL issuer listed.
400 assert!(crl_distribution_point.crl_issuer.is_none());
401
402 // We should be able to parse the distribution point name.
403 let distribution_point_name = crl_distribution_point
404 .names()
405 .expect("failed to parse distribution point names")
406 .expect("missing distribution point name");
407
408 // We expect the distribution point name to be a sequence of GeneralNames, not a name
409 // relative to the CRL issuer.
410 let names = match distribution_point_name {
411 DistributionPointName::NameRelativeToCrlIssuer => {
412 panic!("unexpected name relative to crl issuer")
413 }
414 DistributionPointName::FullName(names) => names,
415 };
416
417 // The general names should parse.
418 let names = names
419 .collect::<Result<Vec<_>, Error>>()
420 .expect("failed to parse general names");
421
422 // There should be one general name.
423 assert_eq!(names.len(), 1);
424 let name: &GeneralName = names.first().expect("missing general name");
425
426 // The general name should be a URI matching the expected value.
427 match name {
428 GeneralName::UniformResourceIdentifier(uri) => {
429 assert_eq!(
430 uri.as_slice_less_safe(),
431 "http://s.symcb.com/pca3-g3.crl".as_bytes()
432 );
433 }
434 _ => panic!("unexpected general name type"),
435 }
436 }
437
438 #[test]
439 #[cfg(feature = "alloc")]
440 fn test_crl_distribution_point_with_reasons() {
441 let der = include_bytes!("../tests/crl_distrib_point/with_reasons.der");
442 let cert =
443 Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
444
445 // We expect to be able to parse the intermediate certificate's CRL distribution points.
446 let crl_distribution_points = cert
447 .crl_distribution_points()
448 .expect("missing distribution points extension")
449 .collect::<Result<Vec<_>, Error>>()
450 .expect("failed to parse distribution points");
451
452 // There should be one distribution point present.
453 assert_eq!(crl_distribution_points.len(), 1);
454 let crl_distribution_point: &CrlDistributionPoint = crl_distribution_points
455 .first()
456 .expect("missing distribution point");
457
458 // The distribution point should include the expected revocation reasons, and no others.
459 let reasons = crl_distribution_point
460 .reasons
461 .as_ref()
462 .expect("missing revocation reasons");
463 let expected = &[
464 RevocationReason::KeyCompromise,
465 RevocationReason::AffiliationChanged,
466 ];
467 for reason in RevocationReason::iter() {
468 #[allow(clippy::as_conversions)]
469 // revocation reason is u8, infallible to convert to usize.
470 match expected.contains(&reason) {
471 true => assert!(reasons.bit_set(reason as usize)),
472 false => assert!(!reasons.bit_set(reason as usize)),
473 }
474 }
475 }
476
477 #[test]
478 #[cfg(feature = "alloc")]
479 fn test_crl_distribution_point_with_crl_issuer() {
480 let der = include_bytes!("../tests/crl_distrib_point/with_crl_issuer.der");
481 let cert =
482 Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
483
484 // We expect to be able to parse the intermediate certificate's CRL distribution points.
485 let crl_distribution_points = cert
486 .crl_distribution_points()
487 .expect("missing distribution points extension")
488 .collect::<Result<Vec<_>, Error>>()
489 .expect("failed to parse distribution points");
490
491 // There should be one distribution point present.
492 assert_eq!(crl_distribution_points.len(), 1);
493 let crl_distribution_point: &CrlDistributionPoint = crl_distribution_points
494 .first()
495 .expect("missing distribution point");
496
497 // The CRL issuer should be present, but not anything else.
498 assert!(crl_distribution_point.crl_issuer.is_some());
499 assert!(crl_distribution_point.distribution_point.is_none());
500 assert!(crl_distribution_point.reasons.is_none());
501 }
502
503 #[test]
504 #[cfg(feature = "alloc")]
505 fn test_crl_distribution_point_bad_der() {
506 // Created w/
507 // ascii2der -i tests/crl_distrib_point/unknown_tag.der.txt -o tests/crl_distrib_point/unknown_tag.der
508 let der = include_bytes!("../tests/crl_distrib_point/unknown_tag.der");
509 let cert =
510 Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
511
512 // We expect there to be a distribution point extension, but parsing it should fail
513 // due to the unknown tag in the SEQUENCE.
514 let result = cert
515 .crl_distribution_points()
516 .expect("missing distribution points extension")
517 .collect::<Result<Vec<_>, Error>>();
518 assert!(matches!(result, Err(Error::BadDer)));
519 }
520
521 #[test]
522 #[cfg(feature = "alloc")]
523 fn test_crl_distribution_point_only_reasons() {
524 // Created w/
525 // ascii2der -i tests/crl_distrib_point/only_reasons.der.txt -o tests/crl_distrib_point/only_reasons.der
526 let der = include_bytes!("../tests/crl_distrib_point/only_reasons.der");
527 let cert =
528 Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
529
530 // We expect there to be a distribution point extension, but parsing it should fail
531 // because no distribution points or cRLIssuer are set in the SEQUENCE, just reason codes.
532 let result = cert
533 .crl_distribution_points()
534 .expect("missing distribution points extension")
535 .collect::<Result<Vec<_>, Error>>();
536 assert!(matches!(result, Err(Error::MalformedExtensions)));
537 }
538
539 #[test]
540 #[cfg(feature = "alloc")]
541 fn test_crl_distribution_point_name_relative_to_issuer() {
542 let der = include_bytes!("../tests/crl_distrib_point/dp_name_relative_to_issuer.der");
543 let cert =
544 Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
545
546 // We expect to be able to parse the intermediate certificate's CRL distribution points.
547 let crl_distribution_points = cert
548 .crl_distribution_points()
549 .expect("missing distribution points extension")
550 .collect::<Result<Vec<_>, Error>>()
551 .expect("failed to parse distribution points");
552
553 // There should be one distribution point present.
554 assert_eq!(crl_distribution_points.len(), 1);
555 let crl_distribution_point: &CrlDistributionPoint = crl_distribution_points
556 .first()
557 .expect("missing distribution point");
558
559 assert!(crl_distribution_point.crl_issuer.is_none());
560 assert!(crl_distribution_point.reasons.is_none());
561
562 // We should be able to parse the distribution point name.
563 let distribution_point_name = crl_distribution_point
564 .names()
565 .expect("failed to parse distribution point names")
566 .expect("missing distribution point name");
567
568 // We expect the distribution point name to be a name relative to the CRL issuer.
569 assert!(matches!(
570 distribution_point_name,
571 DistributionPointName::NameRelativeToCrlIssuer
572 ));
573 }
574
575 #[test]
576 #[cfg(feature = "alloc")]
577 fn test_crl_distribution_point_unknown_name_tag() {
578 // Created w/
579 // ascii2der -i tests/crl_distrib_point/unknown_dp_name_tag.der.txt > tests/crl_distrib_point/unknown_dp_name_tag.der
580 let der = include_bytes!("../tests/crl_distrib_point/unknown_dp_name_tag.der");
581 let cert =
582 Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
583
584 // We expect to be able to parse the intermediate certificate's CRL distribution points.
585 let crl_distribution_points = cert
586 .crl_distribution_points()
587 .expect("missing distribution points extension")
588 .collect::<Result<Vec<_>, Error>>()
589 .expect("failed to parse distribution points");
590
591 // There should be one distribution point present.
592 assert_eq!(crl_distribution_points.len(), 1);
593 let crl_distribution_point: &CrlDistributionPoint = crl_distribution_points
594 .first()
595 .expect("missing distribution point");
596
597 // Parsing the distrubition point names should fail due to the unknown name tag.
598 let result = crl_distribution_point.names();
599 assert!(matches!(result, Err(Error::BadDer)))
600 }
601
602 #[test]
603 #[cfg(feature = "alloc")]
604 fn test_crl_distribution_point_multiple() {
605 let der = include_bytes!("../tests/crl_distrib_point/multiple_distribution_points.der");
606 let cert =
607 Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
608
609 // We expect to be able to parse the intermediate certificate's CRL distribution points.
610 let crl_distribution_points = cert
611 .crl_distribution_points()
612 .expect("missing distribution points extension")
613 .collect::<Result<Vec<_>, Error>>()
614 .expect("failed to parse distribution points");
615
616 // There should be two distribution points present.
617 let (point_a, point_b): (&CrlDistributionPoint, &CrlDistributionPoint) = (
618 crl_distribution_points
619 .first()
620 .expect("missing first distribution point"),
621 crl_distribution_points
622 .get(1)
623 .expect("missing second distribution point"),
624 );
625
626 fn get_names<'a>(
627 point: &'a CrlDistributionPoint<'a>,
628 ) -> impl Iterator<Item = Result<GeneralName<'a>, Error>> {
629 match point
630 .names()
631 .expect("failed to parse distribution point names")
632 .expect("missing distribution point name")
633 {
634 DistributionPointName::NameRelativeToCrlIssuer => {
635 panic!("unexpected relative name")
636 }
637 DistributionPointName::FullName(names) => names,
638 }
639 }
640
641 fn uri_bytes<'a>(name: &'a GeneralName) -> &'a [u8] {
642 match name {
643 GeneralName::UniformResourceIdentifier(uri) => uri.as_slice_less_safe(),
644 _ => panic!("unexpected name type"),
645 }
646 }
647
648 // We expect to find three URIs across the two distribution points.
649 let expected_names = [
650 "http://example.com/crl.1.der".as_bytes(),
651 "http://example.com/crl.2.der".as_bytes(),
652 "http://example.com/crl.3.der".as_bytes(),
653 ];
654 let all_names = get_names(point_a)
655 .chain(get_names(point_b))
656 .collect::<Result<Vec<_>, Error>>()
657 .expect("failed to parse names");
658
659 assert_eq!(
660 all_names.iter().map(uri_bytes).collect::<Vec<_>>(),
661 expected_names
662 );
663 }
664}
665