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