didcomm/protocols/routing/
mod.rs

1mod forward;
2
3use std::collections::HashMap;
4
5use serde_json::{json, Value};
6use uuid::Uuid;
7
8use crate::{
9    algorithms::AnonCryptAlg,
10    did::{DIDCommMessagingService, DIDResolver, Service, ServiceKind},
11    error::{err_msg, ErrorKind, Result, ResultContext, ResultExt},
12    message::{anoncrypt, MessagingServiceMetadata},
13    utils::did::{did_or_url, is_did},
14    Attachment, AttachmentData, Message, PackEncryptedOptions,
15};
16
17pub use self::forward::ParsedForward;
18
19pub(crate) const FORWARD_MSG_TYPE: &str = "https://didcomm.org/routing/2.0/forward";
20
21pub(crate) const DIDCOMM_V2_PROFILE: &str = "didcomm/v2";
22
23async fn find_did_comm_service<'dr>(
24    did: &str,
25    service_id: Option<&str>,
26    did_resolver: &'dr (dyn DIDResolver + 'dr),
27) -> Result<Option<(String, DIDCommMessagingService)>> {
28    let did_doc = did_resolver
29        .resolve(did)
30        .await
31        .context("Unable resolve DID")?
32        .ok_or_else(|| err_msg(ErrorKind::DIDNotResolved, "DID not found"))?;
33
34    match service_id {
35        Some(service_id) => {
36            let service: &Service = did_doc
37                .service
38                .iter()
39                .find(|&service| service.id == service_id)
40                .ok_or_else(|| {
41                    err_msg(
42                        ErrorKind::IllegalArgument,
43                        "Service with the specified ID not found",
44                    )
45                })?;
46
47            match service.service_endpoint {
48                ServiceKind::DIDCommMessaging { ref value } => match value.accept.as_ref() {
49                    Some(accept) => {
50                        if accept.is_empty() || accept.contains(&DIDCOMM_V2_PROFILE.into()) {
51                            Ok(Some((service.id.clone(), value.clone())))
52                        } else {
53                            Err(err_msg(
54                                ErrorKind::IllegalArgument,
55                                "Service with the specified ID does not accept didcomm/v2 profile",
56                            ))
57                        }
58                    }
59                    None => Ok(Some((service.id.clone(), value.clone()))),
60                },
61                _ => Err(err_msg(
62                    ErrorKind::IllegalArgument,
63                    "Service with the specified ID is not of DIDCommMessaging type",
64                )),
65            }
66        }
67
68        None => Ok(did_doc
69            .service
70            .iter()
71            .find_map(|service| match service.service_endpoint {
72                ServiceKind::DIDCommMessaging { ref value } => match value.accept.as_ref() {
73                    Some(accept) => {
74                        if accept.is_empty() || accept.contains(&DIDCOMM_V2_PROFILE.into()) {
75                            Some((service.id.clone(), value.clone()))
76                        } else {
77                            None
78                        }
79                    }
80                    None => Some((service.id.clone(), value.clone())),
81                },
82                _ => None,
83            })),
84    }
85}
86
87async fn resolve_did_comm_services_chain<'dr>(
88    to: &str,
89    service_id: Option<&str>,
90    did_resolver: &'dr (dyn DIDResolver + 'dr),
91) -> Result<Vec<(String, DIDCommMessagingService)>> {
92    let (to_did, _) = did_or_url(to);
93
94    let service = find_did_comm_service(to_did, service_id, did_resolver).await?;
95
96    if service.is_none() {
97        return Ok(vec![]);
98    }
99
100    let mut service = service.unwrap();
101
102    let mut services = vec![service.clone()];
103    let mut service_endpoint = &service.1.uri;
104
105    while is_did(service_endpoint) {
106        // Now alternative endpoints recursion is not supported
107        // (because it should not be used according to the specification)
108        if services.len() > 1 {
109            return Err(err_msg(
110                ErrorKind::InvalidState,
111                "DID doc defines alternative endpoints recursively",
112            ));
113        }
114
115        service = find_did_comm_service(service_endpoint, None, did_resolver)
116            .await?
117            .ok_or_else(|| {
118                err_msg(
119                    // TODO: Think on introducing a more appropriate error kind
120                    ErrorKind::InvalidState,
121                    "Referenced mediator does not provide any DIDCommMessaging services",
122                )
123            })?;
124
125        services.insert(0, service.clone());
126        service_endpoint = &service.1.uri;
127    }
128
129    Ok(services)
130}
131
132fn generate_message_id() -> String {
133    Uuid::new_v4().to_string()
134}
135
136fn build_forward_message(
137    forwarded_msg: &str,
138    next: &str,
139    headers: Option<&HashMap<String, Value>>,
140) -> Result<String> {
141    let body = json!({ "next": next });
142
143    // TODO: Think how to avoid extra deserialization of forwarded_msg here.
144    // (This deserializtion is a double work because the whole Forward message with the attachments
145    // will then be serialized.)
146    let attachment = Attachment::json(
147        serde_json::from_str(forwarded_msg)
148            .kind(ErrorKind::Malformed, "Unable deserialize forwarded message")?,
149    )
150    .finalize();
151
152    let mut msg_builder = Message::build(generate_message_id(), FORWARD_MSG_TYPE.to_owned(), body);
153
154    if let Some(headers) = headers {
155        for (name, value) in headers {
156            msg_builder = msg_builder.header(name.to_owned(), value.to_owned());
157        }
158    }
159
160    msg_builder = msg_builder.attachment(attachment);
161
162    let msg = msg_builder.finalize();
163
164    serde_json::to_string(&msg).kind(ErrorKind::InvalidState, "Unable serialize forward message")
165}
166
167/// Tries to parse plaintext message into `ParsedForward` structure if the message is Forward.
168/// (https://identity.foundation/didcomm-messaging/spec/#messages)
169///
170/// # Parameters
171/// - `msg` plaintext message to try to parse into `ParsedForward` structure
172///
173/// # Returns
174/// `Some` with `ParsedForward` structure if `msg` is Forward message, otherwise `None`.
175pub fn try_parse_forward(msg: &Message) -> Option<ParsedForward> {
176    if msg.type_ != FORWARD_MSG_TYPE {
177        return None;
178    }
179
180    let next = match msg.body {
181        Value::Object(ref body) => match body.get("next") {
182            Some(&Value::String(ref next)) => Some(next),
183            _ => None,
184        },
185        _ => None,
186    };
187
188    if next.is_none() {
189        return None;
190    }
191
192    let next = next.unwrap();
193
194    let json_attachment_data = match msg.attachments {
195        Some(ref attachments) => match &attachments[..] {
196            [attachment, ..] => match &attachment.data {
197                AttachmentData::Json { ref value } => Some(value),
198                _ => None,
199            },
200            _ => None,
201        },
202        None => None,
203    };
204
205    if json_attachment_data.is_none() {
206        return None;
207    }
208
209    let forwarded_msg = &json_attachment_data.unwrap().json;
210
211    Some(ParsedForward {
212        msg,
213        next: next.clone(),
214        forwarded_msg: forwarded_msg.clone(),
215    })
216}
217
218/// Wraps an anoncrypt or authcrypt message into a Forward onion (nested Forward messages).
219/// https://identity.foundation/didcomm-messaging/spec/#messages
220///
221/// # Parameters
222/// - `msg` Anoncrypt or authcrypt message to wrap into Forward onion.
223/// - `headers` (optional) Additional headers to each Forward message of the onion.
224/// - `to` Recipient (a key identifier or DID) of the message being wrapped into Forward onion.
225/// - `routing_keys` Routing keys (each one is a key identifier or DID) to use for encryption of
226/// Forward messages in the onion. The keys must be ordered along the route (so in the opposite
227/// direction to the wrapping steps).
228/// - `enc_alg_anon` Algorithm to use for wrapping into each Forward message of the onion.
229/// - `did_resolver` instance of `DIDResolver` to resolve DIDs.
230///
231/// # Returns
232/// `Result` with the message wrapped into Forward onion or `Error`.
233///
234/// # Errors
235/// - `Malformed` The message to wrap is malformed.
236/// - `DIDNotResolved` Issuer DID not found.
237/// - `DIDUrlNotFound` Issuer authentication verification method is not found.
238/// - `Unsupported` Used crypto or method is unsupported.
239/// - `InvalidState` Indicates a library error.
240pub async fn wrap_in_forward<'dr>(
241    msg: &str,
242    headers: Option<&HashMap<String, Value>>,
243    to: &str,
244    routing_keys: &Vec<String>,
245    enc_alg_anon: &AnonCryptAlg,
246    did_resolver: &'dr (dyn DIDResolver + 'dr),
247) -> Result<String> {
248    let mut tos = routing_keys.clone();
249
250    let mut nexts = tos.clone();
251    nexts.remove(0);
252    nexts.push(to.to_owned());
253
254    tos.reverse();
255    nexts.reverse();
256
257    let mut msg = msg.to_owned();
258
259    for (to_, next_) in tos.iter().zip(nexts.iter()) {
260        msg = build_forward_message(&msg, next_, headers)?;
261        msg = anoncrypt(to_, did_resolver, msg.as_bytes(), enc_alg_anon)
262            .await?
263            .0;
264    }
265
266    Ok(msg)
267}
268
269pub(crate) async fn wrap_in_forward_if_needed<'dr>(
270    msg: &str,
271    to: &str,
272    did_resolver: &'dr (dyn DIDResolver + 'dr),
273    options: &PackEncryptedOptions,
274) -> Result<Option<(String, MessagingServiceMetadata)>> {
275    if !options.forward {
276        return Ok(None);
277    }
278
279    let services_chain =
280        resolve_did_comm_services_chain(to, options.messaging_service.as_deref(), did_resolver)
281            .await?;
282
283    if services_chain.is_empty() {
284        return Ok(None);
285    }
286
287    let mut routing_keys = services_chain[1..]
288        .iter()
289        .map(|service| service.1.uri.clone())
290        .collect::<Vec<_>>();
291
292    routing_keys.append(&mut services_chain.last().unwrap().1.routing_keys.clone());
293
294    if routing_keys.is_empty() {
295        return Ok(None);
296    }
297
298    let forward_msg = wrap_in_forward(
299        msg,
300        options.forward_headers.as_ref(),
301        to,
302        &routing_keys,
303        &options.enc_alg_anon,
304        did_resolver,
305    )
306    .await?;
307
308    let messaging_service = MessagingServiceMetadata {
309        id: services_chain.last().unwrap().0.clone(),
310        service_endpoint: services_chain.first().unwrap().1.uri.clone(),
311    };
312
313    Ok(Some((forward_msg, messaging_service)))
314}