Skip to main content

aitp_session_bundle/
types.rs

1//! Wire types for Session Trust Bundle (RFC-AITP-0010 §3).
2
3use aitp_core::{Aid, Timestamp};
4use aitp_tct::Tct;
5use serde::{Deserialize, Serialize};
6use uuid::Uuid;
7
8/// Coordinator-attested session membership artifact (RFC-AITP-0010 §3).
9///
10/// The schema is `additionalProperties: false`; v0.1 has no `extensions`
11/// slot.
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
13#[serde(deny_unknown_fields)]
14pub struct SessionTrustBundle {
15    /// MUST be `"aitp/0.1"` for this RFC.
16    pub version: String,
17    /// UUID v4 unique to this session. Used as a replay-binding scope.
18    pub session_id: Uuid,
19    /// Coordinator's AID. MUST match the `issuer` of every embedded TCT.
20    pub coordinator: Aid,
21    /// When this bundle was signed.
22    pub issued_at: Timestamp,
23    /// When the bundle MUST NOT be used after. MUST equal
24    /// `min(participants[*].tct.expires_at)` (RFC-AITP-0010 §6).
25    pub expires_at: Timestamp,
26    /// One entry per session participant.
27    pub participants: Vec<ParticipantEntry>,
28    /// Coordinator's signature over the canonical bundle JSON
29    /// excluding `signature`. JCS rules per RFC-AITP-0001 §5.4.1.
30    pub signature: String,
31}
32
33/// One participant in a [`SessionTrustBundle`]: their AID + the
34/// coordinator-issued TCT they received during their bilateral
35/// handshake.
36#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
37#[serde(deny_unknown_fields)]
38pub struct ParticipantEntry {
39    /// Participant's AID. MUST equal the embedded TCT's `audience`.
40    pub aid: Aid,
41    /// Coordinator → participant TCT, audience = participant's AID.
42    pub tct: Tct,
43}
44
45/// HTTP/transport-wrapped form (the `{"session_bundle": {...}}` shape
46/// that matches the JSON Schema `$id`).
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
48#[serde(deny_unknown_fields)]
49pub struct SessionBundleEnvelope {
50    /// The signed inner bundle.
51    pub session_bundle: SessionTrustBundle,
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use serde_json::json;
58
59    #[test]
60    fn rejects_unknown_top_field() {
61        let v = json!({
62            "version": "aitp/0.1",
63            "session_id": "00000000-0000-4000-8000-000000000000",
64            "coordinator": "aid:pubkey:O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
65            "issued_at": 1_700_000_000,
66            "expires_at": 1_700_010_000,
67            "participants": [],
68            "signature": "A".repeat(86),
69            "rogue": 1,
70        });
71        assert!(serde_json::from_value::<SessionTrustBundle>(v).is_err());
72    }
73}