Skip to content
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
11 changes: 6 additions & 5 deletions src/end_entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use pki_types::{
use crate::crl::RevocationOptions;
use crate::error::Error;
use crate::subject_name::{verify_dns_names, verify_ip_address_names};
use crate::verify_cert::{self, KeyUsage, VerifiedPath};
use crate::verify_cert::{self, ExtendedKeyUsageValidator, VerifiedPath};
use crate::{cert, signed_data};

/// An end-entity certificate.
Expand All @@ -31,7 +31,7 @@ use crate::{cert, signed_data};
///
/// * [`EndEntityCert::verify_for_usage()`]: Verify that the peer's certificate
/// is valid for the current usage scenario. For server authentication, use
/// [`KeyUsage::server_auth()`].
/// [`crate::KeyUsage::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 @@ -42,7 +42,7 @@ use crate::{cert, signed_data};
///
/// * [`EndEntityCert::verify_for_usage()`]: Verify that the peer's certificate
/// is valid for the current usage scenario. For client authentication, use
/// [`KeyUsage::client_auth()`].
/// [`crate::KeyUsage::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 @@ -85,7 +85,8 @@ impl EndEntityCert<'_> {
/// * `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.
/// of usage we're verifying the certificate for. The default [`ExtendedKeyUsageValidator`]
/// implementation is [`KeyUsage`](crate::KeyUsage).
/// * `crls` is the list of certificate revocation lists to check
/// the certificate against.
/// * `verify_path` is an optional verification function for path candidates.
Expand All @@ -105,7 +106,7 @@ impl EndEntityCert<'_> {
trust_anchors: &'p [TrustAnchor<'_>],
intermediate_certs: &'p [CertificateDer<'p>],
time: UnixTime,
usage: KeyUsage,
usage: impl ExtendedKeyUsageValidator,
revocation: Option<RevocationOptions<'_>>,
verify_path: Option<&dyn Fn(&VerifiedPath<'_>) -> Result<(), Error>>,
) -> Result<VerifiedPath<'p>, Error> {
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ pub use {
},
rpk_entity::RawPublicKeyEntity,
trust_anchor::anchor_from_trusted_cert,
verify_cert::{KeyUsage, RequiredEkuNotFoundContext, VerifiedPath},
verify_cert::{ExtendedKeyUsageValidator, KeyUsage, RequiredEkuNotFoundContext, VerifiedPath},
};

#[cfg(feature = "alloc")]
Expand Down
147 changes: 89 additions & 58 deletions src/verify_cert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ use crate::{public_values_eq, signed_data, subject_name};

// 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: KeyUsage,
pub(crate) struct ChainOptions<'a, 'p, V> {
pub(crate) eku: V,
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>>,
}

