/rust/registry/src/index.crates.io-6f17d22bba15001f/rustls-webpki-0.102.8/src/crl/mod.rs
Line | Count | Source (jump to first uncovered line) |
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 | 0 |
|
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<'a> RevocationOptions<'a> { |
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 | 0 |
|
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 | 0 | Error::UnsupportedSignatureAlgorithm => Error::UnsupportedCrlSignatureAlgorithm, |
214 | | Error::UnsupportedSignatureAlgorithmForPublicKey => { |
215 | 0 | Error::UnsupportedCrlSignatureAlgorithmForPublicKey |
216 | | } |
217 | 0 | Error::InvalidSignatureForPublicKey => Error::InvalidCrlSignatureForPublicKey, |
218 | 0 | _ => err, |
219 | | } |
220 | 0 | } |
221 | | |
222 | | /// Describes how much of a certificate chain is checked for revocation status. |
223 | | #[derive(Debug, Copy, Clone, PartialEq, Eq)] |
224 | | pub enum RevocationCheckDepth { |
225 | | /// Only check the end entity (leaf) certificate's revocation status. |
226 | | EndEntity, |
227 | | /// Check the revocation status of the end entity (leaf) and all intermediates. |
228 | | Chain, |
229 | | } |
230 | | |
231 | | /// Describes how to handle the case where a certificate's revocation status is unknown. |
232 | | #[derive(Debug, Copy, Clone, PartialEq, Eq)] |
233 | | pub enum UnknownStatusPolicy { |
234 | | /// Treat unknown revocation status permissively, acting as if the certificate were |
235 | | /// not revoked. |
236 | | Allow, |
237 | | /// Treat unknown revocation status as an error condition, yielding |
238 | | /// [Error::UnknownRevocationStatus]. |
239 | | Deny, |
240 | | } |
241 | | |
242 | | /// Describes how to handle the nextUpdate field of the CRL (i.e. expiration). |
243 | | #[derive(Debug, Copy, Clone, PartialEq, Eq)] |
244 | | pub enum ExpirationPolicy { |
245 | | /// Enforce the verification time is before the time in the nextUpdate field. |
246 | | /// Treats an expired CRL as an error condition yielding [Error::CrlExpired]. |
247 | | Enforce, |
248 | | /// Ignore the CRL nextUpdate field. |
249 | | Ignore, |
250 | | } |
251 | | |
252 | | // Zero-sized marker type representing positive assertion that revocation status was checked |
253 | | // for a certificate and the result was that the certificate is not revoked. |
254 | | pub(crate) struct CertNotRevoked(()); |
255 | | |
256 | | impl CertNotRevoked { |
257 | | // Construct a CertNotRevoked marker. |
258 | 0 | fn assertion() -> Self { |
259 | 0 | Self(()) |
260 | 0 | } |
261 | | } |
262 | | |
263 | | #[derive(Debug, Copy, Clone)] |
264 | | /// An opaque error indicating the caller must provide at least one CRL when building a |
265 | | /// [RevocationOptions] instance. |
266 | | pub struct CrlsRequired(pub(crate) ()); |
267 | | |
268 | | #[cfg(test)] |
269 | | mod tests { |
270 | | use super::*; |
271 | | |
272 | | #[test] |
273 | | // redundant clone, clone_on_copy allowed to verify derived traits. |
274 | | #[allow(clippy::redundant_clone, clippy::clone_on_copy)] |
275 | | fn test_revocation_opts_builder() { |
276 | | // Trying to build a RevocationOptionsBuilder w/o CRLs should err. |
277 | | let result = RevocationOptionsBuilder::new(&[]); |
278 | | assert!(matches!(result, Err(CrlsRequired(_)))); |
279 | | |
280 | | // The CrlsRequired error should be debug and clone when alloc is enabled. |
281 | | #[cfg(feature = "alloc")] |
282 | | { |
283 | | let err = result.unwrap_err(); |
284 | | std::println!("{:?}", err.clone()); |
285 | | } |
286 | | |
287 | | // It should be possible to build a revocation options builder with defaults. |
288 | | let crl = include_bytes!("../../tests/crls/crl.valid.der"); |
289 | | let crl = BorrowedCertRevocationList::from_der(&crl[..]) |
290 | | .unwrap() |
291 | | .into(); |
292 | | let crls = [&crl]; |
293 | | let builder = RevocationOptionsBuilder::new(&crls).unwrap(); |
294 | | #[cfg(feature = "alloc")] |
295 | | { |
296 | | // The builder should be debug, and clone when alloc is enabled |
297 | | std::println!("{:?}", builder); |
298 | | _ = builder.clone(); |
299 | | } |
300 | | let opts = builder.build(); |
301 | | assert_eq!(opts.depth, RevocationCheckDepth::Chain); |
302 | | assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny); |
303 | | assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore); |
304 | | assert_eq!(opts.crls.len(), 1); |
305 | | |
306 | | // It should be possible to build a revocation options builder with custom depth. |
307 | | let opts = RevocationOptionsBuilder::new(&crls) |
308 | | .unwrap() |
309 | | .with_depth(RevocationCheckDepth::EndEntity) |
310 | | .build(); |
311 | | assert_eq!(opts.depth, RevocationCheckDepth::EndEntity); |
312 | | assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny); |
313 | | assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore); |
314 | | assert_eq!(opts.crls.len(), 1); |
315 | | |
316 | | // It should be possible to build a revocation options builder that allows unknown |
317 | | // revocation status. |
318 | | let opts = RevocationOptionsBuilder::new(&crls) |
319 | | .unwrap() |
320 | | .with_status_policy(UnknownStatusPolicy::Allow) |
321 | | .build(); |
322 | | assert_eq!(opts.depth, RevocationCheckDepth::Chain); |
323 | | assert_eq!(opts.status_policy, UnknownStatusPolicy::Allow); |
324 | | assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore); |
325 | | assert_eq!(opts.crls.len(), 1); |
326 | | |
327 | | // It should be possible to specify both depth and unknown status policy together. |
328 | | let opts = RevocationOptionsBuilder::new(&crls) |
329 | | .unwrap() |
330 | | .with_status_policy(UnknownStatusPolicy::Allow) |
331 | | .with_depth(RevocationCheckDepth::EndEntity) |
332 | | .build(); |
333 | | assert_eq!(opts.depth, RevocationCheckDepth::EndEntity); |
334 | | assert_eq!(opts.status_policy, UnknownStatusPolicy::Allow); |
335 | | assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore); |
336 | | assert_eq!(opts.crls.len(), 1); |
337 | | |
338 | | // The same should be true for explicitly forbidding unknown status. |
339 | | let opts = RevocationOptionsBuilder::new(&crls) |
340 | | .unwrap() |
341 | | .with_status_policy(UnknownStatusPolicy::Deny) |
342 | | .with_depth(RevocationCheckDepth::EndEntity) |
343 | | .build(); |
344 | | assert_eq!(opts.depth, RevocationCheckDepth::EndEntity); |
345 | | assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny); |
346 | | assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore); |
347 | | assert_eq!(opts.crls.len(), 1); |
348 | | |
349 | | // It should be possible to build a revocation options builder that allows unknown |
350 | | // revocation status. |
351 | | let opts = RevocationOptionsBuilder::new(&crls) |
352 | | .unwrap() |
353 | | .with_expiration_policy(ExpirationPolicy::Enforce) |
354 | | .build(); |
355 | | assert_eq!(opts.depth, RevocationCheckDepth::Chain); |
356 | | assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny); |
357 | | assert_eq!(opts.expiration_policy, ExpirationPolicy::Enforce); |
358 | | assert_eq!(opts.crls.len(), 1); |
359 | | |
360 | | // Built revocation options should be debug and clone when alloc is enabled. |
361 | | #[cfg(feature = "alloc")] |
362 | | { |
363 | | std::println!("{:?}", opts.clone()); |
364 | | } |
365 | | } |
366 | | } |