Coverage Report

Created: 2025-10-10 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rustls-webpki-0.103.7/src/crl/mod.rs
Line
Count
Source
1
// Copyright 2023 Daniel McCarney.
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
use pki_types::{SignatureVerificationAlgorithm, UnixTime};
16
17
use crate::error::Error;
18
use crate::verify_cert::{Budget, PathNode, Role};
19
use crate::{der, public_values_eq};
20
21
use core::fmt::Debug;
22
23
mod types;
24
pub use types::{
25
    BorrowedCertRevocationList, BorrowedRevokedCert, CertRevocationList, RevocationReason,
26
};
27
#[cfg(feature = "alloc")]
28
pub use types::{OwnedCertRevocationList, OwnedRevokedCert};
29
30
/// Builds a RevocationOptions instance to control how revocation checking is performed.
31
#[derive(Debug, Copy, Clone)]
32
pub struct RevocationOptionsBuilder<'a> {
33
    crls: &'a [&'a CertRevocationList<'a>],
34
35
    depth: RevocationCheckDepth,
36
37
    status_policy: UnknownStatusPolicy,
38
39
    expiration_policy: ExpirationPolicy,
40
}
41
42
impl<'a> RevocationOptionsBuilder<'a> {
43
    /// Create a builder that will perform revocation checking using the provided certificate
44
    /// revocation lists (CRLs). At least one CRL must be provided.
45
    ///
46
    /// Use [RevocationOptionsBuilder::build] to create a [RevocationOptions] instance.
47
    ///
48
    /// By default revocation checking will be performed on both the end-entity (leaf) certificate
49
    /// and intermediate certificates. This can be customized using the
50
    /// [RevocationOptionsBuilder::with_depth] method.
51
    ///
52
    /// By default revocation checking will fail if the revocation status of a certificate cannot
53
    /// be determined. This can be customized using the
54
    /// [RevocationOptionsBuilder::with_status_policy] method.
55
    ///
56
    /// By default revocation checking will *not* fail if the verification time is beyond the time
57
    /// in the CRL nextUpdate field. This can be customized using the
58
    /// [RevocationOptionsBuilder::with_expiration_policy] method.
59
0
    pub fn new(crls: &'a [&'a CertRevocationList<'a>]) -> Result<Self, CrlsRequired> {
60
0
        if crls.is_empty() {
61
0
            return Err(CrlsRequired(()));
62
0
        }
63
64
0
        Ok(Self {
65
0
            crls,
66
0
            depth: RevocationCheckDepth::Chain,
67
0
            status_policy: UnknownStatusPolicy::Deny,
68
0
            expiration_policy: ExpirationPolicy::Ignore,
69
0
        })
70
0
    }
71
72
    /// Customize the depth at which revocation checking will be performed, controlling
73
    /// whether only the end-entity (leaf) certificate in the chain to a trust anchor will
74
    /// have its revocation status checked, or whether the intermediate certificates will as well.
75
0
    pub fn with_depth(mut self, depth: RevocationCheckDepth) -> Self {
76
0
        self.depth = depth;
77
0
        self
78
0
    }
79
80
    /// Customize whether unknown revocation status is an error, or permitted.
81
0
    pub fn with_status_policy(mut self, policy: UnknownStatusPolicy) -> Self {
82
0
        self.status_policy = policy;
83
0
        self
84
0
    }
85
86
    /// Customize whether the CRL nextUpdate field (i.e. expiration) is enforced.
87
0
    pub fn with_expiration_policy(mut self, policy: ExpirationPolicy) -> Self {
88
0
        self.expiration_policy = policy;
89
0
        self
90
0
    }
91
92
    /// Construct a [RevocationOptions] instance based on the builder's configuration.
93
0
    pub fn build(self) -> RevocationOptions<'a> {
94
0
        RevocationOptions {
95
0
            crls: self.crls,
96
0
            depth: self.depth,
97
0
            status_policy: self.status_policy,
98
0
            expiration_policy: self.expiration_policy,
99
0
        }
100
0
    }
