1use std::{ops::Deref, time::SystemTime};
4
5use super::{DsigError, SignatureAlgorithm, VerifyingKey};
6use crate::policy::{KeyTrustPolicy, PolicyViolation, VerificationPolicy};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10#[expect(
12 clippy::large_enum_variant,
13 reason = "bounded inline path evidence avoids heap allocation"
14)]
15pub enum KeyTrustEvidence {
16 NotEvaluated,
18 NotEstablished,
20 CallerTrusted,
22 ValidatedX509(X509TrustEvidence),
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct X509TrustEvidence {
30 fingerprints: [[u8; 32]; crate::hard_limits::X509_CHAIN_DEPTH_CEILING],
31 path_len: usize,
32 verification_time: SystemTime,
33 revocation_checked: bool,
34}
35
36impl X509TrustEvidence {
37 pub(crate) fn new(
38 certificates: impl ExactSizeIterator<Item = impl AsRef<[u8]>>,
39 verification_time: SystemTime,
40 revocation_checked: bool,
41 ) -> Result<Self, DsigError> {
42 let path_len = certificates.len();
43 if path_len == 0 || path_len > crate::hard_limits::X509_CHAIN_DEPTH_CEILING {
44 return Err(PolicyViolation::KeyTrust {
45 reason: "invalid validated certificate path",
46 }
47 .into());
48 }
49 let mut fingerprints = [[0; 32]; crate::hard_limits::X509_CHAIN_DEPTH_CEILING];
52 for (fingerprint, certificate) in fingerprints.iter_mut().zip(certificates) {
53 *fingerprint = Self::identity(certificate.as_ref());
54 }
55 Ok(Self {
56 fingerprints,
57 path_len,
58 verification_time,
59 revocation_checked,
60 })
61 }
62
63 fn identity(certificate: &[u8]) -> [u8; 32] {
64 use sha2::{Digest, Sha256};
65 Sha256::digest(certificate).into()
66 }
67
68 pub fn certificate_identity_in<W: structured_asn1::memory::Work>(
73 certificate: &[u8],
74 work: &W,
75 ) -> Result<[u8; 32], structured_asn1::memory::MemoryError> {
76 work.charge_work(
77 u64::try_from(certificate.len())
78 .map_err(|_| structured_asn1::memory::MemoryError::WorkExceeded)?,
79 )?;
80 Ok(Self::identity(certificate))
81 }
82
83 pub fn certificate_fingerprints(&self) -> &[[u8; 32]] {
85 &self.fingerprints[..self.path_len]
86 }
87
88 pub fn anchor_fingerprint(&self) -> &[u8; 32] {
90 &self.fingerprints[self.path_len - 1]
91 }
92
93 pub fn verification_time(&self) -> SystemTime {
95 self.verification_time
96 }
97
98 pub fn validation_instant(
101 &self,
102 ) -> Result<structured_pkix::temporal::Instant, super::X509ChainError> {
103 super::pkix::verification_instant(self.verification_time)
104 }
105
106 pub fn revocation_checked(&self) -> bool {
108 self.revocation_checked
109 }
110}
111
112pub struct TrustedPublicKey {
115 key: Box<dyn VerifyingKey>,
116 trust: KeyTrustPolicy,
117 evidence: X509TrustEvidence,
118}
119
120impl TrustedPublicKey {
121 pub(crate) fn new(
122 key: Box<dyn VerifyingKey>,
123 mut trust: KeyTrustPolicy,
124 evidence: X509TrustEvidence,
125 ) -> Self {
126 trust.verification_time = Some(evidence.verification_time);
130 Self {
131 key,
132 trust,
133 evidence,
134 }
135 }
136
137 pub fn evidence(&self) -> &X509TrustEvidence {
139 &self.evidence
140 }
141}
142
143impl VerifyingKey for TrustedPublicKey {
144 fn association_spki(&self, algorithm: SignatureAlgorithm) -> Option<&[u8]> {
145 self.key.association_spki(algorithm)
146 }
147 fn verification_spki(&self, algorithm: SignatureAlgorithm) -> Result<Option<&[u8]>, DsigError> {
148 self.key.verification_spki(algorithm)
149 }
150
151 fn ecdsa_encoding(&self) -> crate::policy::EcdsaSignatureValueEncoding {
152 self.key.ecdsa_encoding()
153 }
154
155 fn verify_candidate_keys(
156 &self,
157 verify: &mut dyn FnMut(&dyn VerifyingKey) -> Result<bool, DsigError>,
158 ) -> Result<Option<bool>, DsigError> {
159 self.key.verify_candidate_keys(verify)
160 }
161 fn verify_with_context(
162 &self,
163 algorithm: SignatureAlgorithm,
164 context: &super::SignatureContext,
165 data: &[u8],
166 signature: &[u8],
167 ) -> Result<bool, DsigError> {
168 self.key
169 .verify_with_context(algorithm, context, data, signature)
170 }
171 fn validate_policy(&self, policy: &VerificationPolicy) -> Result<(), DsigError> {
172 if self.trust != policy.key_trust {
173 return Err(PolicyViolation::KeyTrust {
174 reason: "validated key belongs to a different trust policy",
175 }
176 .into());
177 }
178 self.key.validate_policy(policy)
179 }
180
181 fn validate_signature_value(
182 &self,
183 algorithm: SignatureAlgorithm,
184 signature: &[u8],
185 ) -> Result<bool, DsigError> {
186 self.key.validate_signature_value(algorithm, signature)
187 }
188
189 fn validate_signature_value_with_policy(
190 &self,
191 policy: &VerificationPolicy,
192 algorithm: SignatureAlgorithm,
193 signature: &[u8],
194 ) -> Result<bool, DsigError> {
195 self.validate_policy(policy)?;
196 self.key
197 .validate_signature_value_with_policy(policy, algorithm, signature)
198 }
199
200 fn verify(
201 &self,
202 algorithm: SignatureAlgorithm,
203 data: &[u8],
204 signature: &[u8],
205 ) -> Result<bool, DsigError> {
206 self.key.verify(algorithm, data, signature)
207 }
208
209 fn verify_with_policy(
210 &self,
211 policy: &VerificationPolicy,
212 algorithm: SignatureAlgorithm,
213 data: &[u8],
214 signature: &[u8],
215 ) -> Result<bool, DsigError> {
216 self.validate_policy(policy)?;
217 self.key
218 .verify_with_policy(policy, algorithm, data, signature)
219 }
220}
221
222#[expect(
224 clippy::large_enum_variant,
225 reason = "one bounded resolver result per operation needs no extra box"
226)]
227pub enum ResolvedVerificationKey<'a> {
228 Candidate(Box<dyn VerifyingKey + 'a>),
230 CallerTrusted(Box<dyn VerifyingKey + 'a>),
232 Validated(TrustedPublicKey),
234}
235
236impl<'a> ResolvedVerificationKey<'a> {
237 pub fn into_key(self) -> Box<dyn VerifyingKey + 'a> {
239 match self {
240 Self::Candidate(key) | Self::CallerTrusted(key) => key,
241 Self::Validated(key) => Box::new(key),
242 }
243 }
244
245 pub(crate) fn authorize(
246 &self,
247 policy: &VerificationPolicy,
248 ) -> Result<KeyTrustEvidence, DsigError> {
249 use crate::policy::VerificationTrustMode;
250 if policy.key_trust.mode == VerificationTrustMode::CryptographicOnly {
254 return Ok(KeyTrustEvidence::NotEstablished);
255 }
256 match self {
257 Self::Candidate(_) => Err(PolicyViolation::KeyTrust {
258 reason: "verification requires an authorized key",
259 }
260 .into()),
261 Self::CallerTrusted(_) => Ok(KeyTrustEvidence::CallerTrusted),
262 Self::Validated(key) => {
263 key.validate_policy(policy)?;
264 Ok(KeyTrustEvidence::ValidatedX509(key.evidence.clone()))
265 }
266 }
267 }
268}
269
270impl<'a> AsRef<dyn VerifyingKey + 'a> for ResolvedVerificationKey<'a> {
271 fn as_ref(&self) -> &(dyn VerifyingKey + 'a) {
272 match self {
273 Self::Candidate(key) | Self::CallerTrusted(key) => key.as_ref(),
274 Self::Validated(key) => key,
275 }
276 }
277}
278
279impl<'a> Deref for ResolvedVerificationKey<'a> {
280 type Target = dyn VerifyingKey + 'a;
281 fn deref(&self) -> &Self::Target {
282 self.as_ref()
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 struct NeverExecuted;
291 impl VerifyingKey for NeverExecuted {
292 fn verify(&self, _: SignatureAlgorithm, _: &[u8], _: &[u8]) -> Result<bool, DsigError> {
293 panic!("stale authorization must fail before mathematical work");
294 }
295 }
296
297 #[test]
298 fn validated_key_is_bound_to_time_purpose_and_policy() {
299 let time = SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(1_800_000_000);
302 let evidence =
303 X509TrustEvidence::new([b"bounded certificate identity"].into_iter(), time, false)
304 .expect("a one-certificate proof fits the hard path bound");
305 let mut policy = VerificationPolicy::default();
306 policy.key_trust.verify_x509_chains = true;
307 let key =
308 TrustedPublicKey::new(Box::new(NeverExecuted), policy.key_trust.clone(), evidence);
309 assert!(key.validate_policy(&policy).is_err());
310 policy.key_trust.verification_time = Some(time);
311 key.validate_policy(&policy)
312 .expect("the exact captured snapshot must match");
313 let mut changed = policy.clone();
314 changed.key_trust.verification_time = Some(time + std::time::Duration::from_secs(1));
315 assert!(
316 key.verify_with_policy(
317 &changed,
318 SignatureAlgorithm::RsaSha256,
319 b"data",
320 b"signature"
321 )
322 .is_err()
323 );
324 changed = policy.clone();
325 changed
326 .key_trust
327 .allowed_extended_key_usages
328 .insert(crate::policy::ExtendedKeyPurpose::CodeSigning);
329 assert!(key.validate_policy(&changed).is_err());
330 changed = policy;
331 changed.key_trust.check_crls = true;
332 assert!(key.validate_policy(&changed).is_err());
333 }
334}