identity_storage/storage/
jwp_document_ext.rs

1// Copyright 2020-2024 IOTA Stiftung, Fondazione Links
2// SPDX-License-Identifier: Apache-2.0
3
4use super::JwkStorageDocumentError as Error;
5use crate::key_id_storage::MethodDigest;
6use crate::try_undo_key_generation;
7use crate::JwkGenOutput;
8use crate::JwkStorageBbsPlusExt;
9use crate::KeyIdStorage;
10use crate::KeyType;
11use crate::Storage;
12use crate::StorageResult;
13use async_trait::async_trait;
14use identity_core::common::Object;
15use identity_core::convert::ToJson;
16use identity_credential::credential::Credential;
17use identity_credential::credential::Jpt;
18use identity_credential::credential::JwpCredentialOptions;
19use identity_credential::presentation::JwpPresentationOptions;
20use identity_credential::presentation::SelectiveDisclosurePresentation;
21use identity_did::DIDUrl;
22use identity_document::document::CoreDocument;
23use identity_verification::MethodData;
24use identity_verification::MethodScope;
25use identity_verification::VerificationMethod;
26use jsonprooftoken::encoding::SerializationType;
27use jsonprooftoken::jpa::algs::ProofAlgorithm;
28use jsonprooftoken::jpt::claims::JptClaims;
29use jsonprooftoken::jwk::key::Jwk;
30use jsonprooftoken::jwp::header::IssuerProtectedHeader;
31use jsonprooftoken::jwp::header::PresentationProtectedHeader;
32use jsonprooftoken::jwp::issued::JwpIssuedBuilder;
33use serde::de::DeserializeOwned;
34use serde::Serialize;
35
36/// Handle JWP-based operations on DID Documents.
37#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
38#[cfg_attr(feature = "send-sync-storage", async_trait)]
39pub trait JwpDocumentExt {
40  /// Generate new key material in the given `storage` and insert a new verification method with the corresponding
41  /// public key material into the DID document. This supports BBS+ keys.
42  async fn generate_method_jwp<K, I>(
43    &mut self,
44    storage: &Storage<K, I>,
45    key_type: KeyType,
46    alg: ProofAlgorithm,
47    fragment: Option<&str>,
48    scope: MethodScope,
49  ) -> StorageResult<String>
50  where
51    K: JwkStorageBbsPlusExt,
52    I: KeyIdStorage;
53
54  /// Compute a JWP in the Issued form representing the Verifiable Credential
55  /// See [JSON Web Proof draft](https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof#name-issued-form)
56  async fn create_issued_jwp<K, I>(
57    &self,
58    storage: &Storage<K, I>,
59    fragment: &str,
60    jpt_claims: &JptClaims,
61    options: &JwpCredentialOptions,
62  ) -> StorageResult<String>
63  where
64    K: JwkStorageBbsPlusExt,
65    I: KeyIdStorage;
66
67  /// Compute a JWP in the Presented form representing the presented Verifiable Credential after the Selective
68  /// Disclosure of attributes See [JSON Web Proof draft](https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-proof#name-presented-form)
69  async fn create_presented_jwp(
70    &self,
71    presentation: &mut SelectiveDisclosurePresentation,
72    method_id: &str,
73    options: &JwpPresentationOptions,
74  ) -> StorageResult<String>;
75
76  /// Produces a JPT where the payload is produced from the given `credential`.
77  async fn create_credential_jpt<K, I, T>(
78    &self,
79    credential: &Credential<T>,
80    storage: &Storage<K, I>,
81    fragment: &str,
82    options: &JwpCredentialOptions,
83    custom_claims: Option<Object>,
84  ) -> StorageResult<Jpt>
85  where
86    K: JwkStorageBbsPlusExt,
87    I: KeyIdStorage,
88    T: ToOwned<Owned = T> + Serialize + DeserializeOwned + Sync;
89
90  /// Produces a JPT where the payload contains the Selective Disclosed attributes of a `credential`.
91  async fn create_presentation_jpt(
92    &self,
93    presentation: &mut SelectiveDisclosurePresentation,
94    method_id: &str,
95    options: &JwpPresentationOptions,
96  ) -> StorageResult<Jpt>;
97}
98
99// ====================================================================================================================
100// CoreDocument
101// ====================================================================================================================
102
103generate_method_for_document_type!(
104  CoreDocument,
105  ProofAlgorithm,
106  JwkStorageBbsPlusExt,
107  JwkStorageBbsPlusExt::generate_bbs,
108  generate_method_core_document
109);
110
111#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
112#[cfg_attr(feature = "send-sync-storage", async_trait)]
113impl JwpDocumentExt for CoreDocument {
114  async fn generate_method_jwp<K, I>(
115    &mut self,
116    storage: &Storage<K, I>,
117    key_type: KeyType,
118    alg: ProofAlgorithm,
119    fragment: Option<&str>,
120    scope: MethodScope,
121  ) -> StorageResult<String>
122  where
123    K: JwkStorageBbsPlusExt,
124    I: KeyIdStorage,
125  {
126    generate_method_core_document(self, storage, key_type, alg, fragment, scope).await
127  }
128
129  async fn create_issued_jwp<K, I>(
130    &self,
131    storage: &Storage<K, I>,
132    fragment: &str,
133    jpt_claims: &JptClaims,
134    options: &JwpCredentialOptions,
135  ) -> StorageResult<String>
136  where
137    K: JwkStorageBbsPlusExt,
138    I: KeyIdStorage,
139  {
140    // Obtain the method corresponding to the given fragment.
141    let method: &VerificationMethod = self.resolve_method(fragment, None).ok_or(Error::MethodNotFound)?;
142    let MethodData::PublicKeyJwk(ref jwk) = method.data() else {
143      return Err(Error::NotPublicKeyJwk);
144    };
145
146    // Extract JwsAlgorithm.
147    let alg: ProofAlgorithm = jwk
148      .alg()
149      .unwrap_or("")
150      .parse()
151      .map_err(|_| Error::InvalidJwpAlgorithm)?;
152
153    let typ = "JPT".to_string();
154
155    let kid = if let Some(ref kid) = options.kid {
156      kid.clone()
157    } else {
158      method.id().to_string()
159    };
160
161    let mut issuer_header = IssuerProtectedHeader::new(alg);
162    issuer_header.set_typ(Some(typ));
163    issuer_header.set_kid(Some(kid));
164
165    // Get the key identifier corresponding to the given method from the KeyId storage.
166    let method_digest: MethodDigest = MethodDigest::new(method).map_err(Error::MethodDigestConstructionError)?;
167    let key_id = <I as KeyIdStorage>::get_key_id(storage.key_id_storage(), &method_digest)
168      .await
169      .map_err(Error::KeyIdStorageError)?;
170
171    let jwp_builder = JwpIssuedBuilder::new(issuer_header, jpt_claims.clone());
172
173    let header = jwp_builder.get_issuer_protected_header().map_or_else(
174      || Err(Error::JwpBuildingError),
175      |h| h.to_json_vec().map_err(|_| Error::JwpBuildingError),
176    )?;
177
178    let data = jwp_builder.get_payloads().map_or_else(
179      || Err(Error::JwpBuildingError),
180      |p| p.to_bytes().map_err(|_| Error::JwpBuildingError),
181    )?;
182
183    let signature = <K as JwkStorageBbsPlusExt>::sign_bbs(storage.key_storage(), &key_id, &data, &header, jwk)
184      .await
185      .map_err(Error::KeyStorageError)?;
186
187    jwp_builder
188      .build_with_proof(signature)
189      .map_err(|_| Error::JwpBuildingError)?
190      .encode(SerializationType::COMPACT)
191      .map_err(|err| Error::EncodingError(Box::new(err)))
192  }
193
194  async fn create_presented_jwp(
195    &self,
196    presentation: &mut SelectiveDisclosurePresentation,
197    method_id: &str,
198    options: &JwpPresentationOptions,
199  ) -> StorageResult<String> {
200    // Obtain the method corresponding to the given fragment.
201    let method: &VerificationMethod = self.resolve_method(method_id, None).ok_or(Error::MethodNotFound)?;
202    let MethodData::PublicKeyJwk(ref jwk) = method.data() else {
203      return Err(Error::NotPublicKeyJwk);
204    };
205
206    // Extract JwsAlgorithm.
207    let alg: ProofAlgorithm = jwk
208      .alg()
209      .unwrap_or("")
210      .parse()
211      .map_err(|_| Error::InvalidJwpAlgorithm)?;
212
213    let public_key: Jwk = jwk.try_into().map_err(|_| Error::NotPublicKeyJwk)?;
214
215    let mut presentation_header = PresentationProtectedHeader::new(alg.into());
216    presentation_header.set_nonce(options.nonce.clone());
217    presentation_header.set_aud(options.audience.as_ref().map(|u| u.to_string()));
218
219    presentation.set_presentation_header(presentation_header);
220
221    let jwp_builder = presentation.builder();
222
223    let presented_jwp = jwp_builder.build(&public_key).map_err(|_| Error::JwpBuildingError)?;
224
225    Ok(
226      presented_jwp
227        .encode(SerializationType::COMPACT)
228        .map_err(|e| Error::EncodingError(Box::new(e)))?,
229    )
230  }
231
232  async fn create_credential_jpt<K, I, T>(
233    &self,
234    credential: &Credential<T>,
235    storage: &Storage<K, I>,
236    fragment: &str,
237    options: &JwpCredentialOptions,
238    custom_claims: Option<Object>,
239  ) -> StorageResult<Jpt>
240  where
241    K: JwkStorageBbsPlusExt,
242    I: KeyIdStorage,
243    T: ToOwned<Owned = T> + Serialize + DeserializeOwned + Sync,
244  {
245    let jpt_claims = credential
246      .serialize_jpt(custom_claims)
247      .map_err(Error::ClaimsSerializationError)?;
248
249    self
250      .create_issued_jwp(storage, fragment, &jpt_claims, options)
251      .await
252      .map(Jpt::new)
253  }
254
255  async fn create_presentation_jpt(
256    &self,
257    presentation: &mut SelectiveDisclosurePresentation,
258    method_id: &str,
259    options: &JwpPresentationOptions,
260  ) -> StorageResult<Jpt> {
261    self
262      .create_presented_jwp(presentation, method_id, options)
263      .await
264      .map(Jpt::new)
265  }
266}
267
268// ====================================================================================================================
269// IotaDocument
270// ====================================================================================================================
271#[cfg(feature = "iota-document")]
272mod iota_document {
273  use super::*;
274  use identity_iota_core::IotaDocument;
275
276  generate_method_for_document_type!(
277    IotaDocument,
278    ProofAlgorithm,
279    JwkStorageBbsPlusExt,
280    JwkStorageBbsPlusExt::generate_bbs,
281    generate_method_iota_document
282  );
283
284  #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
285  #[cfg_attr(feature = "send-sync-storage", async_trait)]
286  impl JwpDocumentExt for IotaDocument {
287    async fn generate_method_jwp<K, I>(
288      &mut self,
289      storage: &Storage<K, I>,
290      key_type: KeyType,
291      alg: ProofAlgorithm,
292      fragment: Option<&str>,
293      scope: MethodScope,
294    ) -> StorageResult<String>
295    where
296      K: JwkStorageBbsPlusExt,
297      I: KeyIdStorage,
298    {
299      generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await
300    }
301
302    async fn create_issued_jwp<K, I>(
303      &self,
304      storage: &Storage<K, I>,
305      fragment: &str,
306      jpt_claims: &JptClaims,
307      options: &JwpCredentialOptions,
308    ) -> StorageResult<String>
309    where
310      K: JwkStorageBbsPlusExt,
311      I: KeyIdStorage,
312    {
313      self
314        .core_document()
315        .create_issued_jwp(storage, fragment, jpt_claims, options)
316        .await
317    }
318
319    async fn create_presented_jwp(
320      &self,
321      presentation: &mut SelectiveDisclosurePresentation,
322      method_id: &str,
323      options: &JwpPresentationOptions,
324    ) -> StorageResult<String> {
325      self
326        .core_document()
327        .create_presented_jwp(presentation, method_id, options)
328        .await
329    }
330
331    async fn create_credential_jpt<K, I, T>(
332      &self,
333      credential: &Credential<T>,
334      storage: &Storage<K, I>,
335      fragment: &str,
336      options: &JwpCredentialOptions,
337      custom_claims: Option<Object>,
338    ) -> StorageResult<Jpt>
339    where
340      K: JwkStorageBbsPlusExt,
341      I: KeyIdStorage,
342      T: ToOwned<Owned = T> + Serialize + DeserializeOwned + Sync,
343    {
344      self
345        .core_document()
346        .create_credential_jpt(credential, storage, fragment, options, custom_claims)
347        .await
348    }
349
350    async fn create_presentation_jpt(
351      &self,
352      presentation: &mut SelectiveDisclosurePresentation,
353      method_id: &str,
354      options: &JwpPresentationOptions,
355    ) -> StorageResult<Jpt> {
356      self
357        .core_document()
358        .create_presentation_jpt(presentation, method_id, options)
359        .await
360    }
361  }
362}