impl<'a, 'p: 'a> ChainOptions<'a, 'p> {
impl<'a, 'p: 'a, V: ExtendedKeyUsageValidator> ChainOptions<'a, 'p, V> {
pub(crate) fn build_chain(
&self,
end_entity: &'p EndEntityCert<'p>,
Expand All @@ -60,7 +60,7 @@ impl<'a, 'p: 'a> ChainOptions<'a, 'p> {
) -> Result<&'p TrustAnchor<'p>, ControlFlow<Error, Error>> {
let role = path.node().role();

check_issuer_independent_properties(path.head(), time, role, sub_ca_count, self.eku.inner)?;
check_issuer_independent_properties(path.head(), time, role, sub_ca_count, &self.eku)?;

// TODO: HPKP checks.

Expand Down Expand Up @@ -349,7 +349,7 @@ fn check_issuer_independent_properties(
time: UnixTime,
role: Role,
sub_ca_count: usize,
eku: ExtendedKeyUsage,
eku: &impl ExtendedKeyUsageValidator,
) -> Result<(), Error> {
// TODO: check_distrust(trust_anchor_subject, trust_anchor_spki)?;
// TODO: Check signature algorithm like mozilla::pkix.
Expand All @@ -367,7 +367,12 @@ fn check_issuer_independent_properties(
untrusted::read_all_optional(cert.basic_constraints, Error::BadDer, |value| {
check_basic_constraints(value, role, sub_ca_count)
})?;
untrusted::read_all_optional(cert.eku, Error::BadDer, |value| eku.check(value))?;
untrusted::read_all_optional(cert.eku, Error::BadDer, |value| match value {
Some(input) => eku.validate(KeyPurposeIdIter { input }),
None => eku.validate(KeyPurposeIdIter {
input: &mut untrusted::Reader::new(untrusted::Input::from(&[])),
}),
})?;

Ok(())
}
Expand Down Expand Up @@ -544,6 +549,55 @@ impl KeyUsage {
pub const CLIENT_AUTH_REPR: &[usize] = &[1, 3, 6, 1, 5, 5, 7, 3, 2];
}

impl ExtendedKeyUsageValidator for KeyUsage {
// https://tools.ietf.org/html/rfc5280#section-4.2.1.12
fn validate(&self, iter: KeyPurposeIdIter<'_, '_>) -> Result<(), Error> {
let mut empty = true;
#[cfg(feature = "alloc")]
let mut present = Vec::new();

for id in iter {
empty = false;
let id = id?;
if self.inner.id() == id {
return Ok(());
}

#[cfg(feature = "alloc")]
present.push(OidDecoder::new(id.oid_value.as_slice_less_safe()).collect());
}

match (empty, self.inner) {
(true, ExtendedKeyUsage::RequiredIfPresent(_)) => Ok(()),
_ => Err(Error::RequiredEkuNotFoundContext(
RequiredEkuNotFoundContext {
#[cfg(feature = "alloc")]
required: Self { inner: self.inner },
#[cfg(feature = "alloc")]
present,
},
)),
}
}
}

impl<V: ExtendedKeyUsageValidator> ExtendedKeyUsageValidator for &V {
fn validate(&self, iter: KeyPurposeIdIter<'_, '_>) -> Result<(), Error> {
(*self).validate(iter)
}
}

/// A trait for validating the Extended Key Usage (EKU) extensions of a certificate.
pub trait ExtendedKeyUsageValidator {
/// Validate the EKU values in a certificate.
///
/// `iter` yields the EKU OIDs in the certificate, or an error if the EKU extension
/// is malformed. `validate()` should yield `Ok(())` if the EKU values match the
/// required policy, or an `Error` if they do not. Ideally the `Error` should be
/// `Error::RequiredEkuNotFoundContext` if the policy is not met.
fn validate(&self, iter: KeyPurposeIdIter<'_, '_>) -> Result<(), Error>;
}

/// Extended Key Usage (EKU) of a certificate.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ExtendedKeyUsage {
Expand All @@ -555,72 +609,47 @@ enum ExtendedKeyUsage {
}

impl ExtendedKeyUsage {
// https://tools.ietf.org/html/rfc5280#section-4.2.1.12
fn check(&self, input: Option<&mut untrusted::Reader<'_>>) -> Result<(), Error> {
let input = match (input, self) {
(Some(input), _) => input,
(None, Self::RequiredIfPresent(_)) => return Ok(()),
(None, Self::Required(_)) => {
return Err(Error::RequiredEkuNotFoundContext(
RequiredEkuNotFoundContext {
#[cfg(feature = "alloc")]
required: KeyUsage { inner: *self },
#[cfg(feature = "alloc")]
present: Vec::new(),
},
));
}
};
fn id(&self) -> KeyPurposeId<'static> {
match self {
Self::Required(id) => *id,
Self::RequiredIfPresent(id) => *id,
}
}
}

#[cfg(feature = "alloc")]
let mut present = Vec::new();
loop {
let value = der::expect_tag(input, der::Tag::OID)?;
if self.key_purpose_id_equals(value) {
input.skip_to_end();
break;
}
pub struct KeyPurposeIdIter<'a, 'r> {
input: &'r mut untrusted::Reader<'a>,
}

#[cfg(feature = "alloc")]
present.push(OidDecoder::new(value.as_slice_less_safe()).collect());
if input.at_end() {
return Err(Error::RequiredEkuNotFoundContext(
RequiredEkuNotFoundContext {
#[cfg(feature = "alloc")]
required: KeyUsage { inner: *self },
#[cfg(feature = "alloc")]
present,
},
));
}
impl<'a, 'r> Iterator for KeyPurposeIdIter<'a, 'r> {
type Item = Result<KeyPurposeId<'a>, Error>;

fn next(&mut self) -> Option<Self::Item> {
if self.input.at_end() {
return None;
}

Ok(())
Some(der::expect_tag(self.input, der::Tag::OID).map(|oid_value| KeyPurposeId { oid_value }))
}
}

fn key_purpose_id_equals(&self, value: untrusted::Input<'_>) -> bool {
public_values_eq(
match self {
Self::Required(eku) => *eku,
Self::RequiredIfPresent(eku) => *eku,
}
.oid_value,
value,
)
impl Drop for KeyPurposeIdIter<'_, '_> {
fn drop(&mut self) {
self.input.skip_to_end();
}
}

/// An OID value indicating an Extended Key Usage (EKU) key purpose.
#[derive(Clone, Copy)]
struct KeyPurposeId<'a> {
pub struct KeyPurposeId<'a> {
oid_value: untrusted::Input<'a>,
}

impl<'a> KeyPurposeId<'a> {
/// Construct a new [`KeyPurposeId`].
///
/// `oid` is the OBJECT IDENTIFIER in bytes.
const fn new(oid: &'a [u8]) -> Self {
pub const fn new(oid: &'a [u8]) -> Self {
Self {
oid_value: untrusted::Input::from(oid),
}
Expand Down Expand Up @@ -888,8 +917,10 @@ mod tests {

#[test]
fn eku_fail_empty() {
let err = ExtendedKeyUsage::Required(KeyPurposeId::new(EKU_SERVER_AUTH))
.check(None)
let err = KeyUsage::required(EKU_SERVER_AUTH)
.validate(KeyPurposeIdIter {
input: &mut untrusted::Reader::new(untrusted::Input::from(&[])),
})
.unwrap_err();
assert_eq!(
err,
Expand All @@ -905,8 +936,8 @@ mod tests {
#[test]
fn eku_key_purpose_id() {
assert!(
ExtendedKeyUsage::RequiredIfPresent(KeyPurposeId::new(EKU_SERVER_AUTH))
.key_purpose_id_equals(KeyPurposeId::new(EKU_SERVER_AUTH).oid_value)
ExtendedKeyUsage::RequiredIfPresent(KeyPurposeId::new(EKU_SERVER_AUTH)).id()
== KeyPurposeId::new(EKU_SERVER_AUTH)
)
}

Expand Down
Loading