Skip to content

Commit ee95916

Browse files
djcctz
authored andcommitted
Introduce more restricted public key usage API
1 parent 3814c03 commit ee95916

File tree

4 files changed

+84
-88
lines changed

4 files changed

+84
-88
lines changed

src/end_entity.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#[cfg(feature = "alloc")]
1616
use crate::subject_name::GeneralDnsNameRef;
1717
use crate::{
18-
cert, signed_data, subject_name, verify_cert, CertRevocationList, Error, ExtendedKeyUsage,
18+
cert, signed_data, subject_name, verify_cert, CertRevocationList, Error, KeyUsage,
1919
NonTlsTrustAnchors, SignatureAlgorithm, SubjectNameRef, Time, TlsClientTrustAnchors,
2020
TlsServerTrustAnchors, TrustAnchor,
2121
};
@@ -81,7 +81,7 @@ impl<'a> EndEntityCert<'a> {
8181
trust_anchors: &[TrustAnchor],
8282
intermediate_certs: &[&[u8]],
8383
time: Time,
84-
eku: ExtendedKeyUsage,
84+
eku: KeyUsage,
8585
crls: &[&dyn CertRevocationList],
8686
) -> Result<(), Error> {
8787
verify_cert::build_chain(
@@ -113,7 +113,7 @@ impl<'a> EndEntityCert<'a> {
113113
&NonTlsTrustAnchors(trust_anchors): &NonTlsTrustAnchors,
114114
intermediate_certs: &[&[u8]],
115115
time: Time,
116-
eku: ExtendedKeyUsage,
116+
eku: KeyUsage,
117117
crls: &[&dyn CertRevocationList],
118118
) -> Result<(), Error> {
119119
self.verify_is_valid_cert(
@@ -148,7 +148,7 @@ impl<'a> EndEntityCert<'a> {
148148
trust_anchors,
149149
intermediate_certs,
150150
time,
151-
ExtendedKeyUsage::RequiredIfPresent(verify_cert::EKU_SERVER_AUTH),
151+
KeyUsage::server_auth(),
152152
&[],
153153
)
154154
}
@@ -177,7 +177,7 @@ impl<'a> EndEntityCert<'a> {
177177
trust_anchors,
178178
intermediate_certs,
179179
time,
180-
ExtendedKeyUsage::RequiredIfPresent(verify_cert::EKU_CLIENT_AUTH),
180+
KeyUsage::client_auth(),
181181
crls,
182182
)
183183
}

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ pub use {
7373
},
7474
time::Time,
7575
trust_anchor::{NonTlsTrustAnchors, TlsClientTrustAnchors, TlsServerTrustAnchors, TrustAnchor},
76-
verify_cert::{ExtendedKeyUsage, KeyPurposeId},
76+
verify_cert::KeyUsage,
7777
};
7878

7979
#[cfg(feature = "alloc")]

src/verify_cert.rs

+67-46
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::{
1919
};
2020

2121
pub(crate) struct ChainOptions<'a> {
22-
pub(crate) eku: ExtendedKeyUsage,
22+
pub(crate) eku: KeyUsage,
2323
pub(crate) supported_sig_algs: &'a [&'a SignatureAlgorithm],
2424
pub(crate) trust_anchors: &'a [TrustAnchor<'a>],
2525
pub(crate) intermediate_certs: &'a [&'a [u8]],
@@ -38,7 +38,7 @@ fn build_chain_inner(
3838
) -> Result<(), Error> {
3939
let used_as_ca = used_as_ca(&cert.ee_or_ca);
4040

41-
check_issuer_independent_properties(cert, time, used_as_ca, sub_ca_count, opts.eku)?;
41+
check_issuer_independent_properties(cert, time, used_as_ca, sub_ca_count, opts.eku.inner)?;
4242

4343
// TODO: HPKP checks.
4444

@@ -59,7 +59,10 @@ fn build_chain_inner(
5959
// for the purpose of name constraints checking, only end-entity server certificates
6060
// could plausibly have a DNS name as a subject commonName that could contribute to
6161
// path validity
62-
let subject_common_name_contents = if opts.eku.key_purpose_id_equals(EKU_SERVER_AUTH.oid_value)
62+
let subject_common_name_contents = if opts
63+
.eku
64+
.inner
65+
.key_purpose_id_equals(EKU_SERVER_AUTH.oid_value)
6366
&& used_as_ca == UsedAsCa::No
6467
{
6568
subject_name::SubjectCommonNameContents::DnsName
@@ -334,9 +337,49 @@ fn check_basic_constraints(
334337
}
335338
}
336339

340+
/// The expected key usage of a certificate.
341+
///
342+
/// This type represents the expected key usage of an end entity certificate. Although for most
343+
/// kinds of certificates the extended key usage extension is optional (and so certificates
344+
/// not carrying a particular value in the EKU extension are acceptable). If the extension
345+
/// is present, the certificate MUST only be used for one of the purposes indicated.
346+
///
347+
/// <https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.12>
348+
#[derive(Clone, Copy)]
349+
pub struct KeyUsage {
350+
inner: ExtendedKeyUsage,
351+
}
352+
353+
impl KeyUsage {
354+
/// Construct a new [`KeyUsage`] as appropriate for server certificate authentication.
355+
///
356+
/// 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.
357+
pub const fn server_auth() -> Self {
358+
Self {
359+
inner: ExtendedKeyUsage::RequiredIfPresent(EKU_SERVER_AUTH),
360+
}
361+
}
362+
363+
/// Construct a new [`KeyUsage`] as appropriate for client certificate authentication.
364+
///
365+
/// As specified in <>, this does not require the certificate to specify the eKU extension.
366+
pub const fn client_auth() -> Self {
367+
Self {
368+
inner: ExtendedKeyUsage::RequiredIfPresent(EKU_CLIENT_AUTH),
369+
}
370+
}
371+
372+
/// Construct a new [`KeyUsage`] requiring a certificate to support the specified OID.
373+
pub const fn required(oid: &'static [u8]) -> Self {
374+
Self {
375+
inner: ExtendedKeyUsage::Required(KeyPurposeId::new(oid)),
376+
}
377+
}
378+
}
379+
337380
/// Extended Key Usage (EKU) of a certificate.
338381
#[derive(Clone, Copy)]
339-
pub enum ExtendedKeyUsage {
382+
enum ExtendedKeyUsage {
340383
/// The certificate must contain the specified [`KeyPurposeId`] as EKU.
341384
Required(KeyPurposeId),
342385

@@ -347,40 +390,25 @@ pub enum ExtendedKeyUsage {
347390
impl ExtendedKeyUsage {
348391
// https://tools.ietf.org/html/rfc5280#section-4.2.1.12
349392
fn check(&self, input: Option<&mut untrusted::Reader>) -> Result<(), Error> {
350-
match input {
351-
Some(input) => {
352-
loop {
353-
let value = der::expect_tag_and_get_value(input, der::Tag::OID)?;
354-
if self.key_purpose_id_equals(value) {
355-
input.skip_to_end();
356-
break;
357-
}
358-
if input.at_end() {
359-
return Err(Error::RequiredEkuNotFound);
360-
}
361-
}
362-
Ok(())
393+
let input = match (input, self) {
394+
(Some(input), _) => input,
395+
(None, Self::RequiredIfPresent(_)) => return Ok(()),
396+
(None, Self::Required(_)) => return Err(Error::RequiredEkuNotFound),
397+
};
398+
399+
loop {
400+
let value = der::expect_tag_and_get_value(input, der::Tag::OID)?;
401+
if self.key_purpose_id_equals(value) {
402+
input.skip_to_end();
403+
break;
363404
}
364-
None => {
365-
if matches!(self, Self::Required(_)) {
366-
return Err(Error::RequiredEkuNotFound);
367-
}
368-
// http://tools.ietf.org/html/rfc6960#section-4.2.2.2:
369-
// "OCSP signing delegation SHALL be designated by the inclusion of
370-
// id-kp-OCSPSigning in an extended key usage certificate extension
371-
// included in the OCSP response signer's certificate."
372-
//
373-
// A missing EKU extension generally means "any EKU", but it is
374-
// important that id-kp-OCSPSigning is explicit so that a normal
375-
// end-entity certificate isn't able to sign trusted OCSP responses
376-
// for itself or for other certificates issued by its issuing CA.
377-
if self.key_purpose_id_equals(EKU_OCSP_SIGNING.oid_value) {
378-
return Err(Error::RequiredEkuNotFound);
379-
}
380405

381-
Ok(())
406+
if input.at_end() {
407+
return Err(Error::RequiredEkuNotFound);
382408
}
383409
}
410+
411+
Ok(())
384412
}
385413

386414
fn key_purpose_id_equals(&self, value: untrusted::Input<'_>) -> bool {
@@ -395,15 +423,15 @@ impl ExtendedKeyUsage {
395423

396424
/// An OID value indicating an Extended Key Usage (EKU) key purpose.
397425
#[derive(Clone, Copy, PartialEq, Eq)]
398-
pub struct KeyPurposeId {
426+
struct KeyPurposeId {
399427
oid_value: untrusted::Input<'static>,
400428
}
401429

402430
impl KeyPurposeId {
403431
/// Construct a new [`KeyPurposeId`].
404432
///
405433
/// `oid` is the OBJECT IDENTIFIER in bytes.
406-
pub const fn new(oid: &'static [u8]) -> Self {
434+
const fn new(oid: &'static [u8]) -> Self {
407435
Self {
408436
oid_value: untrusted::Input::from(oid),
409437
}
@@ -415,18 +443,11 @@ impl KeyPurposeId {
415443

416444
// id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 }
417445
#[allow(clippy::identity_op)] // TODO: Make this clearer
418-
pub(crate) static EKU_SERVER_AUTH: KeyPurposeId =
419-
KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 1]);
446+
const EKU_SERVER_AUTH: KeyPurposeId = KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 1]);
420447

421448
// id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 }
422449
#[allow(clippy::identity_op)] // TODO: Make this clearer
423-
pub(crate) static EKU_CLIENT_AUTH: KeyPurposeId =
424-
KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 2]);
425-
426-
// id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 }
427-
#[allow(clippy::identity_op)] // TODO: Make this clearer
428-
pub(crate) static EKU_OCSP_SIGNING: KeyPurposeId =
429-
KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 9]);
450+
const EKU_CLIENT_AUTH: KeyPurposeId = KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 2]);
430451

431452
// https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.3
432453
#[repr(u8)]
@@ -483,7 +504,7 @@ where
483504

484505
#[cfg(test)]
485506
mod tests {
486-
use crate::{verify_cert::EKU_SERVER_AUTH, ExtendedKeyUsage};
507+
use super::*;
487508

488509
#[test]
489510
fn eku_key_purpose_id() {

tests/custom_ekus.rs

+11-36
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#![cfg(feature = "alloc")]
22

3-
use webpki::ExtendedKeyUsage::{Required, RequiredIfPresent};
3+
use webpki::KeyUsage;
44

55
fn check_cert(
66
ee: &[u8],
77
ca: &[u8],
8-
eku: webpki::ExtendedKeyUsage,
8+
eku: KeyUsage,
99
time: webpki::Time,
1010
result: Result<(), webpki::Error>,
1111
) {
@@ -24,56 +24,31 @@ fn check_cert(
2424
);
2525
}
2626

27-
#[allow(clippy::identity_op)]
28-
static EKU_CLIENT_AUTH: webpki::KeyPurposeId =
29-
webpki::KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 2]);
30-
31-
#[allow(clippy::identity_op)]
32-
static EKU_SERVER_AUTH: webpki::KeyPurposeId =
33-
webpki::KeyPurposeId::new(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 1]);
34-
35-
#[allow(clippy::identity_op)]
36-
static EKU_MDOC_ISSUER_AUTH: webpki::KeyPurposeId =
37-
webpki::KeyPurposeId::new(&[(40 * 1) + 0, 129, 140, 93, 5, 1, 2]);
38-
3927
#[test]
4028
pub fn verify_custom_eku_mdoc() {
4129
let err = Err(webpki::Error::RequiredEkuNotFound);
4230
let time = webpki::Time::from_seconds_since_unix_epoch(1609459200); // Jan 1 01:00:00 CET 2021
4331

4432
let ee = include_bytes!("misc/mdoc_eku.ee.der");
4533
let ca = include_bytes!("misc/mdoc_eku.ca.der");
46-
check_cert(ee, ca, Required(EKU_MDOC_ISSUER_AUTH), time, Ok(()));
47-
check_cert(ee, ca, Required(EKU_SERVER_AUTH), time, err);
48-
check_cert(
49-
ee,
50-
ca,
51-
RequiredIfPresent(EKU_MDOC_ISSUER_AUTH),
52-
time,
53-
Ok(()),
54-
);
55-
check_cert(ee, ca, RequiredIfPresent(EKU_SERVER_AUTH), time, err);
34+
35+
let eku_mdoc = KeyUsage::required(&[(40 * 1) + 0, 129, 140, 93, 5, 1, 2]);
36+
check_cert(ee, ca, eku_mdoc, time, Ok(()));
37+
check_cert(ee, ca, KeyUsage::server_auth(), time, err);
38+
check_cert(ee, ca, eku_mdoc, time, Ok(()));
39+
check_cert(ee, ca, KeyUsage::server_auth(), time, err);
5640
}
5741

5842
#[test]
5943
pub fn verify_custom_eku_client() {
60-
let err = Err(webpki::Error::RequiredEkuNotFound);
6144
let time = webpki::Time::from_seconds_since_unix_epoch(0x1fed_f00d);
6245

6346
let ee = include_bytes!("client_auth/cert_with_no_eku_accepted_for_client_auth.ee.der");
6447
let ca = include_bytes!("client_auth/cert_with_no_eku_accepted_for_client_auth.ca.der");
65-
check_cert(ee, ca, Required(EKU_CLIENT_AUTH), time, err);
66-
check_cert(ee, ca, RequiredIfPresent(EKU_CLIENT_AUTH), time, Ok(()));
48+
check_cert(ee, ca, KeyUsage::client_auth(), time, Ok(()));
6749

6850
let ee = include_bytes!("client_auth/cert_with_both_ekus_accepted_for_client_auth.ee.der");
6951
let ca = include_bytes!("client_auth/cert_with_both_ekus_accepted_for_client_auth.ca.der");
70-
check_cert(ee, ca, Required(EKU_CLIENT_AUTH), time, Ok(()));
71-
check_cert(ee, ca, Required(EKU_SERVER_AUTH), time, Ok(()));
72-
check_cert(ee, ca, RequiredIfPresent(EKU_CLIENT_AUTH), time, Ok(()));
73-
check_cert(ee, ca, RequiredIfPresent(EKU_SERVER_AUTH), time, Ok(()));
74-
}
75-
76-
#[test]
77-
fn key_purpose_id() {
78-
webpki::KeyPurposeId::new(&[1, 2, 3]);
52+
check_cert(ee, ca, KeyUsage::client_auth(), time, Ok(()));
53+
check_cert(ee, ca, KeyUsage::server_auth(), time, Ok(()));
7954
}

0 commit comments

Comments
 (0)