1pub mod error;
5use std::{borrow::Cow, collections::HashMap, hash::Hash};
6
7pub use error::Error;
8
9use iref::{Uri, UriBuf};
10use rdf_types::VocabularyMut;
11use serde::{Deserialize, Serialize};
12use serde_json::Value;
13use ssi_claims::{
14 chrono::{DateTime, Utc},
15 data_integrity::{
16 suite::{CryptographicSuiteSigning, InputProofOptions, InputSignatureOptions},
17 AnyDataIntegrity, AnyProofs, AnySignatureAlgorithm, AnySuite, CryptographicSuite,
18 DataIntegrity, Proof, Proofs,
19 },
20 vc::syntax::{Context, RequiredContext},
21 ClaimsValidity, DateTimeProvider, Eip712TypesLoaderProvider, InvalidClaims, ResolverProvider,
22 SignatureEnvironment, SignatureError, ValidateClaims, VerificationParameters,
23};
24use ssi_json_ld::{JsonLdError, JsonLdLoaderProvider, JsonLdNodeObject, JsonLdObject, Loader};
25use ssi_rdf::{Interpretation, LdEnvironment, LinkedDataResource, LinkedDataSubject};
26use ssi_verification_methods::{AnyMethod, ProofPurpose, VerificationMethodResolver};
27use ssi_verification_methods::{MessageSigner, Signer};
28use static_iref::iri;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
31pub struct SecurityV2;
32
33impl RequiredContext for SecurityV2 {
34 const CONTEXT_IRI: &'static iref::Iri = iri!("https://w3id.org/security/v2");
35}
36
37#[derive(Debug, Serialize, Deserialize, Clone)]
38#[serde(rename_all = "camelCase")]
39pub struct DefaultProps<A> {
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub capability_action: Option<A>,
43
44 #[serde(flatten)]
46 pub extra_fields: HashMap<String, Value>,
47}
48
49impl<A> DefaultProps<A> {
50 pub fn new(capability_action: Option<A>) -> Self {
51 Self {
52 capability_action,
53 extra_fields: HashMap::new(),
54 }
55 }
56}
57
58#[derive(Debug, Serialize, Deserialize, Clone)]
61#[serde(rename_all = "camelCase")]
62pub struct Delegation<C, S = DefaultProps<String>> {
63 #[serde(rename = "@context")]
65 pub context: Context<SecurityV2>,
66
67 pub id: UriBuf,
69
70 pub parent_capability: UriBuf,
72
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub invoker: Option<UriBuf>,
76
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub caveat: Option<C>,
80
81 #[serde(flatten)]
83 pub additional_properties: S,
84}
85
86impl<C, P> Delegation<C, P> {
87 pub fn new(id: UriBuf, parent_capability: UriBuf, additional_properties: P) -> Self {
89 Self {
90 context: Context::default(),
91 id,
92 parent_capability,
93 invoker: None,
94 caveat: None,
95 additional_properties,
96 }
97 }
98
99 pub fn validate(&self, proofs: &Proofs<AnySuite>) -> Result<(), DelegationValidationError> {
100 for proof in proofs.iter() {
101 if proof.configuration().proof_purpose != ProofPurpose::CapabilityDelegation {
102 return Err(DelegationValidationError::InvalidProofPurpose);
103 }
104 }
105
106 Ok(())
107 }
108
109 pub fn validate_invocation_proof(
110 &self,
111 proof: &Proof<AnySuite>,
112 ) -> Result<(), InvocationValidationError> {
113 let id: &Uri = proof
114 .extra_properties
115 .get("capability")
116 .and_then(json_syntax::Value::as_str)
117 .ok_or(InvocationValidationError::MissingTargetId)?
118 .try_into()
119 .map_err(|_| InvocationValidationError::IdMismatch)?;
120
121 if id != &self.id {
122 return Err(InvocationValidationError::IdMismatch);
123 };
124
125 if let Some(invoker) = &self.invoker {
126 if invoker.as_iri() != proof.configuration().verification_method.id() {
127 return Err(InvocationValidationError::IncorrectInvoker);
128 }
129 }
130
131 Ok(())
132 }
133
134 pub async fn sign<S>(
136 self,
137 suite: AnySuite,
138 resolver: &impl VerificationMethodResolver<Method = AnyMethod>,
139 signer: S,
140 proof_configuration: InputProofOptions<AnySuite>,
141 capability_chain: &[&str],
142 ) -> Result<DataIntegrity<Self, AnySuite>, SignatureError>
143 where
144 C: Serialize,
145 P: Serialize,
146 S: Signer<AnyMethod>,
147 S::MessageSigner: MessageSigner<AnySignatureAlgorithm>,
148 {
149 self.sign_with(
150 suite,
151 SignatureEnvironment::default(),
152 resolver,
153 signer,
154 proof_configuration,
155 capability_chain,
156 )
157 .await
158 }
159
160 pub async fn sign_with<D, E, R, S>(
162 self,
163 suite: D,
164 environment: E,
165 resolver: R,
166 signer: S,
167 mut proof_configuration: InputProofOptions<D>,
168 capability_chain: &[&str],
169 ) -> Result<DataIntegrity<Self, D>, SignatureError>
170 where
171 D: CryptographicSuiteSigning<Self, E, R, S>,
172 InputSignatureOptions<D>: Default,
173 {
174 proof_configuration.extra_properties.insert(
175 "capabilityChain".into(),
176 json_syntax::to_value(capability_chain).unwrap(),
177 );
178
179 if proof_configuration.proof_purpose != ProofPurpose::CapabilityDelegation {
180 }
182
183 suite
184 .sign_with(
185 environment,
186 self,
187 resolver,
188 signer,
189 proof_configuration,
190 Default::default(),
191 )
192 .await
193 }
194}
195
196pub trait TargetCapabilityProvider {
197 type Caveat;
198 type AdditionalProperties;
199
200 fn target_capability(&self) -> &Delegation<Self::Caveat, Self::AdditionalProperties>;
201}
202
203impl<E: TargetCapabilityProvider> TargetCapabilityProvider for &E {
204 type Caveat = E::Caveat;
205 type AdditionalProperties = E::AdditionalProperties;
206
207 fn target_capability(&self) -> &Delegation<Self::Caveat, Self::AdditionalProperties> {
208 E::target_capability(*self)
209 }
210}
211
212pub struct InvocationVerifier<'a, C, S, R, L1 = ssi_json_ld::ContextLoader, L2 = ()> {
213 pub resolver: R,
214 pub json_ld_loader: L1,
215 pub eip712_types_loader: L2,
216 pub date_time: Option<DateTime<Utc>>,
217 pub delegation: &'a Delegation<C, S>,
218}
219
220impl<'a, C, S, R> InvocationVerifier<'a, C, S, R> {
221 pub fn from_resolver(resolver: R, delegation: &'a Delegation<C, S>) -> Self {
222 Self::from_verifier(VerificationParameters::from_resolver(resolver), delegation)
223 }
224}
225
226impl<'a, R, L1, L2, C, S> InvocationVerifier<'a, C, S, R, L1, L2> {
227 pub fn from_verifier(
228 verifier: VerificationParameters<R, L1, L2>,
229 delegation: &'a Delegation<C, S>,
230 ) -> Self {
231 Self {
232 resolver: verifier.resolver,
233 json_ld_loader: verifier.json_ld_loader,
234 eip712_types_loader: verifier.eip712_types_loader,
235 date_time: verifier.date_time,
236 delegation,
237 }
238 }
239}
240
241impl<'v, 'a, R, L1, L2, C, S> InvocationVerifier<'a, C, S, &'v R, &'v L1, &'v L2> {
242 pub fn from_verifier_ref(
243 verifier: &'v VerificationParameters<R, L1, L2>,
244 delegation: &'a Delegation<C, S>,
245 ) -> Self {
246 Self {
247 resolver: &verifier.resolver,
248 json_ld_loader: &verifier.json_ld_loader,
249 eip712_types_loader: &verifier.eip712_types_loader,
250 date_time: verifier.date_time,
251 delegation,
252 }
253 }
254}
255
256impl<C, S, R, L1, L2> DateTimeProvider for InvocationVerifier<'_, C, S, R, L1, L2> {
257 fn date_time(&self) -> DateTime<Utc> {
258 self.date_time.unwrap_or_else(Utc::now)
259 }
260}
261
262impl<C, S, R, L1, L2> ResolverProvider for InvocationVerifier<'_, C, S, R, L1, L2> {
263 type Resolver = R;
264
265 fn resolver(&self) -> &Self::Resolver {
266 &self.resolver
267 }
268}
269
270impl<C, S, R, L1: ssi_json_ld::Loader, L2> JsonLdLoaderProvider
271 for InvocationVerifier<'_, C, S, R, L1, L2>
272{
273 type Loader = L1;
274
275 fn loader(&self) -> &Self::Loader {
276 &self.json_ld_loader
277 }
278}
279
280impl<C, S, R, L1, L2: ssi_eip712::TypesLoader> Eip712TypesLoaderProvider
281 for InvocationVerifier<'_, C, S, R, L1, L2>
282{
283 type Loader = L2;
284
285 fn eip712_types(&self) -> &Self::Loader {
286 &self.eip712_types_loader
287 }
288}
289
290impl<C, S, R, L1, L2> TargetCapabilityProvider for InvocationVerifier<'_, C, S, R, L1, L2> {
291 type Caveat = C;
292 type AdditionalProperties = S;
293
294 fn target_capability(&self) -> &Delegation<Self::Caveat, Self::AdditionalProperties> {
295 self.delegation
296 }
297}
298
299impl<C, P> JsonLdObject for Delegation<C, P> {
300 fn json_ld_context(&self) -> Option<Cow<ssi_json_ld::syntax::Context>> {
301 Some(Cow::Borrowed(self.context.as_ref()))
302 }
303}
304
305impl<C, P> JsonLdNodeObject for Delegation<C, P> {}
306
307impl<C, S, E> ValidateClaims<E, AnyProofs> for Delegation<C, S> {
308 fn validate_claims(&self, _: &E, proofs: &AnyProofs) -> ClaimsValidity {
309 self.validate(proofs).map_err(InvalidClaims::other)
310 }
311}
312
313impl<C, P> ssi_json_ld::Expandable for Delegation<C, P>
314where
315 C: Serialize,
316 P: Serialize,
317{
318 type Error = JsonLdError;
319
320 type Expanded<I, V>
321 = ssi_json_ld::ExpandedDocument<V::Iri, V::BlankId>
322 where
323 I: Interpretation,
324 V: VocabularyMut,
325 V::Iri: LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
326 V::BlankId: LinkedDataResource<I, V> + LinkedDataSubject<I, V>;
327
328 async fn expand_with<I, V>(
329 &self,
330 ld: &mut LdEnvironment<V, I>,
331 loader: &impl Loader,
332 ) -> Result<Self::Expanded<I, V>, Self::Error>
333 where
334 I: Interpretation,
335 V: VocabularyMut,
336 V::Iri: Clone + Eq + Hash + LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
337 V::BlankId: Clone + Eq + Hash + LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
338 {
339 let json = json_syntax::to_value(self).unwrap();
340 ssi_json_ld::CompactJsonLd(json)
341 .expand_with(ld, loader)
342 .await
343 }
344}
345
346#[derive(Debug, thiserror::Error)]
347pub enum DelegationValidationError {
348 #[error("invalid proof purpose")]
349 InvalidProofPurpose,
350}
351
352#[derive(Debug, thiserror::Error)]
353pub enum InvocationValidationError {
354 #[error("invalid proof purpose")]
355 InvalidProofPurpose,
356
357 #[error("Target Capability IDs don't match")]
358 IdMismatch,
359
360 #[error("Missing proof target capability ID")]
361 MissingTargetId,
362
363 #[error("Incorrect Invoker")]
364 IncorrectInvoker,
365}
366
367#[derive(Debug, Serialize, Deserialize, Clone)]
369#[serde(rename_all = "camelCase")]
370pub struct Invocation<P = DefaultProps<String>> {
371 #[serde(rename = "@context")]
373 pub context: Context<SecurityV2>,
374
375 pub id: UriBuf,
377
378 #[serde(flatten)]
380 pub property_set: P,
381}
382
383impl<P> Invocation<P> {
384 pub fn new(id: UriBuf, property_set: P) -> Self {
385 Self {
386 context: Context::default(),
387 id,
388 property_set,
389 }
390 }
391
392 pub async fn sign<S>(
394 self,
395 suite: AnySuite,
396 resolver: impl VerificationMethodResolver<Method = AnyMethod>,
397 signer: S,
398 mut proof_configuration: InputProofOptions<AnySuite>,
399 target: &Uri,
400 ) -> Result<AnyDataIntegrity<Invocation<P>>, SignatureError>
401 where
402 P: Serialize,
403 S: Signer<AnyMethod>,
404 S::MessageSigner: MessageSigner<AnySignatureAlgorithm>,
405 {
406 proof_configuration
407 .extra_properties
408 .insert("capability".into(), json_syntax::to_value(target).unwrap());
409
410 suite
411 .sign(self, resolver, signer, proof_configuration)
412 .await
413 }
414
415 pub fn validate<C, Q>(
416 &self,
417 target_capability: &Delegation<C, Q>,
419 proofs: &Proofs<AnySuite>,
420 ) -> Result<(), InvocationValidationError> {
421 for proof in proofs.iter() {
422 if proof.configuration().proof_purpose != ProofPurpose::CapabilityInvocation {
423 return Err(InvocationValidationError::InvalidProofPurpose);
424 }
425
426 target_capability.validate_invocation_proof(proof)?
427 }
428
429 Ok(())
430 }
431}
432
433impl<P> JsonLdObject for Invocation<P> {
434 fn json_ld_context(&self) -> Option<Cow<ssi_json_ld::syntax::Context>> {
435 Some(Cow::Borrowed(self.context.as_ref()))
436 }
437}
438
439impl<P> JsonLdNodeObject for Invocation<P> {}
440
441impl<P> ssi_json_ld::Expandable for Invocation<P>
442where
443 P: Serialize,
444{
445 type Error = JsonLdError;
446
447 type Expanded<I, V>
448 = ssi_json_ld::ExpandedDocument<V::Iri, V::BlankId>
449 where
450 I: Interpretation,
451 V: VocabularyMut,
452 V::Iri: LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
453 V::BlankId: LinkedDataResource<I, V> + LinkedDataSubject<I, V>;
454
455 async fn expand_with<I, V>(
456 &self,
457 ld: &mut LdEnvironment<V, I>,
458 loader: &impl Loader,
459 ) -> Result<Self::Expanded<I, V>, Self::Error>
460 where
461 I: Interpretation,
462 V: VocabularyMut,
463 V::Iri: Clone + Eq + Hash + LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
464 V::BlankId: Clone + Eq + Hash + LinkedDataResource<I, V> + LinkedDataSubject<I, V>,
465 {
466 let json = json_syntax::to_value(self).unwrap();
467 ssi_json_ld::CompactJsonLd(json)
468 .expand_with(ld, loader)
469 .await
470 }
471}
472
473impl<E, S> ValidateClaims<E, AnyProofs> for Invocation<S>
474where
475 E: TargetCapabilityProvider,
476{
477 fn validate_claims(&self, env: &E, proofs: &AnyProofs) -> ClaimsValidity {
478 self.validate(env.target_capability(), proofs)
479 .map_err(InvalidClaims::other)
480 }
481}
482
483#[cfg(test)]
484mod tests {
485 use super::*;
486 use ssi_claims::VerificationParameters;
487 use ssi_data_integrity::DataIntegrity;
488 use ssi_dids_core::{example::ExampleDIDResolver, VerificationMethodDIDResolver};
489 use ssi_jwk::JWK;
490 use ssi_verification_methods::SingleSecretSigner;
491 use static_iref::uri;
492
493 #[derive(Deserialize, PartialEq, Debug, Clone, Serialize)]
494 enum Actions {
495 Read,
496 Write,
497 }
498
499 impl Default for Actions {
500 fn default() -> Self {
501 Self::Read
502 }
503 }
504
505 #[test]
506 fn delegation_from_json() {
507 let zcap_str = include_str!("../../../examples/files/zcap_delegation.jsonld");
508 let zcap: Delegation<(), ()> = serde_json::from_str(zcap_str).unwrap();
509 assert_eq!(
510 zcap.id,
511 uri!("https://whatacar.example/a-fancy-car/proc/7a397d7b")
512 );
513 assert_eq!(
514 zcap.parent_capability,
515 uri!("https://whatacar.example/a-fancy-car")
516 );
517 assert_eq!(
518 zcap.invoker.as_deref(),
519 Some(uri!("https://social.example/alyssa#key-for-car"))
520 );
521 }
522
523 #[test]
524 fn invocation_from_json() {
525 #[derive(Deserialize, PartialEq, Debug, Clone, Serialize)]
526 enum AC {
527 Drive,
528 }
529 let zcap_str = include_str!("../../../examples/files/zcap_invocation.jsonld");
530 let zcap: Invocation<DefaultProps<AC>> = serde_json::from_str(zcap_str).unwrap();
531 assert_eq!(
532 zcap.id,
533 uri!("urn:uuid:ad86cb2c-e9db-434a-beae-71b82120a8a4")
534 );
535 assert_eq!(zcap.property_set.capability_action, Some(AC::Drive));
536 }
537
538 #[async_std::test]
539 async fn round_trip() {
540 use ssi_data_integrity::ProofOptions;
541
542 let dk = VerificationMethodDIDResolver::new(ExampleDIDResolver::new());
543 let params = VerificationParameters::from_resolver(&dk);
544
545 let alice_did = "did:example:foo";
546 let alice_vm = UriBuf::new(format!("{}#key2", alice_did).into_bytes()).unwrap();
547 let alice = SingleSecretSigner::new(JWK {
548 key_id: Some(alice_vm.clone().into()),
549 ..serde_json::from_str(include_str!("../../../tests/ed25519-2020-10-18.json")).unwrap()
550 })
551 .into_local();
552
553 let bob_did = "did:example:bar";
554 let bob_vm = UriBuf::new(format!("{}#key1", bob_did).into_bytes()).unwrap();
555 let bob = SingleSecretSigner::new(JWK {
556 key_id: Some(bob_vm.clone().into()),
557 ..serde_json::from_str(include_str!("../../../tests/ed25519-2021-06-16.json")).unwrap()
558 })
559 .into_local();
560
561 let del: Delegation<(), DefaultProps<Actions>> = Delegation {
562 invoker: Some(bob_vm.clone()),
563 ..Delegation::new(
564 uri!("urn:a_urn").to_owned(),
565 uri!("kepler://alices_orbit").to_owned(),
566 DefaultProps::new(Some(Actions::Read)),
567 )
568 };
569 let inv: Invocation<DefaultProps<Actions>> = Invocation::new(
570 uri!("urn:a_different_urn").to_owned(),
571 DefaultProps::new(Some(Actions::Read)),
572 );
573
574 let ldpo_alice = ProofOptions::new(
575 "2024-02-13T16:25:26Z".parse().unwrap(),
576 alice_vm.clone().into_iri().into(),
577 ProofPurpose::CapabilityDelegation,
578 Default::default(),
579 );
580 let ldpo_bob = ProofOptions::new(
581 "2024-02-13T16:25:26Z".parse().unwrap(),
582 bob_vm.clone().into_iri().into(),
583 ProofPurpose::CapabilityInvocation,
584 Default::default(),
585 );
586
587 let signed_del = del
588 .clone()
589 .sign(
590 AnySuite::pick(alice.secret(), ldpo_alice.verification_method.as_ref()).unwrap(),
591 &dk,
592 &alice,
593 ldpo_alice.clone(),
594 &[],
595 )
596 .await
597 .unwrap();
598
599 let signed_inv = inv
600 .sign(
601 AnySuite::pick(bob.secret(), ldpo_bob.verification_method.as_ref()).unwrap(),
602 &dk,
603 &bob,
604 ldpo_bob,
605 &signed_del.id,
606 )
607 .await
608 .unwrap();
609
610 assert!(signed_del.verify(¶ms).await.unwrap().is_ok());
612
613 assert!(signed_inv
614 .verify(InvocationVerifier::from_verifier_ref(
615 ¶ms,
616 &signed_del.claims
617 ))
618 .await
619 .unwrap()
620 .is_ok());
621
622 let bad_sig_del = DataIntegrity::new(
623 Delegation {
624 invoker: Some(uri!("did:someone_else").to_owned()),
625 ..signed_del.claims.clone()
626 },
627 signed_del.proofs.clone(),
628 );
629
630 let mut bad_sig_inv = signed_inv.clone();
631 bad_sig_inv.id = uri!("urn:different_id").to_owned();
632
633 assert!(bad_sig_del.verify(¶ms).await.unwrap().is_err());
635 assert!(bad_sig_inv
636 .verify(InvocationVerifier::from_verifier_ref(
637 ¶ms,
638 &signed_del.claims
639 ))
640 .await
641 .unwrap()
642 .is_err());
643
644 let wrong_del = Delegation {
646 invoker: Some(uri!("did:example:someone_else").to_owned()),
647 ..del.clone()
648 };
649 let signed_wrong_del = wrong_del
650 .sign(
651 AnySuite::pick(alice.secret(), ldpo_alice.verification_method.as_ref()).unwrap(),
652 &dk,
653 &alice,
654 ldpo_alice,
655 &[],
656 )
657 .await
658 .unwrap();
659 assert!(signed_inv
660 .verify(InvocationVerifier::from_verifier_ref(
661 ¶ms,
662 &signed_wrong_del.claims
663 ))
664 .await
665 .unwrap()
666 .is_err());
667 }
668}