Skip to content
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

Return the trust anchors of the verified paths #1

Merged
merged 3 commits into from
Nov 23, 2023
Merged
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
62 changes: 31 additions & 31 deletions src/end_entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,16 @@ impl<'a> EndEntityCert<'a> {
}

#[allow(clippy::too_many_arguments)]
fn verify_is_valid_cert(
fn verify_is_valid_cert<'b, 'c>(
&self,
supported_sig_algs: &[&SignatureAlgorithm],
trust_anchors: &[TrustAnchor],
intermediate_certs: &[&[u8]],
supported_sig_algs: &'c [&SignatureAlgorithm],
trust_anchors: &'c [TrustAnchor<'b>],
intermediate_certs: &'c [&[u8]],
time: Option<Time>,
eku: KeyUsage,
crls: &[&dyn CertRevocationList],
user_initial_policy_set: &[&[u8]],
) -> Result<(), Error> {
crls: &'c [&dyn CertRevocationList],
user_initial_policy_set: &'c [&[u8]],
) -> Result<TrustAnchor<'b>, Error> {
verify_cert::build_chain(
&verify_cert::ChainOptions {
eku,
Expand Down Expand Up @@ -116,15 +116,15 @@ impl<'a> EndEntityCert<'a> {
/// of usage we're verifying the certificate for.
/// * `crls` is the list of certificate revocation lists to check
/// the certificate against.
pub fn verify_for_usage(
pub fn verify_for_usage<'b, 'c>(
&self,
supported_sig_algs: &[&SignatureAlgorithm],
trust_anchors: &[TrustAnchor],
intermediate_certs: &[&[u8]],
supported_sig_algs: &'c [&SignatureAlgorithm],
trust_anchors: &'c [TrustAnchor<'b>],
intermediate_certs: &'c [&[u8]],
time: Option<Time>,
usage: KeyUsage,
crls: &[&dyn CertRevocationList],
) -> Result<(), Error> {
crls: &'c [&dyn CertRevocationList],
) -> Result<TrustAnchor<'b>, Error> {
self.verify_is_valid_cert(
supported_sig_algs,
trust_anchors,
Expand All @@ -146,16 +146,16 @@ impl<'a> EndEntityCert<'a> {
/// method is equivalent to [`EndEntityCert::verify_for_usage`] if this
/// slice is empty.
#[allow(clippy::too_many_arguments)]
pub fn verify_for_usage_with_policy_check(
pub fn verify_for_usage_with_policy_check<'b, 'c>(
&self,
supported_sig_algs: &[&SignatureAlgorithm],
trust_anchors: &[TrustAnchor],
intermediate_certs: &[&[u8]],
supported_sig_algs: &'c [&SignatureAlgorithm],
trust_anchors: &'c [TrustAnchor<'b>],
intermediate_certs: &'c [&[u8]],
time: Option<Time>,
usage: KeyUsage,
crls: &[&dyn CertRevocationList],
user_initial_policy_set: &[&[u8]],
) -> Result<(), Error> {
crls: &'c [&dyn CertRevocationList],
user_initial_policy_set: &'c [&[u8]],
) -> Result<TrustAnchor<'b>, Error> {
self.verify_is_valid_cert(
supported_sig_algs,
trust_anchors,
Expand Down Expand Up @@ -185,13 +185,13 @@ impl<'a> EndEntityCert<'a> {
The new `verify_for_usage` function expresses trust anchor and end entity purpose with the \
key usage argument."
)]
pub fn verify_is_valid_tls_server_cert(
pub fn verify_is_valid_tls_server_cert<'b, 'c>(
&self,
supported_sig_algs: &[&SignatureAlgorithm],
&TlsServerTrustAnchors(trust_anchors): &TlsServerTrustAnchors,
intermediate_certs: &[&[u8]],
supported_sig_algs: &'c [&SignatureAlgorithm],
&TlsServerTrustAnchors(trust_anchors): &'c TlsServerTrustAnchors<'b>,
intermediate_certs: &'c [&[u8]],
time: Option<Time>,
) -> Result<(), Error> {
) -> Result<TrustAnchor<'b>, Error> {
self.verify_is_valid_cert(
supported_sig_algs,
trust_anchors,
Expand Down Expand Up @@ -222,14 +222,14 @@ impl<'a> EndEntityCert<'a> {
The new `verify_for_usage` function expresses trust anchor and end entity purpose with the \
key usage argument."
)]
pub fn verify_is_valid_tls_client_cert(
pub fn verify_is_valid_tls_client_cert<'b, 'c>(
&self,
supported_sig_algs: &[&SignatureAlgorithm],
&TlsClientTrustAnchors(trust_anchors): &TlsClientTrustAnchors,
intermediate_certs: &[&[u8]],
supported_sig_algs: &'c [&SignatureAlgorithm],
&TlsClientTrustAnchors(trust_anchors): &'c TlsClientTrustAnchors<'b>,
intermediate_certs: &'c [&[u8]],
time: Option<Time>,
crls: &[&dyn CertRevocationList],
) -> Result<(), Error> {
crls: &'c [&dyn CertRevocationList],
) -> Result<TrustAnchor<'b>, Error> {
self.verify_is_valid_cert(
supported_sig_algs,
trust_anchors,
Expand Down
99 changes: 90 additions & 9 deletions src/trust_anchor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use crate::{der, Error};
/// essential elements of trust anchors. The `TrustAnchor::try_from_cert_der`
/// function allows converting X.509 certificates to to the minimized
/// `TrustAnchor` representation, either at runtime or in a build script.
#[derive(Debug)]
///
/// `PartialEq` for `TrustAnchor` compares only the pointer values of the
/// underlying DER data.
#[derive(Clone, Debug)]
pub struct TrustAnchor<'a> {
/// The value of the `subject` field of the trust anchor.
pub subject: &'a [u8],
Expand All @@ -21,6 +24,9 @@ pub struct TrustAnchor<'a> {
/// The value of a DER-encoded NameConstraints, containing name
/// constraints to apply to the trust anchor, if any.
pub name_constraints: Option<&'a [u8]>,

/// Underlying DER representation.
pub(crate) underlying: &'a [u8],
}

/// Trust anchors which may be used for authenticating servers.
Expand Down Expand Up @@ -51,6 +57,7 @@ impl<'a> TrustAnchor<'a> {
/// certificate is self-signed or even that the certificate has the cA basic
/// constraint.
pub fn try_from_cert_der(cert_der: &'a [u8]) -> Result<Self, Error> {
let underlying = cert_der;
let cert_der = untrusted::Input::from(cert_der);

// XXX: `EndEntityOrCA::EndEntity` is used instead of `EndEntityOrCA::CA`
Expand All @@ -63,7 +70,12 @@ impl<'a> TrustAnchor<'a> {
// parser doesn't allow extensions, so there's no need to worry about
// embedded name constraints in a v1 certificate.
match Cert::from_der(cert_der, EndEntityOrCa::EndEntity) {
Ok(cert) => Ok(Self::from(cert)),
Ok(cert) => Ok(TrustAnchor {
subject: cert.subject.as_slice_less_safe(),
spki: cert.spki.value().as_slice_less_safe(),
name_constraints: cert.name_constraints.map(|nc| nc.as_slice_less_safe()),
underlying,
}),
Err(Error::UnsupportedCertVersion) => {
Self::from_v1_der(cert_der).or(Err(Error::BadDer))
}
Expand All @@ -73,6 +85,7 @@ impl<'a> TrustAnchor<'a> {

/// Parses a v1 certificate directly into a TrustAnchor.
fn from_v1_der(cert_der: untrusted::Input<'a>) -> Result<Self, Error> {
let underlying = cert_der.as_slice_less_safe();
// X.509 Certificate: https://tools.ietf.org/html/rfc5280#section-4.1.
cert_der.read_all(Error::BadDer, |cert_der| {
der::nested(cert_der, der::Tag::Sequence, Error::BadDer, |cert_der| {
Expand All @@ -90,6 +103,7 @@ impl<'a> TrustAnchor<'a> {
subject: subject.as_slice_less_safe(),
spki: spki.as_slice_less_safe(),
name_constraints: None,
underlying,
})
});

Expand All @@ -101,18 +115,85 @@ impl<'a> TrustAnchor<'a> {
})
})
}

/// Returns the underlying DER data.
pub fn as_der(&self) -> &[u8] {
self.underlying
}
}

impl<'a> From<Cert<'a>> for TrustAnchor<'a> {
fn from(cert: Cert<'a>) -> Self {
Self {
subject: cert.subject.as_slice_less_safe(),
spki: cert.spki.value().as_slice_less_safe(),
name_constraints: cert.name_constraints.map(|nc| nc.as_slice_less_safe()),
}
impl<'a> PartialEq<TrustAnchor<'_>> for TrustAnchor<'a> {
fn eq(&self, other: &TrustAnchor<'_>) -> bool {
self.underlying.as_ptr() == other.underlying.as_ptr()
}
}

fn skip(input: &mut untrusted::Reader, tag: der::Tag) -> Result<(), Error> {
der::expect_tag_and_get_value(input, tag).map(|_| ())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn trust_anchors_should_be_equal_if_underlying_data_are_identical() {
let underlying = &[0u8, 1u8, 2u8, 3u8];
let subject = &[0u8, 1u8];
let spki = &[2u8, 3u8];
let name_constraints: Option<&[u8]> = Some(&[4u8, 5u8]);
assert_eq!(
TrustAnchor {
subject,
spki,
name_constraints,
underlying,
},
TrustAnchor {
subject,
spki,
name_constraints,
underlying,
},
);
}

#[test]
fn trust_anchors_should_not_be_equal_if_underlying_data_are_non_identical() {
// wraps with Vec to make sure distinct memory blocks are allocated
let underlying1 = &vec![0u8, 1u8, 2u8, 3u8];
let underlying2 = &vec![0u8, 1u8, 2u8, 3u8];
let subject = &[0u8, 1u8];
let spki = &[2u8, 3u8];
let name_constraints: Option<&[u8]> = Some(&[4u8, 5u8]);
assert_ne!(
TrustAnchor {
subject,
spki,
name_constraints,
underlying: underlying1,
},
TrustAnchor {
subject,
spki,
name_constraints,
underlying: underlying2,
},
);
}

#[test]
fn clone_trust_anchor_should_equal_the_original() {
let underlying = &[0u8, 1u8, 2u8, 3u8];
let subject = &[0u8, 1u8];
let spki = &[2u8, 3u8];
let name_constraints: Option<&[u8]> = Some(&[4u8, 5u8]);
let trust_anchor = TrustAnchor {
subject,
spki,
name_constraints,
underlying,
};
assert_eq!(trust_anchor, trust_anchor.clone());
}
}
37 changes: 20 additions & 17 deletions src/verify_cert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,37 @@ use crate::{
SignatureAlgorithm, TrustAnchor,
};

pub(crate) struct ChainOptions<'a> {
pub(crate) struct ChainOptions<'a, 'b> {
pub(crate) eku: KeyUsage,
pub(crate) supported_sig_algs: &'a [&'a SignatureAlgorithm],
pub(crate) trust_anchors: &'a [TrustAnchor<'a>],
// TrustAnchor's contents have a distinct lifetime
// because they are returned from `build_chain`
pub(crate) trust_anchors: &'a [TrustAnchor<'b>],
pub(crate) intermediate_certs: &'a [&'a [u8]],
pub(crate) crls: &'a [&'a dyn CertRevocationList],
/// OIDs of acceptable certificate policies.
/// RFC 5280 Section 6.1.1 (c)
pub(crate) user_initial_policy_set: &'a [&'a [u8]],
}

pub(crate) fn build_chain(
opts: &ChainOptions,
pub(crate) fn build_chain<'b>(
opts: &ChainOptions<'_, 'b>,
cert: &Cert,
time: Option<time::Time>,
) -> Result<(), Error> {
) -> Result<TrustAnchor<'b>, Error> {
build_chain_inner(opts, cert, time, 0, &mut Budget::default()).map_err(|e| match e {
ControlFlow::Break(err) => err,
ControlFlow::Continue(err) => err,
})
}

fn build_chain_inner(
opts: &ChainOptions,
fn build_chain_inner<'b>(
opts: &ChainOptions<'_, 'b>,
cert: &Cert,
time: Option<time::Time>,
sub_ca_count: usize,
budget: &mut Budget,
) -> Result<(), ControlFlow<Error, Error>> {
) -> Result<TrustAnchor<'b>, ControlFlow<Error, Error>> {
let used_as_ca = used_as_ca(&cert.ee_or_ca);

check_issuer_independent_properties(cert, time, used_as_ca, sub_ca_count, opts.eku.inner)?;
Expand Down Expand Up @@ -95,12 +97,12 @@ fn build_chain_inner(
check_policy_tree(cert, opts.user_initial_policy_set)?;
}

Ok(())
Ok(trust_anchor.clone())
},
);

