use std::collections::HashMap;
use ed25519_dalek::{Signature, VerifyingKey};
use thiserror::Error;
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum VerifyError {
#[error("algorithm {0:?} not in verifier allowlist")]
UnsupportedAlgorithm(String),
#[error("unknown key_id: {0}")]
UnknownKeyId(String),
#[error("signature mismatch")]
SignatureMismatch,
#[error("merkle chain broken: {0}")]
Chain(String),
#[error("payload reconstruction failed: {0}")]
Payload(String),
#[error("invalid trusted key: {0}")]
InvalidKey(String),
}
pub trait KeyResolver: Send + Sync {
fn resolve(&self, key_id: &str) -> Option<Vec<u8>>;
}
pub trait Verifier: Send + Sync {
fn algorithm(&self) -> &str;
fn verify(
&self,
public_key: &[u8],
payload: &[u8],
signature: &[u8],
) -> Result<(), VerifyError>;
}
pub struct Ed25519Verifier;
impl Ed25519Verifier {
pub fn new() -> Self {
Self
}
}
impl Default for Ed25519Verifier {
fn default() -> Self {
Self::new()
}
}
impl Verifier for Ed25519Verifier {
fn algorithm(&self) -> &str {
"ed25519"
}
fn verify(
&self,
public_key: &[u8],
payload: &[u8],
signature: &[u8],
) -> Result<(), VerifyError> {
let key_bytes: [u8; 32] = public_key.try_into().map_err(|_| {
VerifyError::InvalidKey(format!(
"ed25519 public key must be 32 bytes, got {}",
public_key.len()
))
})?;
let verifying = VerifyingKey::from_bytes(&key_bytes)
.map_err(|e| VerifyError::InvalidKey(e.to_string()))?;
let sig_bytes: [u8; 64] = signature
.try_into()
.map_err(|_| VerifyError::SignatureMismatch)?;
let sig = Signature::from_bytes(&sig_bytes);
verifying
.verify_strict(payload, &sig)
.map_err(|_| VerifyError::SignatureMismatch)
}
}
#[cfg(feature = "ecdsa-p256")]
pub struct EcdsaP256Verifier;
#[cfg(feature = "ecdsa-p256")]
impl EcdsaP256Verifier {
pub fn new() -> Self {
Self
}
}
#[cfg(feature = "ecdsa-p256")]
impl Default for EcdsaP256Verifier {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "ecdsa-p256")]
impl Verifier for EcdsaP256Verifier {
fn algorithm(&self) -> &str {
"ecdsa-p256"
}
fn verify(
&self,
public_key: &[u8],
payload: &[u8],
signature: &[u8],
) -> Result<(), VerifyError> {
use p256::ecdsa::signature::Verifier as _;
use p256::ecdsa::{DerSignature, VerifyingKey};
use p256::pkcs8::DecodePublicKey;
let verifying = VerifyingKey::from_public_key_der(public_key)
.map_err(|e| VerifyError::InvalidKey(format!("p256 SPKI decode: {e}")))?;
let sig =
DerSignature::from_bytes(signature).map_err(|_| VerifyError::SignatureMismatch)?;
verifying
.verify(payload, &sig)
.map_err(|_| VerifyError::SignatureMismatch)
}
}
#[derive(Default)]
pub struct StaticKeyResolver {
keys: HashMap<String, Vec<u8>>,
}
impl StaticKeyResolver {
pub fn new() -> Self {
Self::default()
}
pub fn with(mut self, key_id: impl Into<String>, public_key: Vec<u8>) -> Self {
self.keys.insert(key_id.into(), public_key);
self
}
pub fn insert(&mut self, key_id: impl Into<String>, public_key: Vec<u8>) {
self.keys.insert(key_id.into(), public_key);
}
}
impl KeyResolver for StaticKeyResolver {
fn resolve(&self, key_id: &str) -> Option<Vec<u8>> {
self.keys.get(key_id).cloned()
}
}
#[cfg(test)]
mod tests {
use super::*;
use ed25519_dalek::{Signer as DalekSigner, SigningKey};
use rand::rngs::OsRng;
#[cfg(feature = "ecdsa-p256")]
mod ecdsa_p256 {
use super::super::{EcdsaP256Verifier, Verifier, VerifyError};
use p256::ecdsa::signature::Signer as _;
use p256::ecdsa::{DerSignature, SigningKey};
use p256::pkcs8::EncodePublicKey;
fn fresh_signer_and_pub_der() -> (SigningKey, Vec<u8>) {
let signing = SigningKey::random(&mut rand::rngs::OsRng);
let pub_der = signing
.verifying_key()
.to_public_key_der()
.unwrap()
.as_bytes()
.to_vec();
(signing, pub_der)
}
#[test]
fn ecdsa_verifier_advertises_algorithm_tag() {
assert_eq!(EcdsaP256Verifier::new().algorithm(), "ecdsa-p256");
}
#[test]
fn ecdsa_verifier_round_trips_a_real_signature() {
let (signing, pub_der) = fresh_signer_and_pub_der();
let payload = b"hello provenance";
let sig: DerSignature = signing.sign(payload);
EcdsaP256Verifier::new()
.verify(&pub_der, payload, sig.as_bytes())
.expect("valid ECDSA-P256 signature must verify");
}
#[test]
fn ecdsa_verifier_rejects_tampered_payload() {
let (signing, pub_der) = fresh_signer_and_pub_der();
let sig: DerSignature = signing.sign(b"original");
let err = EcdsaP256Verifier::new()
.verify(&pub_der, b"tampered", sig.as_bytes())
.unwrap_err();
assert!(matches!(err, VerifyError::SignatureMismatch));
}
#[test]
fn ecdsa_verifier_rejects_malformed_public_key() {
let err = EcdsaP256Verifier::new()
.verify(&[0u8; 8], b"payload", &[0u8; 70])
.unwrap_err();
assert!(matches!(err, VerifyError::InvalidKey(_)));
}
#[test]
fn ecdsa_verifier_rejects_malformed_signature() {
let (_, pub_der) = fresh_signer_and_pub_der();
let err = EcdsaP256Verifier::new()
.verify(&pub_der, b"payload", &[0u8; 4])
.unwrap_err();
assert!(
matches!(err, VerifyError::SignatureMismatch),
"expected SignatureMismatch, got {err:?}"
);
}
#[test]
fn ecdsa_verifier_accepts_both_low_s_and_high_s_signatures() {
use p256::ecdsa::{DerSignature, Signature};
let (signing, pub_der) = fresh_signer_and_pub_der();
let payload = b"malleability-acceptance";
let sig_low: DerSignature = signing.sign(payload);
let sig_low_decoded: Signature = sig_low.clone().try_into().expect("decode");
let (r, s) = sig_low_decoded.split_scalars();
let high_s = -*s.as_ref();
let high_sig = Signature::from_scalars(*r.as_ref(), high_s).expect("high-S construct");
let high_der: DerSignature = high_sig.into();
EcdsaP256Verifier::new()
.verify(&pub_der, payload, sig_low.as_bytes())
.expect("low-S form must verify");
EcdsaP256Verifier::new()
.verify(&pub_der, payload, high_der.as_bytes())
.expect("high-S form must verify (documented CWE-347 acceptance)");
}
}
#[test]
fn ed25519_verifier_round_trips_a_real_signature() {
let mut csprng = OsRng;
let signing = SigningKey::generate(&mut csprng);
let verifying = signing.verifying_key();
let payload = b"hello provenance";
let sig = signing.sign(payload);
let verifier = Ed25519Verifier::new();
verifier
.verify(verifying.as_bytes(), payload, &sig.to_bytes())
.expect("valid signature must verify");
}
#[test]
fn ed25519_verifier_rejects_tampered_payload() {
let mut csprng = OsRng;
let signing = SigningKey::generate(&mut csprng);
let verifying = signing.verifying_key();
let payload = b"original";
let sig = signing.sign(payload);
let verifier = Ed25519Verifier::new();
let err = verifier
.verify(verifying.as_bytes(), b"tampered", &sig.to_bytes())
.unwrap_err();
assert!(matches!(err, VerifyError::SignatureMismatch));
}
#[test]
fn ed25519_verifier_rejects_wrong_key_length() {
let verifier = Ed25519Verifier::new();
let err = verifier
.verify(&[0u8; 16], b"payload", &[0u8; 64])
.unwrap_err();
assert!(matches!(err, VerifyError::InvalidKey(_)));
}
#[test]
fn ed25519_verifier_rejects_wrong_signature_length() {
let mut csprng = OsRng;
let key = SigningKey::generate(&mut csprng).verifying_key();
let verifier = Ed25519Verifier::new();
let err = verifier
.verify(key.as_bytes(), b"payload", &[0u8; 32])
.unwrap_err();
assert!(matches!(err, VerifyError::SignatureMismatch));
}
#[test]
fn static_key_resolver_returns_inserted_key() {
let r = StaticKeyResolver::new().with("k1", vec![1, 2, 3]);
assert_eq!(r.resolve("k1"), Some(vec![1, 2, 3]));
assert!(r.resolve("unknown").is_none());
}
#[test]
fn ed25519_verifier_advertises_algorithm_tag() {
assert_eq!(Ed25519Verifier::new().algorithm(), "ed25519");
}
}