tap_agent/crypto.rs
1//! Cryptographic utilities for the TAP Agent.
2//!
3//! This module provides interfaces and implementations for:
4//! - Message packing and unpacking using DIDComm
5//! - Secret resolution for cryptographic operations
6//! - Security mode handling for different message types
7
8use crate::did::SyncDIDResolver;
9use crate::error::{Error, Result};
10use crate::message::SecurityMode;
11use async_trait::async_trait;
12use serde::de::DeserializeOwned;
13use serde_json::Value;
14use std::fmt::Debug;
15use std::sync::Arc;
16use uuid::Uuid;
17
18/// A trait for packing and unpacking messages with DIDComm.
19///
20/// This trait defines the interface for secure message handling, including
21/// different security modes (Plain, Signed, AuthCrypt).
22#[async_trait]
23pub trait MessagePacker: Send + Sync + Debug {
24 /// Pack a message for the given recipient.
25 ///
26 /// Transforms a serializable message into a DIDComm-encoded message with
27 /// the appropriate security measures applied based on the mode.
28 ///
29 /// # Parameters
30 /// * `message` - The message to pack
31 /// * `to` - The DID of the recipient
32 /// * `from` - The DID of the sender, or None for anonymous messages
33 /// * `mode` - The security mode to use (Plain, Signed, AuthCrypt)
34 ///
35 /// # Returns
36 /// The packed message as a string
37 async fn pack_message(
38 &self,
39 message: &(dyn erased_serde::Serialize + Sync),
40 to: &str,
41 from: Option<&str>,
42 mode: SecurityMode,
43 ) -> Result<String>;
44
45 /// Unpack a message and return the JSON Value.
46 ///
47 /// Transforms a DIDComm-encoded message back into its original JSON content,
48 /// verifying signatures and decrypting content as needed.
49 ///
50 /// # Parameters
51 /// * `packed` - The packed message
52 ///
53 /// # Returns
54 /// The unpacked message as a JSON Value
55 async fn unpack_message_value(&self, packed: &str) -> Result<Value>;
56}
57
58/// A trait to extend types with an as_any method for downcasting.
59pub trait AsAny: 'static {
60 /// Return a reference to self as Any
61 fn as_any(&self) -> &dyn std::any::Any;
62}
63
64impl<T: 'static> AsAny for T {
65 fn as_any(&self) -> &dyn std::any::Any {
66 self
67 }
68}
69
70/// A trait for resolving secrets for use with DIDComm.
71///
72/// This trait extends the built-in secrets resolver functionality from the DIDComm crate
73/// to provide additional functionality needed by the TAP Agent.
74pub trait DebugSecretsResolver: Debug + Send + Sync + AsAny {
75 /// Get a reference to the secrets map for debugging purposes
76 fn get_secrets_map(&self) -> &std::collections::HashMap<String, didcomm::secrets::Secret>;
77}
78
79/// A basic implementation of DebugSecretsResolver.
80///
81/// This implementation provides a simple in-memory store for cryptographic secrets
82/// used by the TAP Agent for DIDComm operations.
83#[derive(Debug, Default)]
84pub struct BasicSecretResolver {
85 /// Maps DIDs to their associated secrets
86 secrets: std::collections::HashMap<String, didcomm::secrets::Secret>,
87}
88
89impl BasicSecretResolver {
90 /// Create a new empty BasicSecretResolver
91 pub fn new() -> Self {
92 Self {
93 secrets: std::collections::HashMap::new(),
94 }
95 }
96
97 /// Add a secret for a DID
98 ///
99 /// # Parameters
100 /// * `did` - The DID to associate with the secret
101 /// * `secret` - The secret to add
102 pub fn add_secret(&mut self, did: &str, secret: didcomm::secrets::Secret) {
103 self.secrets.insert(did.to_string(), secret);
104 }
105}
106
107impl DebugSecretsResolver for BasicSecretResolver {
108 fn get_secrets_map(&self) -> &std::collections::HashMap<String, didcomm::secrets::Secret> {
109 &self.secrets
110 }
111}
112
113/// Default implementation of the MessagePacker trait.
114///
115/// This implementation uses DIDComm for message packing and unpacking,
116/// providing secure communications with support for the different
117/// security modes defined in the TAP protocol.
118#[derive(Debug)]
119pub struct DefaultMessagePacker {
120 /// DID resolver
121 did_resolver: Arc<dyn SyncDIDResolver>,
122 /// Secrets resolver
123 secrets_resolver: Arc<dyn DebugSecretsResolver>,
124}
125
126impl DefaultMessagePacker {
127 /// Create a new DefaultMessagePacker
128 ///
129 /// # Parameters
130 /// * `did_resolver` - The DID resolver to use for resolving DIDs
131 /// * `secrets_resolver` - The secrets resolver to use for cryptographic operations
132 pub fn new(
133 did_resolver: Arc<dyn SyncDIDResolver>,
134 secrets_resolver: Arc<dyn DebugSecretsResolver>,
135 ) -> Self {
136 Self {
137 did_resolver,
138 secrets_resolver,
139 }
140 }
141
142 /// Resolve a DID to a DID document
143 ///
144 /// # Parameters
145 /// * `did` - The DID to resolve
146 ///
147 /// # Returns
148 /// The DID document as a JSON string
149 async fn resolve_did(&self, did: &str) -> Result<String> {
150 // Our SyncDIDResolver returns our own error type, so we don't need to convert it
151 let doc_option = self.did_resolver.resolve(did).await?;
152 let doc = doc_option
153 .ok_or_else(|| Error::DidResolution(format!("Could not resolve DID: {}", did)))?;
154
155 // Convert the DID doc to a JSON string
156 serde_json::to_string(&doc).map_err(|e| Error::Serialization(e.to_string()))
157 }
158
159 /// Select the appropriate security mode for the message
160 ///
161 /// # Parameters
162 /// * `mode` - The requested security mode
163 /// * `has_from` - Whether the message has a sender (from)
164 ///
165 /// # Returns
166 /// The appropriate security mode or an error if the mode is invalid
167 /// with the given parameters
168 fn select_security_mode(&self, mode: SecurityMode, has_from: bool) -> Result<SecurityMode> {
169 match mode {
170 SecurityMode::Plain => Ok(SecurityMode::Plain),
171 SecurityMode::Signed => {
172 if has_from {
173 Ok(SecurityMode::Signed)
174 } else {
175 Err(Error::Validation(
176 "Signed mode requires a 'from' field".to_string(),
177 ))
178 }
179 }
180 SecurityMode::AuthCrypt => {
181 if has_from {
182 Ok(SecurityMode::AuthCrypt)
183 } else {
184 Err(Error::Validation(
185 "AuthCrypt mode requires a 'from' field".to_string(),
186 ))
187 }
188 }
189 SecurityMode::Any => {
190 if has_from {
191 Ok(SecurityMode::AuthCrypt)
192 } else {
193 Ok(SecurityMode::Plain)
194 }
195 }
196 }
197 }
198
199 /// Unpack a message and parse it to the requested type
200 pub async fn unpack_message<T: DeserializeOwned + Send>(&self, packed: &str) -> Result<T> {
201 let value = self.unpack_message_value(packed).await?;
202
203 // Parse the unpacked message to the requested type
204 serde_json::from_value::<T>(value).map_err(|e| Error::Serialization(e.to_string()))
205 }
206}
207
208#[async_trait]
209impl MessagePacker for DefaultMessagePacker {
210 /// Pack a message for the specified recipient using DIDComm
211 ///
212 /// Serializes the message, creates a DIDComm message, and applies
213 /// the appropriate security measures based on the security mode.
214 ///
215 /// # Parameters
216 /// * `message` - The message to pack
217 /// * `to` - The DID of the recipient
218 /// * `from` - The DID of the sender, or None for anonymous messages
219 /// * `mode` - The security mode to use
220 ///
221 /// # Returns
222 /// The packed message as a string
223 async fn pack_message(
224 &self,
225 message: &(dyn erased_serde::Serialize + Sync),
226 to: &str,
227 from: Option<&str>,
228 mode: SecurityMode,
229 ) -> Result<String> {
230 // For proper implementations, we would use the did_resolver to resolve DIDs
231 // and the secrets_resolver for cryptographic operations
232 let _to_doc = self.resolve_did(to).await?;
233
234 // If from is provided, resolve it too
235 if let Some(from_did) = from {
236 let _from_doc = self.resolve_did(from_did).await?;
237 }
238
239 // Serialize the message to a JSON string
240 let mut value =
241 serde_json::to_value(message).map_err(|e| Error::Serialization(e.to_string()))?;
242
243 // Ensure value is an object
244 let obj = value
245 .as_object_mut()
246 .ok_or_else(|| Error::Serialization("Message is not a JSON object".to_string()))?;
247
248 // Add id if not present
249 if !obj.contains_key("id") {
250 obj.insert("id".to_string(), Value::String(Uuid::new_v4().to_string()));
251 }
252
253 // Convert back to string
254 let message_str =
255 serde_json::to_string(&value).map_err(|e| Error::Serialization(e.to_string()))?;
256
257 // Build DIDComm message
258 // Note: In a real implementation, we would use the didcomm crate's Message type
259 // Here we'll create a simplified message structure
260 let id = Uuid::new_v4().to_string();
261 let mut msg = serde_json::json!({
262 "id": id,
263 "body": value,
264 "to": to
265 });
266
267 // Add "from" if provided
268 if let Some(from_did) = from {
269 msg["from"] = Value::String(from_did.to_string());
270 }
271
272 // Select the security mode
273 let actual_mode = self.select_security_mode(mode, from.is_some())?;
274
275 // Pack the message according to the selected mode
276 let packed = match actual_mode {
277 SecurityMode::Plain => {
278 // For Plain mode, just use the serialized message
279 message_str
280 }
281 SecurityMode::Signed => {
282 // For Signed mode, use from and secrets_resolver
283 if let Some(_from_did) = from {
284 // In a real implementation, we would use the secrets_resolver
285 // to sign the message with the sender's key
286 // Accessing the secrets_resolver (now just using it to prevent dead code warning)
287 let _sr = &self.secrets_resolver;
288
289 // For now, just serialize the message with an id field
290 message_str
291 } else {
292 return Err(Error::Validation(
293 "Signed mode requires a from field".to_string(),
294 ));
295 }
296 }
297 SecurityMode::AuthCrypt => {
298 // For AuthCrypt mode, use from, to, and secrets_resolver
299 if let Some(_from_did) = from {
300 // In a real implementation, we would use the secrets_resolver
301 // to encrypt and sign the message
302 // Accessing the secrets_resolver (now just using it to prevent dead code warning)
303 let _sr = &self.secrets_resolver;
304
305 // For now, just serialize the message with an id field
306 message_str
307 } else {
308 return Err(Error::Validation(
309 "AuthCrypt mode requires a from field".to_string(),
310 ));
311 }
312 }
313 SecurityMode::Any => {
314 return Err(Error::Validation(
315 "Cannot use Any mode for packing".to_string(),
316 ));
317 }
318 };
319
320 Ok(packed)
321 }
322
323 /// Unpack a DIDComm message and return its contents as JSON
324 ///
325 /// Verifies signatures and decrypts content as needed based on
326 /// how the message was originally packed.
327 ///
328 /// # Parameters
329 /// * `packed` - The packed DIDComm message
330 ///
331 /// # Returns
332 /// The unpacked message content as JSON Value
333 async fn unpack_message_value(&self, packed: &str) -> Result<Value> {
334 // In a real implementation, we would use the secrets_resolver
335 // to decrypt and verify the message
336 // Accessing the secrets_resolver (now just using it to prevent dead code warning)
337 let _sr = &self.secrets_resolver;
338
339 // Try to parse as JSON first (for Plain mode)
340 if let Ok(value) = serde_json::from_str::<Value>(packed) {
341 return Ok(value);
342 }
343
344 // If that fails, attempt to unpack as a DIDComm message
345 // (for Signed and AuthCrypt modes)
346 // This would involve using the secrets_resolver and did_resolver
347
348 // For now, just return an error
349 Err(Error::Serialization("Failed to unpack message".to_string()))
350 }
351}