1use std::borrow::Cow;
5
6use super::JwkStorageDocumentError as Error;
7use super::JwsSignatureOptions;
8use super::Storage;
9
10use crate::key_id_storage::KeyIdStorage;
11use crate::key_id_storage::KeyIdStorageResult;
12use crate::key_id_storage::MethodDigest;
13use crate::key_storage::JwkGenOutput;
14use crate::key_storage::JwkStorage;
15use crate::key_storage::KeyId;
16use crate::key_storage::KeyStorageResult;
17use crate::key_storage::KeyType;
18
19use async_trait::async_trait;
20use identity_core::common::Object;
21use identity_credential::credential::Credential;
22use identity_credential::credential::CredentialV2;
23use identity_credential::credential::Jws;
24use identity_credential::credential::Jwt;
25use identity_credential::credential::JwtVcV2;
26use identity_credential::presentation::JwtPresentationOptions;
27use identity_credential::presentation::Presentation;
28use identity_did::DIDUrl;
29use identity_document::document::CoreDocument;
30use identity_verification::jose::jws::CompactJwsEncoder;
31use identity_verification::jose::jws::CompactJwsEncodingOptions;
32use identity_verification::jose::jws::JwsAlgorithm;
33use identity_verification::jose::jws::JwsHeader;
34use identity_verification::jws::CharSet;
35use identity_verification::MethodData;
36use identity_verification::MethodScope;
37use identity_verification::VerificationMethod;
38use serde::de::DeserializeOwned;
39use serde::Serialize;
40
41pub type StorageResult<T> = Result<T, Error>;
43
44#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
53#[cfg_attr(feature = "send-sync-storage", async_trait)]
54pub trait JwkDocumentExt: private::Sealed {
55 async fn generate_method<K, I>(
64 &mut self,
65 storage: &Storage<K, I>,
66 key_type: KeyType,
67 alg: JwsAlgorithm,
68 fragment: Option<&str>,
69 scope: MethodScope,
70 ) -> StorageResult<String>
71 where
72 K: JwkStorage,
73 I: KeyIdStorage;
74
75 async fn purge_method<K, I>(&mut self, storage: &Storage<K, I>, id: &DIDUrl) -> StorageResult<()>
82 where
83 K: JwkStorage,
84 I: KeyIdStorage;
85
86 async fn create_jws<K, I>(
92 &self,
93 storage: &Storage<K, I>,
94 fragment: &str,
95 payload: &[u8],
96 options: &JwsSignatureOptions,
97 ) -> StorageResult<Jws>
98 where
99 K: JwkStorage,
100 I: KeyIdStorage;
101
102 async fn create_credential_jwt<K, I, T>(
112 &self,
113 credential: &Credential<T>,
114 storage: &Storage<K, I>,
115 fragment: &str,
116 options: &JwsSignatureOptions,
117 custom_claims: Option<Object>,
118 ) -> StorageResult<Jwt>
119 where
120 K: JwkStorage,
121 I: KeyIdStorage,
122 T: ToOwned<Owned = T> + Serialize + DeserializeOwned + Sync;
123
124 async fn create_credential_v2_jwt<K, I, T>(
131 &self,
132 credential: &CredentialV2<T>,
133 storage: &Storage<K, I>,
134 fragment: &str,
135 options: &JwsSignatureOptions,
136 ) -> StorageResult<JwtVcV2>
137 where
138 K: JwkStorage,
139 I: KeyIdStorage,
140 T: Clone + Serialize + Sync;
141
142 async fn create_presentation_jwt<K, I, CRED, T>(
151 &self,
152 presentation: &Presentation<CRED, T>,
153 storage: &Storage<K, I>,
154 fragment: &str,
155 signature_options: &JwsSignatureOptions,
156 presentation_options: &JwtPresentationOptions,
157 ) -> StorageResult<Jwt>
158 where
159 K: JwkStorage,
160 I: KeyIdStorage,
161 T: Clone + Serialize + DeserializeOwned + Sync,
162 CRED: ToOwned<Owned = CRED> + Serialize + DeserializeOwned + Clone + Sync;
163}
164
165mod private {
166 pub trait Sealed {}
167 impl Sealed for identity_document::document::CoreDocument {}
168 #[cfg(feature = "iota-document")]
169 impl Sealed for identity_iota_core::IotaDocument {}
170}
171
172macro_rules! generate_method_for_document_type {
181 ($t:ty, $a:ty, $k:path, $f:path, $name:ident) => {
182 async fn $name<K, I>(
183 document: &mut $t,
184 storage: &Storage<K, I>,
185 key_type: KeyType,
186 alg: $a,
187 fragment: Option<&str>,
188 scope: MethodScope,
189 ) -> StorageResult<String>
190 where
191 K: $k,
192 I: KeyIdStorage,
193 {
194 let JwkGenOutput { key_id, jwk } = $f(storage.key_storage(), key_type, alg)
195 .await
196 .map_err(Error::KeyStorageError)?;
197
198 let method: VerificationMethod = {
201 match VerificationMethod::new_from_jwk(document.id().clone(), jwk, fragment)
202 .map_err(Error::VerificationMethodConstructionError)
203 {
204 Ok(method) => method,
205 Err(source) => {
206 return Err(try_undo_key_generation(storage, &key_id, source).await);
207 }
208 }
209 };
210
211 let method_digest: MethodDigest = MethodDigest::new(&method).map_err(Error::MethodDigestConstructionError)?;
213 let method_id: DIDUrl = method.id().clone();
214
215 let fragment: String = method_id
217 .fragment()
218 .ok_or(identity_verification::Error::MissingIdFragment)
219 .map_err(Error::VerificationMethodConstructionError)?
220 .to_owned();
221
222 if let Err(error) = document
224 .insert_method(method, scope)
225 .map_err(|_| Error::FragmentAlreadyExists)
226 {
227 return Err(try_undo_key_generation(storage, &key_id, error).await);
228 };
229
230 if let Err(error) = <I as KeyIdStorage>::insert_key_id(&storage.key_id_storage(), method_digest, key_id.clone())
233 .await
234 .map_err(Error::KeyIdStorageError)
235 {
236 let _ = document.remove_method(&method_id);
238 return Err(try_undo_key_generation(storage, &key_id, error).await);
239 }
240
241 Ok(fragment)
242 }
243 };
244}
245
246macro_rules! purge_method_for_document_type {
247 ($t:ty, $name:ident) => {
248 async fn $name<K, I>(document: &mut $t, storage: &Storage<K, I>, id: &DIDUrl) -> StorageResult<()>
249 where
250 K: JwkStorage,
251 I: KeyIdStorage,
252 {
253 let (method, scope) = document.remove_method_and_scope(id).ok_or(Error::MethodNotFound)?;
254
255 let method_digest: MethodDigest = match MethodDigest::new(&method).map_err(Error::MethodDigestConstructionError) {
257 Ok(digest) => digest,
258 Err(error) => {
259 let _ = document.insert_method(method, scope);
261 return Err(error);
262 }
263 };
264
265 let key_id: KeyId = match <I as KeyIdStorage>::get_key_id(&storage.key_id_storage(), &method_digest)
267 .await
268 .map_err(Error::KeyIdStorageError)
269 {
270 Ok(key_id) => key_id,
271 Err(error) => {
272 let _ = document.insert_method(method, scope);
274 return Err(error);
275 }
276 };
277
278 let key_deletion_fut = <K as JwkStorage>::delete(&storage.key_storage(), &key_id);
280 let key_id_deletion_fut = <I as KeyIdStorage>::delete_key_id(&storage.key_id_storage(), &method_digest);
281 let (key_deletion_result, key_id_deletion_result): (KeyStorageResult<()>, KeyIdStorageResult<()>) =
282 futures::join!(key_deletion_fut, key_id_deletion_fut);
283
284 match (key_deletion_result, key_id_deletion_result) {
286 (Ok(_), Ok(_)) => Ok(()),
287 (Ok(_), Err(key_id_deletion_error)) => {
288 Err(Error::UndoOperationFailed {
291 message: format!(
292 "cannot undo key deletion: this results in a stray key id stored under packed method digest: {:?}",
293 &method_digest.pack()
294 ),
295 source: Box::new(Error::KeyIdStorageError(key_id_deletion_error)),
296 undo_error: None,
297 })
298 }
299 (Err(key_deletion_error), Ok(_)) => {
300 if let Err(key_id_insertion_error) =
302 <I as KeyIdStorage>::insert_key_id(&storage.key_id_storage(), (&method_digest).clone(), key_id.clone())
303 .await
304 .map_err(Error::KeyIdStorageError)
305 {
306 Err(Error::UndoOperationFailed {
307 message: format!("cannot revert key id deletion: this results in stray key with key id: {key_id}"),
308 source: Box::new(Error::KeyStorageError(key_deletion_error)),
309 undo_error: Some(Box::new(key_id_insertion_error)),
310 })
311 } else {
312 let _ = document.insert_method(method, scope);
314 Err(Error::KeyStorageError(key_deletion_error))
315 }
316 }
317 (Err(_key_deletion_error), Err(key_id_deletion_error)) => {
318 let _ = document.insert_method(method, scope);
321 Err(Error::KeyIdStorageError(key_id_deletion_error))
322 }
323 }
324 }
325 };
326}
327
328generate_method_for_document_type!(
333 CoreDocument,
334 JwsAlgorithm,
335 JwkStorage,
336 JwkStorage::generate,
337 generate_method_core_document
338);
339purge_method_for_document_type!(CoreDocument, purge_method_core_document);
340
341#[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
342#[cfg_attr(feature = "send-sync-storage", async_trait)]
343impl JwkDocumentExt for CoreDocument {
344 async fn generate_method<K, I>(
345 &mut self,
346 storage: &Storage<K, I>,
347 key_type: KeyType,
348 alg: JwsAlgorithm,
349 fragment: Option<&str>,
350 scope: MethodScope,
351 ) -> StorageResult<String>
352 where
353 K: JwkStorage,
354 I: KeyIdStorage,
355 {
356 generate_method_core_document(self, storage, key_type, alg, fragment, scope).await
357 }
358
359 async fn purge_method<K, I>(&mut self, storage: &Storage<K, I>, id: &DIDUrl) -> StorageResult<()>
360 where
361 K: JwkStorage,
362 I: KeyIdStorage,
363 {
364 purge_method_core_document(self, storage, id).await
365 }
366
367 async fn create_jws<K, I>(
368 &self,
369 storage: &Storage<K, I>,
370 fragment: &str,
371 payload: &[u8],
372 options: &JwsSignatureOptions,
373 ) -> StorageResult<Jws>
374 where
375 K: JwkStorage,
376 I: KeyIdStorage,
377 {
378 let method: &VerificationMethod = self.resolve_method(fragment, None).ok_or(Error::MethodNotFound)?;
380 let MethodData::PublicKeyJwk(ref jwk) = method.data() else {
381 return Err(Error::NotPublicKeyJwk);
382 };
383
384 let alg: JwsAlgorithm = jwk
386 .alg()
387 .unwrap_or("")
388 .parse()
389 .map_err(|_| Error::InvalidJwsAlgorithm)?;
390
391 let header: JwsHeader = {
393 let mut header = JwsHeader::new();
394
395 header.set_alg(alg);
396 if let Some(custom) = &options.custom_header_parameters {
397 header.set_custom(custom.clone())
398 }
399
400 if let Some(ref kid) = options.kid {
401 header.set_kid(kid.clone());
402 } else {
403 header.set_kid(method.id().to_string());
404 }
405
406 if options.attach_jwk {
407 header.set_jwk(jwk.clone())
408 };
409
410 if let Some(b64) = options.b64 {
411 if !b64 {
413 header.set_b64(b64);
414 header.set_crit(["b64"]);
415 }
416 };
417
418 if let Some(typ) = &options.typ {
419 header.set_typ(typ.clone())
420 } else {
421 header.set_typ("JWT")
423 }
424
425 if let Some(cty) = &options.cty {
426 header.set_cty(cty.clone())
427 };
428
429 if let Some(url) = &options.url {
430 header.set_url(url.clone())
431 };
432
433 if let Some(nonce) = &options.nonce {
434 header.set_nonce(nonce.clone())
435 };
436
437 header
438 };
439
440 let method_digest: MethodDigest = MethodDigest::new(method).map_err(Error::MethodDigestConstructionError)?;
442 let key_id = <I as KeyIdStorage>::get_key_id(storage.key_id_storage(), &method_digest)
443 .await
444 .map_err(Error::KeyIdStorageError)?;
445
446 let encoding_options: CompactJwsEncodingOptions = if !options.detached_payload {
448 CompactJwsEncodingOptions::NonDetached {
451 charset_requirements: CharSet::Default,
452 }
453 } else {
454 CompactJwsEncodingOptions::Detached
455 };
456
457 let jws_encoder: CompactJwsEncoder<'_> = CompactJwsEncoder::new_with_options(payload, &header, encoding_options)
458 .map_err(|err| Error::EncodingError(err.into()))?;
459 let signature = <K as JwkStorage>::sign(storage.key_storage(), &key_id, jws_encoder.signing_input(), jwk)
460 .await
461 .map_err(Error::KeyStorageError)?;
462 Ok(Jws::new(jws_encoder.into_jws(&signature)))
463 }
464
465 async fn create_credential_jwt<K, I, T>(
466 &self,
467 credential: &Credential<T>,
468 storage: &Storage<K, I>,
469 fragment: &str,
470 options: &JwsSignatureOptions,
471 custom_claims: Option<Object>,
472 ) -> StorageResult<Jwt>
473 where
474 K: JwkStorage,
475 I: KeyIdStorage,
476 T: ToOwned<Owned = T> + Serialize + DeserializeOwned + Sync,
477 {
478 if options.detached_payload {
479 return Err(Error::EncodingError(Box::<dyn std::error::Error + Send + Sync>::from(
480 "cannot use detached payload for credential signing",
481 )));
482 }
483
484 if !options.b64.unwrap_or(true) {
485 return Err(Error::EncodingError(Box::<dyn std::error::Error + Send + Sync>::from(
487 "cannot use `b64 = false` with JWTs",
488 )));
489 }
490
491 let payload = credential
492 .serialize_jwt(custom_claims)
493 .map_err(Error::ClaimsSerializationError)?;
494 self
495 .create_jws(storage, fragment, payload.as_bytes(), options)
496 .await
497 .map(|jws| Jwt::new(jws.into()))
498 }
499
500 async fn create_credential_v2_jwt<K, I, T>(
501 &self,
502 credential: &CredentialV2<T>,
503 storage: &Storage<K, I>,
504 fragment: &str,
505 options: &JwsSignatureOptions,
506 ) -> StorageResult<JwtVcV2>
507 where
508 K: JwkStorage,
509 I: KeyIdStorage,
510 T: Clone + Serialize + Sync,
511 {
512 if options.detached_payload {
513 return Err(Error::EncodingError(
514 "cannot use detached payload for credential signing".into(),
515 ));
516 }
517
518 if !options.b64.unwrap_or(true) {
519 return Err(Error::EncodingError("cannot use `b64 = false` with JWTs".into()));
521 }
522
523 let payload = credential
524 .serialize_jwt(None)
525 .map_err(Error::ClaimsSerializationError)?;
526
527 let mut options = Cow::Borrowed(options);
529 if options.typ.as_deref() != Some("vc+jwt") {
530 options.to_mut().typ = Some("vc+jwt".to_owned());
531 }
532
533 self
534 .create_jws(storage, fragment, payload.as_bytes(), &options)
535 .await
536 .map(|jws| JwtVcV2::parse(jws.as_str()).expect("valid JWT string containing a VC v2.0"))
537 }
538
539 async fn create_presentation_jwt<K, I, CRED, T>(
540 &self,
541 presentation: &Presentation<CRED, T>,
542 storage: &Storage<K, I>,
543 fragment: &str,
544 jws_options: &JwsSignatureOptions,
545 jwt_options: &JwtPresentationOptions,
546 ) -> StorageResult<Jwt>
547 where
548 K: JwkStorage,
549 I: KeyIdStorage,
550 T: Clone + Serialize + DeserializeOwned + Sync,
551 CRED: ToOwned<Owned = CRED> + Serialize + DeserializeOwned + Clone + Sync,
552 {
553 if jws_options.detached_payload {
554 return Err(Error::EncodingError(
555 "cannot use detached payload for presentation signing".into(),
556 ));
557 }
558
559 if !jws_options.b64.unwrap_or(true) {
560 return Err(Error::EncodingError("cannot use `b64 = false` with JWTs".into()));
562 }
563 let payload = presentation
564 .serialize_jwt(jwt_options)
565 .map_err(Error::ClaimsSerializationError)?;
566
567 let mut jws_options = Cow::Borrowed(jws_options);
568 if presentation.is_v2() {
571 jws_options.to_mut().typ = Some("vp+jwt".to_owned());
572 }
573
574 self
575 .create_jws(storage, fragment, payload.as_bytes(), &jws_options)
576 .await
577 .map(|jws| Jwt::new(jws.into()))
578 }
579}
580
581pub(crate) async fn try_undo_key_generation<K, I>(storage: &Storage<K, I>, key_id: &KeyId, source_error: Error) -> Error
585where
586 K: JwkStorage,
587 I: KeyIdStorage,
588{
589 if let Err(err) = <K as JwkStorage>::delete(storage.key_storage(), key_id).await {
591 Error::UndoOperationFailed {
592 message: format!("unable to delete stray key with id: {}", &key_id),
593 source: Box::new(source_error),
594 undo_error: Some(Box::new(Error::KeyStorageError(err))),
595 }
596 } else {
597 source_error
598 }
599}
600
601#[cfg(feature = "iota-document")]
605mod iota_document {
606 use super::*;
607 use identity_credential::credential::Credential;
608 use identity_credential::credential::Jwt;
609 use identity_iota_core::IotaDocument;
610
611 generate_method_for_document_type!(
612 IotaDocument,
613 JwsAlgorithm,
614 JwkStorage,
615 JwkStorage::generate,
616 generate_method_iota_document
617 );
618 purge_method_for_document_type!(IotaDocument, purge_method_iota_document);
619
620 #[cfg_attr(not(feature = "send-sync-storage"), async_trait(?Send))]
621 #[cfg_attr(feature = "send-sync-storage", async_trait)]
622 impl JwkDocumentExt for IotaDocument {
623 async fn generate_method<K, I>(
624 &mut self,
625 storage: &Storage<K, I>,
626 key_type: KeyType,
627 alg: JwsAlgorithm,
628 fragment: Option<&str>,
629 scope: MethodScope,
630 ) -> StorageResult<String>
631 where
632 K: JwkStorage,
633 I: KeyIdStorage,
634 {
635 generate_method_iota_document(self, storage, key_type, alg, fragment, scope).await
636 }
637
638 async fn purge_method<K, I>(&mut self, storage: &Storage<K, I>, id: &DIDUrl) -> StorageResult<()>
639 where
640 K: JwkStorage,
641 I: KeyIdStorage,
642 {
643 purge_method_iota_document(self, storage, id).await
644 }
645
646 async fn create_jws<K, I>(
647 &self,
648 storage: &Storage<K, I>,
649 fragment: &str,
650 payload: &[u8],
651 options: &JwsSignatureOptions,
652 ) -> StorageResult<Jws>
653 where
654 K: JwkStorage,
655 I: KeyIdStorage,
656 {
657 self
658 .core_document()
659 .create_jws(storage, fragment, payload, options)
660 .await
661 }
662
663 async fn create_credential_jwt<K, I, T>(
664 &self,
665 credential: &Credential<T>,
666 storage: &Storage<K, I>,
667 fragment: &str,
668 options: &JwsSignatureOptions,
669 custom_claims: Option<Object>,
670 ) -> StorageResult<Jwt>
671 where
672 K: JwkStorage,
673 I: KeyIdStorage,
674 T: ToOwned<Owned = T> + Serialize + DeserializeOwned + Sync,
675 {
676 self
677 .core_document()
678 .create_credential_jwt(credential, storage, fragment, options, custom_claims)
679 .await
680 }
681 async fn create_presentation_jwt<K, I, CRED, T>(
682 &self,
683 presentation: &Presentation<CRED, T>,
684 storage: &Storage<K, I>,
685 fragment: &str,
686 options: &JwsSignatureOptions,
687 jwt_options: &JwtPresentationOptions,
688 ) -> StorageResult<Jwt>
689 where
690 K: JwkStorage,
691 I: KeyIdStorage,
692 T: Clone + Serialize + DeserializeOwned + Sync,
693 CRED: ToOwned<Owned = CRED> + Serialize + DeserializeOwned + Clone + Sync,
694 {
695 self
696 .core_document()
697 .create_presentation_jwt(presentation, storage, fragment, options, jwt_options)
698 .await
699 }
700
701 async fn create_credential_v2_jwt<K, I, T>(
702 &self,
703 credential: &CredentialV2<T>,
704 storage: &Storage<K, I>,
705 fragment: &str,
706 options: &JwsSignatureOptions,
707 ) -> StorageResult<JwtVcV2>
708 where
709 K: JwkStorage,
710 I: KeyIdStorage,
711 T: Clone + Serialize + Sync,
712 {
713 self
714 .core_document()
715 .create_credential_v2_jwt(credential, storage, fragment, options)
716 .await
717 }
718 }
719}