101
}
102
103
/// Describes how revocation checking is performed, if at all. Can be constructed with a
104
/// [RevocationOptionsBuilder] instance.
105
#[derive(Debug, Copy, Clone)]
106
pub struct RevocationOptions<'a> {
107
    pub(crate) crls: &'a [&'a CertRevocationList<'a>],
108
    pub(crate) depth: RevocationCheckDepth,
109
    pub(crate) status_policy: UnknownStatusPolicy,
110
    pub(crate) expiration_policy: ExpirationPolicy,
111
}
112
113
impl RevocationOptions<'_> {
114
    #[allow(clippy::too_many_arguments)]
115
0
    pub(crate) fn check(
116
0
        &self,
117
0
        path: &PathNode<'_>,
118
0
        issuer_subject: untrusted::Input<'_>,
119
0
        issuer_spki: untrusted::Input<'_>,
120
0
        issuer_ku: Option<untrusted::Input<'_>>,
121
0
        supported_sig_algs: &[&dyn SignatureVerificationAlgorithm],
122
0
        budget: &mut Budget,
123
0
        time: UnixTime,
124
0
    ) -> Result<Option<CertNotRevoked>, Error> {
125
0
        assert!(public_values_eq(path.cert.issuer, issuer_subject));
126
127
        // If the policy only specifies checking EndEntity revocation state and we're looking at an
128
        // issuer certificate, return early without considering the certificate's revocation state.
129
0
        if let (RevocationCheckDepth::EndEntity, Role::Issuer) = (self.depth, path.role()) {
130
0
            return Ok(None);
131
0
        }
132
133
0
        let crl = self
134
0
            .crls
135
0
            .iter()
136
0
            .find(|candidate_crl| candidate_crl.authoritative(path));
137
138
        use UnknownStatusPolicy::*;
139
0
        let crl = match (crl, self.status_policy) {
140
0
            (Some(crl), _) => crl,
141
            // If the policy allows unknown, return Ok(None) to indicate that the certificate
142
            // was not confirmed as CertNotRevoked, but that this isn't an error condition.
143
0
            (None, Allow) => return Ok(None),
144
            // Otherwise, this is an error condition based on the provided policy.
145
0
            (None, _) => return Err(Error::UnknownRevocationStatus),
146
        };
147
148
        // Verify the CRL signature with the issuer SPKI.
149
        // TODO(XXX): consider whether we can refactor so this happens once up-front, instead
150
        //            of per-lookup.
151
        //            https://github.com/rustls/webpki/issues/81
152
0
        crl.verify_signature(supported_sig_algs, issuer_spki, budget)
153
0
            .map_err(crl_signature_err)?;
154
155
0
        if self.expiration_policy == ExpirationPolicy::Enforce {
156
0
            crl.check_expiration(time)?;
157
0
        }
158
159
        // Verify that if the issuer has a KeyUsage bitstring it asserts cRLSign.
160
0
        KeyUsageMode::CrlSign.check(issuer_ku)?;
161
162
        // Try to find the cert serial in the verified CRL contents.
163
0
        let cert_serial = path.cert.serial.as_slice_less_safe();
164
0
        match crl.find_serial(cert_serial)? {
165
0
            None => Ok(Some(CertNotRevoked::assertion())),
166
0
            Some(_) => Err(Error::CertRevoked),
167
        }
168
0
    }
