| 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 | 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 | /// 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 | pub fn with_depth(mut self, depth: RevocationCheckDepth) -> Self { |
| 76 | self.depth = depth; |
| 77 | self |
| 78 | } |
| 79 | |
| 80 | /// Customize whether unknown revocation status is an error, or permitted. |
| 81 | pub fn with_status_policy(mut self, policy: UnknownStatusPolicy) -> Self { |
| 82 | self.status_policy = policy; |
| 83 | self |
| 84 | } |
| 85 | |
| 86 | /// Customize whether the CRL nextUpdate field (i.e. expiration) is enforced. |
| 87 | pub fn with_expiration_policy(mut self, policy: ExpirationPolicy) -> Self { |
| 88 | self.expiration_policy = policy; |
| 89 | self |
| 90 | } |
| 91 | |
| 92 | /// Construct a [RevocationOptions] instance based on the builder's configuration. |
| 93 | 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 | /// 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 | 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 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 | 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 | // 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 | (None, Allow) => return Ok(None), |
| 144 | // Otherwise, this is an error condition based on the provided policy. |
| 145 | (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 | 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 | // Verify that if the issuer has a KeyUsage bitstring it asserts cRLSign. |
| 160 | KeyUsageMode::CrlSign.check(issuer_ku)?; |
| 161 | |
| 162 | // Try to find the cert serial in the verified CRL contents. |
| 163 | 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 | // 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 | fn check(self, input: Option<untrusted::Input<'_>>) -> Result<(), Error> { |
| 189 | let bit_string: Input<'_> = match input { |
| 190 | Some(input: Input<'_>) => { |
| 191 | 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 | None => return Ok(()), |
| 197 | }; |
| 198 | |
| 199 | let flags: BitStringFlags<'_> = der::bit_string_flags(input:bit_string)?; |
| 200 | #[allow (clippy::as_conversions)] // u8 always fits in usize. |
| 201 | match flags.bit_set(self as usize) { |
| 202 | true => Ok(()), |
| 203 | false => Err(Error::IssuerNotCrlSigner), |
| 204 | } |
| 205 | } |
| 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 | fn crl_signature_err(err: Error) -> Error { |
| 212 | match err { |
| 213 | Error::UnsupportedSignatureAlgorithm => Error::UnsupportedCrlSignatureAlgorithm, |
| 214 | Error::UnsupportedSignatureAlgorithmForPublicKey => { |
| 215 | Error::UnsupportedCrlSignatureAlgorithmForPublicKey |
| 216 | } |
| 217 | Error::InvalidSignatureForPublicKey => Error::InvalidCrlSignatureForPublicKey, |
| 218 | _ => err, |
| 219 | } |
| 220 | } |
| 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 | fn assertion() -> Self { |
| 259 | Self(()) |
| 260 | } |
| 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 | } |
| 367 | |