aitp_session_bundle/
wire.rs1use crate::error::SessionBundleError;
5use crate::types::{SessionBundleEnvelope, SessionTrustBundle, SESSION_BUNDLE_MEMBERS};
6use aitp_core::{check_members, from_serde_error};
7use serde_json::Value;
8
9const SESSION_BUNDLE_ENVELOPE_MEMBERS: &[&str] = &["session_bundle"];
15
16pub fn parse_session_bundle_wire(value: &Value) -> Result<SessionTrustBundle, SessionBundleError> {
51 if value.get("session_bundle").is_some() {
52 check_members(
53 "SessionBundleEnvelope",
54 value,
55 SESSION_BUNDLE_ENVELOPE_MEMBERS,
56 )
57 .map_err(|e| {
58 SessionBundleError::WireFormInvalid(format!(
59 "transport envelope: unknown field `{}` (RFC-AITP-0010 §3 places \
60 `signature` inside the signed body, never beside the wrapper)",
61 e.field
62 ))
63 })?;
64
65 let body = value.get("session_bundle").expect("checked above");
66 check_members("SessionTrustBundle", body, SESSION_BUNDLE_MEMBERS)
67 .map_err(|e| SessionBundleError::UnknownField(e.field))?;
68
69 let env: SessionBundleEnvelope = serde_json::from_value(value.clone()).map_err(|e| {
70 if let Some(field) = from_serde_error(&e) {
71 SessionBundleError::UnknownField(field)
72 } else {
73 SessionBundleError::WireFormInvalid(format!("transport envelope: {e}"))
74 }
75 })?;
76 Ok(env.session_bundle)
77 } else {
78 check_members("SessionTrustBundle", value, SESSION_BUNDLE_MEMBERS)
79 .map_err(|e| SessionBundleError::UnknownField(e.field))?;
80
81 serde_json::from_value(value.clone()).map_err(|e| {
82 if let Some(field) = from_serde_error(&e) {
83 SessionBundleError::UnknownField(field)
84 } else {
85 SessionBundleError::WireFormInvalid(format!("bundle body: {e}"))
86 }
87 })
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use serde_json::json;
95
96 fn valid_body() -> Value {
97 json!({
98 "version": "aitp/0.2",
99 "session_id": "00000000-0000-4000-8000-000000000000",
100 "coordinator": "aid:pubkey:O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
101 "issued_at": 1_700_000_000,
102 "expires_at": 1_700_010_000,
103 "participants": [],
104 "signature": "A".repeat(86),
105 })
106 }
107
108 #[test]
109 fn wrapped_with_signature_inside_parses() {
110 let wire = json!({ "session_bundle": valid_body() });
111 assert!(parse_session_bundle_wire(&wire).is_ok());
112 }
113
114 #[test]
115 fn bare_body_parses() {
116 assert!(parse_session_bundle_wire(&valid_body()).is_ok());
117 }
118
119 #[test]
120 fn sibling_signature_is_rejected() {
121 let mut body = valid_body();
122 let signature = body.as_object_mut().unwrap().remove("signature").unwrap();
123 let wire = json!({ "session_bundle": body, "signature": signature });
124 assert!(matches!(
125 parse_session_bundle_wire(&wire),
126 Err(SessionBundleError::WireFormInvalid(_))
127 ));
128 }
129
130 #[test]
131 fn unexpected_sibling_key_is_rejected() {
132 let wire = json!({ "session_bundle": valid_body(), "extra": "nope" });
133 assert!(matches!(
134 parse_session_bundle_wire(&wire),
135 Err(SessionBundleError::WireFormInvalid(_))
136 ));
137 }
138
139 #[test]
140 fn bare_body_missing_signature_is_rejected() {
141 let mut body = valid_body();
142 body.as_object_mut().unwrap().remove("signature");
143 assert!(matches!(
144 parse_session_bundle_wire(&body),
145 Err(SessionBundleError::WireFormInvalid(_))
146 ));
147 }
148
149 #[test]
154 fn unknown_body_member_is_unknown_field() {
155 let mut body = valid_body();
156 body.as_object_mut()
157 .unwrap()
158 .insert("coordinator_note".into(), json!("primary-region"));
159 let wire = json!({ "session_bundle": body });
160 match parse_session_bundle_wire(&wire) {
161 Err(SessionBundleError::UnknownField(field)) => {
162 assert_eq!(field, "coordinator_note")
163 }
164 other => panic!("expected UnknownField(\"coordinator_note\"), got {other:?}"),
165 }
166 }
167
168 #[test]
172 fn unknown_bare_body_member_is_unknown_field() {
173 let mut body = valid_body();
174 body.as_object_mut()
175 .unwrap()
176 .insert("rogue".into(), json!(1));
177 match parse_session_bundle_wire(&body) {
178 Err(SessionBundleError::UnknownField(field)) => assert_eq!(field, "rogue"),
179 other => panic!("expected UnknownField(\"rogue\"), got {other:?}"),
180 }
181 }
182
183 #[test]
188 fn extensions_contents_are_never_treated_as_unknown_members() {
189 let mut body = valid_body();
190 body.as_object_mut()
191 .unwrap()
192 .insert("extensions".into(), json!({ "tee": { "platform": "sgx" } }));
193 let wire = json!({ "session_bundle": body });
194 assert!(parse_session_bundle_wire(&wire).is_ok());
195 }
196
197 #[test]
204 fn unknown_participant_entry_member_is_unknown_field_via_from_serde_error() {
205 let mut body = valid_body();
206 body["participants"] = json!([{
207 "aid": "aid:pubkey:O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
208 "tct": "a.b.c",
209 "rogue": "nope",
210 }]);
211 let wire = json!({ "session_bundle": body });
212 match parse_session_bundle_wire(&wire) {
213 Err(SessionBundleError::UnknownField(field)) => assert_eq!(field, "rogue"),
214 other => panic!("expected UnknownField(\"rogue\"), got {other:?}"),
215 }
216 }
217
218 #[test]
225 fn unknown_body_member_is_rejected_before_coordinator_key_resolution() {
226 let mut body = valid_body();
227 body["coordinator"] = json!("not-an-aid-and-not-even-attempted");
231 body.as_object_mut()
232 .unwrap()
233 .insert("rogue".into(), json!("nope"));
234 let wire = json!({ "session_bundle": body });
235 match parse_session_bundle_wire(&wire) {
236 Err(SessionBundleError::UnknownField(field)) => assert_eq!(field, "rogue"),
237 other => panic!(
238 "expected the unknown-member check to short-circuit before `coordinator` is \
239 ever interpreted, got {other:?}"
240 ),
241 }
242 }
243}