webpki/
cert.rs

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