Skip to content

Commit caebc6e

Browse files
committed
remove manual verification code and use webpki
1 parent bb63cff commit caebc6e

File tree

2 files changed

+82
-243
lines changed

2 files changed

+82
-243
lines changed

crates/sigstore-verify/src/verify.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,11 @@ impl Verifier {
230230
&self.trusted_root,
231231
)?;
232232

233-
// (1): Verify that the signing certificate chains to the root of trust
234-
// and is valid at the time of signing.
233+
// (1): Verify that the signing certificate chains to the root of trust,
234+
// is valid at the time of signing, and has CODE_SIGNING EKU.
235235
if policy.verify_certificate {
236236
crate::verify_impl::helpers::verify_certificate_chain(
237-
&cert_der,
237+
&bundle.verification_material.content,
238238
validation_time,
239239
&self.trusted_root,
240240
)?;
@@ -249,9 +249,7 @@ impl Verifier {
249249
)?;
250250
}
251251

252-
// (3): Verify that the signing certificate conforms to the Sigstore
253-
// X.509 profile and verify against the given `VerificationPolicy`.
254-
crate::verify_impl::helpers::verify_x509_profile(&cert_der)?;
252+
// (3): Verify against the given `VerificationPolicy`.
255253

256254
// Verify against policy constraints
257255
if let Some(ref expected_identity) = policy.identity {

crates/sigstore-verify/src/verify_impl/helpers.rs

Lines changed: 78 additions & 237 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
//! large verification logic into manageable pieces.
55
66
use 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;
89
use sigstore_crypto::CertificateInfo;
910
use sigstore_trust_root::TrustedRoot;
1011
use sigstore_types::bundle::VerificationMaterialContent;
1112
use 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
1516
pub 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.
204206
pub 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

Comments
 (0)