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