-
Notifications
You must be signed in to change notification settings - Fork 98
Expose builder-like interface for path verification #486
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
856f71a
a2ef6ee
840c3d0
bb48169
c21cc41
ccf7c83
22fe08e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 struct PathBuilder<'a, 'p> { | ||
| pub(crate) eku: &'a 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<RevocationOptions<'a>>, | ||
| #[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 { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In almost any real-world WebPKI implementation, failing to supply intermediate certificates as input into the validation is going to result in bad behavior, so IMO the intermediate certificates should be parameters to |
||
| 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Every X.509 application has to make an explicit decision on what to do about revocation, so again, I think there should be some mechanism to ensure this isn't skipped. |
||
| 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<VerifiedPath<'p>, 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<Error, Error>> { | ||
|
|
@@ -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 <https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.12>, 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 <https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.12>, 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 <https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.12>, 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::<Vec<_>>(), | ||
| ExtendedKeyUsage::SERVER_AUTH_REPR | ||
| ); | ||
| assert_eq!( | ||
| ExtendedKeyUsage::client_auth() | ||
| ExtendedKeyUsage::CLIENT_AUTH | ||
| .oid_values() | ||
| .collect::<Vec<_>>(), | ||
| ExtendedKeyUsage::CLIENT_AUTH_REPR | ||
|
|
@@ -1364,27 +1419,25 @@ mod tests { | |
| verify_path: Option<&dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>, | ||
| budget: Option<Budget>, | ||
| ) -> Result<VerifiedPath<'a>, ControlFlow<Error, Error>> { | ||
| 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(); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ExtendedKeyUsageValidatoris not a sealed trait so this supports arbitrary user-implemented EKU matching, including EKU matching that is totally broken (no-op), right?If it is necessary to have such custom functionality, perhaps there could be a separate
not_for_webpki()constructor that takes a&dyn ExtendedKeyUsageValidator, while havingnewtake a concrete WebPKI-relevant OID foreku.(Also,
KeyUsageis a misleading name for the library-provided implementation of this type, because in X.509KeyUsageis a different thing.)