aitp_session_bundle/error.rs
1//! Session Trust Bundle errors (RFC-AITP-0010 §7 / `BUNDLE_*` codes
2//! tracked in `agentidentitytrustprotocol/plans/v0.2-conformance-followups.md`).
3
4/// Errors from session-bundle construction and verification.
5#[derive(Debug, thiserror::Error)]
6pub enum SessionBundleError {
7 /// `version` field was not `"aitp/0.2"`.
8 #[error("bundle version mismatch")]
9 VersionMismatch,
10 /// Outer bundle signature failed to verify against the
11 /// coordinator's pubkey.
12 #[error("bundle signature is invalid")]
13 InvalidSignature,
14 /// Bundle's `expires_at` is in the past.
15 #[error("bundle has expired")]
16 Expired,
17 /// `expires_at` did not equal `min(participants[*].tct.expires_at)`
18 /// (RFC-AITP-0010 §6).
19 #[error("bundle expires_at does not equal min(participant TCT expiries)")]
20 ExpiryWindowInvariant,
21 /// At least one participant TCT had a different `issuer` from the
22 /// bundle's `coordinator`.
23 #[error("participant TCT issuer does not match coordinator")]
24 CoordinatorIssuerMismatch,
25 /// At least one participant TCT had `audience` ≠ the entry's
26 /// declared `aid` (the bundle distributes participants' OWN TCTs
27 /// back to them, so audience and entry.aid must match).
28 #[error("participant TCT audience does not match entry AID")]
29 AudienceMismatch,
30 /// Verifier's AID is not present in `participants[]`.
31 #[error("verifier is not a member of this bundle")]
32 NotMember,
33 /// Builder was missing a required field.
34 #[error("missing required field: {0}")]
35 MissingField(&'static str),
36 /// Empty participants array — RFC-AITP-0010 §3 requires at least
37 /// one entry.
38 #[error("participants array is empty")]
39 EmptyParticipants,
40 /// JCS canonicalization failed.
41 #[error("canonicalization failed: {0}")]
42 Canonicalization(String),
43 /// TCT verification of an embedded participant TCT failed.
44 #[error("participant TCT verification failed: {0}")]
45 TctVerification(#[from] aitp_tct::TctError),
46 /// Crypto error (e.g. malformed AID-derived key).
47 #[error(transparent)]
48 Crypto(#[from] aitp_crypto::CryptoError),
49 /// The wire form was structurally invalid before any cryptographic
50 /// check could run: the `{"session_bundle": …}` transport wrapper
51 /// carried a member besides `session_bundle` (the pre-erratum shape
52 /// where `signature` sat as a SIBLING of the wrapper), or the inner
53 /// body did not deserialize as a `SessionTrustBundle`.
54 ///
55 /// RFC-AITP-0010 §3 fixes `signature` as a member of the signed body,
56 /// never a sibling of the wrapper, precisely because a bundle is
57 /// redistributable and must carry its own proof across any hop that
58 /// strips the transport wrapper (RFC-AITP-0001 §5.4.1). Maps to
59 /// `SESSION_BUNDLE_INVALID`.
60 #[error("session bundle wire form is invalid: {0}")]
61 WireFormInvalid(String),
62 /// A member outside the schema-declared member set was found on a
63 /// closed object — the wrapper's shape was fine, but the object
64 /// itself (the inner `session_bundle` body, or a nested closed
65 /// object such as a `participants[]` entry) carries a field the
66 /// schema does not define and which is not inside `extensions`.
67 ///
68 /// Distinct from [`SessionBundleError::WireFormInvalid`]: that
69 /// variant is for the wrapper-level defect where `signature` sits
70 /// beside `{"session_bundle": …}` instead of inside it
71 /// (RFC-AITP-0010 §3). This variant is the RFC-AITP-0001 §7 /
72 /// RFC-AITP-0010 §5 body-level check — checked ahead of any
73 /// cryptographic work, including the coordinator key resolution used
74 /// to verify the outer signature — and maps to the **core**
75 /// `UNKNOWN_FIELD` code rather than a `BUNDLE_*` draft code, even
76 /// though the session bundle artifact itself is draft.
77 #[error("unknown field: {0}")]
78 UnknownField(String),
79}