From 65376fbddd0cb6a183c7b606112886feaa13f1d3 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jul 2023 17:10:04 +0200 Subject: [PATCH 1/5] Move check_eku() into ExtendedKeyUsage impl --- src/verify_cert.rs | 78 +++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/verify_cert.rs b/src/verify_cert.rs index 5374d9a4..9e1d4ef1 100644 --- a/src/verify_cert.rs +++ b/src/verify_cert.rs @@ -258,7 +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, eku))?; + untrusted::read_all_optional(cert.eku, Error::BadDer, |value| eku.check(value))?; Ok(()) } @@ -345,6 +345,44 @@ pub 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> { + match input { + Some(input) => { + loop { + let value = der::expect_tag_and_get_value(input, der::Tag::OID)?; + if self.key_purpose_id_equals(value) { + input.skip_to_end(); + break; + } + if input.at_end() { + return Err(Error::RequiredEkuNotFound); + } + } + Ok(()) + } + None => { + if matches!(self, Self::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 + // included in the OCSP response signer's certificate." + // + // A missing EKU extension generally means "any EKU", but it is + // 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 self.key_purpose_id_equals(EKU_OCSP_SIGNING.oid_value) { + return Err(Error::RequiredEkuNotFound); + } + + Ok(()) + } + } + } + fn key_purpose_id_equals(&self, value: untrusted::Input<'_>) -> bool { match self { ExtendedKeyUsage::Required(eku) => *eku, @@ -390,44 +428,6 @@ pub(crate) static EKU_CLIENT_AUTH: KeyPurposeId = 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>, eku: ExtendedKeyUsage) -> Result<(), Error> { - match input { - Some(input) => { - loop { - let value = der::expect_tag_and_get_value(input, der::Tag::OID)?; - if eku.key_purpose_id_equals(value) { - input.skip_to_end(); - break; - } - if input.at_end() { - return Err(Error::RequiredEkuNotFound); - } - } - 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 - // included in the OCSP response signer's certificate." - // - // A missing EKU extension generally means "any EKU", but it is - // 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 eku.key_purpose_id_equals(EKU_OCSP_SIGNING.oid_value) { - return Err(Error::RequiredEkuNotFound); - } - - Ok(()) - } - } -} - // https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3 #[repr(u8)] enum KeyUsageMode { From 2efdf4844f1c722e70791040d2c35a0793f217c0 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jul 2023 17:10:17 +0200 Subject: [PATCH 2/5] Move check_key_usage() into KeyUsageMode impl --- src/verify_cert.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/verify_cert.rs b/src/verify_cert.rs index 9e1d4ef1..72d60889 100644 --- a/src/verify_cert.rs +++ b/src/verify_cert.rs @@ -211,7 +211,7 @@ fn check_crls( .map_err(crl_signature_err)?; // Verify that if the issuer has a KeyUsage bitstring it asserts cRLSign. - check_key_usage(issuer_ku, KeyUsageMode::CrlSign)?; + KeyUsageMode::CrlSign.check(issuer_ku)?; // Try to find the cert serial in the verified CRL contents. let cert_serial = cert.serial.as_slice_less_safe(); @@ -430,6 +430,7 @@ pub(crate) static EKU_OCSP_SIGNING: KeyPurposeId = // https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3 #[repr(u8)] +#[derive(Clone, Copy)] enum KeyUsageMode { // DigitalSignature = 0, // ContentCommitment = 1, @@ -442,24 +443,23 @@ enum KeyUsageMode { // DecipherOnly = 8, } -// https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3 -fn check_key_usage( - input: Option, - required_ku_bit_if_present: KeyUsageMode, -) -> Result<(), Error> { - let bit_string = match input { - Some(input) => input, - // While RFC 5280 requires KeyUsage be present, historically the absence of a KeyUsage - // has been treated as "Any Usage". We follow that convention here and assume the absence - // of KeyUsage implies the required_ku_bit_if_present we're checking for. - None => return Ok(()), - }; +impl KeyUsageMode { + // https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3 + fn check(self, input: Option) -> Result<(), Error> { + let bit_string = match input { + Some(input) => input, + // While RFC 5280 requires KeyUsage be present, historically the absence of a KeyUsage + // has been treated as "Any Usage". We follow that convention here and assume the absence + // of KeyUsage implies the required_ku_bit_if_present we're checking for. + None => return Ok(()), + }; - let flags = der::bit_string_flags(&mut untrusted::Reader::new(bit_string))?; - #[allow(clippy::as_conversions)] // u8 always fits in usize. - match flags.bit_set(required_ku_bit_if_present as usize) { - true => Ok(()), - false => Err(Error::IssuerNotCrlSigner), + let flags = der::bit_string_flags(&mut untrusted::Reader::new(bit_string))?; + #[allow(clippy::as_conversions)] // u8 always fits in usize. + match flags.bit_set(self as usize) { + true => Ok(()), + false => Err(Error::IssuerNotCrlSigner), + } } } From 1e3284e3b71a27846ed86c1e83480881326dff04 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 25 Jul 2023 12:28:35 +0200 Subject: [PATCH 3/5] tests: simplify compile guards in custom_ekus module --- tests/custom_ekus.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/custom_ekus.rs b/tests/custom_ekus.rs index d3d322ac..0c324d98 100644 --- a/tests/custom_ekus.rs +++ b/tests/custom_ekus.rs @@ -1,7 +1,7 @@ -#[cfg(feature = "alloc")] +#![cfg(feature = "alloc")] + use webpki::ExtendedKeyUsage::{Required, RequiredIfPresent}; -#[cfg(feature = "alloc")] fn check_cert( ee: &[u8], ca: &[u8], @@ -24,22 +24,18 @@ fn check_cert( ); } -#[cfg(feature = "alloc")] #[allow(clippy::identity_op)] static EKU_CLIENT_AUTH: webpki::KeyPurposeId = webpki::KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 2]); -#[cfg(feature = "alloc")] #[allow(clippy::identity_op)] static EKU_SERVER_AUTH: webpki::KeyPurposeId = webpki::KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 1]); -#[cfg(feature = "alloc")] #[allow(clippy::identity_op)] static EKU_MDOC_ISSUER_AUTH: webpki::KeyPurposeId = webpki::KeyPurposeId::new(&[(40 * 1) + 0, 129, 140, 93, 5, 1, 2]); -#[cfg(feature = "alloc")] #[test] pub fn verify_custom_eku_mdoc() { let err = Err(webpki::Error::RequiredEkuNotFound); @@ -59,7 +55,6 @@ pub fn verify_custom_eku_mdoc() { check_cert(ee, ca, RequiredIfPresent(EKU_SERVER_AUTH), time, err); } -#[cfg(feature = "alloc")] #[test] pub fn verify_custom_eku_client() { let err = Err(webpki::Error::RequiredEkuNotFound); From a7c2a523fd43f696ff1ef6cc303b2fc4dd95bcc1 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Tue, 25 Jul 2023 12:39:15 +0200 Subject: [PATCH 4/5] Introduce more restricted public key usage API --- src/end_entity.rs | 10 ++-- src/lib.rs | 2 +- src/verify_cert.rs | 113 +++++++++++++++++++++++++------------------ tests/custom_ekus.rs | 47 +++++------------- 4 files changed, 84 insertions(+), 88 deletions(-) diff --git a/src/end_entity.rs b/src/end_entity.rs index aad4e7e6..84f338ae 100644 --- a/src/end_entity.rs +++ b/src/end_entity.rs @@ -15,7 +15,7 @@ #[cfg(feature = "alloc")] use crate::subject_name::GeneralDnsNameRef; use crate::{ - cert, signed_data, subject_name, verify_cert, CertRevocationList, Error, ExtendedKeyUsage, + cert, signed_data, subject_name, verify_cert, CertRevocationList, Error, KeyUsage, NonTlsTrustAnchors, SignatureAlgorithm, SubjectNameRef, Time, TlsClientTrustAnchors, TlsServerTrustAnchors, TrustAnchor, }; @@ -81,7 +81,7 @@ impl<'a> EndEntityCert<'a> { trust_anchors: &[TrustAnchor], intermediate_certs: &[&[u8]], time: Time, - eku: ExtendedKeyUsage, + eku: KeyUsage, crls: &[&dyn CertRevocationList], ) -> Result<(), Error> { verify_cert::build_chain( @@ -113,7 +113,7 @@ impl<'a> EndEntityCert<'a> { &NonTlsTrustAnchors(trust_anchors): &NonTlsTrustAnchors, intermediate_certs: &[&[u8]], time: Time, - eku: ExtendedKeyUsage, + eku: KeyUsage, crls: &[&dyn CertRevocationList], ) -> Result<(), Error> { self.verify_is_valid_cert( @@ -148,7 +148,7 @@ impl<'a> EndEntityCert<'a> { trust_anchors, intermediate_certs, time, - ExtendedKeyUsage::RequiredIfPresent(verify_cert::EKU_SERVER_AUTH), + KeyUsage::server_auth(), &[], ) } @@ -177,7 +177,7 @@ impl<'a> EndEntityCert<'a> { trust_anchors, intermediate_certs, time, - ExtendedKeyUsage::RequiredIfPresent(verify_cert::EKU_CLIENT_AUTH), + KeyUsage::client_auth(), crls, ) } diff --git a/src/lib.rs b/src/lib.rs index 57ca13e1..a5345f0a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,7 @@ pub use { }, time::Time, trust_anchor::{NonTlsTrustAnchors, TlsClientTrustAnchors, TlsServerTrustAnchors, TrustAnchor}, - verify_cert::{ExtendedKeyUsage, KeyPurposeId}, + verify_cert::KeyUsage, }; #[cfg(feature = "alloc")] diff --git a/src/verify_cert.rs b/src/verify_cert.rs index 72d60889..ad416bf8 100644 --- a/src/verify_cert.rs +++ b/src/verify_cert.rs @@ -19,7 +19,7 @@ use crate::{ }; pub(crate) struct ChainOptions<'a> { - pub(crate) eku: ExtendedKeyUsage, + pub(crate) eku: KeyUsage, pub(crate) supported_sig_algs: &'a [&'a SignatureAlgorithm], pub(crate) trust_anchors: &'a [TrustAnchor<'a>], pub(crate) intermediate_certs: &'a [&'a [u8]], @@ -38,7 +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.eku)?; + check_issuer_independent_properties(cert, time, used_as_ca, sub_ca_count, opts.eku.inner)?; // TODO: HPKP checks. @@ -59,7 +59,10 @@ 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.eku.key_purpose_id_equals(EKU_SERVER_AUTH.oid_value) + let subject_common_name_contents = if opts + .eku + .inner + .key_purpose_id_equals(EKU_SERVER_AUTH.oid_value) && used_as_ca == UsedAsCa::No { subject_name::SubjectCommonNameContents::DnsName @@ -334,9 +337,49 @@ fn check_basic_constraints( } } +/// The expected key usage of a certificate. +/// +/// This type represents the expected key usage of an end entity certificate. Although for most +/// kinds of certificates the extended key usage extension is optional (and so certificates +/// not carrying a particular value in the EKU extension are acceptable). If the extension +/// is present, the certificate MUST only be used for one of the purposes indicated. +/// +/// +#[derive(Clone, Copy)] +pub struct KeyUsage { + inner: ExtendedKeyUsage, +} + +impl KeyUsage { + /// Construct a new [`KeyUsage`] as appropriate for server certificate authentication. + /// + /// As specified in , this does not require the certificate to specify the eKU extension. + pub const fn server_auth() -> Self { + Self { + inner: ExtendedKeyUsage::RequiredIfPresent(EKU_SERVER_AUTH), + } + } + + /// Construct a new [`KeyUsage`] 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 { + inner: ExtendedKeyUsage::RequiredIfPresent(EKU_CLIENT_AUTH), + } + } + + /// Construct a new [`KeyUsage`] requiring a certificate to support the specified OID. + pub const fn required(oid: &'static [u8]) -> Self { + Self { + inner: ExtendedKeyUsage::Required(KeyPurposeId::new(oid)), + } + } +} + /// Extended Key Usage (EKU) of a certificate. #[derive(Clone, Copy)] -pub enum ExtendedKeyUsage { +enum ExtendedKeyUsage { /// The certificate must contain the specified [`KeyPurposeId`] as EKU. Required(KeyPurposeId), @@ -347,40 +390,25 @@ pub 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> { - match input { - Some(input) => { - loop { - let value = der::expect_tag_and_get_value(input, der::Tag::OID)?; - if self.key_purpose_id_equals(value) { - input.skip_to_end(); - break; - } - if input.at_end() { - return Err(Error::RequiredEkuNotFound); - } - } - Ok(()) + let input = match (input, self) { + (Some(input), _) => input, + (None, Self::RequiredIfPresent(_)) => return Ok(()), + (None, Self::Required(_)) => return Err(Error::RequiredEkuNotFound), + }; + + loop { + let value = der::expect_tag_and_get_value(input, der::Tag::OID)?; + if self.key_purpose_id_equals(value) { + input.skip_to_end(); + break; } - None => { - if matches!(self, Self::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 - // included in the OCSP response signer's certificate." - // - // A missing EKU extension generally means "any EKU", but it is - // 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 self.key_purpose_id_equals(EKU_OCSP_SIGNING.oid_value) { - return Err(Error::RequiredEkuNotFound); - } - Ok(()) + if input.at_end() { + return Err(Error::RequiredEkuNotFound); } } + + Ok(()) } fn key_purpose_id_equals(&self, value: untrusted::Input<'_>) -> bool { @@ -395,7 +423,7 @@ impl ExtendedKeyUsage { /// An OID value indicating an Extended Key Usage (EKU) key purpose. #[derive(Clone, Copy, PartialEq, Eq)] -pub struct KeyPurposeId { +struct KeyPurposeId { oid_value: untrusted::Input<'static>, } @@ -403,7 +431,7 @@ impl KeyPurposeId { /// Construct a new [`KeyPurposeId`]. /// /// `oid` is the OBJECT IDENTIFIER in bytes. - pub const fn new(oid: &'static [u8]) -> Self { + const fn new(oid: &'static [u8]) -> Self { Self { oid_value: untrusted::Input::from(oid), } @@ -415,18 +443,11 @@ impl KeyPurposeId { // id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 } #[allow(clippy::identity_op)] // TODO: Make this clearer -pub(crate) static EKU_SERVER_AUTH: KeyPurposeId = - KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 1]); +const 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::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::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 9]); +const EKU_CLIENT_AUTH: KeyPurposeId = KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 2]); // https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3 #[repr(u8)] @@ -483,7 +504,7 @@ where #[cfg(test)] mod tests { - use crate::{verify_cert::EKU_SERVER_AUTH, ExtendedKeyUsage}; + use super::*; #[test] fn eku_key_purpose_id() { diff --git a/tests/custom_ekus.rs b/tests/custom_ekus.rs index 0c324d98..ed745059 100644 --- a/tests/custom_ekus.rs +++ b/tests/custom_ekus.rs @@ -1,11 +1,11 @@ #![cfg(feature = "alloc")] -use webpki::ExtendedKeyUsage::{Required, RequiredIfPresent}; +use webpki::KeyUsage; fn check_cert( ee: &[u8], ca: &[u8], - eku: webpki::ExtendedKeyUsage, + eku: KeyUsage, time: webpki::Time, result: Result<(), webpki::Error>, ) { @@ -24,18 +24,6 @@ fn check_cert( ); } -#[allow(clippy::identity_op)] -static EKU_CLIENT_AUTH: webpki::KeyPurposeId = - webpki::KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 2]); - -#[allow(clippy::identity_op)] -static EKU_SERVER_AUTH: webpki::KeyPurposeId = - webpki::KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 1]); - -#[allow(clippy::identity_op)] -static EKU_MDOC_ISSUER_AUTH: webpki::KeyPurposeId = - webpki::KeyPurposeId::new(&[(40 * 1) + 0, 129, 140, 93, 5, 1, 2]); - #[test] pub fn verify_custom_eku_mdoc() { let err = Err(webpki::Error::RequiredEkuNotFound); @@ -43,37 +31,24 @@ pub fn verify_custom_eku_mdoc() { let ee = include_bytes!("misc/mdoc_eku.ee.der"); let ca = include_bytes!("misc/mdoc_eku.ca.der"); - check_cert(ee, ca, Required(EKU_MDOC_ISSUER_AUTH), time, Ok(())); - check_cert(ee, ca, Required(EKU_SERVER_AUTH), time, err); - check_cert( - ee, - ca, - RequiredIfPresent(EKU_MDOC_ISSUER_AUTH), - time, - Ok(()), - ); - check_cert(ee, ca, RequiredIfPresent(EKU_SERVER_AUTH), time, err); + + let eku_mdoc = KeyUsage::required(&[(40 * 1) + 0, 129, 140, 93, 5, 1, 2]); + check_cert(ee, ca, eku_mdoc, time, Ok(())); + check_cert(ee, ca, KeyUsage::server_auth(), time, err); + check_cert(ee, ca, eku_mdoc, time, Ok(())); + check_cert(ee, ca, KeyUsage::server_auth(), time, err); } #[test] pub fn verify_custom_eku_client() { - let err = Err(webpki::Error::RequiredEkuNotFound); let time = webpki::Time::from_seconds_since_unix_epoch(0x1fed_f00d); let ee = include_bytes!("client_auth/cert_with_no_eku_accepted_for_client_auth.ee.der"); let ca = include_bytes!("client_auth/cert_with_no_eku_accepted_for_client_auth.ca.der"); - check_cert(ee, ca, Required(EKU_CLIENT_AUTH), time, err); - check_cert(ee, ca, RequiredIfPresent(EKU_CLIENT_AUTH), time, Ok(())); + check_cert(ee, ca, KeyUsage::client_auth(), time, Ok(())); let ee = include_bytes!("client_auth/cert_with_both_ekus_accepted_for_client_auth.ee.der"); let ca = include_bytes!("client_auth/cert_with_both_ekus_accepted_for_client_auth.ca.der"); - check_cert(ee, ca, Required(EKU_CLIENT_AUTH), time, Ok(())); - check_cert(ee, ca, Required(EKU_SERVER_AUTH), time, Ok(())); - check_cert(ee, ca, RequiredIfPresent(EKU_CLIENT_AUTH), time, Ok(())); - check_cert(ee, ca, RequiredIfPresent(EKU_SERVER_AUTH), time, Ok(())); -} - -#[test] -fn key_purpose_id() { - webpki::KeyPurposeId::new(&[1, 2, 3]); + check_cert(ee, ca, KeyUsage::client_auth(), time, Ok(())); + check_cert(ee, ca, KeyUsage::server_auth(), time, Ok(())); } From e7e1b2146dd976c5fb984ad93c10d2d125ca0138 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Mon, 24 Jul 2023 22:43:20 +0200 Subject: [PATCH 5/5] Unify high-level API to make key usage explicit --- src/end_entity.rs | 36 +++++++++----- src/lib.rs | 4 +- src/trust_anchor.rs | 6 +-- tests/better_tls.rs | 9 ++-- tests/client_auth.rs | 15 ++++-- tests/client_auth_revocation.rs | 8 +-- tests/custom_ekus.rs | 5 +- tests/integration.rs | 87 +++++++++++++++++++++++++-------- tests/tls_server_certs.rs | 14 ++++-- 9 files changed, 126 insertions(+), 58 deletions(-) diff --git a/src/end_entity.rs b/src/end_entity.rs index 84f338ae..2d057059 100644 --- a/src/end_entity.rs +++ b/src/end_entity.rs @@ -16,9 +16,10 @@ use crate::subject_name::GeneralDnsNameRef; use crate::{ cert, signed_data, subject_name, verify_cert, CertRevocationList, Error, KeyUsage, - NonTlsTrustAnchors, SignatureAlgorithm, SubjectNameRef, Time, TlsClientTrustAnchors, - TlsServerTrustAnchors, TrustAnchor, + SignatureAlgorithm, SubjectNameRef, Time, TrustAnchor, }; +#[allow(deprecated)] +use crate::{TlsClientTrustAnchors, TlsServerTrustAnchors}; /// An end-entity certificate. /// @@ -100,20 +101,25 @@ impl<'a> EndEntityCert<'a> { /// 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( + /// * `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). + /// * `usage` is the intended usage of the certificate, indicating what kind + /// 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( &self, supported_sig_algs: &[&SignatureAlgorithm], - &NonTlsTrustAnchors(trust_anchors): &NonTlsTrustAnchors, + trust_anchors: &[TrustAnchor], intermediate_certs: &[&[u8]], time: Time, - eku: KeyUsage, + usage: KeyUsage, crls: &[&dyn CertRevocationList], ) -> Result<(), Error> { self.verify_is_valid_cert( @@ -121,7 +127,7 @@ impl<'a> EndEntityCert<'a> { trust_anchors, intermediate_certs, time, - eku, + usage, crls, ) } @@ -136,6 +142,8 @@ impl<'a> EndEntityCert<'a> { /// intermediate certificates that the server sent in the TLS handshake. /// `time` is the time for which the validation is effective (usually the /// current time). + #[allow(deprecated)] + #[deprecated(since = "0.101.2", note = "Use `verify_for_usage` instead")] pub fn verify_is_valid_tls_server_cert( &self, supported_sig_algs: &[&SignatureAlgorithm], @@ -164,6 +172,8 @@ impl<'a> EndEntityCert<'a> { /// `cert` is the purported end-entity certificate of the client. `time` is /// the time for which the validation is effective (usually the current /// time). + #[allow(deprecated)] + #[deprecated(since = "0.101.2", note = "Use `verify_for_usage` instead")] pub fn verify_is_valid_tls_client_cert( &self, supported_sig_algs: &[&SignatureAlgorithm], diff --git a/src/lib.rs b/src/lib.rs index a5345f0a..617b43bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,6 +58,8 @@ mod crl; mod verify_cert; mod x509; +#[allow(deprecated)] +pub use trust_anchor::{TlsClientTrustAnchors, TlsServerTrustAnchors}; pub use { cert::{Cert, EndEntityOrCa}, crl::{BorrowedCertRevocationList, BorrowedRevokedCert, CertRevocationList, RevocationReason}, @@ -72,7 +74,7 @@ pub use { SubjectNameRef, }, time::Time, - trust_anchor::{NonTlsTrustAnchors, TlsClientTrustAnchors, TlsServerTrustAnchors, TrustAnchor}, + trust_anchor::TrustAnchor, verify_cert::KeyUsage, }; diff --git a/src/trust_anchor.rs b/src/trust_anchor.rs index 129b4587..95a88a9e 100644 --- a/src/trust_anchor.rs +++ b/src/trust_anchor.rs @@ -26,15 +26,13 @@ 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. +#[deprecated(since = "0.101.2")] #[derive(Debug)] pub struct TlsServerTrustAnchors<'a>(pub &'a [TrustAnchor<'a>]); /// Trust anchors which may be used for authenticating clients. +#[deprecated(since = "0.101.2")] #[derive(Debug)] pub struct TlsClientTrustAnchors<'a>(pub &'a [TrustAnchor<'a>]); diff --git a/tests/better_tls.rs b/tests/better_tls.rs index ccf59867..306b283b 100644 --- a/tests/better_tls.rs +++ b/tests/better_tls.rs @@ -1,7 +1,7 @@ use base64::{engine::general_purpose, Engine as _}; use serde::Deserialize; use std::collections::HashMap; -use webpki::TrustAnchor; +use webpki::{KeyUsage, TrustAnchor}; #[test] pub fn path_building() { @@ -11,7 +11,6 @@ pub fn path_building() { let root_der = &better_tls.root_der(); let roots = &[TrustAnchor::try_from_cert_der(root_der).expect("invalid trust anchor")]; - let trust_anchors = &webpki::TlsServerTrustAnchors(roots); let path_building_suite = better_tls .suites @@ -35,11 +34,13 @@ pub fn path_building() { // certificates won't expire. let now = webpki::Time::from_seconds_since_unix_epoch(1_688_651_734); - let result = ee_cert.verify_is_valid_tls_server_cert( + let result = ee_cert.verify_for_usage( &[&webpki::ECDSA_P256_SHA256], // All of the BetterTLS testcases use P256 keys. - trust_anchors, + roots, intermediates, now, + KeyUsage::server_auth(), + &[], ); match testcase.expected { diff --git a/tests/client_auth.rs b/tests/client_auth.rs index bffcb4f4..dd84e6d4 100644 --- a/tests/client_auth.rs +++ b/tests/client_auth.rs @@ -12,7 +12,8 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -extern crate webpki; +#[cfg(feature = "alloc")] +use webpki::KeyUsage; #[cfg(feature = "alloc")] static ALL_SIGALGS: &[&webpki::SignatureAlgorithm] = &[ @@ -29,12 +30,18 @@ static ALL_SIGALGS: &[&webpki::SignatureAlgorithm] = &[ #[cfg(feature = "alloc")] fn check_cert(ee: &[u8], ca: &[u8]) -> Result<(), webpki::Error> { - let anchors = vec![webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; - let anchors = webpki::TlsClientTrustAnchors(&anchors); + let anchors = &[webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; let time = webpki::Time::from_seconds_since_unix_epoch(0x1fed_f00d); let cert = webpki::EndEntityCert::try_from(ee).unwrap(); - cert.verify_is_valid_tls_client_cert(ALL_SIGALGS, &anchors, &[], time, &[]) + cert.verify_for_usage( + ALL_SIGALGS, + anchors, + &[], + time, + KeyUsage::client_auth(), + &[], + ) } // DO NOT EDIT BELOW: generated by tests/generate.py diff --git a/tests/client_auth_revocation.rs b/tests/client_auth_revocation.rs index ec0a7409..68e59a6a 100644 --- a/tests/client_auth_revocation.rs +++ b/tests/client_auth_revocation.rs @@ -12,7 +12,7 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -extern crate webpki; +use webpki::KeyUsage; fn check_cert( ee: &[u8], @@ -21,15 +21,15 @@ fn check_cert( crls: &[&dyn webpki::CertRevocationList], ) -> Result<(), webpki::Error> { let anchors = &[webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; - let anchors = webpki::TlsClientTrustAnchors(anchors.as_slice()); let cert = webpki::EndEntityCert::try_from(ee).unwrap(); let time = webpki::Time::from_seconds_since_unix_epoch(0x1fed_f00d); - cert.verify_is_valid_tls_client_cert( + cert.verify_for_usage( &[&webpki::ECDSA_P256_SHA256], - &anchors, + anchors, intermediates, time, + KeyUsage::client_auth(), crls, ) } diff --git a/tests/custom_ekus.rs b/tests/custom_ekus.rs index ed745059..0fc094df 100644 --- a/tests/custom_ekus.rs +++ b/tests/custom_ekus.rs @@ -9,8 +9,7 @@ fn check_cert( time: webpki::Time, result: Result<(), webpki::Error>, ) { - let anchors = vec![webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; - let anchors = webpki::NonTlsTrustAnchors(&anchors); + let anchors = [webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; let algs = &[ &webpki::RSA_PKCS1_2048_8192_SHA256, &webpki::ECDSA_P256_SHA256, @@ -19,7 +18,7 @@ fn check_cert( let cert = webpki::EndEntityCert::try_from(ee).unwrap(); assert_eq!( - cert.verify_is_valid_cert_with_eku(algs, &anchors, &[], time, eku, &[]), + cert.verify_for_usage(algs, &anchors, &[], time, eku, &[]), result ); } diff --git a/tests/integration.rs b/tests/integration.rs index 9f18f744..85d6e0e6 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -12,7 +12,7 @@ // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -extern crate webpki; +use webpki::KeyUsage; static ALL_SIGALGS: &[&webpki::SignatureAlgorithm] = &[ &webpki::ECDSA_P256_SHA256, @@ -39,15 +39,21 @@ pub fn netflix() { let inter = include_bytes!("netflix/inter.der"); let ca = include_bytes!("netflix/ca.der"); - let anchors = vec![webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; - let anchors = webpki::TlsServerTrustAnchors(&anchors); + let anchors = [webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; let time = webpki::Time::from_seconds_since_unix_epoch(1_492_441_716); // 2017-04-17T15:08:36Z let cert = webpki::EndEntityCert::try_from(ee).unwrap(); assert_eq!( Ok(()), - cert.verify_is_valid_tls_server_cert(ALL_SIGALGS, &anchors, &[inter], time) + cert.verify_for_usage( + ALL_SIGALGS, + &anchors, + &[inter], + time, + KeyUsage::server_auth(), + &[] + ) ); } @@ -59,15 +65,21 @@ pub fn cloudflare_dns() { let inter = include_bytes!("cloudflare_dns/inter.der"); let ca = include_bytes!("cloudflare_dns/ca.der"); - let anchors = vec![webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; - let anchors = webpki::TlsServerTrustAnchors(&anchors); + let anchors = [webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; let time = webpki::Time::from_seconds_since_unix_epoch(1_663_495_771); let cert = webpki::EndEntityCert::try_from(ee).unwrap(); assert_eq!( Ok(()), - cert.verify_is_valid_tls_server_cert(ALL_SIGALGS, &anchors, &[inter], time) + cert.verify_for_usage( + ALL_SIGALGS, + &anchors, + &[inter], + time, + KeyUsage::server_auth(), + &[] + ) ); let check_name = |name: &str| { @@ -107,15 +119,21 @@ pub fn wpt() { let ee: &[u8] = include_bytes!("wpt/ee.der"); let ca = include_bytes!("wpt/ca.der"); - let anchors = vec![webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; - let anchors = webpki::TlsServerTrustAnchors(&anchors); + let anchors = [webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; let time = webpki::Time::from_seconds_since_unix_epoch(1_619_256_684); // 2021-04-24T09:31:24Z let cert = webpki::EndEntityCert::try_from(ee).unwrap(); assert_eq!( Ok(()), - cert.verify_is_valid_tls_server_cert(ALL_SIGALGS, &anchors, &[], time) + cert.verify_for_usage( + ALL_SIGALGS, + &anchors, + &[], + time, + KeyUsage::server_auth(), + &[] + ) ); } @@ -124,15 +142,21 @@ pub fn ed25519() { let ee: &[u8] = include_bytes!("ed25519/ee.der"); let ca = include_bytes!("ed25519/ca.der"); - let anchors = vec![webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; - let anchors = webpki::TlsServerTrustAnchors(&anchors); + let anchors = [webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; let time = webpki::Time::from_seconds_since_unix_epoch(1_547_363_522); // 2019-01-13T07:12:02Z let cert = webpki::EndEntityCert::try_from(ee).unwrap(); assert_eq!( Ok(()), - cert.verify_is_valid_tls_server_cert(ALL_SIGALGS, &anchors, &[], time) + cert.verify_for_usage( + ALL_SIGALGS, + &anchors, + &[], + time, + KeyUsage::server_auth(), + &[] + ) ); } @@ -144,16 +168,31 @@ fn critical_extensions() { let time = webpki::Time::from_seconds_since_unix_epoch(1_670_779_098); let anchors = [webpki::TrustAnchor::try_from_cert_der(root).unwrap()]; - let anchors = webpki::TlsServerTrustAnchors(&anchors); let ee = include_bytes!("critical_extensions/ee-cert-noncrit-unknown-ext.der"); - let res = webpki::EndEntityCert::try_from(&ee[..]) - .and_then(|cert| cert.verify_is_valid_tls_server_cert(ALL_SIGALGS, &anchors, &[ca], time)); + let res = webpki::EndEntityCert::try_from(&ee[..]).and_then(|cert| { + cert.verify_for_usage( + ALL_SIGALGS, + &anchors, + &[ca], + time, + KeyUsage::server_auth(), + &[], + ) + }); assert_eq!(res, Ok(()), "accept non-critical unknown extension"); let ee = include_bytes!("critical_extensions/ee-cert-crit-unknown-ext.der"); - let res = webpki::EndEntityCert::try_from(&ee[..]) - .and_then(|cert| cert.verify_is_valid_tls_server_cert(ALL_SIGALGS, &anchors, &[ca], time)); + let res = webpki::EndEntityCert::try_from(&ee[..]).and_then(|cert| { + cert.verify_for_usage( + ALL_SIGALGS, + &anchors, + &[ca], + time, + KeyUsage::server_auth(), + &[], + ) + }); assert_eq!( res, Err(webpki::Error::UnsupportedCriticalExtension), @@ -180,15 +219,21 @@ fn read_ee_with_neg_serial() { let ca: &[u8] = include_bytes!("misc/serial_neg_ca.der"); let ee: &[u8] = include_bytes!("misc/serial_neg_ee.der"); - let anchors = vec![webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; - let anchors = webpki::TlsServerTrustAnchors(&anchors); + let anchors = [webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; let time = webpki::Time::from_seconds_since_unix_epoch(1_667_401_500); // 2022-11-02T15:05:00Z let cert = webpki::EndEntityCert::try_from(ee).unwrap(); assert_eq!( Ok(()), - cert.verify_is_valid_tls_server_cert(ALL_SIGALGS, &anchors, &[], time) + cert.verify_for_usage( + ALL_SIGALGS, + &anchors, + &[], + time, + KeyUsage::server_auth(), + &[] + ) ); } diff --git a/tests/tls_server_certs.rs b/tests/tls_server_certs.rs index b59d9999..e0d8f2ef 100644 --- a/tests/tls_server_certs.rs +++ b/tests/tls_server_certs.rs @@ -13,7 +13,7 @@ // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #![cfg(feature = "alloc")] -extern crate webpki; +use webpki::KeyUsage; static ALL_SIGALGS: &[&webpki::SignatureAlgorithm] = &[ &webpki::ECDSA_P256_SHA256, @@ -33,12 +33,18 @@ fn check_cert( valid_names: &[&str], invalid_names: &[&str], ) -> Result<(), webpki::Error> { - let anchors = vec![webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; - let anchors = webpki::TlsServerTrustAnchors(&anchors); + let anchors = [webpki::TrustAnchor::try_from_cert_der(ca).unwrap()]; let time = webpki::Time::from_seconds_since_unix_epoch(0x1fed_f00d); let cert = webpki::EndEntityCert::try_from(ee).unwrap(); - cert.verify_is_valid_tls_server_cert(ALL_SIGALGS, &anchors, &[], time)?; + cert.verify_for_usage( + ALL_SIGALGS, + &anchors, + &[], + time, + KeyUsage::server_auth(), + &[], + )?; for valid in valid_names { let name = webpki::SubjectNameRef::try_from_ascii_str(valid).unwrap();