use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum UcanParseError {
#[error("malformed JWT: expected 3 segments, got {0}")]
MalformedJwt(usize),
#[error("base64url decode failed in {segment}: {reason}")]
Base64Decode {
segment: &'static str,
reason: String,
},
#[error("JSON parse failed in {segment}: {reason}")]
JsonParse {
segment: &'static str,
reason: String,
},
#[error("unsupported alg: expected EdDSA, got {0:?}")]
UnsupportedAlg(String),
#[error("unsupported typ: expected ucan/1.0+jwt, got {0:?}")]
UnsupportedTyp(String),
#[error("unsupported ucv: expected 1.0, got {0:?}")]
UnsupportedUcv(String),
#[error("non-atd-cap UCAN: expected cmd=\"atd-cap\", got {0:?}")]
NonAtdCap(String),
#[error("unsupported DID method in {field}: {did:?} (only did:key:z... accepted)")]
UnsupportedDidMethod { field: &'static str, did: String },
}
impl UcanParseError {
pub(crate) fn base64(segment: &'static str, e: impl std::fmt::Display) -> Self {
Self::Base64Decode {
segment,
reason: e.to_string(),
}
}
pub(crate) fn json(segment: &'static str, e: impl std::fmt::Display) -> Self {
Self::JsonParse {
segment,
reason: e.to_string(),
}
}
}
#[derive(Debug, Error)]
pub enum UcanVerifyError {
#[error("UCAN parse error: {0}")]
Parse(#[from] UcanParseError),
#[error("Ed25519 signature verification failed for token {cid}")]
BadSignature { cid: String },
#[error("malformed signature on token {cid}: {reason}")]
MalformedSignature { cid: String, reason: String },
#[error("malformed did:key in {field}: {reason}")]
MalformedDidKey { field: &'static str, reason: String },
#[error("UCAN expired at link {cid}: exp={exp}, now={now}")]
Expired { cid: String, exp: i64, now: i64 },
#[error("attenuation widening at link {cid}: parent={parent:?}, child={child:?}")]
WideningAttenuation {
cid: String,
parent: Vec<String>,
child: Vec<String>,
},
#[error(
"chain broken between {parent_cid} (aud={parent_aud}) and {child_cid} (iss={child_iss})"
)]
ChainBroken {
parent_cid: String,
parent_aud: String,
child_cid: String,
child_iss: String,
},
#[error("audience mismatch: leaf.aud={leaf_aud}, expected={expected}")]
AudienceMismatch { leaf_aud: String, expected: String },
#[error("chain too deep: depth={depth}, max={max}")]
ChainTooDeep { depth: u8, max: u8 },
#[error("UCAN revoked: cid={cid}")]
Revoked { cid: String },
#[error("multi-parent UCAN not supported in v1 (link {cid} has {n_parents} parents)")]
MultiParentNotSupported { cid: String, n_parents: usize },
}
pub fn wire_code(err: &UcanVerifyError) -> u16 {
use atd_protocol::{
ERR_AUDIENCE_MISMATCH, ERR_DELEGATION_TOO_DEEP, ERR_UCAN_EXPIRED, ERR_UCAN_INVALID,
};
match err {
UcanVerifyError::Expired { .. } => ERR_UCAN_EXPIRED,
UcanVerifyError::ChainTooDeep { .. } => ERR_DELEGATION_TOO_DEEP,
UcanVerifyError::AudienceMismatch { .. } => ERR_AUDIENCE_MISMATCH,
_ => ERR_UCAN_INVALID,
}
}