Skip to content

Commit 8370a78

Browse files
committed
fix to verify the presence and correctness of critical Key Usage and Basic Constraints extensions in all certificates
Signed-off-by: Jun Kimura <[email protected]>
1 parent a0cdc2d commit 8370a78

File tree

6 files changed

+356
-10
lines changed

6 files changed

+356
-10
lines changed

crates/collaterals/src/certs.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -255,11 +255,11 @@ pub fn gen_pck_cert_ca(
255255
builder.append_extension(
256256
KeyUsage::new()
257257
.critical()
258-
.digital_signature()
259-
.non_repudiation()
258+
.key_cert_sign()
259+
.crl_sign()
260260
.build()?,
261261
)?;
262-
builder.append_extension(BasicConstraints::new().critical().build()?)?;
262+
builder.append_extension(BasicConstraints::new().critical().ca().pathlen(0).build()?)?;
263263

264264
let ctx = builder.x509v3_context(Some(root_cert), None);
265265
builder.append_extension(
@@ -460,7 +460,7 @@ impl Validity {
460460
}
461461
}
462462

463-
fn build_x509_name(cn: &str) -> Result<X509Name, ErrorStack> {
463+
pub fn build_x509_name(cn: &str) -> Result<X509Name, ErrorStack> {
464464
let mut builder = X509Name::builder()?;
465465
builder.append_entry_by_text("CN", cn)?;
466466
builder.append_entry_by_text("O", "Intel Corporation")?;
@@ -477,7 +477,7 @@ fn calc_skid(pubkey: &PKeyRef<Private>) -> Vec<u8> {
477477
}
478478

479479
#[allow(deprecated)]
480-
fn gen_skid(pubkey: &PKeyRef<Private>) -> X509Extension {
480+
pub fn gen_skid(pubkey: &PKeyRef<Private>) -> X509Extension {
481481
let skid = calc_skid(pubkey);
482482
X509Extension::new(
483483
None,

crates/quote-verifier/src/cert.rs

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,55 @@
11
use crate::crypto::verify_p256_signature_der;
22
use anyhow::bail;
3+
use core::ops::{BitAnd, BitOr, Deref};
34
use core::str::FromStr;
45
use dcap_types::cert::{SgxExtensionTcbLevel, SgxExtensions};
56
use dcap_types::tcb_info::{TcbComponent, TcbInfoV3};
67
use dcap_types::TcbInfoV3TcbStatus;
78
use dcap_types::{SGX_TEE_TYPE, TDX_TEE_TYPE};
89
use x509_parser::prelude::*;
910

11+
pub const KU_NONE: KeyUsageFlags = KeyUsageFlags(0);
12+
pub const KU_DIGITAL_SIGNATURE: KeyUsageFlags = KeyUsageFlags(1);
13+
pub const KU_NON_REPUDIATION: KeyUsageFlags = KeyUsageFlags(1 << 1);
14+
pub const KU_KEY_ENCIPHERMENT: KeyUsageFlags = KeyUsageFlags(1 << 2);
15+
pub const KU_DATA_ENCIPHERMENT: KeyUsageFlags = KeyUsageFlags(1 << 3);
16+
pub const KU_KEY_AGREEMENT: KeyUsageFlags = KeyUsageFlags(1 << 4);
17+
pub const KU_KEY_CERT_SIGN: KeyUsageFlags = KeyUsageFlags(1 << 5);
18+
pub const KU_CRL_SIGN: KeyUsageFlags = KeyUsageFlags(1 << 6);
19+
pub const KU_ENCIPHER_ONLY: KeyUsageFlags = KeyUsageFlags(1 << 7);
20+
pub const KU_DECIPHER_ONLY: KeyUsageFlags = KeyUsageFlags(1 << 8);
21+
22+
/// KeyUsageFlags is a bitmask of Key Usage flags.
23+
///
24+
/// Each flag corresponds to a specific key usage as defined in RFC 5280.
25+
/// ref. https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3
26+
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
27+
pub struct KeyUsageFlags(pub u16);
28+
29+
impl Deref for KeyUsageFlags {
30+
type Target = u16;
31+
32+
fn deref(&self) -> &Self::Target {
33+
&self.0
34+
}
35+
}
36+
37+
impl BitOr for KeyUsageFlags {
38+
type Output = KeyUsageFlags;
39+
40+
fn bitor(self, rhs: KeyUsageFlags) -> Self::Output {
41+
KeyUsageFlags(self.0 | rhs.0)
42+
}
43+
}
44+
45+
impl BitAnd for KeyUsageFlags {
46+
type Output = KeyUsageFlags;
47+
48+
fn bitand(self, rhs: KeyUsageFlags) -> Self::Output {
49+
KeyUsageFlags(self.0 & rhs.0)
50+
}
51+
}
52+
1053
/// Parse a PEM-encoded certificate chain into a vector of `X509Certificate`.
1154
pub fn parse_certchain(pem_certs: &[Pem]) -> crate::Result<Vec<X509Certificate>> {
1255
Ok(pem_certs
@@ -210,9 +253,77 @@ fn match_tdxtcbcomp(tee_tcb_svn: &[u8; 16], tdxtcbcomponents: &[TcbComponent; 16
210253
.all(|(tee, tcb)| *tee >= tcb.svn)
211254
}
212255

256+
/// Validates the critical `KeyUsage` and `BasicConstraints` extensions of an X.509 certificate.
257+
///
258+
/// This function ensures that the certificate includes both `KeyUsage` and `BasicConstraints`
259+
/// extensions marked as critical, and that their values match the expected ones.
260+
///
261+
/// - For `KeyUsage`, it checks that all bits specified in `expected_ku` are present in the certificate's `KeyUsage`.
262+
/// Additional bits set in the certificate are allowed and not rejected.
263+
/// - For `BasicConstraints`, it checks that the `ca` flag and the `path_len_constraint` match exactly.
264+
///
265+
/// # Arguments
266+
/// * `cert` - The X.509 certificate to validate.
267+
/// * `expected_ca` - Whether the certificate is expected to be a CA.
268+
/// * `expected_pathlen` - The expected `pathLenConstraint` value (if any).
269+
/// * `expected_ku` - The expected combination of `KeyUsage` bits that must be present.
270+
///
271+
/// # Errors
272+
/// Returns an error if:
273+
/// - A required extension is missing,
274+
/// - A known critical extension does not match the expected values,
275+
/// - An unknown critical extension is present.
276+
pub(crate) fn validate_cert_extensions(
277+
cert: &X509Certificate,
278+
expected_ca: bool,
279+
expected_pathlen: Option<u32>,
280+
expected_ku: KeyUsageFlags,
281+
) -> crate::Result<()> {
282+
let mut ku_validated = false;
283+
let mut bc_validated = false;
284+
for ext in cert.extensions().iter().filter(|ext| ext.critical) {
285+
match ext.parsed_extension() {
286+
ParsedExtension::KeyUsage(ku) => {
287+
// check that all expected bits are set
288+
if ku.flags & expected_ku.0 != expected_ku.0 {
289+
bail!(
290+
"Certificate Key Usage mismatch: expected={:b}, actual={:b}",
291+
expected_ku.0,
292+
ku.flags
293+
);
294+
}
295+
ku_validated = true;
296+
}
297+
ParsedExtension::BasicConstraints(bc) => {
298+
if bc.ca != expected_ca || bc.path_len_constraint != expected_pathlen {
299+
bail!(
300+
"Certificate Basic Constraints mismatch: ca={}, pathlen={:?}",
301+
expected_ca,
302+
expected_pathlen
303+
);
304+
}
305+
bc_validated = true;
306+
}
307+
_ => bail!("Unknown critical extension: {}", ext.oid),
308+
}
309+
}
310+
if !ku_validated {
311+
bail!("Missing critical Key Usage extension");
312+
}
313+
if !bc_validated {
314+
bail!("Missing critical Basic Constraints extension");
315+
}
316+
Ok(())
317+
}
318+
213319
#[cfg(test)]
214320
mod tests {
215321
use super::*;
322+
use dcap_collaterals::{
323+
certs::{build_x509_name, gen_skid, Validity},
324+
openssl::x509::X509,
325+
utils::gen_key,
326+
};
216327
use dcap_types::utils::{parse_crl_der, parse_x509_der};
217328

218329
#[test]
@@ -226,4 +337,197 @@ mod tests {
226337

227338
assert!(verify_crl_signature(&intel_sgx_root_ca_crl, &intel_sgx_root_ca).is_ok());
228339
}
340+
341+
#[test]
342+
fn test_validate_cert_extensions() {
343+
use x509_parser::prelude::FromDer;
344+
345+
// OK: Valid certificate: leaf cert with expected KU and basic constraints
346+
{
347+
let cert = gen_cert(false, None, KU_DIGITAL_SIGNATURE | KU_NON_REPUDIATION);
348+
let der = cert.to_der().unwrap();
349+
let (_, cert) = X509Certificate::from_der(&der).unwrap();
350+
351+
let res = validate_cert_extensions(
352+
&cert,
353+
false,
354+
None,
355+
KU_DIGITAL_SIGNATURE | KU_NON_REPUDIATION,
356+
);
357+
assert!(res.is_ok(), "valid cert should pass: {:?}", res);
358+
}
359+
360+
// Failed: Missing Key Usage bit (non_repudiation is missing)
361+
{
362+
let cert = gen_cert(false, None, KU_DIGITAL_SIGNATURE); // only digital_signature
363+
let der = cert.to_der().unwrap();
364+
let (_, cert) = X509Certificate::from_der(&der).unwrap();
365+
366+
let res = validate_cert_extensions(
367+
&cert,
368+
false,
369+
None,
370+
KU_DIGITAL_SIGNATURE | KU_NON_REPUDIATION,
371+
);
372+
assert!(res.is_err(), "missing KU bit should fail");
373+
}
374+
375+
// Failed: Mismatched BasicConstraints CA flag (expected false, actual true)
376+
{
377+
let cert = gen_cert(true, None, KU_DIGITAL_SIGNATURE | KU_NON_REPUDIATION); // CA = true
378+
let der = cert.to_der().unwrap();
379+
let (_, cert) = X509Certificate::from_der(&der).unwrap();
380+
381+
let res = validate_cert_extensions(
382+
&cert,
383+
false,
384+
None,
385+
KU_DIGITAL_SIGNATURE | KU_NON_REPUDIATION,
386+
);
387+
assert!(res.is_err(), "CA mismatch should fail");
388+
}
389+
390+
// OK: Mismatched BasicConstraints path length
391+
{
392+
let cert = gen_cert(true, Some(1), KU_KEY_CERT_SIGN | KU_CRL_SIGN);
393+
let der = cert.to_der().unwrap();
394+
let (_, cert) = X509Certificate::from_der(&der).unwrap();
395+
396+
let res = validate_cert_extensions(
397+
&cert,
398+
true,
399+
Some(0), // expected pathlen: 0, actual: 1
400+
KU_KEY_CERT_SIGN | KU_CRL_SIGN,
401+
);
402+
assert!(res.is_err(), "pathlen mismatch should fail");
403+
}
404+
405+
// Failed: Missing Key Usage extension entirely (critical extension missing)
406+
{
407+
let cert = gen_cert(false, None, KU_NONE); // no KeyUsage
408+
let der = cert.to_der().unwrap();
409+
let (_, cert) = X509Certificate::from_der(&der).unwrap();
410+
411+
let res = validate_cert_extensions(&cert, false, None, KU_DIGITAL_SIGNATURE);
412+
assert!(res.is_err(), "missing KeyUsage extension should fail");
413+
}
414+
415+
// OK: Valid CA certificate with correct KeyUsage and BasicConstraints
416+
{
417+
let cert = gen_cert(true, Some(0), KU_KEY_CERT_SIGN | KU_CRL_SIGN);
418+
let der = cert.to_der().unwrap();
419+
let (_, cert) = X509Certificate::from_der(&der).unwrap();
420+
421+
let res =
422+
validate_cert_extensions(&cert, true, Some(0), KU_KEY_CERT_SIGN | KU_CRL_SIGN);
423+
assert!(res.is_ok(), "valid CA cert should pass");
424+
}
425+
426+
// OK: Valid certificate with extra KeyUsage bits
427+
{
428+
// Expected: digital_signature + non_repudiation
429+
// Extra : key_encipherment + key_agreement
430+
let cert = gen_cert(
431+
false,
432+
None,
433+
KU_DIGITAL_SIGNATURE | KU_NON_REPUDIATION | KU_KEY_ENCIPHERMENT | KU_KEY_AGREEMENT,
434+
);
435+
436+
let der = cert.to_der().unwrap();
437+
let (_, cert) = X509Certificate::from_der(&der).unwrap();
438+
439+
// We only require digital_signature and non_repudiation to be present.
440+
let res = validate_cert_extensions(
441+
&cert,
442+
false,
443+
None,
444+
KU_DIGITAL_SIGNATURE | KU_NON_REPUDIATION,
445+
);
446+
447+
assert!(
448+
res.is_ok(),
449+
"cert with extra KeyUsage bits should still pass validation"
450+
);
451+
}
452+
}
453+
454+
fn gen_cert(ca: bool, path_len: Option<u32>, ku: KeyUsageFlags) -> X509 {
455+
use dcap_collaterals::openssl::x509::extension::{BasicConstraints, KeyUsage};
456+
use dcap_collaterals::openssl::{
457+
asn1::Asn1Integer, bn::BigNum, hash::MessageDigest, x509::X509Builder,
458+
};
459+
460+
let mut builder = X509Builder::new().unwrap();
461+
builder.set_version(2).unwrap(); // X.509 v3
462+
463+
let serial = BigNum::from_u32(1).unwrap(); // Dummy serial
464+
let serial = Asn1Integer::from_bn(&serial).unwrap();
465+
builder.set_serial_number(&serial).unwrap();
466+
467+
builder
468+
.set_not_before(&Validity::long_duration().not_before())
469+
.unwrap();
470+
builder
471+
.set_not_after(&Validity::long_duration().not_after())
472+
.unwrap();
473+
474+
let pkey = gen_key();
475+
builder.set_pubkey(&pkey).unwrap();
476+
477+
let name = build_x509_name("Test CA").unwrap();
478+
builder.set_subject_name(&name).unwrap();
479+
builder.set_issuer_name(&name).unwrap();
480+
builder.append_extension(gen_skid(&pkey)).unwrap();
481+
482+
// Key Usage extension
483+
let mut key_usage = KeyUsage::new();
484+
if ku != KU_NONE {
485+
key_usage.critical();
486+
if ku & KU_DIGITAL_SIGNATURE != KU_NONE {
487+
key_usage.digital_signature();
488+
}
489+
if ku & KU_NON_REPUDIATION != KU_NONE {
490+
key_usage.non_repudiation();
491+
}
492+
if ku & KU_KEY_ENCIPHERMENT != KU_NONE {
493+
key_usage.key_encipherment();
494+
}
495+
if ku & KU_DATA_ENCIPHERMENT != KU_NONE {
496+
key_usage.data_encipherment();
497+
}
498+
if ku & KU_KEY_AGREEMENT != KU_NONE {
499+
key_usage.key_agreement();
500+
}
501+
if ku & KU_KEY_CERT_SIGN != KU_NONE {
502+
key_usage.key_cert_sign();
503+
}
504+
if ku & KU_CRL_SIGN != KU_NONE {
505+
key_usage.crl_sign();
506+
}
507+
if ku & KU_ENCIPHER_ONLY != KU_NONE {
508+
key_usage.encipher_only();
509+
}
510+
if ku & KU_DECIPHER_ONLY != KU_NONE {
511+
key_usage.decipher_only();
512+
}
513+
514+
builder
515+
.append_extension(key_usage.build().unwrap())
516+
.unwrap();
517+
}
518+
519+
// Basic Constraints extension
520+
let mut bc = BasicConstraints::new();
521+
bc.critical();
522+
if ca {
523+
bc.ca();
524+
}
525+
if let Some(plc) = path_len {
526+
bc.pathlen(plc);
527+
}
528+
builder.append_extension(bc.build().unwrap()).unwrap();
529+
530+
builder.sign(&pkey, MessageDigest::sha256()).unwrap();
531+
builder.build()
532+
}
229533
}

0 commit comments

Comments
 (0)