Skip to main content

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