1use super::JwkStorageDocumentError as Error;
5use super::JwsSignatureOptions;
6use super::Storage;
7
8use crate::key_id_storage::KeyIdStorage;
9use crate::key_id_storage::KeyIdStorageResult;
10use crate::key_id_storage::MethodDigest;
11use crate::key_storage::JwkGenOutput;
12use crate::key_storage::JwkStorage;
13use crate::key_storage::KeyId;
14use crate::key_storage::KeyStorageResult;
15use crate::key_storage::KeyType;
16
17use async_trait::async_trait;
18use identity_core::common::Object;
19use identity_credential::credential::Credential;
20use identity_credential::credential::Jws;
21use identity_credential::credential::Jwt;
22use identity_credential::presentation::JwtPresentationOptions;
23use identity_credential::presentation::Presentation;
24use identity_did::DIDUrl;
25use identity_document::document::CoreDocument;
26use identity_verification::jose::jws::CompactJwsEncoder;
27use identity_verification::jose::jws::CompactJwsEncodingOptions;
28use identity_verification::jose::jws::JwsAlgorithm;
29use identity_verification::jose::jws::JwsHeader;
30use identity_verification::jws::CharSet;
31use identity_verification::MethodData;
32use identity_verification::MethodScope;
33use identity_verification::VerificationMethod;
34use serde::de::DeserializeOwned;
35use serde::Serialize;
36
37pub type StorageResult<T> = Result<T, Error>;
39
40#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
49#[cfg_attr(feature = "send-sync-storage", async_trait)]
50pub trait JwkDocumentExt: private::Sealed {
51 async fn generate_method<K, I>(
60 &mut self,
61 storage: &Storage<K, I>,
62 key_type: KeyType,
63 alg: JwsAlgorithm,
64 fragment: Option<&str>,
65 scope: MethodScope,
66 ) -> StorageResult<String>
67 where
68 K: JwkStorage,
69 I: KeyIdStorage;
70
71 async fn purge_method<K, I>(&mut self, storage: &Storage<K, I>, id: &DIDUrl) -> StorageResult<()>
78 where
79 K: JwkStorage,
80 I: KeyIdStorage;
81
82 async fn create_jws<K, I>(
88 &self,
89 storage: &Storage<K, I>,
90 fragment: &str,
91 payload: &[u8],
92 options: &JwsSignatureOptions,
93 ) -> StorageResult<Jws>
94 where
95 K: JwkStorage,
96 I: KeyIdStorage;
97
98 async fn create_credential_jwt<K, I, T>(
107 &self,
108 credential: &Credential<T>,
109 storage: &Storage<K, I>,
110 fragment: &str,
111 options: &JwsSignatureOptions,
112 custom_claims: Option<Object>,
113 ) -> StorageResult<Jwt>
114 where
115 K: JwkStorage,
116 I: KeyIdStorage,
117 T: ToOwned<Owned = T> + Serialize + DeserializeOwned + Sync;
118
119 async fn create_presentation_jwt<K, I, CRED, T>(
126 &self,
127 presentation: &Presentation<CRED, T>,
128 storage: &Storage<K, I>,
129 fragment: &str,
130 signature_options: &JwsSignatureOptions,
131 presentation_options: &JwtPresentationOptions,
132 ) -> StorageResult<Jwt>
133 where
134 K: JwkStorage,
135 I: KeyIdStorage,
136 T: ToOwned<Owned = T> + Serialize + DeserializeOwned + Sync,
137 CRED: ToOwned<Owned = CRED> + Serialize + DeserializeOwned + Clone + Sync;
138}
139
140mod private {
141 pub trait Sealed {}
142 impl Sealed for identity_document::document::CoreDocument {}
143 #[cfg(feature = "iota-document")]
144 impl Sealed for identity_iota_core::IotaDocument {}
145}
146
147macro_rules! generate_method_for_document_type {
156 ($t:ty, $a:ty, $k:path, $f:path, $name:ident) => {
157 async fn $name<K, I>(
158 document: &mut $t,
159 storage: &Storage<K, I>,
160 key_type: KeyType,
161 alg: $a,
162 fragment: Option<&str>,
163 scope: MethodScope,
164 ) -> StorageResult<String>
165 where
166 K: $k,
167 I: KeyIdStorage,
168 {
169 let JwkGenOutput { key_id, jwk } = $f(storage.key_storage(), key_type, alg)
170 .await
171 .map_err(Error::KeyStorageError)?;
172
173 let method: VerificationMethod = {
176 match VerificationMethod::new_from_jwk(document.id().clone(), jwk, fragment)
177 .map_err(Error::VerificationMethodConstructionError)
178 {
179 Ok(method) => method,
180 Err(source) => {
181 return Err(try_undo_key_generation(storage, &key_id, source).await);
182 }
183 }
184 };
185
186 let method_digest: MethodDigest = MethodDigest::new(&method).map_err(Error::MethodDigestConstructionError)?;
188 let method_id: DIDUrl = method.id().clone();
189
190 let fragment: String = method_id
192 .fragment()
193 .ok_or(identity_verification::Error::MissingIdFragment)
194 .map_err(Error::VerificationMethodConstructionError)?
195 .to_owned();
196
197 if let Err(error) = document
199 .insert_method(method, scope)
200 .map_err(|_| Error::FragmentAlreadyExists)
201 {
202 return Err(try_undo_key_generation(storage, &key_id, error).await);
203 };
204
205 if let Err(error) = <I as KeyIdStorage>::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone())
208 .await
209 .map_err(Error::KeyIdStorageError)
210 {
211 let _ = document.remove_method(&method_id);
213 return Err(try_undo_key_generation(storage, &key_id, error).await);
214 }
215
216 Ok(fragment)
217 }
218 };
219}
220
221macro_rules! purge_method_for_document_type {
222 ($t:ty, $name:ident) => {
223 async fn $name<K, I>(document: &mut $t, storage: &Storage<K, I>, id: &DIDUrl) -> StorageResult<()>
224 where
225 K: JwkStorage,
226 I: KeyIdStorage,
227 {
228 let (method, scope) = document.remove_method_and_scope(id).ok_or(Error::MethodNotFound)?;
229
230 let method_digest: MethodDigest = match MethodDigest::new(&method).map_err(Error::MethodDigestConstructionError) {
232 Ok(digest) => digest,
233 Err(error) => {
234 let _ = document.insert_method(method, scope);
236 return Err(error);
237 }
238 };
239
240 let key_id: KeyId = match <I as KeyIdStorage>::get_key_id(&storage.key_id_storage(), &method_digest)
242 .await
243 .map_err(Error::KeyIdStorageError)
244 {
245 Ok(key_id) => key_id,
246 Err(error) => {
247 let _ = document.insert_method(method, scope);
249 return Err(error);
250 }
251 };
252
253 let key_deletion_fut = <K as JwkStorage>::delete(&storage.key_storage(), &key_id);
255 let key_id_deletion_fut = <I as KeyIdStorage>::delete_key_id(&storage.key_id_storage(), &method_digest);
256 let (key_deletion_result, key_id_deletion_result): (KeyStorageResult<()>, KeyIdStorageResult<()>) =
257 futures::join!(key_deletion_fut, key_id_deletion_fut);
258
259 match (key_deletion_result, key_id_deletion_result) {
261 (Ok(_), Ok(_)) => Ok(()),
262 (Ok(_), Err(key_id_deletion_error)) => {
263 Err(Error::UndoOperationFailed {
266 message: format!(
267 "cannot undo key deletion: this results in a stray key id stored under packed method digest: {:?}",
268 &method_digest.pack()
269 ),
270 source: Box::new(Error::KeyIdStorageError(key_id_deletion_error)),
271 undo_error: None,
272 })
273 }
274 (Err(key_deletion_error), Ok(_)) => {
275 if let Err(key_id_insertion_error) =
277 <I as KeyIdStorage>::insert_key_id(&storage.key_id_storage(), (&method_digest).clone(), key_id.clone())
278 .await
279 .map_err(Error::KeyIdStorageError)
280 {
281 Err(Error::UndoOperationFailed {
282 message: format!("cannot revert key id deletion: this results in stray key with key id: {key_id}"),
283 source: Box::new(Error::KeyStorageError(key_deletion_error)),
284 undo_error: Some(Box::new(key_id_insertion_error)),
285 })
286 } else {
287 let _ = document.insert_method(method, scope);
289 Err(Error::KeyStorageError(key_deletion_error))
290 }
291 }
292 (Err(_key_deletion_error), Err(key_id_deletion_error)) => {
293 let _ = document.insert_method(method, scope);
296 Err(Error::KeyIdStorageError(key_id_deletion_error))
297 }
298 }
299 }
300 };
301}
302
303generate_method_for_document_type!(
308 CoreDocument,
309 JwsAlgorithm,
310 JwkStorage,
311 JwkStorage::generate,
312 generate_method_core_document
313);
314purge_method_for_document_type!(CoreDocument, purge_method_core_document);
315
316#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
317#[cfg_attr(feature = "send-sync-storage", async_trait)]
318impl JwkDocumentExt for CoreDocument {
319 async fn generate_method<K, I>(
320 &mut self,
321 storage: &Storage<K, I>,
322 key_type: KeyType,
323 alg: JwsAlgorithm,
324 fragment: Option<&str>,
325 scope: MethodScope,
326 ) -> StorageResult<String>
327 where
328 K: JwkStorage,
329 I: KeyIdStorage,
330 {
331 generate_method_core_document(self, storage, key_type, alg, fragment, scope).await
332 }
333
334 async fn purge_method<K, I>(&mut self, storage: &Storage<K, I>, id: &DIDUrl) -> StorageResult<()>
335 where
336 K: JwkStorage,
337 I: KeyIdStorage,
338 {
339 purge_method_core_document(self, storage, id).await
340 }
341
342 async fn create_jws<K, I>(
343 &self,
344 storage: &Storage<K, I>,
345 fragment: &str,
346 payload: &[u8],
347 options: &JwsSignatureOptions,
348 ) -> StorageResult<Jws>
349 where
350 K: JwkStorage,
351 I: KeyIdStorage,
352 {
353 let method: &VerificationMethod = self.resolve_method(fragment, None).ok_or(Error::MethodNotFound)?;
355 let MethodData::PublicKeyJwk(ref jwk) = method.data() else {
356 return Err(Error::NotPublicKeyJwk);
357 };
358
359 let alg: JwsAlgorithm = jwk
361 .alg()
362 .unwrap_or("")
363 .parse()
364 .map_err(|_| Error::InvalidJwsAlgorithm)?;
365
366 let header: JwsHeader = {
368 let mut header = JwsHeader::new();
369
370 header.set_alg(alg);
371 if let Some(custom) = &options.custom_header_parameters {
372 header.set_custom(custom.clone())
373 }
374
375 if let Some(ref kid) = options.kid {
376 header.set_kid(kid.clone());
377 } else {
378 header.set_kid(method.id().to_string());
379 }
380
381 if options.attach_jwk {
382 header.set_jwk(jwk.clone())
383 };
384
385 if let Some(b64) = options.b64 {
386 if !b64 {
388 header.set_b64(b64);
389 header.set_crit(["b64"]);
390 }
391 };
392
393 if let Some(typ) = &options.typ {
394 header.set_typ(typ.clone())
395 } else {
396 header.set_typ("JWT")
398 }
399
400 if let Some(cty) = &options.cty {
401 header.set_cty(cty.clone())
402 };
403
404 if let Some(url) = &options.url {
405 header.set_url(url.clone())
406 };
407
408 if let Some(nonce) = &options.nonce {
409 header.set_nonce(nonce.clone())
410 };
411
412 header
413 };
414
415 let method_digest: MethodDigest = MethodDigest::new(method).map_err(Error::MethodDigestConstructionError)?;
417 let key_id = <I as KeyIdStorage>::get_key_id(storage.key_id_storage(), &method_digest)
418 .await
419 .map_err(Error::KeyIdStorageError)?;
420
421 let encoding_options: CompactJwsEncodingOptions = if !options.detached_payload {
423 CompactJwsEncodingOptions::NonDetached {
426 charset_requirements: CharSet::Default,
427 }
428 } else {
429 CompactJwsEncodingOptions::Detached
430 };
431
432 let jws_encoder: CompactJwsEncoder<'_> = CompactJwsEncoder::new_with_options(payload, &header, encoding_options)
433 .map_err(|err| Error::EncodingError(err.into()))?;
434 let signature = <K as JwkStorage>::sign(storage.key_storage(), &key_id, jws_encoder.signing_input(), jwk)
435 .await
436 .map_err(Error::KeyStorageError)?;
437 Ok(Jws::new(jws_encoder.into_jws(&signature)))
438 }
439
440 async fn create_credential_jwt<K, I, T>(
441 &self,
442 credential: &Credential<T>,
443 storage: &Storage<K, I>,
444 fragment: &str,
445 options: &JwsSignatureOptions,
446 custom_claims: Option<Object>,
447 ) -> StorageResult<Jwt>
448 where
449 K: JwkStorage,
450 I: KeyIdStorage,
451 T: ToOwned<Owned = T> + Serialize + DeserializeOwned + Sync,
452 {
453 if options.detached_payload {
454 return Err(Error::EncodingError(Box::<dyn std::error::Error + Send + Sync>::from(
455 "cannot use detached payload for credential signing",
456 )));
457 }
458
459 if !options.b64.unwrap_or(true) {
460 return Err(Error::EncodingError(Box::<dyn std::error::Error + Send + Sync>::from(
462 "cannot use `b64 = false` with JWTs",
463 )));
464 }
465
466 let payload = credential
467 .serialize_jwt(custom_claims)
468 .map_err(Error::ClaimsSerializationError)?;
469 self
470 .create_jws(storage, fragment, payload.as_bytes(), options)
471 .await
472 .map(|jws| Jwt::new(jws.into()))
473 }
474
475 async fn create_presentation_jwt<K, I, CRED, T>(
476 &self,
477 presentation: &Presentation<CRED, T>,
478 storage: &Storage<K, I>,
479 fragment: &str,
480 jws_options: &JwsSignatureOptions,
481 jwt_options: &JwtPresentationOptions,
482 ) -> StorageResult<Jwt>
483 where
484 K: JwkStorage,
485 I: KeyIdStorage,
486 T: ToOwned<Owned = T> + Serialize + DeserializeOwned + Sync,
487 CRED: ToOwned<Owned = CRED> + Serialize + DeserializeOwned + Clone + Sync,
488 {
489 if jws_options.detached_payload {
490 return Err(Error::EncodingError(Box::<dyn std::error::Error + Send + Sync>::from(
491 "cannot use detached payload for presentation signing",
492 )));
493 }
494
495 if !jws_options.b64.unwrap_or(true) {
496 return Err(Error::EncodingError(Box::<dyn std::error::Error + Send + Sync>::from(
498 "cannot use `b64 = false` with JWTs",
499 )));
500 }
501 let payload = presentation
502 .serialize_jwt(jwt_options)
503 .map_err(Error::ClaimsSerializationError)?;
504 self
505 .create_jws(storage, fragment, payload.as_bytes(), jws_options)
506 .await
507 .map(|jws| Jwt::new(jws.into()))
508 }
509}
510
511pub(crate) async fn try_undo_key_generation<K, I>(storage: &Storage<K, I>, key_id: &KeyId, source_error: Error) -> Error
515where
516 K: JwkStorage,
517 I: KeyIdStorage,
518{
519 if let Err(err) = <K as JwkStorage>::delete(storage.key_storage(), key_id).await {
521 Error::UndoOperationFailed {
522 message: format!("unable to delete stray key with id: {}", &key_id),
523 source: Box::new(source_error),
524 undo_error: Some(Box::new(Error::KeyStorageError(err))),
525 }
526 } else {
527 source_error
528 }
529}
530
531#[cfg(feature = "iota-document")]
535mod iota_document {
536 use super::*;
537 use identity_credential::credential::Jwt;
538 use identity_iota_core::IotaDocument;
539
540 generate_method_for_document_type!(
541 IotaDocument,
542 JwsAlgorithm,
543 JwkStorage,
544 JwkStorage::generate,
545 generate_method_iota_document
546 );
547 purge_method_for_document_type!(IotaDocument, purge_method_iota_document);
548
549 #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
550 #[cfg_attr(feature = "send-sync-storage", async_trait)]
551 impl JwkDocumentExt for IotaDocument {
552 async fn generate_method<K, I>(
553 &mut self,
554 storage: &Storage<K, I>,
555 key_type: KeyType,
556 alg: JwsAlgorithm,
557 fragment: Option<&str>,
558 scope: MethodScope,
559 ) -> StorageResult<String>
560 where
561 K: JwkStorage,
562 I: KeyIdStorage,
563 {
564 generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await
565 }
566
567 async fn purge_method<K, I>(&mut self, storage: &Storage<K, I>, id: &DIDUrl) -> StorageResult<()>
568 where
569 K: JwkStorage,
570 I: KeyIdStorage,
571 {
572 purge_method_iota_document(self, storage, id).await
573 }
574
575 async fn create_jws<K, I>(
576 &self,
577 storage: &Storage<K, I>,
578 fragment: &str,
579 payload: &[u8],
580 options: &JwsSignatureOptions,
581 ) -> StorageResult<Jws>
582 where
583 K: JwkStorage,
584 I: KeyIdStorage,
585 {
586 self
587 .core_document()
588 .create_jws(storage, fragment, payload, options)
589 .await
590 }
591
592 async fn create_credential_jwt<K, I, T>(
593 &self,
594 credential: &Credential<T>,
595 storage: &Storage<K, I>,
596 fragment: &str,
597 options: &JwsSignatureOptions,
598 custom_claims: Option<Object>,
599 ) -> StorageResult<Jwt>
600 where
601 K: JwkStorage,
602 I: KeyIdStorage,
603 T: ToOwned<Owned = T> + Serialize + DeserializeOwned + Sync,
604 {
605 self
606 .core_document()
607 .create_credential_jwt(credential, storage, fragment, options, custom_claims)
608 .await
609 }
610 async fn create_presentation_jwt<K, I, CRED, T>(
611 &self,
612 presentation: &Presentation<CRED, T>,
613 storage: &Storage<K, I>,
614 fragment: &str,
615 options: &JwsSignatureOptions,
616 jwt_options: &JwtPresentationOptions,
617 ) -> StorageResult<Jwt>
618 where
619 K: JwkStorage,
620 I: KeyIdStorage,
621 T: ToOwned<Owned = T> + Serialize + DeserializeOwned + Sync,
622 CRED: ToOwned<Owned = CRED> + Serialize + DeserializeOwned + Clone + Sync,
623 {
624 self
625 .core_document()
626 .create_presentation_jwt(presentation, storage, fragment, options, jwt_options)
627 .await
628 }
629 }
630}