use crate::error::{Error, Result};
use const_oid::db::rfc6962::CT_PRECERT_SCTS;
use sigstore_crypto::{verify_signature, SigningScheme};
use sigstore_trust_root::TrustedRoot;
use sigstore_types::{DerPublicKey, SignatureBytes};
use tls_codec::{SerializeBytes, TlsByteVecU16, TlsByteVecU24, TlsSerializeBytes, TlsSize};
use x509_cert::{
der::{Decode, Encode},
ext::pkix::{sct::Version, SignedCertificateTimestamp, SignedCertificateTimestampList},
Certificate,
};
const ECDSA_SHA256: u16 = 0x0403;
const ECDSA_SHA384: u16 = 0x0503;
const RSA_PKCS1_SHA256: u16 = 0x0401;
const RSA_PKCS1_SHA384: u16 = 0x0501;
const RSA_PKCS1_SHA512: u16 = 0x0601;
#[derive(PartialEq, Debug, TlsSerializeBytes, TlsSize)]
#[repr(u8)]
enum SignatureType {
CertificateTimestamp = 0,
TreeHash = 1,
}
#[derive(PartialEq, Debug)]
#[repr(u16)]
enum LogEntryType {
X509Entry = 0,
PrecertEntry = 1,
}
#[derive(PartialEq, Debug, TlsSerializeBytes, TlsSize)]
struct PreCert {
issuer_key_hash: [u8; 32],
tbs_certificate: TlsByteVecU24,
}
#[derive(PartialEq, Debug, TlsSerializeBytes, TlsSize)]
#[repr(u16)]
enum SignedEntry {
#[allow(unused)]
#[tls_codec(discriminant = "LogEntryType::X509Entry")]
X509Entry(TlsByteVecU24),
#[tls_codec(discriminant = "LogEntryType::PrecertEntry")]
PrecertEntry(PreCert),
}
#[derive(PartialEq, Debug, TlsSerializeBytes, TlsSize)]
pub struct DigitallySigned {
version: Version,
signature_type: SignatureType,
timestamp: u64,
signed_entry: SignedEntry,
extensions: TlsByteVecU16,
#[tls_codec(skip)]
log_id: [u8; 32],
#[tls_codec(skip)]
signature: Vec<u8>,
}
impl DigitallySigned {
pub fn from_embedded_sct(
cert: &Certificate,
sct: &SignedCertificateTimestamp,
issuer_key_hash: [u8; 32],
) -> Result<Self> {
let mut tbs_precert = cert.tbs_certificate.clone();
tbs_precert.extensions = tbs_precert.extensions.map(|exts| {
exts.iter()
.filter(|ext| ext.extn_id != CT_PRECERT_SCTS)
.cloned()
.collect()
});
let mut tbs_precert_der = Vec::new();
tbs_precert
.encode_to_vec(&mut tbs_precert_der)
.map_err(|e| Error::Verification(format!("failed to encode precert TBS: {}", e)))?;
Ok(DigitallySigned {
version: match sct.version {
Version::V1 => Version::V1,
},
signature_type: SignatureType::CertificateTimestamp,
timestamp: sct.timestamp,
signed_entry: SignedEntry::PrecertEntry(PreCert {
issuer_key_hash,
tbs_certificate: tbs_precert_der.as_slice().into(),
}),
extensions: sct.extensions.clone(),
log_id: sct.log_id.key_id,
signature: sct.signature.signature.clone().into(),
})
}
pub fn verify(
&self,
public_key: &DerPublicKey,
sig_alg: u16,
signature: &SignatureBytes,
) -> Result<()> {
let signed_data = self
.tls_serialize()
.map_err(|e| Error::Verification(format!("failed to serialize SCT data: {}", e)))?;
let scheme = match sig_alg {
ECDSA_SHA256 => SigningScheme::EcdsaP256Sha256,
ECDSA_SHA384 => SigningScheme::EcdsaP384Sha384,
RSA_PKCS1_SHA256 => SigningScheme::RsaPkcs1Sha256,
RSA_PKCS1_SHA384 => SigningScheme::RsaPkcs1Sha384,
RSA_PKCS1_SHA512 => SigningScheme::RsaPkcs1Sha512,
_ => {
return Err(Error::Verification(format!(
"unsupported SCT signature algorithm: 0x{:04x}",
sig_alg
)))
}
};
verify_signature(public_key, &signed_data, signature, scheme)
.map_err(|e| Error::Verification(format!("SCT signature verification failed: {}", e)))
}
}
pub fn extract_sct(
cert: &Certificate,
issuer_spki_der: &[u8],
) -> Result<(SignedCertificateTimestamp, [u8; 32])> {
let scts: SignedCertificateTimestampList = match cert.tbs_certificate.get() {
Ok(Some((_, ext))) => ext,
_ => {
return Err(Error::Verification(
"certificate is missing SCT extension (Signed Certificate Timestamp)".to_string(),
))
}
};
let timestamps = scts
.parse_timestamps()
.map_err(|e| Error::Verification(format!("failed to parse SCT list: {:?}", e)))?;
let sct = match timestamps.as_slice() {
[single] => single
.parse_timestamp()
.map_err(|e| Error::Verification(format!("failed to parse SCT: {:?}", e)))?,
[] => {
return Err(Error::Verification(
"no SCTs found in certificate".to_string(),
))
}
_ => {
return Err(Error::Verification(
"certificate contains multiple SCTs, expected exactly one".to_string(),
))
}
};
let issuer_key_hash = *sigstore_crypto::sha256(issuer_spki_der).as_bytes();
Ok((sct, issuer_key_hash))
}
pub fn verify_sct(
cert_der: &[u8],
issuer_spki_der: &[u8],
trusted_root: &TrustedRoot,
) -> Result<()> {
let cert = Certificate::from_der(cert_der)
.map_err(|e| Error::Verification(format!("failed to parse certificate: {}", e)))?;
let (sct, issuer_key_hash) = extract_sct(&cert, issuer_spki_der)?;
let ct_keys = trusted_root
.ctfe_keys_with_ids()
.map_err(|e| Error::Verification(format!("failed to get CT log keys: {}", e)))?;
if ct_keys.is_empty() {
return Err(Error::Verification(
"no CT log keys in trusted root".to_string(),
));
}
let log_id = &sct.log_id.key_id;
let (_, public_key) = ct_keys.iter().find(|(id, _)| id == log_id).ok_or_else(|| {
Error::Verification(format!(
"SCT log ID {:?} not found in trusted root CT logs",
hex::encode(log_id)
))
})?;
let digitally_signed = DigitallySigned::from_embedded_sct(&cert, &sct, issuer_key_hash)?;
let sig_alg_bytes = sct.signature.algorithm.tls_serialize().map_err(|e| {
Error::Verification(format!("failed to serialize signature algorithm: {}", e))
})?;
let sig_alg = u16::from_be_bytes([sig_alg_bytes[0], sig_alg_bytes[1]]);
let signature = SignatureBytes::new(sct.signature.signature.clone().into_vec());
digitally_signed.verify(public_key, sig_alg, &signature)?;
Ok(())
}