Coverage Report

Created: 2026-07-25 06:05

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