44//! large verification logic into manageable pieces.
55
66use crate :: error:: { Error , Result } ;
7- use const_oid:: db:: rfc5912:: { ECDSA_WITH_SHA_256 , ECDSA_WITH_SHA_384 , SECP_256_R_1 , SECP_384_R_1 } ;
7+ use const_oid:: db:: rfc5912:: ID_KP_CODE_SIGNING ;
8+ use rustls_pki_types:: CertificateDer ;
89use sigstore_crypto:: CertificateInfo ;
910use sigstore_trust_root:: TrustedRoot ;
1011use sigstore_types:: bundle:: VerificationMaterialContent ;
1112use sigstore_types:: { Bundle , SignatureContent } ;
12- use x509_cert :: der :: Encode ;
13+ use webpki :: { anchor_from_trusted_cert , EndEntityCert , KeyUsage , ALL_VERIFICATION_ALGS } ;
1314
1415/// Extract and decode the signing certificate from verification material
1516pub fn extract_certificate_der (
@@ -200,16 +201,37 @@ pub fn validate_certificate_time(validation_time: i64, cert_info: &CertificateIn
200201/// Verify the certificate chain to the Fulcio root of trust
201202///
202203/// This function verifies that the signing certificate chains to a trusted
203- /// Fulcio root certificate at the given verification time.
204+ /// Fulcio root certificate at the given verification time. It also verifies
205+ /// that the certificate has the CODE_SIGNING extended key usage.
204206pub fn verify_certificate_chain (
205- cert_der : & [ u8 ] ,
206- _validation_time : i64 ,
207+ verification_material : & VerificationMaterialContent ,
208+ validation_time : i64 ,
207209 trusted_root : & TrustedRoot ,
208210) -> Result < ( ) > {
209- use x509_cert:: der:: Decode ;
210- use x509_cert:: Certificate ;
211+ // Extract the end-entity certificate and any intermediates from the bundle
212+ let ( ee_cert_der, intermediate_ders) = match verification_material {
213+ VerificationMaterialContent :: Certificate ( cert) => {
214+ ( cert. raw_bytes . as_bytes ( ) . to_vec ( ) , Vec :: new ( ) )
215+ }
216+ VerificationMaterialContent :: X509CertificateChain { certificates } => {
217+ if certificates. is_empty ( ) {
218+ return Err ( Error :: Verification ( "no certificates in chain" . to_string ( ) ) ) ;
219+ }
220+ let ee = certificates[ 0 ] . raw_bytes . as_bytes ( ) . to_vec ( ) ;
221+ let intermediates: Vec < Vec < u8 > > = certificates[ 1 ..]
222+ . iter ( )
223+ . map ( |c| c. raw_bytes . as_bytes ( ) . to_vec ( ) )
224+ . collect ( ) ;
225+ ( ee, intermediates)
226+ }
227+ VerificationMaterialContent :: PublicKey { .. } => {
228+ return Err ( Error :: Verification (
229+ "public key verification not yet supported" . to_string ( ) ,
230+ ) ) ;
231+ }
232+ } ;
211233
212- // Get Fulcio certificates from trusted root
234+ // Get Fulcio certificates from trusted root to use as trust anchors
213235 let fulcio_certs = trusted_root
214236 . fulcio_certs ( )
215237 . map_err ( |e| Error :: Verification ( format ! ( "failed to get Fulcio certs: {}" , e) ) ) ?;
@@ -220,180 +242,61 @@ pub fn verify_certificate_chain(
220242 ) ) ;
221243 }
222244
223- // Parse the end-entity certificate
224- let ee_cert = Certificate :: from_der ( cert_der) . map_err ( |e| {
225- Error :: Verification ( format ! ( "failed to parse end-entity certificate: {}" , e) )
226- } ) ?;
227-
228- // Get the issuer from the EE certificate
229- let ee_issuer = & ee_cert. tbs_certificate . issuer ;
230-
231- // Extract the original TBS DER bytes from the certificate
232- // CRITICAL: We must use the original DER bytes, not re-serialize, because
233- // re-serialization can produce different bytes even if semantically equivalent,
234- // which will break signature verification.
235- let tbs_der = extract_tbs_der ( cert_der) . map_err ( |e| {
236- Error :: Verification ( format ! ( "failed to extract TBS certificate bytes: {}" , e) )
237- } ) ?;
238-
239- // Try to find a matching Fulcio root by comparing issuers
240- let mut found_issuer = false ;
241- for fulcio_cert_der in & fulcio_certs {
242- if let Ok ( fulcio_cert) = Certificate :: from_der ( fulcio_cert_der) {
243- let fulcio_subject = & fulcio_cert. tbs_certificate . subject ;
244-
245- // Check if the EE certificate's issuer matches this Fulcio cert's subject
246- if ee_issuer == fulcio_subject {
247- // Verify the signature
248- let Some ( signature) = ee_cert. signature . as_bytes ( ) else {
249- continue ;
250- } ;
251-
252- // Determine the signing scheme by combining:
253- // 1. The curve from the issuer's public key (SPKI)
254- // 2. The hash algorithm from the signature algorithm OID
255- let sig_alg_oid = ee_cert. signature_algorithm . oid ;
256-
257- // Get the curve from the issuer's public key
258- let issuer_spki = & fulcio_cert. tbs_certificate . subject_public_key_info ;
259- let curve_oid = match extract_ec_curve_oid ( issuer_spki) {
260- Ok ( oid) => oid,
261- Err ( _) => continue ,
262- } ;
263-
264- // Map (curve, hash) to SigningScheme using OID constants
265- let scheme = if curve_oid == SECP_256_R_1 && sig_alg_oid == ECDSA_WITH_SHA_256 {
266- // P-256 with SHA-256
267- sigstore_crypto:: SigningScheme :: EcdsaP256Sha256
268- } else if curve_oid == SECP_256_R_1 && sig_alg_oid == ECDSA_WITH_SHA_384 {
269- // P-256 with SHA-384 (non-standard but valid)
270- sigstore_crypto:: SigningScheme :: EcdsaP256Sha384
271- } else if curve_oid == SECP_384_R_1 && sig_alg_oid == ECDSA_WITH_SHA_384 {
272- // P-384 with SHA-384
273- sigstore_crypto:: SigningScheme :: EcdsaP384Sha384
274- } else {
275- tracing:: warn!(
276- "Unknown curve/signature algorithm combination: curve={}, sig_alg={}" ,
277- curve_oid,
278- sig_alg_oid
279- ) ;
280- continue ;
281- } ;
282-
283- let Some ( issuer_pub_key) = issuer_spki. subject_public_key . as_bytes ( ) else {
284- continue ;
285- } ;
286-
287- if sigstore_crypto:: verify_signature ( issuer_pub_key, & tbs_der, signature, scheme)
288- . is_ok ( )
289- {
290- found_issuer = true ;
291- break ;
292- }
293- }
294- }
295- }
296-
297- if !found_issuer {
245+ // Build trust anchors from Fulcio root certificates
246+ let trust_anchors: Vec < _ > = fulcio_certs
247+ . iter ( )
248+ . filter_map ( |cert_der| {
249+ let cert = CertificateDer :: from ( & cert_der[ ..] ) ;
250+ anchor_from_trusted_cert ( & cert)
251+ . map ( |anchor| anchor. to_owned ( ) )
252+ . ok ( )
253+ } )
254+ . collect ( ) ;
255+
256+ if trust_anchors. is_empty ( ) {
298257 return Err ( Error :: Verification (
299- "certificate does not chain to any trusted Fulcio root " . to_string ( ) ,
258+ "failed to create trust anchors from Fulcio certificates " . to_string ( ) ,
300259 ) ) ;
301260 }
302261
303- // Verify certificate validity period
304- let cert_info = sigstore_crypto:: parse_certificate_info ( cert_der) ?;
305- validate_certificate_time ( _validation_time, & cert_info) ?;
262+ // Convert intermediate certificates to CertificateDer
263+ let intermediate_certs: Vec < CertificateDer < ' static > > = intermediate_ders
264+ . into_iter ( )
265+ . map ( |der| CertificateDer :: from ( der) . into_owned ( ) )
266+ . collect ( ) ;
306267
307- Ok ( ( ) )
308- }
309-
310- /// Extract the EC curve OID from a SubjectPublicKeyInfo
311- ///
312- /// For EC keys, the algorithm parameters contain the curve OID
313- fn extract_ec_curve_oid (
314- spki : & x509_cert:: spki:: SubjectPublicKeyInfoOwned ,
315- ) -> Result < const_oid:: ObjectIdentifier > {
316- use const_oid:: db:: rfc5912:: ID_EC_PUBLIC_KEY ;
317- use const_oid:: ObjectIdentifier ;
318-
319- // For EC keys, the algorithm OID should be id-ecPublicKey (1.2.840.10045.2.1)
320- if spki. algorithm . oid != ID_EC_PUBLIC_KEY {
321- return Err ( Error :: Verification ( "Not an EC public key" . to_string ( ) ) ) ;
322- }
323-
324- // The parameters field contains the curve OID
325- let Some ( params) = & spki. algorithm . parameters else {
326- return Err ( Error :: Verification (
327- "EC public key missing curve parameters" . to_string ( ) ,
328- ) ) ;
329- } ;
330-
331- // The AnyRef value() gives us the raw content bytes (without tag/length).
332- // For an OID, this is the encoded OID bytes.
333- // ObjectIdentifier::from_bytes expects raw OID bytes (without tag/length header).
334- let curve_oid = ObjectIdentifier :: from_bytes ( params. value ( ) )
335- . map_err ( |e| Error :: Verification ( format ! ( "failed to parse EC curve OID: {}" , e) ) ) ?;
336-
337- Ok ( curve_oid)
338- }
268+ // Parse the end-entity certificate for webpki
269+ let ee_cert_der_ref = CertificateDer :: from ( ee_cert_der. as_slice ( ) ) ;
270+ let end_entity_cert = EndEntityCert :: try_from ( & ee_cert_der_ref) . map_err ( |e| {
271+ Error :: Verification ( format ! ( "failed to parse end-entity certificate: {}" , e) )
272+ } ) ?;
339273
340- /// Extract the original TBS (To Be Signed) certificate DER bytes from a certificate
341- ///
342- /// CRITICAL: This extracts the original DER bytes without re-parsing and re-serializing,
343- /// which is necessary for correct signature verification.
344- fn extract_tbs_der ( cert_der : & [ u8 ] ) -> Result < Vec < u8 > > {
345- use x509_cert:: der:: { Decode , Reader , SliceReader } ;
346-
347- // A Certificate is a SEQUENCE containing:
348- // 1. TBSCertificate (SEQUENCE)
349- // 2. signatureAlgorithm (SEQUENCE)
350- // 3. signatureValue (BIT STRING)
351- //
352- // We need to extract the raw bytes of the TBSCertificate element.
353-
354- let mut reader = SliceReader :: new ( cert_der)
355- . map_err ( |e| Error :: Verification ( format ! ( "failed to create DER reader: {}" , e) ) ) ?;
356-
357- // Decode the outer SEQUENCE header
358- let outer_header = x509_cert:: der:: Header :: decode ( & mut reader)
359- . map_err ( |e| Error :: Verification ( format ! ( "failed to decode certificate header: {}" , e) ) ) ?;
360-
361- // The remaining bytes should be the certificate contents
362- let cert_contents = reader
363- . read_slice ( outer_header. length )
364- . map_err ( |e| Error :: Verification ( format ! ( "failed to read certificate contents: {}" , e) ) ) ?;
365-
366- // Now decode the TBS header from the certificate contents
367- let mut tbs_reader = SliceReader :: new ( cert_contents)
368- . map_err ( |e| Error :: Verification ( format ! ( "failed to create TBS reader: {}" , e) ) ) ?;
369-
370- let tbs_header = x509_cert:: der:: Header :: decode ( & mut tbs_reader)
371- . map_err ( |e| Error :: Verification ( format ! ( "failed to decode TBS header: {}" , e) ) ) ?;
372-
373- // Calculate the total length of the TBS including its header
374- let header_len: usize = tbs_header
375- . encoded_len ( )
376- . map_err ( |e| Error :: Verification ( format ! ( "failed to encode TBS header length: {}" , e) ) ) ?
377- . try_into ( )
378- . map_err ( |_| Error :: Verification ( "TBS header length too large" . to_string ( ) ) ) ?;
379-
380- let body_len: usize = tbs_header
381- . length
382- . try_into ( )
383- . map_err ( |_| Error :: Verification ( "TBS body length too large" . to_string ( ) ) ) ?;
384-
385- let tbs_total_len = header_len
386- . checked_add ( body_len)
387- . ok_or_else ( || Error :: Verification ( "TBS length calculation overflow" . to_string ( ) ) ) ?;
388-
389- // Extract the TBS bytes (header + body)
390- if tbs_total_len > cert_contents. len ( ) {
391- return Err ( Error :: Verification (
392- "TBS length exceeds certificate contents" . to_string ( ) ,
393- ) ) ;
394- }
274+ // Convert validation time to webpki UnixTime
275+ let verification_time = webpki:: types:: UnixTime :: since_unix_epoch (
276+ std:: time:: Duration :: from_secs ( validation_time as u64 ) ,
277+ ) ;
278+
279+ // Verify the certificate chain with CODE_SIGNING EKU
280+ // This performs:
281+ // - Chain building from end-entity to trust anchor
282+ // - Signature verification at each step
283+ // - Time validity checking
284+ // - Extended Key Usage validation (CODE_SIGNING)
285+ end_entity_cert
286+ . verify_for_usage (
287+ ALL_VERIFICATION_ALGS ,
288+ & trust_anchors,
289+ & intermediate_certs,
290+ verification_time,
291+ KeyUsage :: required ( ID_KP_CODE_SIGNING . as_bytes ( ) ) ,
292+ None , // No revocation checking
293+ None , // No path verification callback
294+ )
295+ . map_err ( |e| Error :: Verification ( format ! ( "certificate chain validation failed: {}" , e) ) ) ?;
296+
297+ tracing:: debug!( "Certificate chain validated successfully with CODE_SIGNING EKU" ) ;
395298
396- Ok ( cert_contents [ ..tbs_total_len ] . to_vec ( ) )
299+ Ok ( ( ) )
397300}
398301
399302/// Verify the Signed Certificate Timestamp (SCT) embedded in the certificate
@@ -473,65 +376,3 @@ fn get_issuer_spki(
473376 "could not find issuer certificate for SCT verification" . to_string ( ) ,
474377 ) )
475378}
476-
477- /// Verify that the certificate conforms to the Sigstore X.509 profile
478- ///
479- /// This checks:
480- /// - KeyUsage extension contains digitalSignature
481- /// - ExtendedKeyUsage extension contains codeSigning
482- pub fn verify_x509_profile ( cert_der : & [ u8 ] ) -> Result < ( ) > {
483- use x509_cert:: der:: Decode ;
484- use x509_cert:: ext:: pkix:: { ExtendedKeyUsage , KeyUsage , KeyUsages } ;
485- use x509_cert:: Certificate ;
486-
487- // OID constants for X.509 extensions
488- use const_oid:: db:: rfc5280:: { ID_CE_EXT_KEY_USAGE , ID_CE_KEY_USAGE } ;
489- use const_oid:: db:: rfc5912:: ID_KP_CODE_SIGNING ;
490-
491- let cert = Certificate :: from_der ( cert_der)
492- . map_err ( |e| Error :: Verification ( format ! ( "failed to parse certificate: {}" , e) ) ) ?;
493-
494- let extensions = cert
495- . tbs_certificate
496- . extensions
497- . as_ref ( )
498- . ok_or_else ( || Error :: Verification ( "certificate has no extensions" . to_string ( ) ) ) ?;
499-
500- // Check KeyUsage extension (OID 2.5.29.15)
501- let key_usage_ext = extensions
502- . iter ( )
503- . find ( |ext| ext. extn_id == ID_CE_KEY_USAGE )
504- . ok_or_else ( || {
505- Error :: Verification ( "certificate is missing KeyUsage extension" . to_string ( ) )
506- } ) ?;
507-
508- let key_usage = KeyUsage :: from_der ( key_usage_ext. extn_value . as_bytes ( ) )
509- . map_err ( |e| Error :: Verification ( format ! ( "failed to parse KeyUsage extension: {}" , e) ) ) ?;
510-
511- if !key_usage. 0 . contains ( KeyUsages :: DigitalSignature ) {
512- return Err ( Error :: Verification (
513- "KeyUsage extension does not contain digitalSignature" . to_string ( ) ,
514- ) ) ;
515- }
516-
517- // Check ExtendedKeyUsage extension (OID 2.5.29.37)
518- let eku_ext = extensions
519- . iter ( )
520- . find ( |ext| ext. extn_id == ID_CE_EXT_KEY_USAGE )
521- . ok_or_else ( || {
522- Error :: Verification ( "certificate is missing ExtendedKeyUsage extension" . to_string ( ) )
523- } ) ?;
524-
525- let eku = ExtendedKeyUsage :: from_der ( eku_ext. extn_value . as_bytes ( ) ) . map_err ( |e| {
526- Error :: Verification ( format ! ( "failed to parse ExtendedKeyUsage extension: {}" , e) )
527- } ) ?;
528-
529- // Check for code signing OID (1.3.6.1.5.5.7.3.3)
530- if !eku. 0 . contains ( & ID_KP_CODE_SIGNING ) {
531- return Err ( Error :: Verification (
532- "ExtendedKeyUsage extension does not contain codeSigning" . to_string ( ) ,
533- ) ) ;
534- }
535-
536- Ok ( ( ) )
537- }
0 commit comments