use base64::Engine as _;
use base64::engine::general_purpose::URL_SAFE_NO_PAD as B64;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::core::KeyId;
use super::card::AgentCard;
pub const ALG: &str = "EdDSA";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CardSignature {
pub protected: String,
pub signature: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub header: Option<Value>,
}
pub trait CardSigner: Send + Sync + std::fmt::Debug {
fn key_id(&self) -> KeyId;
fn sign_bytes(&self, message: &[u8]) -> Vec<u8>;
}
pub trait CardVerifier: Send + Sync + std::fmt::Debug {
fn verify_bytes(&self, key_id: &str, message: &[u8], signature: &[u8]) -> bool;
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum CardSignatureError {
#[error("the card carries no signatures, and one was required")]
Unsigned,
#[error("a card signature is not valid base64url")]
Malformed,
#[error("a card signature's protected header is not JSON: {0}")]
BadHeader(String),
#[error(
"a card signature declares algorithm '{0}', and this verifier only \
accepts {ALG} — an algorithm read from the document being checked is \
how a verifier is talked into accepting 'none'"
)]
WrongAlgorithm(String),
#[error("no signature on this card verifies against a key this verifier trusts")]
Untrusted,
#[error("the card could not be canonicalized: {0}")]
Canonical(String),
#[error(
"the card carries integer {value} at {path}, outside ±2^53 — JCS reads \
every number as an IEEE-754 double, so a conforming verifier would \
canonicalize this to different bytes than were signed and each side \
would be correct under its own reading. Carry a value that large as a \
string"
)]
UnrepresentableNumber { value: String, path: String },
}
const MAX_EXACT_DOUBLE: u64 = 1 << 53;
fn representable(value: &Value, path: &str) -> Result<(), CardSignatureError> {
match value {
Value::Number(n) => {
let out_of_range = n.as_u64().is_some_and(|u| u > MAX_EXACT_DOUBLE)
|| n.as_i64().is_some_and(|i| i < -(1_i64 << 53));
if out_of_range {
return Err(CardSignatureError::UnrepresentableNumber {
value: n.to_string(),
path: path.to_owned(),
});
}
Ok(())
}
Value::Object(map) => map
.iter()
.try_for_each(|(k, v)| representable(v, &format!("{path}/{k}"))),
Value::Array(items) => items
.iter()
.enumerate()
.try_for_each(|(i, v)| representable(v, &format!("{path}/{i}"))),
_ => Ok(()),
}
}
pub fn signing_input(card: &AgentCard, protected_b64: &str) -> Result<Vec<u8>, CardSignatureError> {
let mut value =
serde_json::to_value(card).map_err(|e| CardSignatureError::Canonical(e.to_string()))?;
if let Some(obj) = value.as_object_mut() {
obj.remove("signatures");
}
representable(&value, "")?;
let payload = crate::core::canon::value_bytes(&value);
let mut input = Vec::with_capacity(protected_b64.len() + 1 + payload.len() * 2);
input.extend_from_slice(protected_b64.as_bytes());
input.push(b'.');
input.extend_from_slice(B64.encode(&payload).as_bytes());
Ok(input)
}
impl AgentCard {
pub fn sign(&mut self, signer: &dyn CardSigner) -> Result<(), CardSignatureError> {
let protected = serde_json::json!({ "alg": ALG, "kid": signer.key_id().as_str() });
let protected_b64 = B64.encode(crate::core::canon::value_bytes(&protected));
let input = signing_input(self, &protected_b64)?;
let signature = B64.encode(signer.sign_bytes(&input));
self.signatures.push(CardSignature {
protected: protected_b64,
signature,
header: None,
});
Ok(())
}
pub fn verify(&self, verifier: &dyn CardVerifier) -> Result<KeyId, CardSignatureError> {
if self.signatures.is_empty() {
return Err(CardSignatureError::Unsigned);
}
let mut wrong_alg = None;
for sig in &self.signatures {
let header = B64
.decode(&sig.protected)
.map_err(|_| CardSignatureError::Malformed)?;
let header: Value = serde_json::from_slice(&header)
.map_err(|e| CardSignatureError::BadHeader(e.to_string()))?;
let alg = header
.get("alg")
.and_then(Value::as_str)
.unwrap_or_default();
if alg != ALG {
wrong_alg = Some(alg.to_owned());
continue;
}
let Some(kid) = header.get("kid").and_then(Value::as_str) else {
continue;
};
let input = signing_input(self, &sig.protected)?;
let Ok(raw) = B64.decode(&sig.signature) else {
return Err(CardSignatureError::Malformed);
};
if verifier.verify_bytes(kid, &input, &raw) {
return Ok(KeyId::from(kid.to_owned()));
}
}
wrong_alg.map_or(Err(CardSignatureError::Untrusted), |alg| {
Err(CardSignatureError::WrongAlgorithm(alg))
})
}
}