typesec_integrations/did/
typedid.rs1use std::collections::HashMap;
5
6use serde::{Deserialize, Serialize};
7
8use super::crypto::{contains, intersects};
9use super::document::DidResolver;
10use super::envelope::{DidEnvelope, DidMessageBody};
11use super::error::DidError;
12use super::identifier::Did;
13use super::keystore::DidKeyStore;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "snake_case")]
18pub enum TypeDidMode {
19 Send,
21 RequestReply,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct TypeDidConversation {
28 pub conversation_id: String,
30 pub mode: TypeDidMode,
32 pub profile: String,
34 pub protocol: String,
36 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub expires_at: Option<u64>,
39}
40
41impl TypeDidConversation {
42 pub fn new(
44 conversation_id: impl Into<String>,
45 mode: TypeDidMode,
46 profile: impl Into<String>,
47 protocol: impl Into<String>,
48 ) -> Self {
49 Self {
50 conversation_id: conversation_id.into(),
51 mode,
52 profile: profile.into(),
53 protocol: protocol.into(),
54 expires_at: None,
55 }
56 }
57
58 pub fn with_expires_at(mut self, expires_at: u64) -> Self {
60 self.expires_at = Some(expires_at);
61 self
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct TypeDidProfile {
68 pub id: String,
70 #[serde(default)]
72 pub did_methods: Vec<String>,
73 #[serde(default)]
75 pub signing: Vec<String>,
76 #[serde(default)]
78 pub key_agreement: Vec<String>,
79 #[serde(default)]
81 pub encryption: Vec<String>,
82 #[serde(default)]
84 pub transport_bindings: Vec<String>,
85 #[serde(default)]
87 pub modes: Vec<TypeDidMode>,
88 #[serde(default, skip_serializing_if = "Option::is_none")]
90 pub max_payload_bytes: Option<usize>,
91 #[serde(default)]
93 pub required_claims: Vec<String>,
94 #[serde(default)]
96 pub policy_actions: Vec<String>,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
99 pub retention: Option<String>,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
102 pub audit: Option<String>,
103}
104
105impl TypeDidProfile {
106 pub fn ed25519_x25519_chacha20() -> Self {
109 Self {
110 id: "typedid/v1/x25519-chacha20poly1305-ed25519".to_owned(),
111 did_methods: vec![
112 "did:web".to_owned(),
113 "did:key".to_owned(),
114 "did:indy".to_owned(),
115 ],
116 signing: vec!["Ed25519".to_owned()],
117 key_agreement: vec!["X25519".to_owned()],
118 encryption: vec!["ChaCha20-Poly1305".to_owned()],
119 transport_bindings: vec![
120 "a2a".to_owned(),
121 "acp".to_owned(),
122 "band".to_owned(),
123 "https".to_owned(),
124 "websocket".to_owned(),
125 ],
126 modes: vec![TypeDidMode::Send, TypeDidMode::RequestReply],
127 max_payload_bytes: Some(1024 * 1024),
128 required_claims: vec![
129 "org".to_owned(),
130 "agent_id".to_owned(),
131 "purpose".to_owned(),
132 ],
133 policy_actions: vec![
134 "agent:message".to_owned(),
135 "agent:delegate".to_owned(),
136 "ai:infer".to_owned(),
137 ],
138 retention: Some("sender-encrypted-payload-only".to_owned()),
139 audit: Some("envelope-metadata-and-policy-decision".to_owned()),
140 }
141 }
142
143 pub fn is_compatible_with(&self, remote: &Self, protocol: &str, mode: TypeDidMode) -> bool {
145 self.id == remote.id
146 && contains(&self.transport_bindings, protocol)
147 && contains(&remote.transport_bindings, protocol)
148 && self.modes.contains(&mode)
149 && remote.modes.contains(&mode)
150 && intersects(&self.did_methods, &remote.did_methods)
151 && intersects(&self.signing, &remote.signing)
152 && intersects(&self.key_agreement, &remote.key_agreement)
153 && intersects(&self.encryption, &remote.encryption)
154 }
155
156 pub fn negotiate<'a>(
158 local: &'a [Self],
159 remote: &[Self],
160 protocol: &str,
161 mode: TypeDidMode,
162 ) -> Result<&'a Self, DidError> {
163 local
164 .iter()
165 .find(|candidate| {
166 remote
167 .iter()
168 .any(|other| candidate.is_compatible_with(other, protocol, mode))
169 })
170 .ok_or(DidError::NoCompatibleTypeDidProfile)
171 }
172}
173
174pub trait TypeDidProfileResolver: Send + Sync {
176 fn resolve_profiles(&self, target: &str) -> Result<Vec<TypeDidProfile>, DidError>;
178}
179
180#[derive(Debug, Default, Clone)]
182pub struct StaticTypeDidProfileResolver {
183 profiles: HashMap<String, Vec<TypeDidProfile>>,
184}
185
186impl StaticTypeDidProfileResolver {
187 pub fn new() -> Self {
189 Self::default()
190 }
191
192 pub fn with_profiles(
194 mut self,
195 target: impl Into<String>,
196 profiles: Vec<TypeDidProfile>,
197 ) -> Self {
198 self.profiles.insert(target.into(), profiles);
199 self
200 }
201}
202
203impl TypeDidProfileResolver for StaticTypeDidProfileResolver {
204 fn resolve_profiles(&self, target: &str) -> Result<Vec<TypeDidProfile>, DidError> {
205 self.profiles
206 .get(target)
207 .cloned()
208 .ok_or_else(|| DidError::Unresolved(target.to_owned()))
209 }
210}
211
212pub trait SecureEnvelopeAdapter {
214 fn protocol(&self) -> &str;
216
217 fn content_type(&self) -> &'static str {
219 "application/vnd.typedid.envelope+json"
220 }
221
222 fn wrap(
224 &self,
225 request: TypeDidWrapRequest<'_>,
226 resolver: &dyn DidResolver,
227 key_store: &dyn DidKeyStore,
228 ) -> Result<DidEnvelope, DidError> {
229 let profile = TypeDidProfile::negotiate(
230 request.local_profiles,
231 request.remote_profiles,
232 self.protocol(),
233 request.mode,
234 )?;
235 if let Some(max) = profile.max_payload_bytes
238 && request.payload.len() > max
239 {
240 return Err(DidError::PayloadTooLarge {
241 size: request.payload.len(),
242 max,
243 });
244 }
245 let conversation = TypeDidConversation::new(
246 request.conversation_id,
247 request.mode,
248 profile.id.clone(),
249 self.protocol(),
250 );
251 DidEnvelope::typedid(
252 request.id,
253 request.from,
254 request.to,
255 request.body,
256 conversation,
257 request.payload,
258 resolver,
259 key_store,
260 )
261 }
262}
263
264pub struct TypeDidWrapRequest<'a> {
266 pub id: String,
268 pub from: Did,
270 pub to: Did,
272 pub conversation_id: String,
274 pub mode: TypeDidMode,
276 pub body: DidMessageBody,
278 pub payload: &'a [u8],
280 pub local_profiles: &'a [TypeDidProfile],
282 pub remote_profiles: &'a [TypeDidProfile],
284}
285
286#[derive(Debug, Default, Clone, Copy)]
288pub struct A2aTypeDidAdapter;
289
290impl SecureEnvelopeAdapter for A2aTypeDidAdapter {
291 fn protocol(&self) -> &str {
292 "a2a"
293 }
294}
295
296#[derive(Debug, Default, Clone, Copy)]
298pub struct AcpTypeDidAdapter;
299
300impl SecureEnvelopeAdapter for AcpTypeDidAdapter {
301 fn protocol(&self) -> &str {
302 "acp"
303 }
304}
305
306#[derive(Debug, Default, Clone, Copy)]
308pub struct BandSecureEnvelopeAdapter;
309
310impl SecureEnvelopeAdapter for BandSecureEnvelopeAdapter {
311 fn protocol(&self) -> &str {
312 "band"
313 }
314}
315
316#[derive(Debug, Default, Clone, Copy)]
318pub struct HttpTypeDidAdapter;
319
320impl SecureEnvelopeAdapter for HttpTypeDidAdapter {
321 fn protocol(&self) -> &str {
322 "https"
323 }
324}