agentstategraph_policy/verifier.rs
1//! Signature verifier trait for `PolicyStore`.
2//!
3//! This trait lives in the main `agentstategraph-policy` crate (rather
4//! than in `agentstategraph-policy-sign`) so the policy crate does not
5//! need to pull in crypto dependencies to hold the hook. §2c will ship
6//! a concrete `Ed25519SignatureVerifier` implementation that composes
7//! the `-sign` crate's `Ed25519Verifier` + canonical-JSON helper.
8//!
9//! Implementations are expected to:
10//!
11//! 1. Compute the canonical-JSON bytes of the policy with the
12//! `signature` field excluded (via
13//! `agentstategraph_policy_sign::canonicalize` or equivalent).
14//! 2. Unpack the policy's `signature` field into
15//! `(signer_key_id, raw_bytes)` and call their verifier.
16
17use thiserror::Error;
18
19use crate::types::Policy;
20
21/// Verify a policy's embedded signature.
22///
23/// Implementations are expected to compute canonical bytes via the
24/// `agentstategraph-policy-sign` crate.
25pub trait SignatureVerifier: Send + Sync {
26 /// Verify the signature on `policy`. Returns `Ok(())` on a valid
27 /// signature; any rejection surfaces as an error.
28 fn verify_policy(&self, policy: &Policy) -> Result<(), SignatureVerificationError>;
29}
30
31/// Why a signature was rejected.
32#[derive(Debug, Error)]
33pub enum SignatureVerificationError {
34 /// The policy carries no signature at all. Only fatal when the
35 /// caller treats unsigned policies as invalid (e.g. server config
36 /// `require_signed_policies=true`); the policy crate itself maps
37 /// this to "treat as not-currently-active" in that mode.
38 #[error("policy has no signature")]
39 Missing,
40
41 /// The signature algorithm tag is not supported by this verifier.
42 #[error("unsupported algorithm")]
43 UnsupportedAlgorithm,
44
45 /// The signature payload (length, hex encoding, key id) is
46 /// malformed.
47 #[error("invalid signature length or encoding")]
48 Encoding,
49
50 /// Cryptographic verification rejected the signature.
51 #[error("signature rejected: {0}")]
52 Invalid(String),
53
54 /// Any other failure — key-not-found, I/O, canonicalization, etc.
55 #[error(transparent)]
56 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
57}