use std::{ops::Deref, time::SystemTime};
use super::{DsigError, SignatureAlgorithm, VerifyingKey};
use crate::policy::{KeyTrustPolicy, PolicyViolation, VerificationPolicy};
#[derive(Debug, Clone, PartialEq, Eq)]
#[expect(
clippy::large_enum_variant,
reason = "bounded inline path evidence avoids heap allocation"
)]
pub enum KeyTrustEvidence {
NotEvaluated,
NotEstablished,
CallerTrusted,
ValidatedX509(X509TrustEvidence),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct X509TrustEvidence {
fingerprints: [[u8; 32]; crate::hard_limits::X509_CHAIN_DEPTH_CEILING],
path_len: usize,
verification_time: SystemTime,
revocation_checked: bool,
}
impl X509TrustEvidence {
pub(crate) fn new(
certificates: impl ExactSizeIterator<Item = impl AsRef<[u8]>>,
verification_time: SystemTime,
revocation_checked: bool,
) -> Result<Self, DsigError> {
use sha2::{Digest, Sha256};
let path_len = certificates.len();
if path_len == 0 || path_len > crate::hard_limits::X509_CHAIN_DEPTH_CEILING {
return Err(PolicyViolation::KeyTrust {
reason: "invalid validated certificate path",
}
.into());
}
let mut fingerprints = [[0; 32]; crate::hard_limits::X509_CHAIN_DEPTH_CEILING];
for (fingerprint, certificate) in fingerprints.iter_mut().zip(certificates) {
*fingerprint = Sha256::digest(certificate.as_ref()).into();
}
Ok(Self {
fingerprints,
path_len,
verification_time,
revocation_checked,
})
}
pub fn certificate_fingerprints(&self) -> &[[u8; 32]] {
&self.fingerprints[..self.path_len]
}
pub fn anchor_fingerprint(&self) -> &[u8; 32] {
&self.fingerprints[self.path_len - 1]
}
pub fn verification_time(&self) -> SystemTime {
self.verification_time
}
pub fn revocation_checked(&self) -> bool {
self.revocation_checked
}
}
pub struct TrustedPublicKey {
key: Box<dyn VerifyingKey>,
trust: KeyTrustPolicy,
evidence: X509TrustEvidence,
}
impl TrustedPublicKey {
pub(crate) fn new(
key: Box<dyn VerifyingKey>,
mut trust: KeyTrustPolicy,
evidence: X509TrustEvidence,
) -> Self {
trust.verification_time = Some(evidence.verification_time);
Self {
key,
trust,
evidence,
}
}
pub fn evidence(&self) -> &X509TrustEvidence {
&self.evidence
}
}
impl VerifyingKey for TrustedPublicKey {
fn verification_spki(&self, algorithm: SignatureAlgorithm) -> Result<Option<&[u8]>, DsigError> {
self.key.verification_spki(algorithm)
}
fn ecdsa_encoding(&self) -> crate::policy::EcdsaSignatureValueEncoding {
self.key.ecdsa_encoding()
}
fn verify_candidate_keys(
&self,
verify: &mut dyn FnMut(&dyn VerifyingKey) -> Result<bool, DsigError>,
) -> Result<Option<bool>, DsigError> {
self.key.verify_candidate_keys(verify)
}
fn verify_with_context(
&self,
algorithm: SignatureAlgorithm,
context: &super::SignatureContext,
data: &[u8],
signature: &[u8],
) -> Result<bool, DsigError> {
self.key
.verify_with_context(algorithm, context, data, signature)
}
fn validate_policy(&self, policy: &VerificationPolicy) -> Result<(), DsigError> {
if self.trust != policy.key_trust {
return Err(PolicyViolation::KeyTrust {
reason: "validated key belongs to a different trust policy",
}
.into());
}
self.key.validate_policy(policy)
}
fn validate_signature_value(
&self,
algorithm: SignatureAlgorithm,
signature: &[u8],
) -> Result<bool, DsigError> {
self.key.validate_signature_value(algorithm, signature)
}
fn validate_signature_value_with_policy(
&self,
policy: &VerificationPolicy,
algorithm: SignatureAlgorithm,
signature: &[u8],
) -> Result<bool, DsigError> {
self.validate_policy(policy)?;
self.key
.validate_signature_value_with_policy(policy, algorithm, signature)
}
fn verify(
&self,
algorithm: SignatureAlgorithm,
data: &[u8],
signature: &[u8],
) -> Result<bool, DsigError> {
self.key.verify(algorithm, data, signature)
}
fn verify_with_policy(
&self,
policy: &VerificationPolicy,
algorithm: SignatureAlgorithm,
data: &[u8],
signature: &[u8],
) -> Result<bool, DsigError> {
self.validate_policy(policy)?;
self.key
.verify_with_policy(policy, algorithm, data, signature)
}
}
#[expect(
clippy::large_enum_variant,
reason = "one bounded resolver result per operation needs no extra box"
)]
pub enum ResolvedVerificationKey<'a> {
Candidate(Box<dyn VerifyingKey + 'a>),
CallerTrusted(Box<dyn VerifyingKey + 'a>),
Validated(TrustedPublicKey),
}
impl<'a> ResolvedVerificationKey<'a> {
pub fn into_key(self) -> Box<dyn VerifyingKey + 'a> {
match self {
Self::Candidate(key) | Self::CallerTrusted(key) => key,
Self::Validated(key) => Box::new(key),
}
}
pub(crate) fn authorize(
&self,
policy: &VerificationPolicy,
) -> Result<KeyTrustEvidence, DsigError> {
use crate::policy::VerificationTrustMode;
if policy.key_trust.mode == VerificationTrustMode::CryptographicOnly {
return Ok(KeyTrustEvidence::NotEstablished);
}
match self {
Self::Candidate(_) => Err(PolicyViolation::KeyTrust {
reason: "verification requires an authorized key",
}
.into()),
Self::CallerTrusted(_) => Ok(KeyTrustEvidence::CallerTrusted),
Self::Validated(key) => {
key.validate_policy(policy)?;
Ok(KeyTrustEvidence::ValidatedX509(key.evidence.clone()))
}
}
}
}
impl<'a> AsRef<dyn VerifyingKey + 'a> for ResolvedVerificationKey<'a> {
fn as_ref(&self) -> &(dyn VerifyingKey + 'a) {
match self {
Self::Candidate(key) | Self::CallerTrusted(key) => key.as_ref(),
Self::Validated(key) => key,
}
}
}
impl<'a> Deref for ResolvedVerificationKey<'a> {
type Target = dyn VerifyingKey + 'a;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct NeverExecuted;
impl VerifyingKey for NeverExecuted {
fn verify(&self, _: SignatureAlgorithm, _: &[u8], _: &[u8]) -> Result<bool, DsigError> {
panic!("stale authorization must fail before mathematical work");
}
}
#[test]
fn validated_key_is_bound_to_time_purpose_and_policy() {
let time = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_800_000_000);
let evidence =
X509TrustEvidence::new([b"bounded certificate identity"].into_iter(), time, false)
.expect("a one-certificate proof fits the hard path bound");
let mut policy = VerificationPolicy::default();
policy.key_trust.verify_x509_chains = true;
let key =
TrustedPublicKey::new(Box::new(NeverExecuted), policy.key_trust.clone(), evidence);
assert!(key.validate_policy(&policy).is_err());
policy.key_trust.verification_time = Some(time);
key.validate_policy(&policy)
.expect("the exact captured snapshot must match");
let mut changed = policy.clone();
changed.key_trust.verification_time = Some(time + std::time::Duration::from_secs(1));
assert!(
key.verify_with_policy(
&changed,
SignatureAlgorithm::RsaSha256,
b"data",
b"signature"
)
.is_err()
);
changed = policy.clone();
changed
.key_trust
.allowed_extended_key_usages
.insert(crate::policy::ExtendedKeyPurpose::CodeSigning);
assert!(key.validate_policy(&changed).is_err());
changed = policy;
changed.key_trust.check_crls = true;
assert!(key.validate_policy(&changed).is_err());
}
}