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
86 changes: 66 additions & 20 deletions src/end_entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
#[cfg(feature = "alloc")]
use crate::subject_name::GeneralDnsNameRef;
use crate::{
cert, signed_data, subject_name, verify_cert, CertRevocationList, Error, SignatureAlgorithm,
SubjectNameRef, Time, TlsClientTrustAnchors, TlsServerTrustAnchors,
cert, signed_data, subject_name, verify_cert, CertRevocationList, Error, ExtendedKeyUsage,
NonTlsTrustAnchors, SignatureAlgorithm, SubjectNameRef, Time, TlsClientTrustAnchors,
TlsServerTrustAnchors, TrustAnchor,
};

/// An end-entity certificate.
Expand Down Expand Up @@ -74,6 +75,57 @@ impl<'a> EndEntityCert<'a> {
&self.inner
}

fn verify_is_valid_cert(
&self,
supported_sig_algs: &[&SignatureAlgorithm],
trust_anchors: &[TrustAnchor],
intermediate_certs: &[&[u8]],
time: Time,
eku: ExtendedKeyUsage,
crls: &[&dyn CertRevocationList],
) -> Result<(), Error> {
verify_cert::build_chain(
&verify_cert::ChainOptions {
eku,
supported_sig_algs,
trust_anchors,
intermediate_certs,
crls,
},
&self.inner,
time,
)
}

/// 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. `intermediate_certs` is the sequence of
/// intermediate certificates that the server sent in the TLS handshake.
/// `time` is the time for which the validation is effective (usually the
/// current time).
pub fn verify_is_valid_cert_with_eku(
&self,
supported_sig_algs: &[&SignatureAlgorithm],
&NonTlsTrustAnchors(trust_anchors): &NonTlsTrustAnchors,
intermediate_certs: &[&[u8]],
time: Time,
eku: ExtendedKeyUsage,
crls: &[&dyn CertRevocationList],
) -> Result<(), Error> {
self.verify_is_valid_cert(
supported_sig_algs,
trust_anchors,
intermediate_certs,
time,
eku,
crls,
)
}

/// Verifies that the end-entity certificate is valid for use by a TLS
/// server.
///
Expand All @@ -91,16 +143,13 @@ impl<'a> EndEntityCert<'a> {
intermediate_certs: &[&[u8]],
time: Time,
) -> Result<(), Error> {
verify_cert::build_chain(
&verify_cert::ChainOptions {
required_eku_if_present: verify_cert::EKU_SERVER_AUTH,
supported_sig_algs,
trust_anchors,
intermediate_certs,
crls: &[],
},
&self.inner,
self.verify_is_valid_cert(
supported_sig_algs,
trust_anchors,
intermediate_certs,
time,
ExtendedKeyUsage::RequiredIfPresent(verify_cert::EKU_SERVER_AUTH),
&[],
)
}

Expand All @@ -123,16 +172,13 @@ impl<'a> EndEntityCert<'a> {
time: Time,
crls: &[&dyn CertRevocationList],
) -> Result<(), Error> {
verify_cert::build_chain(
&verify_cert::ChainOptions {
required_eku_if_present: verify_cert::EKU_CLIENT_AUTH,
supported_sig_algs,
trust_anchors,
intermediate_certs,
crls,
},
&self.inner,
self.verify_is_valid_cert(
supported_sig_algs,
trust_anchors,
intermediate_certs,
time,
ExtendedKeyUsage::RequiredIfPresent(verify_cert::EKU_CLIENT_AUTH),
crls,
)
}

Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ pub use {
SubjectNameRef,
},
time::Time,
trust_anchor::{TlsClientTrustAnchors, TlsServerTrustAnchors, TrustAnchor},
trust_anchor::{NonTlsTrustAnchors, TlsClientTrustAnchors, TlsServerTrustAnchors, TrustAnchor},
verify_cert::{ExtendedKeyUsage, KeyPurposeId},
};

#[cfg(feature = "alloc")]
Expand Down
4 changes: 4 additions & 0 deletions src/trust_anchor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ pub struct TrustAnchor<'a> {
pub name_constraints: Option<&'a [u8]>,
}

/// Trust anchors which may be used for authenticating certificates of any kind.
#[derive(Debug)]
pub struct NonTlsTrustAnchors<'a>(pub &'a [TrustAnchor<'a>]);

/// Trust anchors which may be used for authenticating servers.
#[derive(Debug)]
pub struct TlsServerTrustAnchors<'a>(pub &'a [TrustAnchor<'a>]);
Expand Down
102 changes: 68 additions & 34 deletions src/verify_cert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::{
};

pub(crate) struct ChainOptions<'a> {
pub(crate) required_eku_if_present: KeyPurposeId,
pub(crate) eku: ExtendedKeyUsage,
pub(crate) supported_sig_algs: &'a [&'a SignatureAlgorithm],
pub(crate) trust_anchors: &'a [TrustAnchor<'a>],
pub(crate) intermediate_certs: &'a [&'a [u8]],
Expand All @@ -38,13 +38,7 @@ fn build_chain_inner(
) -> Result<(), 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.required_eku_if_present,
)?;
check_issuer_independent_properties(cert, time, used_as_ca, sub_ca_count, opts.eku)?;

// TODO: HPKP checks.

