use crate::crypto::cert::X509Certificate;
use crate::dsig::algorithms::SignatureAlgorithm;
use crate::error::Error;
#[derive(Debug, Clone, Default)]
pub struct KeyInfo {
pub key_name: Option<String>,
pub x509_certificates_base64: Vec<String>,
pub x509_subject_names: Vec<String>,
pub x509_issuer_serials: Vec<(String, String)>,
}
impl KeyInfo {
pub fn trusted_inline_certs(
&self,
trusted_candidates: &[X509Certificate],
) -> Vec<X509Certificate> {
let trusted_fingerprints: Vec<[u8; 32]> = trusted_candidates
.iter()
.map(X509Certificate::fingerprint_sha256)
.collect();
let mut out = Vec::new();
for blob in &self.x509_certificates_base64 {
let Ok(cert) = X509Certificate::from_base64_x509(blob) else {
continue;
};
if trusted_fingerprints.contains(&cert.fingerprint_sha256()) {
out.push(cert);
}
}
out
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VerifyMatch {
pub cert_fingerprint: [u8; 32],
pub algorithm: SignatureAlgorithm,
}
pub trait SignatureVerifier: Send + Sync {
fn verify(
&self,
algorithm: SignatureAlgorithm,
signed_bytes: &[u8],
signature_bytes: &[u8],
candidate_certs: &[X509Certificate],
allowed_algorithms: &[SignatureAlgorithm],
key_info: &KeyInfo,
) -> Result<VerifyMatch, Error>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DefaultVerifier;
impl SignatureVerifier for DefaultVerifier {
fn verify(
&self,
algorithm: SignatureAlgorithm,
signed_bytes: &[u8],
signature_bytes: &[u8],
candidate_certs: &[X509Certificate],
allowed_algorithms: &[SignatureAlgorithm],
key_info: &KeyInfo,
) -> Result<VerifyMatch, Error> {
if !allowed_algorithms.contains(&algorithm) {
return Err(Error::DisallowedAlgorithm {
alg: algorithm_label(algorithm),
});
}
for cert in candidate_certs {
if try_verify(cert, algorithm, signed_bytes, signature_bytes) {
return Ok(VerifyMatch {
cert_fingerprint: cert.fingerprint_sha256(),
algorithm,
});
}
}
for cert in key_info.trusted_inline_certs(candidate_certs) {
if try_verify(&cert, algorithm, signed_bytes, signature_bytes) {
return Ok(VerifyMatch {
cert_fingerprint: cert.fingerprint_sha256(),
algorithm,
});
}
}
Err(Error::SignatureVerification {
reason: "no candidate cert matched",
})
}
}
fn try_verify(
cert: &X509Certificate,
algorithm: SignatureAlgorithm,
signed_bytes: &[u8],
signature_bytes: &[u8],
) -> bool {
cert.public_key()
.verify_signature(algorithm, signed_bytes, signature_bytes)
.is_ok()
}
fn algorithm_label(alg: SignatureAlgorithm) -> String {
alg.uri().to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::cert::test_vectors::*;
use crate::crypto::keypair::KeyPair;
fn sign_test_payload() -> (X509Certificate, Vec<u8>, Vec<u8>) {
let kp = KeyPair::from_pkcs8_pem(RSA_KEY_PKCS8_PEM).unwrap();
let cert = X509Certificate::from_pem(RSA_CERT_PEM).unwrap();
let payload = b"<ds:SignedInfo>canonical-form bytes</ds:SignedInfo>".to_vec();
let sig = kp.sign(SignatureAlgorithm::RsaSha256, &payload).unwrap();
(cert, payload, sig)
}
#[test]
fn verifies_with_trusted_cert() {
let (cert, payload, sig) = sign_test_payload();
let verifier = DefaultVerifier;
let key_info = KeyInfo::default();
let allowed = vec![SignatureAlgorithm::RsaSha256];
let m = verifier
.verify(
SignatureAlgorithm::RsaSha256,
&payload,
&sig,
std::slice::from_ref(&cert),
&allowed,
&key_info,
)
.expect("should verify");
assert_eq!(m.cert_fingerprint, cert.fingerprint_sha256());
assert_eq!(m.algorithm, SignatureAlgorithm::RsaSha256);
}
#[test]
fn rejects_when_algorithm_not_in_allow_list() {
let (cert, payload, sig) = sign_test_payload();
let verifier = DefaultVerifier;
let allowed = vec![SignatureAlgorithm::RsaSha512]; let err = verifier
.verify(
SignatureAlgorithm::RsaSha256,
&payload,
&sig,
&[cert],
&allowed,
&KeyInfo::default(),
)
.expect_err("should reject");
assert!(matches!(err, Error::DisallowedAlgorithm { .. }));
}
#[test]
fn rejects_when_no_candidate_cert_matches() {
let (_cert, payload, sig) = sign_test_payload();
let other = X509Certificate::from_pem(EC_P256_CERT_PEM).unwrap();
let verifier = DefaultVerifier;
let err = verifier
.verify(
SignatureAlgorithm::RsaSha256,
&payload,
&sig,
&[other],
&[SignatureAlgorithm::RsaSha256],
&KeyInfo::default(),
)
.expect_err("should reject");
assert!(matches!(
err,
Error::SignatureVerification {
reason: "no candidate cert matched"
}
));
}
#[test]
fn inline_cert_honored_when_fingerprint_trusted() {
let (cert, payload, sig) = sign_test_payload();
let key_info = KeyInfo {
x509_certificates_base64: vec![cert.to_base64_x509()],
..KeyInfo::default()
};
let verifier = DefaultVerifier;
let m = verifier
.verify(
SignatureAlgorithm::RsaSha256,
&payload,
&sig,
std::slice::from_ref(&cert), &[SignatureAlgorithm::RsaSha256],
&key_info,
)
.expect("should verify via inline cert");
assert_eq!(m.cert_fingerprint, cert.fingerprint_sha256());
}
#[test]
fn inline_cert_rejected_when_fingerprint_unknown() {
let (cert, payload, sig) = sign_test_payload();
let key_info = KeyInfo {
x509_certificates_base64: vec![cert.to_base64_x509()],
..KeyInfo::default()
};
let unrelated = X509Certificate::from_pem(EC_P256_CERT_PEM).unwrap();
let verifier = DefaultVerifier;
let err = verifier
.verify(
SignatureAlgorithm::RsaSha256,
&payload,
&sig,
&[unrelated], &[SignatureAlgorithm::RsaSha256],
&key_info,
)
.expect_err("inline cert must not be trusted by itself");
assert!(matches!(
err,
Error::SignatureVerification {
reason: "no candidate cert matched"
}
));
}
#[test]
fn key_info_trusted_inline_certs_filters_by_fingerprint() {
let cert = X509Certificate::from_pem(RSA_CERT_PEM).unwrap();
let other = X509Certificate::from_pem(EC_P256_CERT_PEM).unwrap();
let key_info = KeyInfo {
x509_certificates_base64: vec![cert.to_base64_x509(), other.to_base64_x509()],
..KeyInfo::default()
};
let filtered = key_info.trusted_inline_certs(std::slice::from_ref(&cert));
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0], cert);
}
#[test]
fn key_info_skips_malformed_inline_blobs() {
let cert = X509Certificate::from_pem(RSA_CERT_PEM).unwrap();
let key_info = KeyInfo {
x509_certificates_base64: vec!["this is not base64".to_string(), cert.to_base64_x509()],
..KeyInfo::default()
};
let filtered = key_info.trusted_inline_certs(std::slice::from_ref(&cert));
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0], cert);
}
#[test]
fn public_key_algorithm_is_in_scope() {
use crate::crypto::cert::PublicKeyAlgorithm;
let cert = X509Certificate::from_pem(RSA_CERT_PEM).unwrap();
assert_eq!(
cert.public_key().algorithm_family(),
PublicKeyAlgorithm::Rsa
);
}
}