Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 7 additions & 58 deletions src/end_entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,20 @@

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.
///
/// 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
Expand All @@ -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.
Expand Down Expand Up @@ -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<RevocationOptions<'_>>,
verify_path: Option<&dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>,
) -> Result<VerifiedPath<'p>, 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,
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};

Expand Down
143 changes: 98 additions & 45 deletions src/verify_cert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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 {
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<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>> {
Expand All @@ -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);
};

Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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 {
Expand All @@ -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.
Expand Down Expand Up @@ -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;

Expand All @@ -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
Expand Down Expand Up @@ -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();
}
Loading
Loading