169
}
170
171
// https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3
172
#[repr(u8)]
173
#[derive(Clone, Copy)]
174
enum KeyUsageMode {
175
    // DigitalSignature = 0,
176
    // ContentCommitment = 1,
177
    // KeyEncipherment = 2,
178
    // DataEncipherment = 3,
179
    // KeyAgreement = 4,
180
    // CertSign = 5,
181
    CrlSign = 6,
182
    // EncipherOnly = 7,
183
    // DecipherOnly = 8,
184
}
185
186
impl KeyUsageMode {
187
    // https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3
188
0
    fn check(self, input: Option<untrusted::Input<'_>>) -> Result<(), Error> {
189
0
        let bit_string = match input {
190
0
            Some(input) => {
191
0
                der::expect_tag(&mut untrusted::Reader::new(input), der::Tag::BitString)?
192
            }
193
            // While RFC 5280 requires KeyUsage be present, historically the absence of a KeyUsage
194
            // has been treated as "Any Usage". We follow that convention here and assume the absence
195
            // of KeyUsage implies the required_ku_bit_if_present we're checking for.
196
0
            None => return Ok(()),
197
        };
198
199
0
        let flags = der::bit_string_flags(bit_string)?;
200
        #[allow(clippy::as_conversions)] // u8 always fits in usize.
201
0
        match flags.bit_set(self as usize) {
202
0
            true => Ok(()),
203
0
            false => Err(Error::IssuerNotCrlSigner),
204
        }
205
0
    }
206
}
207
208
// When verifying CRL signed data we want to disambiguate the context of possible errors by mapping
209
// them to CRL specific variants that a consumer can use to tell the issue was with the CRL's
210
// signature, not a certificate.
211
0
fn crl_signature_err(err: Error) -> Error {
212
0
    match err {
213
        #[allow(deprecated)]
214
0
        Error::UnsupportedSignatureAlgorithm => Error::UnsupportedCrlSignatureAlgorithm,
215
0
        Error::UnsupportedSignatureAlgorithmContext(cx) => {
216
0
            Error::UnsupportedCrlSignatureAlgorithmContext(cx)
217
        }
218
        #[allow(deprecated)]
219
        Error::UnsupportedSignatureAlgorithmForPublicKey => {
220
0
            Error::UnsupportedCrlSignatureAlgorithmForPublicKey
221
        }
222
0
        Error::UnsupportedSignatureAlgorithmForPublicKeyContext(cx) => {
223
0
            Error::UnsupportedCrlSignatureAlgorithmForPublicKeyContext(cx)
224
        }
225
0
        Error::InvalidSignatureForPublicKey => Error::InvalidCrlSignatureForPublicKey,
226
0
        _ => err,
227
    }
228
0
}
229
230
/// Describes how much of a certificate chain is checked for revocation status.
231
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
232
pub enum RevocationCheckDepth {
233
    /// Only check the end entity (leaf) certificate's revocation status.
234
    EndEntity,
235
    /// Check the revocation status of the end entity (leaf) and all intermediates.
236
    Chain,
237
}
238
239
/// Describes how to handle the case where a certificate's revocation status is unknown.
240
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
241
pub enum UnknownStatusPolicy {
242
    /// Treat unknown revocation status permissively, acting as if the certificate were
243
    /// not revoked.
244
    Allow,
245
    /// Treat unknown revocation status as an error condition, yielding
246
    /// [Error::UnknownRevocationStatus].
247
    Deny,
248
}
249
250
/// Describes how to handle the nextUpdate field of the CRL (i.e. expiration).
251
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
252
pub enum ExpirationPolicy {
253
    /// Enforce the verification time is before the time in the nextUpdate field.
254
    /// Treats an expired CRL as an error condition yielding [Error::CrlExpired].
255
    Enforce,
256
    /// Ignore the CRL nextUpdate field.
257
    Ignore,
258
}
259
260
// Zero-sized marker type representing positive assertion that revocation status was checked
261
// for a certificate and the result was that the certificate is not revoked.
262
pub(crate) struct CertNotRevoked(());
263
264
impl CertNotRevoked {
265
    // Construct a CertNotRevoked marker.
266
0
    fn assertion() -> Self {
267
0
        Self(())
268
0
    }
269
}
270
271
#[derive(Debug, Copy, Clone)]
272
/// An opaque error indicating the caller must provide at least one CRL when building a
273
/// [RevocationOptions] instance.
274
pub struct CrlsRequired(pub(crate) ());
275
276
#[cfg(test)]
277
mod tests {
278
    use super::*;
279
280
    #[test]
281
    // redundant clone, clone_on_copy allowed to verify derived traits.
282
    #[allow(clippy::redundant_clone, clippy::clone_on_copy)]
283
    fn test_revocation_opts_builder() {
284
        // Trying to build a RevocationOptionsBuilder w/o CRLs should err.
285
        let result = RevocationOptionsBuilder::new(&[]);
286
        assert!(matches!(result, Err(CrlsRequired(_))));
287
288
        // The CrlsRequired error should be debug and clone when alloc is enabled.
289
        #[cfg(feature = "alloc")]
290
        {
291
            let err = result.unwrap_err();
292
            std::println!("{:?}", err.clone());
293
        }
294
295
        // It should be possible to build a revocation options builder with defaults.
296
        let crl = include_bytes!("../../tests/crls/crl.valid.der");
297
        let crl = BorrowedCertRevocationList::from_der(&crl[..])
298
            .unwrap()
299
            .into();
300
        let crls = [&crl];
301
        let builder = RevocationOptionsBuilder::new(&crls).unwrap();
302
        #[cfg(feature = "alloc")]
303
        {
304
            // The builder should be debug, and clone when alloc is enabled
305
            std::println!("{builder:?}");
306
            _ = builder.clone();
307
        }
308
        let opts = builder.build();
309
        assert_eq!(opts.depth, RevocationCheckDepth::Chain);
310
        assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny);
311
        assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore);
