aph_core/discovery/
did_document.rs1#[derive(std::fmt::Debug, std::clone::Clone, serde::Serialize, serde::Deserialize)]
18pub struct DidDocument {
19 pub id: String,
21 #[serde(default, rename = "verificationMethod")]
24 pub verification_method: std::vec::Vec<VerificationMethod>,
25 #[serde(default, rename = "assertionMethod")]
27 pub assertion_method: std::vec::Vec<serde_json::Value>,
28}
29
30#[derive(std::fmt::Debug, std::clone::Clone, serde::Serialize, serde::Deserialize)]
32pub struct VerificationMethod {
33 pub id: String,
35 #[serde(rename = "type")]
37 pub r#type: String,
38 #[serde(default)]
40 pub controller: std::option::Option<String>,
41 #[serde(default, rename = "publicKeyMultibase")]
43 pub public_key_multibase: std::option::Option<String>,
44 #[serde(default, rename = "publicKeyJwk")]
47 pub public_key_jwk: std::option::Option<serde_json::Value>,
48}
49
50pub fn parse_did_document(
52 json: &str,
53) -> std::result::Result<DidDocument, crate::errors::AphError> {
54 match serde_json::from_str::<DidDocument>(json) {
55 std::result::Result::Ok(doc) => std::result::Result::Ok(doc),
56 std::result::Result::Err(_) => {
59 std::result::Result::Err(crate::errors::AphError::NotaryServiceUnreachable)
60 }
61 }
62}
63
64impl DidDocument {
65 pub fn allows_assertion(&self, did_url: &str) -> bool {
71 self.assertion_method.iter().any(|entry| match entry {
72 serde_json::Value::String(s) => s == did_url,
73 serde_json::Value::Object(o) => {
74 o.get("id").and_then(|v| v.as_str()) == std::option::Option::Some(did_url)
75 }
76 _ => false,
77 })
78 }
79
80 pub fn resolve_key(
87 &self,
88 did_url: &str,
89 ) -> std::result::Result<super::NotaryPublicKey, crate::errors::AphError> {
90 let parsed = super::DidUrl::parse(did_url);
91 let entry = match parsed.fragment {
92 std::option::Option::Some(_) => self
93 .verification_method
94 .iter()
95 .find(|vm| vm.id == did_url)
96 .or_else(|| {
99 let want = std::format!("#{}", parsed.fragment.clone().unwrap_or_default());
100 self.verification_method.iter().find(|vm| vm.id == want)
101 }),
102 std::option::Option::None => {
103 if self.verification_method.len() == 1 {
104 self.verification_method.first()
105 } else {
106 std::option::Option::None
107 }
108 }
109 };
110
111 let entry = match entry {
112 std::option::Option::Some(e) => e,
113 std::option::Option::None => {
120 return std::result::Result::Err(crate::errors::AphError::notary_key_not_published(
121 std::format!("DID Document names no verificationMethod matching `{}`", did_url),
122 ));
123 }
124 };
125
126 let multibase = match entry.public_key_multibase.as_deref() {
127 std::option::Option::Some(m) => m,
128 std::option::Option::None => {
129 return std::result::Result::Err(crate::errors::AphError::unsupported_algorithm(
132 "verificationMethod without publicKeyMultibase (publicKeyJwk not supported)",
133 ));
134 }
135 };
136
137 match crate::crypto::did_key::decode_multibase_key(multibase)? {
138 crate::crypto::did_key::DecodedDidKey::Ed25519(key) => {
139 std::result::Result::Ok(super::NotaryPublicKey {
140 algorithm: super::KeyAlgorithm::Ed25519,
141 key_bytes: key.as_bytes().to_vec(),
142 kid: parsed.fragment,
143 })
144 }
145 crate::crypto::did_key::DecodedDidKey::P256(bytes) => {
146 std::result::Result::Ok(super::NotaryPublicKey {
147 algorithm: super::KeyAlgorithm::P256,
148 key_bytes: bytes,
149 kid: parsed.fragment,
150 })
151 }
152 }
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 const SPEC_EXAMPLE: &str = r#"{
160 "@context": ["https://www.w3.org/ns/did/v1"],
161 "id": "did:web:notary.example.com",
162 "verificationMethod": [
163 {
164 "id": "did:web:notary.example.com#k1",
165 "type": "Multikey",
166 "controller": "did:web:notary.example.com",
167 "publicKeyMultibase": "z6MkpTHR8VNsBxYAAWHut2Geadd9jSdoVTwBaPaeT1KhFmkV"
168 }
169 ],
170 "assertionMethod": ["did:web:notary.example.com#k1"]
171 }"#;
172
173 #[test]
174 fn parses_the_spec_example_document() {
175 let doc = super::parse_did_document(SPEC_EXAMPLE).unwrap();
178 std::assert_eq!(doc.id, "did:web:notary.example.com");
179 std::assert_eq!(doc.verification_method.len(), 1);
180 }
181
182 #[test]
183 fn resolves_the_key_named_by_the_fragment() {
184 let doc = super::parse_did_document(SPEC_EXAMPLE).unwrap();
186 let key = doc.resolve_key("did:web:notary.example.com#k1").unwrap();
187 std::assert_eq!(key.algorithm, crate::discovery::KeyAlgorithm::Ed25519);
188 std::assert_eq!(key.key_bytes.len(), 32);
189 std::assert_eq!(key.kid.as_deref(), Some("k1"));
190 }
191
192 #[test]
193 fn unknown_fragment_does_not_fall_back_to_another_key() {
194 let doc = super::parse_did_document(SPEC_EXAMPLE).unwrap();
197 std::assert!(doc.resolve_key("did:web:notary.example.com#k9").is_err());
198 }
199
200 #[test]
201 fn fragmentless_url_resolves_only_when_the_document_is_unambiguous() {
202 let doc = super::parse_did_document(SPEC_EXAMPLE).unwrap();
205 std::assert!(doc.resolve_key("did:web:notary.example.com").is_ok());
206
207 let multi = r#"{
208 "id": "did:web:n.example",
209 "verificationMethod": [
210 {"id":"did:web:n.example#a","type":"Multikey","publicKeyMultibase":"z6MkpTHR8VNsBxYAAWHut2Geadd9jSdoVTwBaPaeT1KhFmkV"},
211 {"id":"did:web:n.example#b","type":"Multikey","publicKeyMultibase":"z6MkpTHR8VNsBxYAAWHut2Geadd9jSdoVTwBaPaeT1KhFmkV"}
212 ]
213 }"#;
214 let doc = super::parse_did_document(multi).unwrap();
215 std::assert!(doc.resolve_key("did:web:n.example").is_err());
216 }
217
218 #[test]
219 fn unknown_document_fields_are_tolerated() {
220 let doc = r##"{
225 "@context": ["https://www.w3.org/ns/did/v1"],
226 "id": "did:web:n.example",
227 "service": [{"id":"#hub","type":"Hub","serviceEndpoint":"https://n.example/hub"}],
228 "authentication": ["did:web:n.example#k1"],
229 "verificationMethod": [
230 {"id":"did:web:n.example#k1","type":"Multikey","publicKeyMultibase":"z6MkpTHR8VNsBxYAAWHut2Geadd9jSdoVTwBaPaeT1KhFmkV"}
231 ]
232 }"##;
233 std::assert!(super::parse_did_document(doc).unwrap().resolve_key("did:web:n.example#k1").is_ok());
234 }
235
236 #[test]
237 fn assertion_method_membership_is_reported() {
238 let doc = super::parse_did_document(SPEC_EXAMPLE).unwrap();
241 std::assert!(doc.allows_assertion("did:web:notary.example.com#k1"));
242 std::assert!(!doc.allows_assertion("did:web:notary.example.com#k2"));
243 }
244
245 #[test]
246 fn jwk_only_entry_reports_unsupported_rather_than_bad_signature() {
247 let doc = r#"{
250 "id": "did:web:n.example",
251 "verificationMethod": [
252 {"id":"did:web:n.example#k1","type":"JsonWebKey","publicKeyJwk":{"kty":"OKP"}}
253 ]
254 }"#;
255 let parsed = super::parse_did_document(doc).unwrap();
256 std::assert_eq!(
257 parsed.resolve_key("did:web:n.example#k1").unwrap_err().code(),
258 "APH_E010"
259 );
260 }
261
262 #[test]
263 fn malformed_json_reports_the_notary_as_unreachable() {
264 std::assert_eq!(
267 super::parse_did_document("{not json").unwrap_err().code(),
268 "APH_E008"
269 );
270 }
271}