1use std::collections::HashMap;
31
32use ed25519_dalek::{Signature, VerifyingKey};
33use thiserror::Error;
34
35#[non_exhaustive]
37#[derive(Debug, Error)]
38pub enum VerifyError {
39 #[error("algorithm {0:?} not in verifier allowlist")]
43 UnsupportedAlgorithm(String),
44 #[error("unknown key_id: {0}")]
46 UnknownKeyId(String),
47 #[error("signature mismatch")]
49 SignatureMismatch,
50 #[error("merkle chain broken: {0}")]
53 Chain(String),
54 #[error("payload reconstruction failed: {0}")]
56 Payload(String),
57 #[error("invalid trusted key: {0}")]
60 InvalidKey(String),
61}
62
63pub trait KeyResolver: Send + Sync {
67 fn resolve(&self, key_id: &str) -> Option<Vec<u8>>;
70}
71
72pub trait Verifier: Send + Sync {
78 fn algorithm(&self) -> &str;
83
84 fn verify(
89 &self,
90 public_key: &[u8],
91 payload: &[u8],
92 signature: &[u8],
93 ) -> Result<(), VerifyError>;
94}
95
96pub struct Ed25519Verifier;
99
100impl Ed25519Verifier {
101 pub fn new() -> Self {
103 Self
104 }
105}
106
107impl Default for Ed25519Verifier {
108 fn default() -> Self {
109 Self::new()
110 }
111}
112
113impl Verifier for Ed25519Verifier {
114 fn algorithm(&self) -> &str {
115 "ed25519"
116 }
117
118 fn verify(
119 &self,
120 public_key: &[u8],
121 payload: &[u8],
122 signature: &[u8],
123 ) -> Result<(), VerifyError> {
124 let key_bytes: [u8; 32] = public_key.try_into().map_err(|_| {
125 VerifyError::InvalidKey(format!(
126 "ed25519 public key must be 32 bytes, got {}",
127 public_key.len()
128 ))
129 })?;
130 let verifying = VerifyingKey::from_bytes(&key_bytes)
131 .map_err(|e| VerifyError::InvalidKey(e.to_string()))?;
132 let sig_bytes: [u8; 64] = signature
133 .try_into()
134 .map_err(|_| VerifyError::SignatureMismatch)?;
135 let sig = Signature::from_bytes(&sig_bytes);
136 verifying
137 .verify_strict(payload, &sig)
138 .map_err(|_| VerifyError::SignatureMismatch)
139 }
140}
141
142#[cfg(feature = "ecdsa-p256")]
167pub struct EcdsaP256Verifier;
168
169#[cfg(feature = "ecdsa-p256")]
170impl EcdsaP256Verifier {
171 pub fn new() -> Self {
173 Self
174 }
175}
176
177#[cfg(feature = "ecdsa-p256")]
178impl Default for EcdsaP256Verifier {
179 fn default() -> Self {
180 Self::new()
181 }
182}
183
184#[cfg(feature = "ecdsa-p256")]
185impl Verifier for EcdsaP256Verifier {
186 fn algorithm(&self) -> &str {
187 "ecdsa-p256"
188 }
189
190 fn verify(
191 &self,
192 public_key: &[u8],
193 payload: &[u8],
194 signature: &[u8],
195 ) -> Result<(), VerifyError> {
196 use p256::ecdsa::signature::Verifier as _;
197 use p256::ecdsa::{DerSignature, VerifyingKey};
198 use p256::pkcs8::DecodePublicKey;
199
200 let verifying = VerifyingKey::from_public_key_der(public_key)
201 .map_err(|e| VerifyError::InvalidKey(format!("p256 SPKI decode: {e}")))?;
202 let sig =
207 DerSignature::from_bytes(signature).map_err(|_| VerifyError::SignatureMismatch)?;
208 verifying
209 .verify(payload, &sig)
210 .map_err(|_| VerifyError::SignatureMismatch)
211 }
212}
213
214#[derive(Default)]
222pub struct StaticKeyResolver {
223 keys: HashMap<String, Vec<u8>>,
224}
225
226impl StaticKeyResolver {
227 pub fn new() -> Self {
229 Self::default()
230 }
231
232 pub fn with(mut self, key_id: impl Into<String>, public_key: Vec<u8>) -> Self {
235 self.keys.insert(key_id.into(), public_key);
236 self
237 }
238
239 pub fn insert(&mut self, key_id: impl Into<String>, public_key: Vec<u8>) {
241 self.keys.insert(key_id.into(), public_key);
242 }
243}
244
245impl KeyResolver for StaticKeyResolver {
246 fn resolve(&self, key_id: &str) -> Option<Vec<u8>> {
247 self.keys.get(key_id).cloned()
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use ed25519_dalek::{Signer as DalekSigner, SigningKey};
255 use rand::rngs::OsRng;
256
257 #[cfg(feature = "ecdsa-p256")]
258 mod ecdsa_p256 {
259 use super::super::{EcdsaP256Verifier, Verifier, VerifyError};
260 use p256::ecdsa::signature::Signer as _;
261 use p256::ecdsa::{DerSignature, SigningKey};
262 use p256::pkcs8::EncodePublicKey;
263
264 fn fresh_signer_and_pub_der() -> (SigningKey, Vec<u8>) {
265 let signing = SigningKey::random(&mut rand::rngs::OsRng);
266 let pub_der = signing
267 .verifying_key()
268 .to_public_key_der()
269 .unwrap()
270 .as_bytes()
271 .to_vec();
272 (signing, pub_der)
273 }
274
275 #[test]
276 fn ecdsa_verifier_advertises_algorithm_tag() {
277 assert_eq!(EcdsaP256Verifier::new().algorithm(), "ecdsa-p256");
278 }
279
280 #[test]
281 fn ecdsa_verifier_round_trips_a_real_signature() {
282 let (signing, pub_der) = fresh_signer_and_pub_der();
283 let payload = b"hello provenance";
284 let sig: DerSignature = signing.sign(payload);
285 EcdsaP256Verifier::new()
286 .verify(&pub_der, payload, sig.as_bytes())
287 .expect("valid ECDSA-P256 signature must verify");
288 }
289
290 #[test]
291 fn ecdsa_verifier_rejects_tampered_payload() {
292 let (signing, pub_der) = fresh_signer_and_pub_der();
293 let sig: DerSignature = signing.sign(b"original");
294 let err = EcdsaP256Verifier::new()
295 .verify(&pub_der, b"tampered", sig.as_bytes())
296 .unwrap_err();
297 assert!(matches!(err, VerifyError::SignatureMismatch));
298 }
299
300 #[test]
301 fn ecdsa_verifier_rejects_malformed_public_key() {
302 let err = EcdsaP256Verifier::new()
303 .verify(&[0u8; 8], b"payload", &[0u8; 70])
304 .unwrap_err();
305 assert!(matches!(err, VerifyError::InvalidKey(_)));
306 }
307
308 #[test]
309 fn ecdsa_verifier_rejects_malformed_signature() {
310 let (_, pub_der) = fresh_signer_and_pub_der();
311 let err = EcdsaP256Verifier::new()
315 .verify(&pub_der, b"payload", &[0u8; 4])
316 .unwrap_err();
317 assert!(
318 matches!(err, VerifyError::SignatureMismatch),
319 "expected SignatureMismatch, got {err:?}"
320 );
321 }
322
323 #[test]
331 fn ecdsa_verifier_accepts_both_low_s_and_high_s_signatures() {
332 use p256::ecdsa::{DerSignature, Signature};
333 let (signing, pub_der) = fresh_signer_and_pub_der();
334 let payload = b"malleability-acceptance";
335
336 let sig_low: DerSignature = signing.sign(payload);
337 let sig_low_decoded: Signature = sig_low.clone().try_into().expect("decode");
338 let (r, s) = sig_low_decoded.split_scalars();
339 let high_s = -*s.as_ref();
340 let high_sig = Signature::from_scalars(*r.as_ref(), high_s).expect("high-S construct");
341 let high_der: DerSignature = high_sig.into();
342
343 EcdsaP256Verifier::new()
344 .verify(&pub_der, payload, sig_low.as_bytes())
345 .expect("low-S form must verify");
346 EcdsaP256Verifier::new()
347 .verify(&pub_der, payload, high_der.as_bytes())
348 .expect("high-S form must verify (documented CWE-347 acceptance)");
349 }
350 }
351
352 #[test]
353 fn ed25519_verifier_round_trips_a_real_signature() {
354 let mut csprng = OsRng;
355 let signing = SigningKey::generate(&mut csprng);
356 let verifying = signing.verifying_key();
357 let payload = b"hello provenance";
358 let sig = signing.sign(payload);
359
360 let verifier = Ed25519Verifier::new();
361 verifier
362 .verify(verifying.as_bytes(), payload, &sig.to_bytes())
363 .expect("valid signature must verify");
364 }
365
366 #[test]
367 fn ed25519_verifier_rejects_tampered_payload() {
368 let mut csprng = OsRng;
369 let signing = SigningKey::generate(&mut csprng);
370 let verifying = signing.verifying_key();
371 let payload = b"original";
372 let sig = signing.sign(payload);
373
374 let verifier = Ed25519Verifier::new();
375 let err = verifier
376 .verify(verifying.as_bytes(), b"tampered", &sig.to_bytes())
377 .unwrap_err();
378 assert!(matches!(err, VerifyError::SignatureMismatch));
379 }
380
381 #[test]
382 fn ed25519_verifier_rejects_wrong_key_length() {
383 let verifier = Ed25519Verifier::new();
384 let err = verifier
385 .verify(&[0u8; 16], b"payload", &[0u8; 64])
386 .unwrap_err();
387 assert!(matches!(err, VerifyError::InvalidKey(_)));
388 }
389
390 #[test]
391 fn ed25519_verifier_rejects_wrong_signature_length() {
392 let mut csprng = OsRng;
393 let key = SigningKey::generate(&mut csprng).verifying_key();
394 let verifier = Ed25519Verifier::new();
395 let err = verifier
396 .verify(key.as_bytes(), b"payload", &[0u8; 32])
397 .unwrap_err();
398 assert!(matches!(err, VerifyError::SignatureMismatch));
399 }
400
401 #[test]
402 fn static_key_resolver_returns_inserted_key() {
403 let r = StaticKeyResolver::new().with("k1", vec![1, 2, 3]);
404 assert_eq!(r.resolve("k1"), Some(vec![1, 2, 3]));
405 assert!(r.resolve("unknown").is_none());
406 }
407
408 #[test]
409 fn ed25519_verifier_advertises_algorithm_tag() {
410 assert_eq!(Ed25519Verifier::new().algorithm(), "ed25519");
411 }
412}