did_jwk/
lib.rs

1use ssi_dids_core::{
2    document::{
3        self,
4        representation::{self, MediaType},
5        verification_method::DIDVerificationMethod,
6        VerificationRelationships,
7    },
8    resolution::{DIDMethodResolver, Error, Metadata, Options, Output},
9    DIDBuf, DIDMethod, DIDURLBuf, Document, RelativeDIDURLBuf,
10};
11use ssi_jwk::JWK;
12use ssi_verification_methods::ProofPurposes;
13
14mod vm;
15pub use vm::*;
16
17/// Verification method returned by `did:jwk`.
18pub struct VerificationMethod {
19    pub type_: VerificationMethodType,
20
21    /// Verification method identifier.
22    pub id: DIDURLBuf,
23
24    // Key controller.
25    pub controller: DIDBuf,
26
27    /// Public key.
28    pub public_key: PublicKey,
29}
30
31impl VerificationMethod {
32    pub fn new(
33        type_: VerificationMethodType,
34        id: DIDURLBuf,
35        controller: DIDBuf,
36        public_key: PublicKey,
37    ) -> Self {
38        Self {
39            type_,
40            id,
41            controller,
42            public_key,
43        }
44    }
45}
46
47impl From<VerificationMethod> for DIDVerificationMethod {
48    fn from(value: VerificationMethod) -> Self {
49        DIDVerificationMethod::new(
50            value.id,
51            value.type_.name().to_owned(),
52            value.controller,
53            [(
54                value.public_key.property().to_owned(),
55                value.public_key.into_json(),
56            )]
57            .into_iter()
58            .collect(),
59        )
60    }
61}
62
63/// JSON Web Token (`jwt`) DID method.
64pub struct DIDJWK;
65
66impl DIDJWK {
67    /// Generates a JWK DID from the given key.
68    ///
69    /// Note: the resulting DID points to the DID document containing the key,
70    /// not the key itself. Use [`Self::generate_url`] to generate a DID URL
71    /// pointing to the key.
72    ///
73    /// # Example
74    ///
75    /// ```
76    /// use did_jwk::DIDJWK;
77    ///
78    /// let jwk: ssi_jwk::JWK = serde_json::from_value(serde_json::json!({
79    ///   "crv": "P-256",
80    ///   "kty": "EC",
81    ///   "x": "acbIQiuMs3i8_uszEjJ2tpTtRM4EU3yz91PH6CdH2V0",
82    ///   "y": "_KcyLj9vWMptnmKtm46GqDz8wf74I5LKgrl2GzH3nSE"
83    /// })).unwrap();
84    ///
85    /// let did = DIDJWK::generate(&jwk);
86    /// assert_eq!(did, "did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9");
87    /// ```
88    pub fn generate(key: &JWK) -> DIDBuf {
89        let key = key.to_public();
90        let normalized = serde_jcs::to_string(&key).unwrap();
91        let method_id = multibase::Base::Base64Url.encode(normalized);
92        DIDBuf::new(format!("did:jwk:{method_id}").into_bytes()).unwrap()
93    }
94
95    /// Generates a JWK DID URL referring to the given key.
96    pub fn generate_url(key: &JWK) -> DIDURLBuf {
97        let key = key.to_public();
98        let normalized = serde_jcs::to_string(&key).unwrap();
99        let method_id = multibase::Base::Base64Url.encode(normalized);
100        DIDURLBuf::new(format!("did:jwk:{method_id}#0").into_bytes()).unwrap()
101    }
102}
103
104impl DIDMethod for DIDJWK {
105    const DID_METHOD_NAME: &'static str = "jwk";
106}
107
108impl DIDMethodResolver for DIDJWK {
109    async fn resolve_method_representation<'a>(
110        &'a self,
111        method_specific_id: &'a str,
112        options: Options,
113    ) -> Result<Output<Vec<u8>>, Error> {
114        resolve_method_representation(method_specific_id, options)
115    }
116}
117
118fn resolve_method_representation(
119    method_specific_id: &str,
120    options: Options,
121) -> Result<Output<Vec<u8>>, Error> {
122    let data = multibase::Base::decode(&multibase::Base::Base64Url, method_specific_id)
123        .map_err(|_| Error::InvalidMethodSpecificId(method_specific_id.to_string()))?;
124
125    let jwk: JWK = serde_json::from_slice(&data)
126        .map_err(|_| Error::InvalidMethodSpecificId(method_specific_id.to_string()))?;
127
128    let public_jwk = jwk.to_public();
129
130    if public_jwk != jwk {
131        return Err(Error::InvalidMethodSpecificId(
132            method_specific_id.to_string(),
133        ));
134    }
135
136    let did = DIDBuf::new(format!("did:jwk:{method_specific_id}").into_bytes()).unwrap();
137
138    let vm_type = match options.parameters.public_key_format {
139        Some(name) => VerificationMethodType::from_name(&name).ok_or_else(|| {
140            Error::Internal(format!(
141                "verification method type `{name}` unsupported by did:jwk"
142            ))
143        })?,
144        None => VerificationMethodType::Multikey,
145    };
146
147    let public_key = vm_type.encode_public_key(jwk)?;
148
149    let document = Document {
150        verification_method: vec![VerificationMethod::new(
151            vm_type,
152            DIDURLBuf::new(format!("did:jwk:{method_specific_id}#0").into_bytes()).unwrap(),
153            did.clone(),
154            public_key,
155        )
156        .into()],
157        verification_relationships: VerificationRelationships::from_reference(
158            RelativeDIDURLBuf::new(b"#0".to_vec()).unwrap().into(),
159            ProofPurposes::all(),
160        ),
161        ..Document::new(did)
162    };
163
164    let represented = document.into_representation(representation::Options::from_media_type(
165        options.accept.unwrap_or(MediaType::JsonLd),
166        || representation::json_ld::Options {
167            context: representation::json_ld::Context::array(
168                representation::json_ld::DIDContext::V1,
169                vec![vm_type.context_entry()],
170            ),
171        },
172    ));
173
174    Ok(Output::new(
175        represented.to_bytes(),
176        document::Metadata::default(),
177        Metadata::from_content_type(Some(represented.media_type().to_string())),
178    ))
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use ssi_dids_core::{resolution, DIDResolver, DIDURL};
185
186    #[async_std::test]
187    async fn p256_roundtrip() {
188        let jwk = JWK::generate_p256();
189
190        let expected_public_key = VerificationMethodType::Multikey
191            .encode_public_key(jwk.clone())
192            .unwrap()
193            .into_json();
194
195        let did_url = DIDJWK::generate_url(&jwk);
196        let resolved = DIDJWK.dereference(&did_url).await.unwrap();
197
198        let vm = resolved.content.as_verification_method().unwrap();
199
200        let public_key = vm.properties.get("publicKeyMultibase").unwrap();
201
202        assert_eq!(*public_key, expected_public_key);
203    }
204
205    #[async_std::test]
206    async fn from_p256() {
207        let did_url = DIDURL::new(b"did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#0").unwrap();
208        let resolved = DIDJWK.dereference(did_url).await.unwrap();
209
210        let vm = resolved.content.as_verification_method().unwrap();
211
212        let public_key = vm.properties.get("publicKeyMultibase").unwrap();
213
214        assert_eq!(vm.id, did_url);
215        assert_eq!(vm.controller, did_url.did());
216
217        let jwk = serde_json::from_value(serde_json::json!({
218            "kty": "EC",
219            "crv": "P-256",
220            "x": "acbIQiuMs3i8_uszEjJ2tpTtRM4EU3yz91PH6CdH2V0",
221            "y": "_KcyLj9vWMptnmKtm46GqDz8wf74I5LKgrl2GzH3nSE"
222        }))
223        .unwrap();
224
225        let expected_public_key = VerificationMethodType::Multikey
226            .encode_public_key(jwk)
227            .unwrap()
228            .into_json();
229
230        assert_eq!(*public_key, expected_public_key);
231    }
232
233    #[async_std::test]
234    async fn to_p256() {
235        let jwk: ssi_jwk::JWK = serde_json::from_value(serde_json::json!({
236            "crv": "P-256",
237            "kty": "EC",
238            "x": "acbIQiuMs3i8_uszEjJ2tpTtRM4EU3yz91PH6CdH2V0",
239            "y": "_KcyLj9vWMptnmKtm46GqDz8wf74I5LKgrl2GzH3nSE"
240        }))
241        .unwrap();
242
243        let expected_public_key = VerificationMethodType::Multikey
244            .encode_public_key(jwk.clone())
245            .unwrap()
246            .into_json();
247
248        let expected = "did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9";
249        let did = DIDJWK::generate(&jwk);
250        assert_eq!(did, expected);
251
252        let resolved = DIDJWK
253            .resolve_with(&did, resolution::Options::default())
254            .await
255            .unwrap();
256
257        let vm = resolved.document.verification_method.first().unwrap();
258
259        let public_key = vm.properties.get("publicKeyMultibase").unwrap();
260
261        assert_eq!(*public_key, expected_public_key);
262    }
263
264    #[async_std::test]
265    async fn from_x25519() {
266        let did_url = DIDURL::new(b"did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9#0").unwrap();
267
268        let mut options = resolution::Options::default();
269        options.parameters.public_key_format = Some("JsonWebKey2020".to_owned());
270
271        let resolved = DIDJWK.dereference_with(did_url, options).await.unwrap();
272
273        let vm = resolved.content.as_verification_method().unwrap();
274
275        let public_key = vm.properties.get("publicKeyJwk").unwrap();
276
277        assert_eq!(vm.id, did_url);
278        assert_eq!(vm.controller, did_url.did());
279
280        let jwk = serde_json::from_value(serde_json::json!({
281            "kty": "OKP",
282            "crv": "X25519",
283            "use": "enc",
284            "x": "3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08"
285        }))
286        .unwrap();
287
288        let expected_public_key = VerificationMethodType::JsonWebKey2020
289            .encode_public_key(jwk)
290            .unwrap()
291            .into_json();
292
293        assert_eq!(*public_key, expected_public_key);
294    }
295
296    #[async_std::test]
297    async fn to_x25519() {
298        let jwk: JWK = serde_json::from_value(serde_json::json!({
299            "kty": "OKP",
300            "crv": "X25519",
301            "use": "enc",
302            "x": "3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08"
303        }))
304        .unwrap();
305
306        let expected_public_key = VerificationMethodType::JsonWebKey2020
307            .encode_public_key(jwk.clone())
308            .unwrap()
309            .into_json();
310
311        let expected = "did:jwk:eyJjcnYiOiJYMjU1MTkiLCJrdHkiOiJPS1AiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9";
312        let did = DIDJWK::generate(&jwk);
313        assert_eq!(did, expected);
314
315        let mut options = resolution::Options::default();
316        options.parameters.public_key_format = Some("JsonWebKey2020".to_owned());
317
318        let resolved = DIDJWK.resolve_with(&did, options).await.unwrap();
319
320        let vm = resolved.document.verification_method.first().unwrap();
321
322        let public_key = vm.properties.get("publicKeyJwk").unwrap();
323
324        assert_eq!(*public_key, expected_public_key);
325    }
326
327    #[async_std::test]
328    async fn deny_private_key() {
329        let jwk = JWK::generate_ed25519().unwrap();
330        let json = serde_jcs::to_string(&jwk).unwrap();
331        let json_encoded = multibase::Base::Base64Url.encode(&json);
332        let did = DIDBuf::new(format!("did:jwk:{}", json_encoded).into_bytes()).unwrap();
333        assert!(matches!(
334            DIDJWK.resolve(&did).await.unwrap_err(),
335            Error::InvalidMethodSpecificId(_)
336        ),);
337    }
338}