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 = &'a 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 = &'a 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    #[cfg(feature = "alloc")]
396    use alloc::vec::Vec;
397
398    use super::*;
399    #[cfg(feature = "alloc")]
400    use crate::crl::RevocationReason;
401
402    #[test]
403    // Note: cert::parse_cert is crate-local visibility, and EndEntityCert doesn't expose the
404    //       inner Cert, or the serial number. As a result we test that the raw serial value
405    //       is read correctly here instead of in tests/integration.rs.
406    fn test_serial_read() {
407        let ee = include_bytes!("../tests/misc/serial_neg_ee.der");
408        let cert = Cert::from_der(untrusted::Input::from(ee)).expect("failed to parse certificate");
409        assert_eq!(cert.serial.as_slice_less_safe(), &[255, 33, 82, 65, 17]);
410
411        let ee = include_bytes!("../tests/misc/serial_large_positive.der");
412        let cert = Cert::from_der(untrusted::Input::from(ee)).expect("failed to parse certificate");
413        assert_eq!(
414            cert.serial.as_slice_less_safe(),
415            &[
416                0, 230, 9, 254, 122, 234, 0, 104, 140, 224, 36, 180, 237, 32, 27, 31, 239, 82, 180,
417                68, 209
418            ]
419        )
420    }
421
422    #[cfg(feature = "alloc")]
423    #[test]
424    fn test_spki_read() {
425        let ee = include_bytes!("../tests/ed25519/ee.der");
426        let cert = Cert::from_der(untrusted::Input::from(ee)).expect("failed to parse certificate");
427        // How did I get this lovely string of hex bytes?
428        // openssl x509 -in tests/ed25519/ee.der -pubkey -noout > pubkey.pem
429        // openssl ec -pubin -in pubkey.pem -outform DER -out pubkey.der
430        // xxd -plain -cols 1 pubkey.der
431        let expected_spki = [
432            0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00, 0xfe, 0x5a,
433            0x1e, 0x36, 0x6c, 0x17, 0x27, 0x5b, 0xf1, 0x58, 0x1e, 0x3a, 0x0e, 0xe6, 0x56, 0x29,
434            0x8d, 0x9e, 0x1b, 0x3f, 0xd3, 0x3f, 0x96, 0x46, 0xef, 0xbf, 0x04, 0x6b, 0xc7, 0x3d,
435            0x47, 0x5c,
436        ];
437        assert_eq!(expected_spki, *cert.subject_public_key_info())
438    }
439
440    #[test]
441    #[cfg(feature = "alloc")]
442    fn test_crl_distribution_point_netflix() {
443        let ee = include_bytes!("../tests/netflix/ee.der");
444        let inter = include_bytes!("../tests/netflix/inter.der");
445        let ee_cert = Cert::from_der(untrusted::Input::from(ee)).expect("failed to parse EE cert");
446        let cert =
447            Cert::from_der(untrusted::Input::from(inter)).expect("failed to parse certificate");
448
449        // The end entity certificate shouldn't have a distribution point.
450        assert!(ee_cert.crl_distribution_points.is_none());
451
452        // We expect to be able to parse the intermediate certificate's CRL distribution points.
453        let crl_distribution_points = cert
454            .crl_distribution_points()
455            .expect("missing distribution points extension")
456            .collect::<Result<Vec<_>, Error>>()
457            .expect("failed to parse distribution points");
458
459        // There should be one distribution point present.
460        assert_eq!(crl_distribution_points.len(), 1);
461        let crl_distribution_point = crl_distribution_points
462            .first()
463            .expect("missing distribution point");
464
465        // The distribution point shouldn't have revocation reasons listed.
466        assert!(crl_distribution_point.reasons.is_none());
467
468        // The distribution point shouldn't have a CRL issuer listed.
469        assert!(crl_distribution_point.crl_issuer.is_none());
470
471        // We should be able to parse the distribution point name.
472        let distribution_point_name = crl_distribution_point
473            .names()
474            .expect("failed to parse distribution point names")
475            .expect("missing distribution point name");
476
477        // We expect the distribution point name to be a sequence of GeneralNames, not a name
478        // relative to the CRL issuer.
479        let names = match distribution_point_name {
480            DistributionPointName::NameRelativeToCrlIssuer => {
481                panic!("unexpected name relative to crl issuer")
482            }
483            DistributionPointName::FullName(names) => names,
484        };
485
486        // The general names should parse.
487        let names = names
488            .collect::<Result<Vec<_>, Error>>()
489            .expect("failed to parse general names");
490
491        // There should be one general name.
492        assert_eq!(names.len(), 1);
493        let name = names.first().expect("missing general name");
494
495        // The general name should be a URI matching the expected value.
496        match name {
497            GeneralName::UniformResourceIdentifier(uri) => {
498                assert_eq!(
499                    uri.as_slice_less_safe(),
500                    "http://s.symcb.com/pca3-g3.crl".as_bytes()
501                );
502            }
503            _ => panic!("unexpected general name type"),
504        }
505    }
506
507    #[test]
508    #[cfg(feature = "alloc")]
509    fn test_crl_distribution_point_with_reasons() {
510        let der = include_bytes!("../tests/crl_distrib_point/with_reasons.der");
511        let cert =
512            Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
513
514        // We expect to be able to parse the intermediate certificate's CRL distribution points.
515        let crl_distribution_points = cert
516            .crl_distribution_points()
517            .expect("missing distribution points extension")
518            .collect::<Result<Vec<_>, Error>>()
519            .expect("failed to parse distribution points");
520
521        // There should be one distribution point present.
522        assert_eq!(crl_distribution_points.len(), 1);
523        let crl_distribution_point = crl_distribution_points
524            .first()
525            .expect("missing distribution point");
526
527        // The distribution point should include the expected revocation reasons, and no others.
528        let reasons = crl_distribution_point
529            .reasons
530            .as_ref()
531            .expect("missing revocation reasons");
532        let expected = &[
533            RevocationReason::KeyCompromise,
534            RevocationReason::AffiliationChanged,
535        ];
536        for reason in RevocationReason::iter() {
537            #[allow(clippy::as_conversions)]
538            // revocation reason is u8, infallible to convert to usize.
539            match expected.contains(&reason) {
540                true => assert!(reasons.bit_set(reason as usize)),
541                false => assert!(!reasons.bit_set(reason as usize)),
542            }
543        }
544    }
545
546    #[test]
547    #[cfg(feature = "alloc")]
548    fn test_crl_distribution_point_with_crl_issuer() {
549        let der = include_bytes!("../tests/crl_distrib_point/with_crl_issuer.der");
550        let cert =
551            Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
552
553        // We expect to be able to parse the intermediate certificate's CRL distribution points.
554        let crl_distribution_points = cert
555            .crl_distribution_points()
556            .expect("missing distribution points extension")
557            .collect::<Result<Vec<_>, Error>>()
558            .expect("failed to parse distribution points");
559
560        // There should be one distribution point present.
561        assert_eq!(crl_distribution_points.len(), 1);
562        let crl_distribution_point = crl_distribution_points
563            .first()
564            .expect("missing distribution point");
565
566        // The CRL issuer should be present, but not anything else.
567        assert!(crl_distribution_point.crl_issuer.is_some());
568        assert!(crl_distribution_point.distribution_point.is_none());
569        assert!(crl_distribution_point.reasons.is_none());
570    }
571
572    #[test]
573    #[cfg(feature = "alloc")]
574    fn test_crl_distribution_point_bad_der() {
575        // Created w/
576        //   ascii2der -i tests/crl_distrib_point/unknown_tag.der.txt -o tests/crl_distrib_point/unknown_tag.der
577        let der = include_bytes!("../tests/crl_distrib_point/unknown_tag.der");
578        let cert =
579            Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
580
581        // We expect there to be a distribution point extension, but parsing it should fail
582        // due to the unknown tag in the SEQUENCE.
583        let result = cert
584            .crl_distribution_points()
585            .expect("missing distribution points extension")
586            .collect::<Result<Vec<_>, Error>>();
587        assert!(matches!(result, Err(Error::BadDer)));
588    }
589
590    #[test]
591    #[cfg(feature = "alloc")]
592    fn test_crl_distribution_point_only_reasons() {
593        // Created w/
594        //   ascii2der -i tests/crl_distrib_point/only_reasons.der.txt -o tests/crl_distrib_point/only_reasons.der
595        let der = include_bytes!("../tests/crl_distrib_point/only_reasons.der");
596        let cert =
597            Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
598
599        // We expect there to be a distribution point extension, but parsing it should fail
600        // because no distribution points or cRLIssuer are set in the SEQUENCE, just reason codes.
601        let result = cert
602            .crl_distribution_points()
603            .expect("missing distribution points extension")
604            .collect::<Result<Vec<_>, Error>>();
605        assert!(matches!(result, Err(Error::MalformedExtensions)));
606    }
607
608    #[test]
609    #[cfg(feature = "alloc")]
610    fn test_crl_distribution_point_name_relative_to_issuer() {
611        let der = include_bytes!("../tests/crl_distrib_point/dp_name_relative_to_issuer.der");
612        let cert =
613            Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
614
615        // We expect to be able to parse the intermediate certificate's CRL distribution points.
616        let crl_distribution_points = cert
617            .crl_distribution_points()
618            .expect("missing distribution points extension")
619            .collect::<Result<Vec<_>, Error>>()
620            .expect("failed to parse distribution points");
621
622        // There should be one distribution point present.
623        assert_eq!(crl_distribution_points.len(), 1);
624        let crl_distribution_point = crl_distribution_points
625            .first()
626            .expect("missing distribution point");
627
628        assert!(crl_distribution_point.crl_issuer.is_none());
629        assert!(crl_distribution_point.reasons.is_none());
630
631        // We should be able to parse the distribution point name.
632        let distribution_point_name = crl_distribution_point
633            .names()
634            .expect("failed to parse distribution point names")
635            .expect("missing distribution point name");
636
637        // We expect the distribution point name to be a name relative to the CRL issuer.
638        assert!(matches!(
639            distribution_point_name,
640            DistributionPointName::NameRelativeToCrlIssuer
641        ));
642    }
643
644    #[test]
645    #[cfg(feature = "alloc")]
646    fn test_crl_distribution_point_unknown_name_tag() {
647        // Created w/
648        //   ascii2der -i tests/crl_distrib_point/unknown_dp_name_tag.der.txt > tests/crl_distrib_point/unknown_dp_name_tag.der
649        let der = include_bytes!("../tests/crl_distrib_point/unknown_dp_name_tag.der");
650        let cert =
651            Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
652
653        // We expect to be able to parse the intermediate certificate's CRL distribution points.
654        let crl_distribution_points = cert
655            .crl_distribution_points()
656            .expect("missing distribution points extension")
657            .collect::<Result<Vec<_>, Error>>()
658            .expect("failed to parse distribution points");
659
660        // There should be one distribution point present.
661        assert_eq!(crl_distribution_points.len(), 1);
662        let crl_distribution_point = crl_distribution_points
663            .first()
664            .expect("missing distribution point");
665
666        // Parsing the distrubition point names should fail due to the unknown name tag.
667        let result = crl_distribution_point.names();
668        assert!(matches!(result, Err(Error::BadDer)))
669    }
670
671    #[test]
672    #[cfg(feature = "alloc")]
673    fn test_crl_distribution_point_multiple() {
674        let der = include_bytes!("../tests/crl_distrib_point/multiple_distribution_points.der");
675        let cert =
676            Cert::from_der(untrusted::Input::from(der)).expect("failed to parse certificate");
677
678        // We expect to be able to parse the intermediate certificate's CRL distribution points.
679        let crl_distribution_points = cert
680            .crl_distribution_points()
681            .expect("missing distribution points extension")
682            .collect::<Result<Vec<_>, Error>>()
683            .expect("failed to parse distribution points");
684
685        // There should be two distribution points present.
686        let (point_a, point_b) = (
687            crl_distribution_points
688                .first()
689                .expect("missing first distribution point"),
690            crl_distribution_points
691                .get(1)
692                .expect("missing second distribution point"),
693        );
694
695        fn get_names<'a>(
696            point: &'a CrlDistributionPoint<'a>,
697        ) -> impl Iterator<Item = Result<GeneralName<'a>, Error>> {
698            match point
699                .names()
700                .expect("failed to parse distribution point names")
701                .expect("missing distribution point name")
702            {
703                DistributionPointName::NameRelativeToCrlIssuer => {
704                    panic!("unexpected relative name")
705                }
706                DistributionPointName::FullName(names) => names,
707            }
708        }
709
710        fn uri_bytes<'a>(name: &'a GeneralName<'a>) -> &'a [u8] {
711            match name {
712                GeneralName::UniformResourceIdentifier(uri) => uri.as_slice_less_safe(),
713                _ => panic!("unexpected name type"),
714            }
715        }
716
717        // We expect to find three URIs across the two distribution points.
718        let expected_names = [
719            "http://example.com/crl.1.der".as_bytes(),
720            "http://example.com/crl.2.der".as_bytes(),
721            "http://example.com/crl.3.der".as_bytes(),
722        ];
723        let all_names = get_names(point_a)
724            .chain(get_names(point_b))
725            .collect::<Result<Vec<_>, Error>>()
726            .expect("failed to parse names");
727
728        assert_eq!(
729            all_names.iter().map(uri_bytes).collect::<Vec<_>>(),
730            expected_names
731        );
732    }
733}