1use pki_types::{SignatureVerificationAlgorithm, UnixTime};
16
17use crate::error::Error;
18use crate::verify_cert::{Budget, PathNode, Role};
19use crate::{der, public_values_eq};
20
21use core::fmt::Debug;
22
23mod types;
24pub use types::{
25 BorrowedCertRevocationList, BorrowedRevokedCert, CertRevocationList, RevocationReason,
26};
27#[cfg(feature = "alloc")]
28pub use types::{OwnedCertRevocationList, OwnedRevokedCert};
29
30#[derive(Debug, Copy, Clone)]
32pub 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
42impl<'a> RevocationOptionsBuilder<'a> {
43 pub fn new(crls: &'a [&'a CertRevocationList<'a>]) -> Result<Self, CrlsRequired> {
60 if crls.is_empty() {
61 return Err(CrlsRequired(()));
62 }
63
64 Ok(Self {
65 crls,
66 depth: RevocationCheckDepth::Chain,
67 status_policy: UnknownStatusPolicy::Deny,
68 expiration_policy: ExpirationPolicy::Ignore,
69 })
70 }
71
72 pub fn with_depth(mut self, depth: RevocationCheckDepth) -> Self {
76 self.depth = depth;
77 self
78 }
79
80 pub fn with_status_policy(mut self, policy: UnknownStatusPolicy) -> Self {
82 self.status_policy = policy;
83 self
84 }
85
86 pub fn with_expiration_policy(mut self, policy: ExpirationPolicy) -> Self {
88 self.expiration_policy = policy;
89 self
90 }
91
92 pub fn build(self) -> RevocationOptions<'a> {
94 RevocationOptions {
95 crls: self.crls,
96 depth: self.depth,
97 status_policy: self.status_policy,
98 expiration_policy: self.expiration_policy,
99 }
100 }
101}
102
103#[derive(Debug, Copy, Clone)]
106pub 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
113impl RevocationOptions<'_> {
114 #[allow(clippy::too_many_arguments)]
115 pub(crate) fn check(
116 &self,
117 path: &PathNode<'_>,
118 issuer_subject: untrusted::Input<'_>,
119 issuer_spki: untrusted::Input<'_>,
120 issuer_ku: Option<untrusted::Input<'_>>,
121 supported_sig_algs: &[&dyn SignatureVerificationAlgorithm],
122 budget: &mut Budget,
123 time: UnixTime,
124 ) -> Result<Option<CertNotRevoked>, Error> {
125 assert!(public_values_eq(path.cert.issuer, issuer_subject));
126
127 if let (RevocationCheckDepth::EndEntity, Role::Issuer) = (self.depth, path.role()) {
130 return Ok(None);
131 }
132
133 let crl = self
134 .crls
135 .iter()
136 .find(|candidate_crl| candidate_crl.authoritative(path));
137
138 use UnknownStatusPolicy::*;
139 let crl = match (crl, self.status_policy) {
140 (Some(crl), _) => crl,
141 (None, Allow) => return Ok(None),
144 (None, _) => return Err(Error::UnknownRevocationStatus),
146 };
147
148 crl.verify_signature(supported_sig_algs, issuer_spki, budget)
153 .map_err(crl_signature_err)?;
154
155 if self.expiration_policy == ExpirationPolicy::Enforce {
156 crl.check_expiration(time)?;
157 }
158
159 KeyUsageMode::CrlSign.check(issuer_ku)?;
161
162 let cert_serial = path.cert.serial.as_slice_less_safe();
164 match crl.find_serial(cert_serial)? {
165 None => Ok(Some(CertNotRevoked::assertion())),
166 Some(_) => Err(Error::CertRevoked),
167 }
168 }
169}
170
171#[repr(u8)]
173#[derive(Clone, Copy)]
174enum KeyUsageMode {
175 CrlSign = 6,
182 }
185
186impl KeyUsageMode {
187 fn check(self, input: Option<untrusted::Input<'_>>) -> Result<(), Error> {
189 let bit_string = match input {
190 Some(input) => {
191 der::expect_tag(&mut untrusted::Reader::new(input), der::Tag::BitString)?
192 }
193 None => return Ok(()),
197 };
198
199 let flags = der::bit_string_flags(bit_string)?;
200 #[allow(clippy::as_conversions)] match flags.bit_set(self as usize) {
202 true => Ok(()),
203 false => Err(Error::IssuerNotCrlSigner),
204 }
205 }
206}
207
208fn crl_signature_err(err: Error) -> Error {
212 match err {
213 #[allow(deprecated)]
214 Error::UnsupportedSignatureAlgorithm => Error::UnsupportedCrlSignatureAlgorithm,
215 Error::UnsupportedSignatureAlgorithmContext(cx) => {
216 Error::UnsupportedCrlSignatureAlgorithmContext(cx)
217 }
218 #[allow(deprecated)]
219 Error::UnsupportedSignatureAlgorithmForPublicKey => {
220 Error::UnsupportedCrlSignatureAlgorithmForPublicKey
221 }
222 Error::UnsupportedSignatureAlgorithmForPublicKeyContext(cx) => {
223 Error::UnsupportedCrlSignatureAlgorithmForPublicKeyContext(cx)
224 }
225 Error::InvalidSignatureForPublicKey => Error::InvalidCrlSignatureForPublicKey,
226 _ => err,
227 }
228}
229
230#[derive(Debug, Copy, Clone, PartialEq, Eq)]
232pub enum RevocationCheckDepth {
233 EndEntity,
235 Chain,
237}
238
239#[derive(Debug, Copy, Clone, PartialEq, Eq)]
241pub enum UnknownStatusPolicy {
242 Allow,
245 Deny,
248}
249
250#[derive(Debug, Copy, Clone, PartialEq, Eq)]
252pub enum ExpirationPolicy {
253 Enforce,
256 Ignore,
258}
259
260pub(crate) struct CertNotRevoked(());
263
264impl CertNotRevoked {
265 fn assertion() -> Self {
267 Self(())
268 }
269}
270
271#[derive(Debug, Copy, Clone)]
272pub struct CrlsRequired(pub(crate) ());
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[test]
281 #[allow(clippy::redundant_clone, clippy::clone_on_copy)]
283 fn test_revocation_opts_builder() {
284 let result = RevocationOptionsBuilder::new(&[]);
286 assert!(matches!(result, Err(CrlsRequired(_))));
287
288 #[cfg(feature = "alloc")]
290 {
291 let err = result.unwrap_err();
292 std::println!("{:?}", err.clone());
293 }
294
295 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 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 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 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 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 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 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 #[cfg(feature = "alloc")]
370 {
371 std::println!("{:?}", opts.clone());
372 }
373 }
374}