baseid_did/methods/
jwk.rs1use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
9use baseid_core::error::DidError;
10use baseid_crypto::{Jwk, PublicKey};
11
12use crate::document::{DidDocument, VerificationMethod, VerificationRelationship};
13use crate::resolution::{DidResolver, ResolutionMetadata, ResolutionResult};
14use crate::url::DidUrl;
15
16const DID_CONTEXT: &str = "https://www.w3.org/ns/did/v1";
18const JWS_CONTEXT: &str = "https://w3id.org/security/suites/jws-2020/v1";
20
21pub struct DidJwkResolver;
23
24impl DidResolver for DidJwkResolver {
25 fn method(&self) -> &str {
26 "jwk"
27 }
28
29 async fn resolve(&self, did: &str) -> baseid_core::Result<ResolutionResult> {
30 let url = DidUrl::parse(did)?;
31 if url.method != "jwk" {
32 return Err(DidError::UnsupportedMethod.into());
33 }
34
35 let jwk_json = URL_SAFE_NO_PAD
37 .decode(&url.method_id)
38 .map_err(|_| DidError::ResolutionFailed)?;
39
40 let jwk: Jwk = serde_json::from_slice(&jwk_json).map_err(|_| DidError::ResolutionFailed)?;
41
42 let public_key = jwk
43 .to_public_key()
44 .map_err(|_| DidError::ResolutionFailed)?;
45
46 let document = Self::create(&public_key)?;
47
48 Ok(ResolutionResult {
49 document: Some(document),
50 metadata: ResolutionMetadata {
51 content_type: Some("application/did+ld+json".to_string()),
52 error: None,
53 },
54 })
55 }
56}
57
58impl DidJwkResolver {
59 pub fn create(public_key: &PublicKey) -> baseid_core::Result<DidDocument> {
65 let jwk = Jwk::from_public_key(public_key)?;
66
67 let jwk_json = serde_json::to_string(&jwk).map_err(|_| DidError::ResolutionFailed)?;
69
70 let encoded = URL_SAFE_NO_PAD.encode(jwk_json.as_bytes());
71 let did = format!("did:jwk:{encoded}");
72
73 let vm_id = format!("{did}#0");
75 let jwk_value = serde_json::to_value(&jwk).map_err(|_| DidError::ResolutionFailed)?;
76 let vm = VerificationMethod {
77 id: vm_id.clone(),
78 r#type: "JsonWebKey2020".to_string(),
79 controller: did.clone(),
80 public_key_jwk: Some(jwk_value),
81 public_key_multibase: None,
82 };
83
84 Ok(DidDocument {
85 id: did,
86 context: vec![DID_CONTEXT.to_string(), JWS_CONTEXT.to_string()],
87 verification_method: vec![vm],
88 authentication: vec![VerificationRelationship::Reference(vm_id.clone())],
89 assertion_method: vec![VerificationRelationship::Reference(vm_id)],
90 key_agreement: vec![],
91 service: vec![],
92 })
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use baseid_core::types::KeyType;
100 use baseid_crypto::KeyPair;
101
102 #[test]
103 fn create_ed25519() {
104 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
105 let doc = DidJwkResolver::create(&kp.public).unwrap();
106
107 assert!(doc.id.starts_with("did:jwk:"));
108 assert_eq!(doc.context.len(), 2);
109 assert_eq!(doc.context[0], DID_CONTEXT);
110 assert_eq!(doc.context[1], JWS_CONTEXT);
111 assert_eq!(doc.verification_method.len(), 1);
112 assert_eq!(doc.verification_method[0].r#type, "JsonWebKey2020");
113 assert!(doc.verification_method[0].public_key_jwk.is_some());
114 assert_eq!(doc.authentication.len(), 1);
115 assert_eq!(doc.assertion_method.len(), 1);
116 }
117
118 #[test]
119 fn create_p256() {
120 let kp = KeyPair::generate(KeyType::P256).unwrap();
121 let doc = DidJwkResolver::create(&kp.public).unwrap();
122
123 assert!(doc.id.starts_with("did:jwk:"));
124 assert_eq!(doc.verification_method.len(), 1);
125
126 let jwk = doc.verification_method[0].public_key_jwk.as_ref().unwrap();
127 assert_eq!(jwk["crv"], "P-256");
128 assert_eq!(jwk["kty"], "EC");
129 }
130
131 #[tokio::test]
132 async fn resolve_ed25519() {
133 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
134 let created = DidJwkResolver::create(&kp.public).unwrap();
135
136 let resolver = DidJwkResolver;
137 let result = resolver.resolve(&created.id).await.unwrap();
138 let resolved = result.document.unwrap();
139
140 assert_eq!(resolved.id, created.id);
141 assert_eq!(resolved.verification_method.len(), 1);
142 assert_eq!(resolved.verification_method[0].r#type, "JsonWebKey2020");
143 assert_eq!(
144 result.metadata.content_type.as_deref(),
145 Some("application/did+ld+json")
146 );
147 }
148
149 #[tokio::test]
150 async fn resolve_p256() {
151 let kp = KeyPair::generate(KeyType::P256).unwrap();
152 let created = DidJwkResolver::create(&kp.public).unwrap();
153
154 let resolver = DidJwkResolver;
155 let result = resolver.resolve(&created.id).await.unwrap();
156 let resolved = result.document.unwrap();
157
158 assert_eq!(resolved.id, created.id);
159 let jwk = resolved.verification_method[0]
160 .public_key_jwk
161 .as_ref()
162 .unwrap();
163 assert_eq!(jwk["crv"], "P-256");
164 assert_eq!(jwk["kty"], "EC");
165 }
166
167 #[tokio::test]
168 async fn resolve_roundtrip() {
169 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
170 let created = DidJwkResolver::create(&kp.public).unwrap();
171 let did_string = created.id.clone();
172
173 let resolver = DidJwkResolver;
174 let result = resolver.resolve(&did_string).await.unwrap();
175 let resolved = result.document.unwrap();
176
177 let jwk_value = resolved.verification_method[0]
180 .public_key_jwk
181 .as_ref()
182 .unwrap();
183 let jwk: Jwk = serde_json::from_value(jwk_value.clone()).unwrap();
184 let recovered = jwk.to_public_key().unwrap();
185
186 assert_eq!(recovered.bytes, kp.public.bytes);
187 assert_eq!(recovered.key_type, kp.public.key_type);
188 }
189
190 #[tokio::test]
191 async fn resolve_wrong_method() {
192 let resolver = DidJwkResolver;
193 let result = resolver
194 .resolve("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK")
195 .await;
196 assert!(result.is_err());
197 }
198
199 #[tokio::test]
200 async fn resolve_invalid_base64() {
201 let resolver = DidJwkResolver;
202 let result = resolver.resolve("did:jwk:not-valid-base64!!!").await;
203 assert!(result.is_err());
204 }
205
206 #[tokio::test]
207 async fn create_and_resolve_all_key_types() {
208 let resolver = DidJwkResolver;
209
210 for key_type in [
211 KeyType::Ed25519,
212 KeyType::P256,
213 KeyType::P384,
214 KeyType::Secp256k1,
215 ] {
216 let kp = KeyPair::generate(key_type).unwrap();
217 let created = DidJwkResolver::create(&kp.public).unwrap();
218
219 assert!(
220 created.id.starts_with("did:jwk:"),
221 "DID for {key_type:?} should start with did:jwk:"
222 );
223
224 let result = resolver.resolve(&created.id).await.unwrap();
225 let resolved = result.document.unwrap();
226
227 assert_eq!(resolved.id, created.id);
228 assert_eq!(resolved.verification_method.len(), 1);
229 assert_eq!(resolved.verification_method[0].r#type, "JsonWebKey2020");
230
231 let jwk_value = resolved.verification_method[0]
233 .public_key_jwk
234 .as_ref()
235 .unwrap();
236 let jwk: Jwk = serde_json::from_value(jwk_value.clone()).unwrap();
237 let recovered = jwk.to_public_key().unwrap();
238
239 assert_eq!(
240 recovered.bytes, kp.public.bytes,
241 "key bytes mismatch for {key_type:?}"
242 );
243 assert_eq!(
244 recovered.key_type, key_type,
245 "key type mismatch for {key_type:?}"
246 );
247 }
248 }
249}