312
        assert_eq!(opts.crls.len(), 1);
313
314
        // It should be possible to build a revocation options builder with custom depth.
315
        let opts = RevocationOptionsBuilder::new(&crls)
316
            .unwrap()
317
            .with_depth(RevocationCheckDepth::EndEntity)
318
            .build();
319
        assert_eq!(opts.depth, RevocationCheckDepth::EndEntity);
320
        assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny);
321
        assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore);
322
        assert_eq!(opts.crls.len(), 1);
323
324
        // It should be possible to build a revocation options builder that allows unknown
325
        // revocation status.
326
        let opts = RevocationOptionsBuilder::new(&crls)
327
            .unwrap()
328
            .with_status_policy(UnknownStatusPolicy::Allow)
329
            .build();
330
        assert_eq!(opts.depth, RevocationCheckDepth::Chain);
331
        assert_eq!(opts.status_policy, UnknownStatusPolicy::Allow);
332
        assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore);
333
        assert_eq!(opts.crls.len(), 1);
334
335
        // It should be possible to specify both depth and unknown status policy together.
336
        let opts = RevocationOptionsBuilder::new(&crls)
337
            .unwrap()
338
            .with_status_policy(UnknownStatusPolicy::Allow)
339
            .with_depth(RevocationCheckDepth::EndEntity)
340
            .build();
341
        assert_eq!(opts.depth, RevocationCheckDepth::EndEntity);
342
        assert_eq!(opts.status_policy, UnknownStatusPolicy::Allow);
343
        assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore);
344
        assert_eq!(opts.crls.len(), 1);
345
346
        // The same should be true for explicitly forbidding unknown status.
347
        let opts = RevocationOptionsBuilder::new(&crls)
348
            .unwrap()
349
            .with_status_policy(UnknownStatusPolicy::Deny)
350
            .with_depth(RevocationCheckDepth::EndEntity)
351
            .build();
352
        assert_eq!(opts.depth, RevocationCheckDepth::EndEntity);
353
        assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny);
354
        assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore);
355
        assert_eq!(opts.crls.len(), 1);
356
357
        // It should be possible to build a revocation options builder that allows unknown
358
        // revocation status.
359
        let opts = RevocationOptionsBuilder::new(&crls)
360
            .unwrap()
361
            .with_expiration_policy(ExpirationPolicy::Enforce)
362
            .build();
363
        assert_eq!(opts.depth, RevocationCheckDepth::Chain);
364
        assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny);
365
        assert_eq!(opts.expiration_policy, ExpirationPolicy::Enforce);
366
        assert_eq!(opts.crls.len(), 1);
367
368
        // Built revocation options should be debug and clone when alloc is enabled.
369
        #[cfg(feature = "alloc")]
370
        {
371
            std::println!("{:?}", opts.clone());
372
        }
373
    }
374
}