1use jose_jwk::Jwk;
2use serde::{
3 Deserialize,
4 Serialize,
5};
6use serde_json::Value;
7use serde_with::{
8 serde_as,
9 skip_serializing_none,
10};
11use smol_str::SmolStr;
12
13use crate::{
14 did::Did,
15 did_url::{
16 relative::RelativeDidUrl,
17 url::DidUrl,
18 },
19};
20
21#[skip_serializing_none]
22#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
23#[serde(rename_all = "camelCase")]
24#[serde_as]
25pub struct Document {
26 #[serde(rename = "@context")]
29 #[serde_as(as = "Option<OneOrMany<_>>")]
30 pub context: Option<Vec<Value>>,
31 pub id: Did,
32 pub also_known_as: Option<Vec<String>>,
33 #[serde_as(as = "Option<OneOrMany<_>>")]
34 pub controller: Option<Vec<Did>>,
35 pub verification_method: Option<Vec<VerificationMethodMap>>,
36 pub authentication: Option<Vec<VerificationMethod>>,
37 pub assertion_method: Option<Vec<VerificationMethod>>,
38 pub key_agreement: Option<Vec<VerificationMethod>>,
39 pub capability_invocation: Option<Vec<VerificationMethod>>,
40 pub capability_delegation: Option<Vec<VerificationMethod>>,
41 pub service: Option<Vec<ServiceEndpoint>>,
42}
43
44impl Document {
45 #[must_use]
52 pub fn resolve_verification_method_url(
53 &self,
54 url: &DidUrl,
55 role: VerificationRole,
56 ) -> Option<&VerificationMethodMap> {
57 let methods = match role {
58 VerificationRole::Assertion => self.assertion_method.as_deref(),
59 VerificationRole::Authentication => self.authentication.as_deref(),
60 VerificationRole::CapabilityDelegation => self.capability_delegation.as_deref(),
61 VerificationRole::CapabilityInvocation => self.capability_invocation.as_deref(),
62 VerificationRole::KeyAgreement => self.key_agreement.as_deref(),
63 }
64 .unwrap_or_default();
65
66 let mut referenced = None;
69
70 for method in methods {
71 let denotes_url = match method {
72 VerificationMethod::Map(map) => {
73 if map.id == *url && *map.id.did() == self.id {
74 return Some(map);
75 }
76 continue;
77 }
78 VerificationMethod::RelativeUrl(relative) => url.matches_relative(relative),
79 VerificationMethod::Url(reference) => reference == url,
80 };
81
82 if denotes_url
83 && let Some(found) = *referenced.get_or_insert_with(|| self.lookup_method(url))
84 {
85 return Some(found);
86 }
87 }
88
89 None
90 }
91
92 fn lookup_method(&self, url: &DidUrl) -> Option<&VerificationMethodMap> {
93 if *url.did() != self.id {
94 return None;
95 }
96
97 self.verification_method
98 .as_deref()?
99 .iter()
100 .find(|method| method.id == *url)
101 }
102
103 #[must_use]
111 pub fn resolve_verification_method<'a>(
112 &'a self,
113 method: &'a VerificationMethod,
114 ) -> Option<&'a VerificationMethodMap> {
115 let map = match method {
116 VerificationMethod::Map(map) => map.as_ref(),
117 VerificationMethod::RelativeUrl(relative_url) => {
118 self.resolve_relative_url(relative_url)?
119 }
120 VerificationMethod::Url(url) => {
121 if *url.did() != self.id {
122 return None;
124 }
125
126 self.resolve_relative_url(&url.to_relative()?)?
127 }
128 };
129
130 (*map.id.did() == self.id).then_some(map)
131 }
132
133 fn resolve_relative_url(&self, url: &RelativeDidUrl) -> Option<&VerificationMethodMap> {
134 self.verification_method
135 .as_deref()?
136 .iter()
137 .find(|method| method.id.matches_relative(url))
138 }
139}
140
141#[derive(Debug, Copy, Clone, PartialEq, Eq)]
142pub enum VerificationRole {
143 Assertion,
144 Authentication,
145 CapabilityDelegation,
146 CapabilityInvocation,
147 KeyAgreement,
148}
149
150#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
151#[serde(untagged)]
152pub enum VerificationMethod {
153 Map(Box<VerificationMethodMap>),
154 RelativeUrl(RelativeDidUrl),
155 Url(DidUrl),
156}
157
158#[skip_serializing_none]
159#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
160pub struct VerificationMethodMap {
161 pub id: DidUrl,
162 pub controller: Did,
163 #[serde(rename = "type")]
164 pub typ: SmolStr,
165 pub public_key_jwk: Option<Jwk>,
166 pub public_key_multibase: Option<String>,
168}
169
170#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
171#[serde(rename_all = "camelCase")]
172#[serde_as]
173pub struct ServiceEndpoint {
174 pub id: String,
175 #[serde(rename = "type")]
176 #[serde_as(as = "OneOrMany<_>")]
177 pub typ: Vec<String>,
178 #[serde_as(as = "OneOrMany<_>")]
182 pub service_endpoint: Vec<String>,
183}
184
185#[cfg(test)]
186mod tests {
187 use std::str::FromStr;
188
189 use smol_str::SmolStr;
190
191 use super::*;
192 use crate::did_url::relative::RelativeDidUrlPath;
193
194 fn did(s: &str) -> Did {
195 Did::from_str(s).expect("valid DID")
196 }
197
198 fn url(did: &Did, fragment: &str) -> DidUrl {
199 DidUrl::new(did.clone(), None, None, Some(SmolStr::new(fragment))).expect("valid DID URL")
200 }
201
202 fn map(id: DidUrl, controller: &Did) -> VerificationMethodMap {
203 VerificationMethodMap {
204 id,
205 controller: controller.clone(),
206 typ: "JsonWebKey2020".into(),
207 public_key_jwk: None,
208 public_key_multibase: None,
209 }
210 }
211
212 fn document(id: Did, authentication: Vec<VerificationMethod>) -> Document {
213 Document {
214 context: None,
215 id,
216 also_known_as: None,
217 controller: None,
218 verification_method: None,
219 authentication: Some(authentication),
220 assertion_method: None,
221 key_agreement: None,
222 capability_invocation: None,
223 capability_delegation: None,
224 service: None,
225 }
226 }
227
228 #[test]
229 fn resolves_embedded_map() {
230 let me = did("did:web:example.com");
231 let key = url(&me, "key1");
232 let mut doc = document(
233 me.clone(),
234 vec![VerificationMethod::Map(Box::new(map(key.clone(), &me)))],
235 );
236 doc.verification_method = None;
237
238 assert_eq!(
239 doc.resolve_verification_method_url(&key, VerificationRole::Authentication),
240 Some(&map(key.clone(), &me))
241 );
242 }
243
244 #[test]
245 fn resolves_absolute_and_relative_references() {
246 let me = did("did:web:example.com");
247 let key = url(&me, "key1");
248
249 let relative =
250 RelativeDidUrl::new(RelativeDidUrlPath::Empty, None, Some(SmolStr::new("key1")))
251 .expect("valid relative DID URL");
252
253 for reference in [
254 VerificationMethod::Url(key.clone()),
255 VerificationMethod::RelativeUrl(relative),
256 ] {
257 let mut doc = document(me.clone(), vec![reference]);
258 doc.verification_method = Some(vec![map(key.clone(), &me)]);
259
260 assert_eq!(
261 doc.resolve_verification_method_url(&key, VerificationRole::Authentication),
262 Some(&map(key.clone(), &me))
263 );
264 }
265 }
266
267 #[test]
268 fn returns_none_for_unlisted_url() {
269 let me = did("did:web:example.com");
270 let key = url(&me, "key1");
271 let other = url(&me, "key2");
272
273 let mut doc = document(me.clone(), vec![VerificationMethod::Url(key.clone())]);
274 doc.verification_method = Some(vec![map(key, &me)]);
275
276 assert_eq!(
277 doc.resolve_verification_method_url(&other, VerificationRole::Authentication),
278 None
279 );
280 }
281
282 #[test]
283 fn rejects_embedded_map_claiming_another_did() {
284 let attacker = did("did:web:evil.example");
285 let victim = did("did:web:victim.example");
286 let victim_key = url(&victim, "key1");
287
288 let doc = document(
289 attacker,
290 vec![VerificationMethod::Map(Box::new(map(
291 victim_key.clone(),
292 &victim,
293 )))],
294 );
295
296 assert_eq!(
297 doc.resolve_verification_method_url(&victim_key, VerificationRole::Authentication),
298 None,
299 "a document must not speak for an identifier it does not control"
300 );
301 }
302
303 #[test]
304 fn rejects_verification_method_entry_for_another_did() {
305 let attacker = did("did:web:evil.example");
306 let victim = did("did:web:victim.example");
307 let victim_key = url(&victim, "key1");
308
309 let mut doc = document(attacker, vec![VerificationMethod::Url(victim_key.clone())]);
310 doc.verification_method = Some(vec![map(victim_key.clone(), &victim)]);
311
312 assert_eq!(
313 doc.resolve_verification_method_url(&victim_key, VerificationRole::Authentication),
314 None
315 );
316 }
317
318 #[test]
319 fn role_is_respected() {
320 let me = did("did:web:example.com");
321 let key = url(&me, "key1");
322
323 let mut doc = document(me.clone(), vec![VerificationMethod::Url(key.clone())]);
324 doc.verification_method = Some(vec![map(key.clone(), &me)]);
325
326 assert!(
327 doc.resolve_verification_method_url(&key, VerificationRole::Authentication)
328 .is_some()
329 );
330 assert!(
331 doc.resolve_verification_method_url(&key, VerificationRole::KeyAgreement)
332 .is_none()
333 );
334 }
335}