switchyard/adapters/
attest.rs

1use crate::types::errors::{Error, ErrorKind};
2use thiserror::Error;
3
4#[derive(Debug, Error)]
5pub enum AttestationError {
6    #[error("attestation signing failed: {msg}")]
7    Signing { msg: String },
8    #[error("attestation verification failed: {msg}")]
9    Verification { msg: String },
10}
11
12impl From<AttestationError> for Error {
13    fn from(e: AttestationError) -> Self {
14        Error {
15            kind: ErrorKind::Io, // Using Io for now, could be more specific
16            msg: e.to_string(),
17        }
18    }
19}
20
21#[derive(Clone, Debug)]
22pub struct Signature(pub Vec<u8>);
23
24pub trait Attestor: Send + Sync {
25    /// Sign the given bundle.
26    /// # Errors
27    /// Returns an error if signing fails.
28    fn sign(&self, bundle: &[u8]) -> Result<Signature, AttestationError>;
29    /// Return a stable identifier for the public key used to sign (e.g., fingerprint or KID).
30    fn key_id(&self) -> String;
31    /// Return the signature algorithm label for attestations. Defaults to "ed25519".
32    fn algorithm(&self) -> &'static str {
33        "ed25519"
34    }
35}
36
37/// Build a JSON object with attestation fields for emission given an attestor and a bundle.
38/// Returns None if signing fails.
39pub fn build_attestation_fields<A: Attestor + ?Sized>(
40    att: &A,
41    bundle: &[u8],
42) -> Option<serde_json::Value> {
43    use base64::Engine as _;
44    use sha2::Digest as _;
45    let sig = att.sign(bundle).ok()?;
46    let sig_b64 = base64::engine::general_purpose::STANDARD.encode(sig.0.clone());
47    let mut hasher = sha2::Sha256::new();
48    hasher.update(bundle);
49    let bundle_hash = hex::encode(hasher.finalize());
50    Some(serde_json::json!({
51        "sig_alg": att.algorithm(),
52        "signature": sig_b64,
53        "bundle_hash": bundle_hash,
54        "public_key_id": att.key_id(),
55    }))
56}