11use crate :: crypto:: verify_p256_signature_der;
22use anyhow:: bail;
3+ use core:: ops:: { BitAnd , BitOr , Deref } ;
34use core:: str:: FromStr ;
45use dcap_types:: cert:: { SgxExtensionTcbLevel , SgxExtensions } ;
56use dcap_types:: tcb_info:: { TcbComponent , TcbInfoV3 } ;
67use dcap_types:: TcbInfoV3TcbStatus ;
78use dcap_types:: { SGX_TEE_TYPE , TDX_TEE_TYPE } ;
89use 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`.
1154pub 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) ]
214320mod 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