diff --git a/src/end_entity.rs b/src/end_entity.rs index 3bb049bb..ff4b702b 100644 --- a/src/end_entity.rs +++ b/src/end_entity.rs @@ -14,14 +14,10 @@ use core::ops::Deref; -use pki_types::{ - CertificateDer, ServerName, SignatureVerificationAlgorithm, TrustAnchor, UnixTime, -}; +use pki_types::{CertificateDer, ServerName, SignatureVerificationAlgorithm}; -use crate::crl::RevocationOptions; use crate::error::Error; use crate::subject_name::{verify_dns_names, verify_ip_address_names}; -use crate::verify_cert::{self, ExtendedKeyUsageValidator, VerifiedPath}; use crate::{cert, sct, signed_data}; /// An end-entity certificate. @@ -29,9 +25,9 @@ use crate::{cert, sct, signed_data}; /// Server certificate processing in a TLS connection consists of several /// steps. All of these steps are necessary: /// -/// * [`EndEntityCert::verify_for_usage()`]: Verify that the peer's certificate -/// is valid for the current usage scenario. For server authentication, use -/// [`crate::ExtendedKeyUsage::server_auth()`]. +/// * Build a [`VerifiedPath`][crate::VerifiedPath] using a [PathBuilder][crate::PathBuilder]: +/// verify that the peer's certificate is valid for the current usage scenario. For server +/// authentication, use [`crate::ExtendedKeyUsage::SERVER_AUTH`]. /// * [`EndEntityCert::verify_is_valid_for_subject_name()`]: Verify that the server's /// certificate is valid for the host or IP address that is being connected to. /// * [`EndEntityCert::verify_signature()`]: Verify that the signature of server's @@ -40,9 +36,9 @@ use crate::{cert, sct, signed_data}; /// Client certificate processing in a TLS connection consists of analogous /// steps. All of these steps are necessary: /// -/// * [`EndEntityCert::verify_for_usage()`]: Verify that the peer's certificate -/// is valid for the current usage scenario. For client authentication, use -/// [`crate::ExtendedKeyUsage::client_auth()`]. +/// * Build a [`VerifiedPath`][crate::VerifiedPath] using a [PathBuilder][crate::PathBuilder]: +/// verify that the peer's certificate is valid for the current usage scenario. For client +/// authentication, use [`crate::ExtendedKeyUsage::CLIENT_AUTH`]. /// * [`EndEntityCert::verify_signature()`]: Verify that the signature of client's /// `CertificateVerify` message is valid using the public key from the /// client's certificate. @@ -73,53 +69,6 @@ impl<'a> TryFrom<&'a CertificateDer<'a>> for EndEntityCert<'a> { } impl EndEntityCert<'_> { - /// Verifies that the end-entity certificate is valid for use against the - /// specified Extended Key Usage (EKU). - /// - /// * `supported_sig_algs` is the list of signature algorithms that are - /// trusted for use in certificate signatures; the end-entity certificate's - /// public key is not validated against this list. - /// * `trust_anchors` is the list of root CAs to trust in the built path. - /// * `intermediate_certs` is the sequence of intermediate certificates that - /// a peer sent for the purpose of path building. - /// * `time` is the time for which the validation is effective (usually the - /// current time). - /// * `usage` is the intended usage of the certificate, indicating what kind - /// of usage we're verifying the certificate for. The default [`ExtendedKeyUsageValidator`] - /// implementation is [`ExtendedKeyUsage`](crate::ExtendedKeyUsage). - /// * `crls` is the list of certificate revocation lists to check - /// the certificate against. - /// * `verify_path` is an optional verification function for path candidates. - /// - /// If successful, yields a `VerifiedPath` type that can be used to inspect a verified chain - /// of certificates that leads from the `end_entity` to one of the `self.trust_anchors`. - /// - /// `verify_path` will only be called for potentially verified paths, that is, paths that - /// have been verified up to the trust anchor. As such, `verify_path()` cannot be used to - /// verify a path that doesn't satisfy the constraints listed above; it can only be used to - /// reject a path that does satisfy the aforementioned constraints. If `verify_path` returns - /// an error, path building will continue in order to try other options. - #[expect(clippy::too_many_arguments, clippy::type_complexity)] - pub fn verify_for_usage<'p>( - &'p self, - supported_sig_algs: &[&dyn SignatureVerificationAlgorithm], - trust_anchors: &'p [TrustAnchor<'_>], - intermediate_certs: &'p [CertificateDer<'p>], - time: UnixTime, - usage: &dyn ExtendedKeyUsageValidator, - revocation: Option>, - verify_path: Option<&dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>, - ) -> Result, Error> { - verify_cert::ChainOptions { - eku: usage, - supported_sig_algs, - trust_anchors, - intermediate_certs, - revocation, - } - .build_chain(self, time, verify_path) - } - /// Verifies that the certificate is valid for the given Subject Name. pub fn verify_is_valid_for_subject_name( &self, diff --git a/src/lib.rs b/src/lib.rs index dcf149cf..8615fd17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,7 +83,7 @@ pub use { trust_anchor::anchor_from_trusted_cert, verify_cert::{ ExtendedKeyUsage, ExtendedKeyUsageValidator, IntermediateIterator, KeyPurposeId, - KeyPurposeIdIter, RequiredEkuNotFoundContext, VerifiedPath, + KeyPurposeIdIter, PathBuilder, RequiredEkuNotFoundContext, VerifiedPath, }, }; diff --git a/src/verify_cert.rs b/src/verify_cert.rs index 41dd1ce3..67b218c5 100644 --- a/src/verify_cert.rs +++ b/src/verify_cert.rs @@ -30,37 +30,97 @@ use crate::error::Error; use crate::spki_for_anchor; use crate::{public_values_eq, subject_name}; +/// Build a [`VerifiedPath`] for an end-entity certificate from the given trust anchors. // Use `'a` for lifetimes that we don't care about, `'p` for lifetimes that become a part of // the `VerifiedPath`. -pub(crate) struct ChainOptions<'a, 'p> { - pub(crate) eku: &'a dyn ExtendedKeyUsageValidator, +pub struct PathBuilder<'a, 'p> { + pub(crate) eku: &'p dyn ExtendedKeyUsageValidator, pub(crate) supported_sig_algs: &'a [&'a dyn SignatureVerificationAlgorithm], pub(crate) trust_anchors: &'p [TrustAnchor<'p>], pub(crate) intermediate_certs: &'p [CertificateDer<'p>], pub(crate) revocation: Option>, + #[expect(clippy::type_complexity)] + pub(crate) verify_path: Option<&'a dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>, } -impl<'a, 'p: 'a> ChainOptions<'a, 'p> { - #[expect(clippy::type_complexity)] - pub(crate) fn build_chain( +impl<'a, 'p: 'a> PathBuilder<'a, 'p> { + /// Build a new [`PathBuilder`] with the given parameters. + /// + /// * `eku` is the intended usage of the certificate, indicating what kind + /// of usage we're verifying the certificate for. The default [`ExtendedKeyUsageValidator`] + /// implementation is [`ExtendedKeyUsage`]. + /// * `supported_sig_algs` is the list of signature algorithms that are + /// trusted for use in certificate signatures; the end-entity certificate's + /// public key is not validated against this list. + /// * `trust_anchors` is the list of root CAs to trust in the built path. + pub fn new( + eku: &'p dyn ExtendedKeyUsageValidator, + supported_sig_algs: &'a [&'a dyn SignatureVerificationAlgorithm], + trust_anchors: &'p [TrustAnchor<'p>], + ) -> Self { + Self { + eku, + supported_sig_algs, + trust_anchors, + intermediate_certs: &[], + revocation: None, + verify_path: None, + } + } + + /// Set the sequence of intermediate certificates to use for path building. + /// + /// These should be sent by the peer. Defaults to empty. + pub fn with_intermediate_certs(mut self, intermediate_certs: &'p [CertificateDer<'p>]) -> Self { + self.intermediate_certs = intermediate_certs; + self + } + + /// Set the revocation options to use for path building. + /// + /// By default, revocation checking is disabled. + pub fn with_revocation(mut self, revocation: RevocationOptions<'a>) -> Self { + self.revocation = Some(revocation); + self + } + + /// Set a path verification function to use for path building. + /// + /// `verify()` will only be called for potentially verified paths, that is, paths that + /// have been verified up to the trust anchor. As such, `verify()` cannot be used to + /// verify a path that doesn't satisfy the constraints listed above; it can only be used to + /// reject a path that does satisfy the aforementioned constraints. If `verify()` returns + /// an error, path building will continue in order to try other options. + /// + /// By default, no additional path verification is done. + pub fn with_path_verification( + mut self, + verify: &'a dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>, + ) -> Self { + self.verify_path = Some(verify); + self + } + + /// Build a [`VerifiedPath`] for `end_entity` at the given `time`. + /// + /// If successful, yields a `VerifiedPath` type that can be used to inspect a verified chain + /// of certificates that leads from the `end_entity` to one of the `self.trust_anchors`. + pub fn build( &self, end_entity: &'p EndEntityCert<'p>, time: UnixTime, - verify_path: Option<&dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>, ) -> Result, Error> { let mut path = PartialPath::new(end_entity); - match self.build_chain_inner(&mut path, time, verify_path, 0, &mut Budget::default()) { + match self.build_chain_inner(&mut path, time, 0, &mut Budget::default()) { Ok(anchor) => Ok(VerifiedPath::new(end_entity, anchor, path)), Err(ControlFlow::Break(err)) | Err(ControlFlow::Continue(err)) => Err(err), } } - #[expect(clippy::type_complexity)] fn build_chain_inner( &self, path: &mut PartialPath<'p>, time: UnixTime, - verify_path: Option<&dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>, sub_ca_count: usize, budget: &mut Budget, ) -> Result<&'p TrustAnchor<'p>, ControlFlow> { @@ -83,7 +143,7 @@ impl<'a, 'p: 'a> ChainOptions<'a, 'p> { self.check_signed_chain(&node, time, trust_anchor, budget)?; check_signed_chain_name_constraints(&node, trust_anchor, budget)?; - let Some(verify) = verify_path else { + let Some(verify) = self.verify_path else { return Ok(trust_anchor); }; @@ -130,7 +190,7 @@ impl<'a, 'p: 'a> ChainOptions<'a, 'p> { budget.consume_build_chain_call()?; path.push(potential_issuer)?; - let result = self.build_chain_inner(path, time, verify_path, next_sub_ca_count, budget); + let result = self.build_chain_inner(path, time, next_sub_ca_count, budget); if result.is_err() { path.pop(); } @@ -175,9 +235,7 @@ impl<'a, 'p: 'a> ChainOptions<'a, 'p> { } } -/// Path from end-entity certificate to trust anchor that's been verified. -/// -/// See [`EndEntityCert::verify_for_usage()`] for more details on what verification entails. +/// Path from end-entity certificate to trust anchor that's been verified by a [`PathBuilder`]. pub struct VerifiedPath<'p> { end_entity: &'p EndEntityCert<'p>, intermediates: Intermediates<'p>, @@ -522,21 +580,6 @@ pub struct ExtendedKeyUsage { } impl ExtendedKeyUsage { - /// Construct a new [`ExtendedKeyUsage`] as appropriate for server certificate authentication. - /// - /// As specified in , this does not - /// require the certificate to specify the eKU extension. - pub const fn server_auth() -> Self { - Self::required_if_present(EKU_SERVER_AUTH) - } - - /// Construct a new [`ExtendedKeyUsage`] as appropriate for client certificate authentication. - /// - /// As specified in <>, this does not require the certificate to specify the eKU extension. - pub const fn client_auth() -> Self { - Self::required_if_present(EKU_CLIENT_AUTH) - } - /// Construct a new [`ExtendedKeyUsage`] requiring a certificate to support the specified OID. pub const fn required(oid: &'static [u8]) -> Self { Self { @@ -563,6 +606,17 @@ impl ExtendedKeyUsage { ) } + /// An [`ExtendedKeyUsage`] for server certificate authentication. + /// + /// As specified in , this does not + /// require the certificate to specify the eKU extension. + pub const SERVER_AUTH: Self = Self::required_if_present(EKU_SERVER_AUTH); + + /// An [`ExtendedKeyUsage`] as appropriate for client certificate authentication. + /// + /// As specified in , this does not require the certificate to specify the eKU extension. + pub const CLIENT_AUTH: Self = Self::required_if_present(EKU_CLIENT_AUTH); + /// Human-readable representation of the server authentication OID. pub const SERVER_AUTH_REPR: &[usize] = &[1, 3, 6, 1, 5, 5, 7, 3, 1]; /// Human-readable representation of the client authentication OID. @@ -910,6 +964,7 @@ pub(crate) enum Role { mod tests { use alloc::borrow::ToOwned; use alloc::string::{String, ToString}; + use core::time::Duration; use std::dbg; use std::slice; @@ -936,13 +991,13 @@ mod tests { #[test] fn oid_decoding() { assert_eq!( - ExtendedKeyUsage::server_auth() + ExtendedKeyUsage::SERVER_AUTH .oid_values() .collect::>(), ExtendedKeyUsage::SERVER_AUTH_REPR ); assert_eq!( - ExtendedKeyUsage::client_auth() + ExtendedKeyUsage::CLIENT_AUTH .oid_values() .collect::>(), ExtendedKeyUsage::CLIENT_AUTH_REPR @@ -1364,27 +1419,25 @@ mod tests { verify_path: Option<&dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>, budget: Option, ) -> Result, ControlFlow> { - use core::time::Duration; - let time = UnixTime::since_unix_epoch(Duration::from_secs(0x1fed_f00d)); let mut path = PartialPath::new(ee_cert); - let opts = ChainOptions { - eku: &ExtendedKeyUsage::server_auth(), - supported_sig_algs: rustls_aws_lc_rs::ALL_VERIFICATION_ALGS, + + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + rustls_aws_lc_rs::ALL_VERIFICATION_ALGS, trust_anchors, - intermediate_certs, - revocation: None, + ) + .with_intermediate_certs(intermediate_certs); + let builder = match verify_path { + Some(verify) => builder.with_path_verification(verify), + None => builder, }; - match opts.build_chain_inner( - &mut path, - time, - verify_path, - 0, - &mut budget.unwrap_or_default(), - ) { + match builder.build_chain_inner(&mut path, time, 0, &mut budget.unwrap_or_default()) { Ok(anchor) => Ok(VerifiedPath::new(ee_cert, anchor, path)), Err(err) => Err(err), } } + + //const EKU_SERVER_AUTH: ExtendedKeyUsage = ExtendedKeyUsage::server_auth(); } diff --git a/tests/amazon.rs b/tests/amazon.rs index 588bc9b2..7e2955d1 100644 --- a/tests/amazon.rs +++ b/tests/amazon.rs @@ -6,7 +6,7 @@ use core::time::Duration; use pki_types::{CertificateDer, ServerName, UnixTime}; use rustls_aws_lc_rs::ALL_VERIFICATION_ALGS; use webpki::{ - CertRevocationList, EndEntityCert, ExtendedKeyUsage, OwnedCertRevocationList, + CertRevocationList, EndEntityCert, ExtendedKeyUsage, OwnedCertRevocationList, PathBuilder, RevocationCheckDepth, RevocationOptions, RevocationOptionsBuilder, UnknownStatusPolicy, anchor_from_trusted_cert, }; @@ -21,7 +21,6 @@ fn revocation_options_for_test<'a>( .build() } -#[cfg(feature = "alloc")] #[test] pub fn amazon() { // The 4 Amazon roots @@ -237,45 +236,62 @@ pub fn amazon() { Some(&intermediates_crls), Some(&all_crls), ] { - assert!( - cert.verify_for_usage( - ALL_VERIFICATION_ALGS, - &anchors, - &intermediates, - time, - &ExtendedKeyUsage::server_auth(), - crls.map(|l| revocation_options_for_test(l)), - None, - ) - .is_ok() - ); + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + ALL_VERIFICATION_ALGS, + &anchors, + ) + .with_intermediate_certs(&intermediates); - assert!( - cert.verify_for_usage( - ALL_VERIFICATION_ALGS, - &legacy_anchors, - &intermediates_legacy, - time, - &ExtendedKeyUsage::server_auth(), - crls.map(|l| revocation_options_for_test(l)), - None, - ) - .is_ok() - ); + let builder = match crls { + Some(crls) => builder.with_revocation(revocation_options_for_test(crls)), + None => builder, + }; + + assert!(builder.build(&cert, time).is_ok()); - let path = cert - .verify_for_usage( - ALL_VERIFICATION_ALGS, - &all_anchors, - &intermediates_legacy, - time, - &ExtendedKeyUsage::server_auth(), - crls.map(|l| revocation_options_for_test(l)), - None, - ) - .unwrap(); + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + ALL_VERIFICATION_ALGS, + &legacy_anchors, + ) + .with_intermediate_certs(&intermediates_legacy); + + let builder = match crls { + Some(crls) => builder.with_revocation(revocation_options_for_test(crls)), + None => builder, + }; + + assert!(builder.build(&cert, time).is_ok()); + + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + ALL_VERIFICATION_ALGS, + &all_anchors, + ) + .with_intermediate_certs(&intermediates_legacy); + + let builder = match crls { + Some(crls) => builder.with_revocation(revocation_options_for_test(crls)), + None => builder, + }; + + assert!(builder.build(&cert, time).is_ok()); + + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + ALL_VERIFICATION_ALGS, + &all_anchors, + ) + .with_intermediate_certs(&intermediates_legacy); + + let builder = match crls { + Some(crls) => builder.with_revocation(revocation_options_for_test(crls)), + None => builder, + }; // verify should find shortest path + let path = builder.build(&cert, time).unwrap(); assert!(anchors.contains(path.anchor())); } } @@ -285,51 +301,52 @@ pub fn amazon() { let cert = EndEntityCert::try_from(&cert).unwrap(); for &crls in &[None, Some(&roots_crls)] { - assert!( - cert.verify_for_usage( - ALL_VERIFICATION_ALGS, - &anchors, - &intermediates, - time, - &ExtendedKeyUsage::server_auth(), - crls.map(|l| revocation_options_for_test(l)), - None, - ) - .is_ok() - ); + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + ALL_VERIFICATION_ALGS, + &anchors, + ) + .with_intermediate_certs(&intermediates); + + let builder = match crls { + Some(crls) => builder.with_revocation(revocation_options_for_test(crls)), + None => builder, + }; + + assert!(builder.build(&cert, time).is_ok()); } for &crls in &[&intermediates_crls, &all_crls] { + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + ALL_VERIFICATION_ALGS, + &anchors, + ) + .with_intermediate_certs(&intermediates) + .with_revocation(revocation_options_for_test(crls)); + assert!( - cert.verify_for_usage( - ALL_VERIFICATION_ALGS, - &anchors, - &intermediates, - time, - &ExtendedKeyUsage::server_auth(), - Some(revocation_options_for_test(crls)), - None, - ) - .is_err_and(|e| matches!(e, webpki::Error::CertRevoked)) + builder + .build(&cert, time) + .is_err_and(|e| matches!(e, webpki::Error::CertRevoked)) ); } } for &(cert, _dns_name) in expired_certs { + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + ALL_VERIFICATION_ALGS, + &anchors, + ) + .with_intermediate_certs(&intermediates); + let cert = CertificateDer::from(cert); let cert = EndEntityCert::try_from(&cert).unwrap(); - assert!( - cert.verify_for_usage( - ALL_VERIFICATION_ALGS, - &anchors, - &intermediates, - time, - &ExtendedKeyUsage::server_auth(), - None, - None, - ) - .is_err_and(|e| matches!(e, webpki::Error::CertExpired { .. })) + builder + .build(&cert, time) + .is_err_and(|e| matches!(e, webpki::Error::CertExpired { .. })) ); } } diff --git a/tests/client_auth.rs b/tests/client_auth.rs index 11866bea..0383be48 100644 --- a/tests/client_auth.rs +++ b/tests/client_auth.rs @@ -19,7 +19,7 @@ use std::error::Error as StdError; use core::time::Duration; use pki_types::{CertificateDer, UnixTime}; use rcgen::{Certificate, ExtendedKeyUsagePurpose}; -use webpki::{ExtendedKeyUsage, RequiredEkuNotFoundContext, anchor_from_trusted_cert}; +use webpki::{ExtendedKeyUsage, PathBuilder, RequiredEkuNotFoundContext, anchor_from_trusted_cert}; mod common; use common::{make_end_entity, make_issuer}; @@ -65,7 +65,7 @@ fn cert_with_serverauth_eku_rejected_for_client_auth() { assert_eq!( err, webpki::Error::RequiredEkuNotFound(RequiredEkuNotFoundContext { - required: ExtendedKeyUsage::client_auth(), + required: ExtendedKeyUsage::CLIENT_AUTH, present: vec![vec![1, 3, 6, 1, 5, 5, 7, 3, 1]], }) ); @@ -78,20 +78,16 @@ fn cert_with_serverauth_eku_rejected_for_client_auth() { fn check_cert(ee: &[u8], ca: CertificateDer<'static>) -> Result<(), webpki::Error> { let anchors = &[anchor_from_trusted_cert(&ca).unwrap()]; + let builder = PathBuilder::new( + &ExtendedKeyUsage::CLIENT_AUTH, + rustls_aws_lc_rs::ALL_VERIFICATION_ALGS, + anchors, + ); let time = UnixTime::since_unix_epoch(Duration::from_secs(0x1fed_f00d)); let ee = CertificateDer::from(ee); let cert = webpki::EndEntityCert::try_from(&ee).unwrap(); - cert.verify_for_usage( - rustls_aws_lc_rs::ALL_VERIFICATION_ALGS, - anchors, - &[], - time, - &ExtendedKeyUsage::client_auth(), - None, - None, - ) - .map(|_| ()) + builder.build(&cert, time).map(|_| ()) } fn test_certs( diff --git a/tests/client_auth_revocation.rs b/tests/client_auth_revocation.rs index d4655325..8d4bc1c9 100644 --- a/tests/client_auth_revocation.rs +++ b/tests/client_auth_revocation.rs @@ -26,8 +26,9 @@ use rcgen::{ #[cfg(feature = "alloc")] use webpki::OwnedCertRevocationList; use webpki::{ - BorrowedCertRevocationList, CertRevocationList, ExtendedKeyUsage, RevocationCheckDepth, - RevocationOptions, RevocationOptionsBuilder, UnknownStatusPolicy, anchor_from_trusted_cert, + BorrowedCertRevocationList, CertRevocationList, ExtendedKeyUsage, PathBuilder, + RevocationCheckDepth, RevocationOptions, RevocationOptionsBuilder, UnknownStatusPolicy, + anchor_from_trusted_cert, }; use x509_parser::oid_registry; @@ -44,24 +45,23 @@ fn check_cert( ) -> Result<(), webpki::Error> { let ca = CertificateDer::from(ca); let anchors = &[anchor_from_trusted_cert(&ca).unwrap()]; - let ee = CertificateDer::from(ee); - let cert = webpki::EndEntityCert::try_from(&ee).unwrap(); - let time = UnixTime::since_unix_epoch(Duration::from_secs(0x1fed_f00d)); let intermediates = intermediates .iter() .map(|cert| CertificateDer::from(*cert)) .collect::>(); - cert.verify_for_usage( - ALGS, - anchors, - &intermediates, - time, - &ExtendedKeyUsage::client_auth(), - revocation, - None, - ) - .map(|_| ()) + let builder = PathBuilder::new(&ExtendedKeyUsage::CLIENT_AUTH, ALGS, anchors) + .with_intermediate_certs(&intermediates); + + let builder = match revocation { + Some(crls) => builder.with_revocation(crls), + None => builder, + }; + + let ee = CertificateDer::from(ee); + let cert = webpki::EndEntityCert::try_from(&ee).unwrap(); + let time = UnixTime::since_unix_epoch(Duration::from_secs(0x1fed_f00d)); + builder.build(&cert, time).map(|_| ()) } #[test] diff --git a/tests/custom_ekus.rs b/tests/custom_ekus.rs index 00dcb7ca..4f5d8f6c 100644 --- a/tests/custom_ekus.rs +++ b/tests/custom_ekus.rs @@ -3,7 +3,7 @@ use core::time::Duration; use pki_types::{CertificateDer, UnixTime}; -use webpki::{ExtendedKeyUsage, RequiredEkuNotFoundContext, anchor_from_trusted_cert}; +use webpki::{ExtendedKeyUsage, PathBuilder, RequiredEkuNotFoundContext, anchor_from_trusted_cert}; fn check_cert( ee: &[u8], @@ -14,23 +14,11 @@ fn check_cert( ) { let ca = CertificateDer::from(ca); let anchors = [anchor_from_trusted_cert(&ca).unwrap()]; + let builder = PathBuilder::new(eku, rustls_aws_lc_rs::ALL_VERIFICATION_ALGS, &anchors); let ee = CertificateDer::from(ee); let cert = webpki::EndEntityCert::try_from(&ee).unwrap(); - - assert_eq!( - cert.verify_for_usage( - rustls_aws_lc_rs::ALL_VERIFICATION_ALGS, - &anchors, - &[], - time, - eku, - None, - None, - ) - .map(|_| ()), - result - ); + assert_eq!(builder.build(&cert, time).map(|_| ()), result); } #[test] @@ -45,11 +33,11 @@ pub fn verify_custom_eku_mdoc() { check_cert( ee, ca, - &ExtendedKeyUsage::server_auth(), + &ExtendedKeyUsage::SERVER_AUTH, time, Err(webpki::Error::RequiredEkuNotFound( RequiredEkuNotFoundContext { - required: ExtendedKeyUsage::server_auth(), + required: ExtendedKeyUsage::SERVER_AUTH, present: vec![vec![1, 0, 18013, 5, 1, 2]], }, )), @@ -58,11 +46,11 @@ pub fn verify_custom_eku_mdoc() { check_cert( ee, ca, - &ExtendedKeyUsage::server_auth(), + &ExtendedKeyUsage::SERVER_AUTH, time, Err(webpki::Error::RequiredEkuNotFound( RequiredEkuNotFoundContext { - required: ExtendedKeyUsage::server_auth(), + required: ExtendedKeyUsage::SERVER_AUTH, present: vec![vec![1, 0, 18013, 5, 1, 2]], }, )), @@ -75,12 +63,12 @@ pub fn verify_custom_eku_client() { let ee = include_bytes!("custom_ekus/cert_with_no_eku_accepted_for_client_auth.ee.der"); let ca = include_bytes!("custom_ekus/cert_with_no_eku_accepted_for_client_auth.ca.der"); - check_cert(ee, ca, &ExtendedKeyUsage::client_auth(), time, Ok(())); + check_cert(ee, ca, &ExtendedKeyUsage::CLIENT_AUTH, time, Ok(())); let ee = include_bytes!("custom_ekus/cert_with_both_ekus_accepted_for_client_auth.ee.der"); let ca = include_bytes!("custom_ekus/cert_with_both_ekus_accepted_for_client_auth.ca.der"); - check_cert(ee, ca, &ExtendedKeyUsage::client_auth(), time, Ok(())); - check_cert(ee, ca, &ExtendedKeyUsage::server_auth(), time, Ok(())); + check_cert(ee, ca, &ExtendedKeyUsage::CLIENT_AUTH, time, Ok(())); + check_cert(ee, ca, &ExtendedKeyUsage::SERVER_AUTH, time, Ok(())); } #[test] diff --git a/tests/integration.rs b/tests/integration.rs index 286c4049..ea7bc62b 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -19,7 +19,7 @@ use core::time::Duration; use pki_types::{CertificateDer, UnixTime}; use rustls_aws_lc_rs::ALL_VERIFICATION_ALGS; use webpki::sct::LogIdAndTimestamp; -use webpki::{ExtendedKeyUsage, anchor_from_trusted_cert}; +use webpki::{ExtendedKeyUsage, PathBuilder, anchor_from_trusted_cert}; /* Checks we can verify netflix's cert chain. This is notable * because they're rooted at a Verisign v1 root. */ @@ -31,23 +31,18 @@ fn netflix() { let ca = CertificateDer::from(&include_bytes!("netflix/ca.der")[..]); let anchors = [anchor_from_trusted_cert(&ca).unwrap()]; + let intermediates = &[inter]; + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + ALL_VERIFICATION_ALGS, + &anchors, + ) + .with_intermediate_certs(intermediates); let time = UnixTime::since_unix_epoch(Duration::from_secs(1_492_441_716)); // 2017-04-17T15:08:36Z - let ee = CertificateDer::from(ee); let cert = webpki::EndEntityCert::try_from(&ee).unwrap(); - assert!( - cert.verify_for_usage( - ALL_VERIFICATION_ALGS, - &anchors, - &[inter], - time, - &ExtendedKeyUsage::server_auth(), - None, - None, - ) - .is_ok() - ); + assert!(builder.build(&cert, time).is_ok()); } /// See also https://github.com/rustls/rustls/issues/2448 @@ -59,23 +54,18 @@ fn sanofi_rsa_signature_with_absent_algorithm_params() { let ca = CertificateDer::from(&include_bytes!("sanofi/ca.der")[..]); let anchors = [anchor_from_trusted_cert(&ca).unwrap()]; + let intermediates = &[inter]; + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + ALL_VERIFICATION_ALGS, + &anchors, + ) + .with_intermediate_certs(intermediates); let time = UnixTime::since_unix_epoch(Duration::from_secs(1_746_549_566)); // 2025-05-06T17:39:26Z - let ee = CertificateDer::from(ee); let cert = webpki::EndEntityCert::try_from(&ee).unwrap(); - assert!( - cert.verify_for_usage( - ALL_VERIFICATION_ALGS, - &anchors, - &[inter], - time, - &ExtendedKeyUsage::server_auth(), - None, - None, - ) - .is_ok() - ); + assert!(builder.build(&cert, time).is_ok()); } /* This is notable because it is a popular use of IP address subjectAltNames. */ @@ -88,25 +78,19 @@ fn cloudflare_dns() { let inter = CertificateDer::from(&include_bytes!("cloudflare_dns/inter.der")[..]); let ca = CertificateDer::from(&include_bytes!("cloudflare_dns/ca.der")[..]); - let ca_cert = CertificateDer::from(&ca[..]); - let anchors = [anchor_from_trusted_cert(&ca_cert).unwrap()]; + let anchors = [anchor_from_trusted_cert(&ca).unwrap()]; + let intermediates = &[inter]; + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + ALL_VERIFICATION_ALGS, + &anchors, + ) + .with_intermediate_certs(intermediates); let time = UnixTime::since_unix_epoch(Duration::from_secs(1_663_495_771)); - let ee = CertificateDer::from(ee); let cert = webpki::EndEntityCert::try_from(&ee).unwrap(); - assert!( - cert.verify_for_usage( - ALL_VERIFICATION_ALGS, - &anchors, - &[inter], - time, - &ExtendedKeyUsage::server_auth(), - None, - None, - ) - .is_ok() - ); + assert!(builder.build(&cert, time).is_ok()); let check_name = |name: &str| { let subject_name_ref = ServerName::try_from(name).unwrap(); @@ -146,21 +130,15 @@ fn wpt() { let ca = CertificateDer::from(&include_bytes!("wpt/ca.der")[..]); let anchors = [anchor_from_trusted_cert(&ca).unwrap()]; + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + ALL_VERIFICATION_ALGS, + &anchors, + ); let time = UnixTime::since_unix_epoch(Duration::from_secs(1_619_256_684)); // 2021-04-24T09:31:24Z let cert = webpki::EndEntityCert::try_from(&ee).unwrap(); - assert!( - cert.verify_for_usage( - ALL_VERIFICATION_ALGS, - &anchors, - &[], - time, - &ExtendedKeyUsage::server_auth(), - None, - None, - ) - .is_ok() - ); + assert!(builder.build(&cert, time).is_ok()); } #[test] @@ -169,22 +147,15 @@ fn ed25519() { let ca = CertificateDer::from(&include_bytes!("ed25519/ca.der")[..]); let anchors = [anchor_from_trusted_cert(&ca).unwrap()]; + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + ALL_VERIFICATION_ALGS, + &anchors, + ); let time = UnixTime::since_unix_epoch(Duration::from_secs(1_547_363_522)); // 2019-01-13T07:12:02Z - let cert = webpki::EndEntityCert::try_from(&ee).unwrap(); - assert!( - cert.verify_for_usage( - ALL_VERIFICATION_ALGS, - &anchors, - &[], - time, - &ExtendedKeyUsage::server_auth(), - None, - None, - ) - .is_ok() - ); + assert!(builder.build(&cert, time).is_ok()); } #[test] @@ -193,26 +164,22 @@ fn critical_extensions() { let root = CertificateDer::from(&include_bytes!("critical_extensions/root-cert.der")[..]); let ca = CertificateDer::from(&include_bytes!("critical_extensions/ca-cert.der")[..]); - let time = UnixTime::since_unix_epoch(Duration::from_secs(1_670_779_098)); let anchors = [anchor_from_trusted_cert(&root).unwrap()]; - let intermediates = [ca]; + let intermediates = &[ca]; + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + ALL_VERIFICATION_ALGS, + &anchors, + ) + .with_intermediate_certs(intermediates); + let time = UnixTime::since_unix_epoch(Duration::from_secs(1_670_779_098)); let ee = CertificateDer::from( &include_bytes!("critical_extensions/ee-cert-noncrit-unknown-ext.der")[..], ); let ee_cert = webpki::EndEntityCert::try_from(&ee).unwrap(); assert!( - ee_cert - .verify_for_usage( - ALL_VERIFICATION_ALGS, - &anchors, - &intermediates, - time, - &ExtendedKeyUsage::server_auth(), - None, - None, - ) - .is_ok(), + builder.build(&ee_cert, time).is_ok(), "accept non-critical unknown extension" ); @@ -247,22 +214,15 @@ fn read_ee_with_neg_serial() { let ee = CertificateDer::from(&include_bytes!("misc/serial_neg_ee.der")[..]); let anchors = [anchor_from_trusted_cert(&ca).unwrap()]; + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + ALL_VERIFICATION_ALGS, + &anchors, + ); let time = UnixTime::since_unix_epoch(Duration::from_secs(1_667_401_500)); // 2022-11-02T15:05:00Z - let cert = webpki::EndEntityCert::try_from(&ee).unwrap(); - assert!( - cert.verify_for_usage( - ALL_VERIFICATION_ALGS, - &anchors, - &[], - time, - &ExtendedKeyUsage::server_auth(), - None, - None, - ) - .is_ok() - ); + assert!(builder.build(&cert, time).is_ok()); } #[test] @@ -419,6 +379,12 @@ fn cert_time_validity() { let ca = CertificateDer::from(&include_bytes!("netflix/ca.der")[..]); let anchors = [anchor_from_trusted_cert(&ca).unwrap()]; + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + ALL_VERIFICATION_ALGS, + &anchors, + ) + .with_intermediate_certs(slice::from_ref(&inter)); let not_before = UnixTime::since_unix_epoch(Duration::from_secs(1_478_563_200)); let not_after = UnixTime::since_unix_epoch(Duration::from_secs(1_541_203_199)); @@ -430,16 +396,7 @@ fn cert_time_validity() { let cert = webpki::EndEntityCert::try_from(&ee).unwrap(); assert_eq!( - cert.verify_for_usage( - ALL_VERIFICATION_ALGS, - &anchors, - slice::from_ref(&inter), - just_before, - &ExtendedKeyUsage::server_auth(), - None, - None, - ) - .err(), + builder.build(&cert, just_before).err(), Some(webpki::Error::CertNotValidYet { time: just_before, not_before @@ -447,16 +404,7 @@ fn cert_time_validity() { ); assert_eq!( - cert.verify_for_usage( - ALL_VERIFICATION_ALGS, - &anchors, - &[inter], - just_after, - &ExtendedKeyUsage::server_auth(), - None, - None, - ) - .err(), + builder.build(&cert, just_after).err(), Some(webpki::Error::CertExpired { time: just_after, not_after diff --git a/tests/tls_server_certs.rs b/tests/tls_server_certs.rs index a2a47de1..3c04cca8 100644 --- a/tests/tls_server_certs.rs +++ b/tests/tls_server_certs.rs @@ -21,7 +21,7 @@ use rcgen::{ DistinguishedName, DnType, GeneralSubtree, IsCa, KeyPair, NameConstraints, SanType, date_time_ymd, }; -use webpki::{ExtendedKeyUsage, InvalidNameContext, anchor_from_trusted_cert}; +use webpki::{ExtendedKeyUsage, InvalidNameContext, PathBuilder, anchor_from_trusted_cert}; mod common; use common::issuer_params; @@ -36,19 +36,16 @@ fn check_cert( ) -> Result<(), webpki::Error> { let ca_cert_der = CertificateDer::from(ca); let anchors = [anchor_from_trusted_cert(&ca_cert_der).unwrap()]; + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + rustls_aws_lc_rs::ALL_VERIFICATION_ALGS, + &anchors, + ); let ee_der = CertificateDer::from(ee); let time = UnixTime::since_unix_epoch(Duration::from_secs(0x1fed_f00d)); let cert = webpki::EndEntityCert::try_from(&ee_der).unwrap(); - cert.verify_for_usage( - rustls_aws_lc_rs::ALL_VERIFICATION_ALGS, - &anchors, - &[], - time, - &ExtendedKeyUsage::server_auth(), - None, - None, - )?; + builder.build(&cert, time)?; for valid in valid_names { let name = ServerName::try_from(*valid).unwrap(); diff --git a/tests/x509_limbo.rs b/tests/x509_limbo.rs index dfeeec4c..72f9dcb1 100644 --- a/tests/x509_limbo.rs +++ b/tests/x509_limbo.rs @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; use pki_types::pem::PemObject; use pki_types::{CertificateDer, CertificateRevocationListDer, ServerName, UnixTime}; use webpki::{ - EndEntityCert, ExpirationPolicy, ExtendedKeyUsage, OwnedCertRevocationList, + EndEntityCert, ExpirationPolicy, ExtendedKeyUsage, OwnedCertRevocationList, PathBuilder, RevocationCheckDepth, RevocationOptionsBuilder, UnknownStatusPolicy, anchor_from_trusted_cert, }; @@ -86,16 +86,6 @@ fn evaluate_testcase(tc: &Testcase, exceptions: &HashMap) -> /// Run validation and return Ok(()) on success, or an error message on failure fn run_validation(tc: &Testcase) -> Result<(), String> { - let leaf_der = cert_der_from_pem(&tc.peer_certificate); - let leaf = - EndEntityCert::try_from(&leaf_der).map_err(|e| format!("leaf cert parse failed: {e}"))?; - - let intermediates = tc - .untrusted_intermediates - .iter() - .map(|ic| cert_der_from_pem(ic)) - .collect::>(); - let trust_anchor_ders = tc .trusted_certs .iter() @@ -111,13 +101,18 @@ fn run_validation(tc: &Testcase) -> Result<(), String> { return Err("trust anchor extraction failed".into()); } - let validation_time = UnixTime::since_unix_epoch( - (tc.validation_time.unwrap_or_else(Utc::now) - DateTime::UNIX_EPOCH) - .to_std() - .expect("invalid validation time!"), - ); + let intermediates = tc + .untrusted_intermediates + .iter() + .map(|ic| cert_der_from_pem(ic)) + .collect::>(); - let sig_algs = rustls_aws_lc_rs::ALL_VERIFICATION_ALGS; + let builder = PathBuilder::new( + &ExtendedKeyUsage::SERVER_AUTH, + rustls_aws_lc_rs::ALL_VERIFICATION_ALGS, + &trust_anchors, + ) + .with_intermediate_certs(&intermediates); let crls = tc .crls @@ -134,26 +129,32 @@ fn run_validation(tc: &Testcase) -> Result<(), String> { .collect::>(); let crls = crls.iter().collect::>(); - let revocation_options = if !crls.is_empty() { - let opts = RevocationOptionsBuilder::new(crls.as_slice()).unwrap(); - opts.with_depth(RevocationCheckDepth::Chain); - opts.with_status_policy(UnknownStatusPolicy::Deny); - opts.with_expiration_policy(ExpirationPolicy::Enforce); - Some(opts.build()) + let builder = if !crls.is_empty() { + builder.with_revocation( + RevocationOptionsBuilder::new(crls.as_slice()) + .unwrap() + .with_depth(RevocationCheckDepth::Chain) + .with_status_policy(UnknownStatusPolicy::Deny) + .with_expiration_policy(ExpirationPolicy::Enforce) + .build(), + ) } else { - None + builder }; - leaf.verify_for_usage( - sig_algs, - &trust_anchors, - &intermediates[..], - validation_time, - &ExtendedKeyUsage::server_auth(), - revocation_options, - None, - ) - .map_err(|e| e.to_string())?; + let leaf_der = cert_der_from_pem(&tc.peer_certificate); + let leaf = + EndEntityCert::try_from(&leaf_der).map_err(|e| format!("leaf cert parse failed: {e}"))?; + + let validation_time = UnixTime::since_unix_epoch( + (tc.validation_time.unwrap_or_else(Utc::now) - DateTime::UNIX_EPOCH) + .to_std() + .expect("invalid validation time!"), + ); + + builder + .build(&leaf, validation_time) + .map_err(|e| e.to_string())?; // Verify subject name if expected if let Some(peer_name) = tc.expected_peer_name.as_ref() {