Expand All @@ -65,12 +59,13 @@ fn build_chain_inner(
// for the purpose of name constraints checking, only end-entity server certificates
// could plausibly have a DNS name as a subject commonName that could contribute to
// path validity
let subject_common_name_contents =
if opts.required_eku_if_present == EKU_SERVER_AUTH && used_as_ca == UsedAsCa::No {
subject_name::SubjectCommonNameContents::DnsName
} else {
subject_name::SubjectCommonNameContents::Ignore
};
let subject_common_name_contents = if opts.eku.key_purpose_id_equals(EKU_SERVER_AUTH.oid_value)
&& used_as_ca == UsedAsCa::No
{
subject_name::SubjectCommonNameContents::DnsName
} else {
subject_name::SubjectCommonNameContents::Ignore
};

let result = loop_while_non_fatal_error(
Error::UnknownIssuer,
Expand Down Expand Up @@ -245,7 +240,7 @@ fn check_issuer_independent_properties(
time: time::Time,
used_as_ca: UsedAsCa,
sub_ca_count: usize,
required_eku_if_present: KeyPurposeId,
eku: ExtendedKeyUsage,
) -> Result<(), Error> {
// TODO: check_distrust(trust_anchor_subject, trust_anchor_spki)?;
// TODO: Check signature algorithm like mozilla::pkix.
Expand All @@ -263,9 +258,7 @@ fn check_issuer_independent_properties(
untrusted::read_all_optional(cert.basic_constraints, Error::BadDer, |value| {
check_basic_constraints(value, used_as_ca, sub_ca_count)
})?;
untrusted::read_all_optional(cert.eku, Error::BadDer, |value| {
check_eku(value, required_eku_if_present)
})?;
untrusted::read_all_optional(cert.eku, Error::BadDer, |value| check_eku(value, eku))?;

Ok(())
}
Expand Down Expand Up @@ -341,42 +334,69 @@ fn check_basic_constraints(
}
}

/// Extended Key Usage (EKU) of a certificate.
#[derive(Clone, Copy)]
pub enum ExtendedKeyUsage {
/// The certificate must contain the specified [`KeyPurposeId`] as EKU.
Required(KeyPurposeId),

/// If the certificate has EKUs, then the specified [`KeyPurposeId`] must be included.
RequiredIfPresent(KeyPurposeId),
}

impl ExtendedKeyUsage {
fn key_purpose_id_equals(&self, value: untrusted::Input<'_>) -> bool {
match self {
ExtendedKeyUsage::Required(eku) => *eku,
ExtendedKeyUsage::RequiredIfPresent(eku) => *eku,
}
.oid_value
== value
}
}

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

impl KeyPurposeId {
/// Construct a new [`KeyPurposeId`].
///
/// `oid` is the OBJECT IDENTIFIER in bytes.
pub const fn new(oid: &'static [u8]) -> Self {
Self {
oid_value: untrusted::Input::from(oid),
}
}
}

// id-pkix OBJECT IDENTIFIER ::= { 1 3 6 1 5 5 7 }
// id-kp OBJECT IDENTIFIER ::= { id-pkix 3 }

// id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 }
#[allow(clippy::identity_op)] // TODO: Make this clearer
pub(crate) static EKU_SERVER_AUTH: KeyPurposeId = KeyPurposeId {
oid_value: untrusted::Input::from(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 1]),
};
pub(crate) static EKU_SERVER_AUTH: KeyPurposeId =
KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 1]);

// id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 }
#[allow(clippy::identity_op)] // TODO: Make this clearer
pub(crate) static EKU_CLIENT_AUTH: KeyPurposeId = KeyPurposeId {
oid_value: untrusted::Input::from(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 2]),
};
pub(crate) static EKU_CLIENT_AUTH: KeyPurposeId =
KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 2]);

// id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 }
#[allow(clippy::identity_op)] // TODO: Make this clearer
pub(crate) static EKU_OCSP_SIGNING: KeyPurposeId = KeyPurposeId {
oid_value: untrusted::Input::from(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 9]),
};
pub(crate) static EKU_OCSP_SIGNING: KeyPurposeId =
KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 9]);

// https://tools.ietf.org/html/rfc5280#section-4.2.1.12
fn check_eku(
input: Option<&mut untrusted::Reader>,
required_eku_if_present: KeyPurposeId,
) -> Result<(), Error> {
fn check_eku(input: Option<&mut untrusted::Reader>, eku: ExtendedKeyUsage) -> Result<(), Error> {
match input {
Some(input) => {
loop {
let value = der::expect_tag_and_get_value(input, der::Tag::OID)?;
if value == required_eku_if_present.oid_value {
if eku.key_purpose_id_equals(value) {
input.skip_to_end();
break;
}
Expand All @@ -387,6 +407,9 @@ fn check_eku(
Ok(())
}
None => {
if matches!(eku, ExtendedKeyUsage::Required(_)) {
return Err(Error::RequiredEkuNotFound);
}
// http://tools.ietf.org/html/rfc6960#section-4.2.2.2:
// "OCSP signing delegation SHALL be designated by the inclusion of
// id-kp-OCSPSigning in an extended key usage certificate extension
Expand All @@ -396,7 +419,7 @@ fn check_eku(
// important that id-kp-OCSPSigning is explicit so that a normal
// end-entity certificate isn't able to sign trusted OCSP responses
// for itself or for other certificates issued by its issuing CA.
if required_eku_if_present.oid_value == EKU_OCSP_SIGNING.oid_value {
if eku.key_purpose_id_equals(EKU_OCSP_SIGNING.oid_value) {
return Err(Error::RequiredEkuNotFound);
}

Expand Down Expand Up @@ -457,3 +480,14 @@ where
}
Err(error)
}

#[cfg(test)]
mod tests {
use crate::{verify_cert::EKU_SERVER_AUTH, ExtendedKeyUsage};

#[test]
fn eku_key_purpose_id() {
assert!(ExtendedKeyUsage::RequiredIfPresent(EKU_SERVER_AUTH)
.key_purpose_id_equals(EKU_SERVER_AUTH.oid_value))
}
}
Loading