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}