let err = match result {
Ok(()) => return Ok(()),
Ok(trust_anchor) => return Ok(trust_anchor),
// Fatal errors should halt further path building.
res @ Err(ControlFlow::Break(_)) => return res,
// Non-fatal errors should be carried forward as the default_error for subsequent
Expand Down Expand Up @@ -681,18 +683,18 @@ impl KeyUsageMode {
}
}

fn loop_while_non_fatal_error<V>(
fn loop_while_non_fatal_error<T, V>(
default_error: Error,
values: V,
mut f: impl FnMut(V::Item) -> Result<(), ControlFlow<Error, Error>>,
) -> Result<(), ControlFlow<Error, Error>>
mut f: impl FnMut(V::Item) -> Result<T, ControlFlow<Error, Error>>,
) -> Result<T, ControlFlow<Error, Error>>
where
V: IntoIterator,
{
let mut error = default_error;
for v in values {
match f(v) {
Ok(()) => return Ok(()),
Ok(res) => return Ok(res),
// Fatal errors should halt further looping.
res @ Err(ControlFlow::Break(_)) => return res,
// Non-fatal errors should be ranked by specificity and only returned
Expand Down Expand Up @@ -801,6 +803,7 @@ mod tests {
&make_end_entity(&issuer),
None,
)
.map(|_| ())
}

#[test]
Expand Down Expand Up @@ -902,12 +905,12 @@ mod tests {
}

#[cfg(feature = "alloc")]
fn verify_chain(
trust_anchor_der: &[u8],
fn verify_chain<'a>(
trust_anchor_der: &'a [u8],
intermediates_der: &[Vec<u8>],
ee_cert_der: &[u8],
budget: Option<Budget>,
) -> Result<(), ControlFlow<Error, Error>> {
) -> Result<TrustAnchor<'a>, ControlFlow<Error, Error>> {
use crate::ECDSA_P256_SHA256;
use crate::{EndEntityCert, Time};

Expand Down
1 change: 1 addition & 0 deletions tests/client_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ fn check_cert(ee: &[u8], ca: &[u8]) -> Result<(), webpki::Error> {
KeyUsage::client_auth(),
&[],
)
.map(|_| ())
}

// DO NOT EDIT BELOW: generated by tests/generate.py
Expand Down
1 change: 1 addition & 0 deletions tests/client_auth_revocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ fn check_cert(
KeyUsage::client_auth(),
crls,
)
.map(|_| ())
}

// DO NOT EDIT BELOW: generated by tests/generate.py
Expand Down
2 changes: 1 addition & 1 deletion tests/custom_ekus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fn check_cert(

assert_eq!(
cert.verify_for_usage(algs, &anchors, &[], Some(time), eku, &[]),
result
result.map(|_| anchors[0].clone())
);
}

Expand Down
Loading
Loading