aitp_session_bundle/
wire.rs1use crate::error::SessionBundleError;
5use crate::types::{SessionBundleEnvelope, SessionTrustBundle, SESSION_BUNDLE_MEMBERS};
6use aitp_core::{check_members, from_serde_error, CheckedJson};
7use serde_json::Value;
8
9const SESSION_BUNDLE_ENVELOPE_MEMBERS: &[&str] = &["session_bundle"];
15
16pub fn parse_session_bundle_wire(
51 value: &CheckedJson,
52) -> Result<SessionTrustBundle, SessionBundleError> {
53 let value: &Value = value.as_value();
54 if value.get("session_bundle").is_some() {
55 check_members(
56 "SessionBundleEnvelope",
57 value,
58 SESSION_BUNDLE_ENVELOPE_MEMBERS,
59 )
60 .map_err(|e| {
61 SessionBundleError::WireFormInvalid(format!(
62 "transport envelope: unknown field `{}` (RFC-AITP-0010 §3 places \
63 `signature` inside the signed body, never beside the wrapper)",
64 e.field
65 ))
66 })?;
67
68 let body = value.get("session_bundle").expect("checked above");
69 check_members("SessionTrustBundle", body, SESSION_BUNDLE_MEMBERS)
70 .map_err(|e| SessionBundleError::UnknownField(e.field))?;
71
72 let env: SessionBundleEnvelope = serde_json::from_value(value.clone()).map_err(|e| {
73 if let Some(field) = from_serde_error(&e) {
74 SessionBundleError::UnknownField(field)
75 } else {
76 SessionBundleError::WireFormInvalid(format!("transport envelope: {e}"))
77 }
78 })?;
79 Ok(env.session_bundle)
80 } else {
81 check_members("SessionTrustBundle", value, SESSION_BUNDLE_MEMBERS)
82 .map_err(|e| SessionBundleError::UnknownField(e.field))?;
83
84 serde_json::from_value(value.clone()).map_err(|e| {
85 if let Some(field) = from_serde_error(&e) {
86 SessionBundleError::UnknownField(field)
87 } else {
88 SessionBundleError::WireFormInvalid(format!("bundle body: {e}"))
89 }
90 })
91 }
92}
93
94pub fn parse_session_bundle_envelope(
107 value: &CheckedJson,
108) -> Result<SessionTrustBundle, SessionBundleError> {
109 if value.as_value().get("session_bundle").is_none() {
110 return Err(SessionBundleError::WireFormInvalid(
111 "missing `session_bundle` transport wrapper (RFC-AITP-0010 §4.3.1 \
112 rejects a bare body)"
113 .into(),
114 ));
115 }
116 parse_session_bundle_wire(value)
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use serde_json::json;
123
124 fn checked(v: &serde_json::Value) -> aitp_core::CheckedJson {
127 v.to_string().parse().expect("valid JSON")
128 }
129
130 #[test]
131 fn envelope_parser_rejects_bare_body() {
132 match parse_session_bundle_envelope(&checked(&valid_body())) {
133 Err(SessionBundleError::WireFormInvalid(msg)) => {
134 assert!(msg.contains("session_bundle"), "got {msg}")
135 }
136 other => panic!("expected WireFormInvalid, got {other:?}"),
137 }
138 }
139
140 #[test]
141 fn envelope_parser_accepts_wrapped_body() {
142 let wire = json!({ "session_bundle": valid_body() });
143 assert!(parse_session_bundle_envelope(&checked(&wire)).is_ok());
144 }
145
146 fn valid_body() -> Value {
147 json!({
148 "version": "aitp/0.3",
149 "session_id": "00000000-0000-4000-8000-000000000000",
150 "coordinator": "aid:pubkey:O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
151 "issued_at": 1_700_000_000,
152 "expires_at": 1_700_010_000,
153 "participants": [],
154 "signature": "A".repeat(86),
155 })
156 }
157
158 #[test]
159 fn wrapped_with_signature_inside_parses() {
160 let wire = json!({ "session_bundle": valid_body() });
161 assert!(parse_session_bundle_wire(&checked(&wire)).is_ok());
162 }
163
164 #[test]
165 fn bare_body_parses() {
166 assert!(parse_session_bundle_wire(&checked(&valid_body())).is_ok());
167 }
168
169 #[test]
170 fn sibling_signature_is_rejected() {
171 let mut body = valid_body();
172 let signature = body.as_object_mut().unwrap().remove("signature").unwrap();
173 let wire = json!({ "session_bundle": body, "signature": signature });
174 assert!(matches!(
175 parse_session_bundle_wire(&checked(&wire)),
176 Err(SessionBundleError::WireFormInvalid(_))
177 ));
178 }
179
180 #[test]
181 fn unexpected_sibling_key_is_rejected() {
182 let wire = json!({ "session_bundle": valid_body(), "extra": "nope" });
183 assert!(matches!(
184 parse_session_bundle_wire(&checked(&wire)),
185 Err(SessionBundleError::WireFormInvalid(_))
186 ));
187 }
188
189 #[test]
190 fn bare_body_missing_signature_is_rejected() {
191 let mut body = valid_body();
192 body.as_object_mut().unwrap().remove("signature");
193 assert!(matches!(
194 parse_session_bundle_wire(&checked(&body)),
195 Err(SessionBundleError::WireFormInvalid(_))
196 ));
197 }
198
199 #[test]
204 fn unknown_body_member_is_unknown_field() {
205 let mut body = valid_body();
206 body.as_object_mut()
207 .unwrap()
208 .insert("coordinator_note".into(), json!("primary-region"));
209 let wire = json!({ "session_bundle": body });
210 match parse_session_bundle_wire(&checked(&wire)) {
211 Err(SessionBundleError::UnknownField(field)) => {
212 assert_eq!(field, "coordinator_note")
213 }
214 other => panic!("expected UnknownField(\"coordinator_note\"), got {other:?}"),
215 }
216 }
217
218 #[test]
222 fn unknown_bare_body_member_is_unknown_field() {
223 let mut body = valid_body();
224 body.as_object_mut()
225 .unwrap()
226 .insert("rogue".into(), json!(1));
227 match parse_session_bundle_wire(&checked(&body)) {
228 Err(SessionBundleError::UnknownField(field)) => assert_eq!(field, "rogue"),
229 other => panic!("expected UnknownField(\"rogue\"), got {other:?}"),
230 }
231 }
232
233 #[test]
238 fn extensions_contents_are_never_treated_as_unknown_members() {
239 let mut body = valid_body();
240 body.as_object_mut()
241 .unwrap()
242 .insert("extensions".into(), json!({ "tee": { "platform": "sgx" } }));
243 let wire = json!({ "session_bundle": body });
244 assert!(parse_session_bundle_wire(&checked(&wire)).is_ok());
245 }
246
247 #[test]
254 fn unknown_participant_entry_member_is_unknown_field_via_from_serde_error() {
255 let mut body = valid_body();
256 body["participants"] = json!([{
257 "aid": "aid:pubkey:O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
258 "tct": "a.b.c",
259 "rogue": "nope",
260 }]);
261 let wire = json!({ "session_bundle": body });
262 match parse_session_bundle_wire(&checked(&wire)) {
263 Err(SessionBundleError::UnknownField(field)) => assert_eq!(field, "rogue"),
264 other => panic!("expected UnknownField(\"rogue\"), got {other:?}"),
265 }
266 }
267
268 #[test]
275 fn unknown_body_member_is_rejected_before_coordinator_key_resolution() {
276 let mut body = valid_body();
277 body["coordinator"] = json!("not-an-aid-and-not-even-attempted");
281 body.as_object_mut()
282 .unwrap()
283 .insert("rogue".into(), json!("nope"));
284 let wire = json!({ "session_bundle": body });
285 match parse_session_bundle_wire(&checked(&wire)) {
286 Err(SessionBundleError::UnknownField(field)) => assert_eq!(field, "rogue"),
287 other => panic!(
288 "expected the unknown-member check to short-circuit before `coordinator` is \
289 ever interpreted, got {other:?}"
290 ),
291 }
292 }
293}