From f21bc7848826e9ca14f487fa17b4fdc42129d7c8 Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Fri, 25 Mar 2022 12:39:27 -0400 Subject: [PATCH 1/7] Update for updated cacao-rs - Use VM for URI/invoker now that this works --- Cargo.toml | 2 +- src/lib.rs | 80 ++++++++++++++++++++++------------- tests/delegation0-zcap.jsonld | 4 +- tests/delegation0.siwe | 2 +- 4 files changed, 55 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a8a8a0a..1f66c26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ chrono = "0.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" -siwe = { version = "0.1" } +siwe = { version = "0.2" } async-std = { version = "1.9", features = ["attributes"] } async-trait = "0.1" multibase = "0.8" diff --git a/src/lib.rs b/src/lib.rs index 98b5c22..33b0c27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,11 @@ use async_trait::async_trait; -use cacao::siwe::SignInWithEthereum; +use cacao::siwe_cacao::SignInWithEthereum; use cacao::{BasicSignature, Header, Payload, SignatureScheme, Version as CacaoVersion, CACAO}; use chrono::prelude::DateTime; use iri_string::types::UriString; use serde::{ser::Error, Deserialize, Serialize, Serializer}; use serde_json::{json, Value}; +use siwe::TimeStamp; use ssi::did_resolve::DIDResolver; use ssi::error::Error as SSIError; use ssi::jsonld::SECURITY_V2_CONTEXT; @@ -190,13 +191,13 @@ where let Payload { domain, iss: issuer, - statement, + statement: statement_opt, aud, version, nonce, - iat: iat_string, - exp: exp_string_opt, - nbf: nbf_string_opt, + iat, + exp: exp_opt, + nbf: nbf_opt, request_id: req_id_opt, resources, } = cacao.payload(); @@ -206,6 +207,14 @@ where _ => return Err(CacaoToZcapError::UnknownCacaoVersion), } let signature = cacao.signature(); + let valid_from_opt = match nbf_opt { + Some(nbf) => Some(nbf.to_string()), + None => None, + }; + let exp_string_opt = match exp_opt { + Some(ts) => Some(ts.to_string()), + None => None, + }; let (header_type, signature_type) = get_header_and_signature_type(&header)?; let request_id = req_id_opt.as_ref().ok_or(CacaoToZcapError::MissingId)?; @@ -233,7 +242,7 @@ where .to_string(); let invoker_uri = URI::String(aud.as_str().to_string()); - let created_datetime = DateTime::parse_from_rfc3339(&iat_string) + let created_datetime = DateTime::parse_from_rfc3339(&iat.to_string()) .map_err(CacaoToZcapError::ParseIssuedAtDate)? .into(); @@ -262,10 +271,10 @@ where let delegation_extraprops = CacaoZcapExtraProps { r#type: String::from("CacaoZcap2022"), expires: exp_string_opt.clone(), - valid_from: nbf_string_opt.clone(), + valid_from: valid_from_opt, invocation_target: invocation_target.to_string(), cacao_payload_type: header_type.to_string(), - cacao_statement: Some(statement.to_string()), + cacao_statement: statement_opt.clone(), }; let mut delegation = Delegation { context: Contexts::Many(vec![ @@ -302,10 +311,6 @@ pub enum ZcapToCacaoError { #[error("Delegation object is missing invoker property")] MissingInvoker, - /// Delegation object is missing cacaoStatement property - #[error("Delegation object is missing cacaoStatement property")] - MissingCacaoStatement, - /// Proof object is missing signature (proofValue) #[error("Proof object is missing signature (proofValue)")] MissingProofValue, @@ -395,6 +400,14 @@ pub enum ZcapToCacaoError { /// Unknown delegation type #[error("Unknown delegation type")] UnknownDelegationType, + + /// Unable to parse validFrom timestamp + #[error("Unable to parse validFrom timestamp")] + UnableToParseValidFromTimestamp(chrono::format::ParseError), + + /// Unable to parse expires timestamp + #[error("Unable to parse expires timestamp")] + UnableToParseExpiresTimestamp(chrono::format::ParseError), } /// Root URN for authorization capability @@ -477,7 +490,7 @@ where r#type: zcap_type, invocation_target, expires: expires_opt, - valid_from: nbf_opt, + valid_from: valid_from_opt, cacao_payload_type, cacao_statement: cacao_statement_opt, } = zcap_extraprops; @@ -516,11 +529,6 @@ where .ok_or(ZcapToCacaoError::MissingInvoker)? .to_string(); - let statement = cacao_statement_opt - .as_ref() - .ok_or(ZcapToCacaoError::MissingCacaoStatement)? - .to_string(); - let sig_mb = proof .proof_value .as_ref() @@ -537,7 +545,21 @@ where let created = created_opt .as_ref() .ok_or(ZcapToCacaoError::MissingProofCreated)?; - let iat = created.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true); + let iat = TimeStamp::from(created.clone()); + let nbf_opt = match valid_from_opt { + Some(valid_from) => Some( + TimeStamp::from_str(valid_from) + .map_err(ZcapToCacaoError::UnableToParseValidFromTimestamp)?, + ), + None => None, + }; + let exp_opt = match expires_opt { + Some(vcdt) => Some( + TimeStamp::from_str(&String::from(vcdt.clone())) + .map_err(ZcapToCacaoError::UnableToParseExpiresTimestamp)?, + ), + None => None, + }; /// First value of capability chain is the root capability; that is decoded to get the /// invocation target which becomes the first value of the resources array. /// Remaining values of the capability chain are delegation capability ids, that are passed @@ -583,15 +605,15 @@ where let payload = Payload { domain: domain.to_string().try_into().unwrap(), iss: issuer.try_into().map_err(ZcapToCacaoError::IssuerParse)?, - statement: statement.to_string(), + statement: cacao_statement_opt.clone(), aud: invoker .as_str() .try_into() .map_err(ZcapToCacaoError::InvokerParseAud)?, version: CacaoVersion::V1, nonce: nonce.to_string(), - iat: iat.to_string(), - exp: expires_opt.clone(), + iat: iat, + exp: exp_opt.clone(), nbf: nbf_opt.clone(), request_id: Some(id.to_string()), resources, @@ -735,7 +757,7 @@ impl ProofSuite for CacaoZcapProof2022 { mod tests { use super::*; use pretty_assertions::assert_eq; - use siwe::eip4361::Message; + use siwe::Message; pub struct ExampleDIDPKH; use async_trait::async_trait; @@ -812,7 +834,7 @@ mod tests { #[async_std::test] async fn siwe_verify() { - let message: Payload = Message::from_str( + let message = Message::from_str( r#"localhost:4361 wants you to sign in with your Ethereum account: 0x6Da01670d8fc844e736095918bbE11fE8D564163 @@ -824,15 +846,15 @@ Chain ID: 1 Nonce: kEWepMt9knR6lWJ6A Issued At: 2021-12-07T18:28:18.807Z"#, ) - .unwrap() - .into(); + .unwrap(); + let payload = Payload::from(message); // Sanity check: verify signature let sig_mb = r#"f6228b3ecd7bf2df018183aeab6b6f1db1e9f4e3cbe24560404112e25363540eb679934908143224d746bbb5e1aa65ab435684081f4dbb74a0fec57f98f40f5051c"#; let (_base, sig) = multibase::decode(&sig_mb).unwrap(); let sig = BasicSignature { s: sig.try_into().unwrap(), }; - let cacao = CACAO::::new(message, sig); + let cacao = CACAO::::new(payload, sig); cacao.verify().await.unwrap(); // This SIWE is not expected to be valid as a CACAO Zcap, but is here as an example that is // verifiable. @@ -843,12 +865,12 @@ Issued At: 2021-12-07T18:28:18.807Z"#, let siwe_msg_str = include_str!("../tests/delegation0.siwe"); let siwe_msg_sig_hex = include_str!("../tests/delegation0.siwe.sig"); let siwe_msg = Message::from_str(siwe_msg_str).unwrap(); - let message: Payload = siwe_msg.into(); + let payload = Payload::from(siwe_msg); let (_base, sig) = multibase::decode(&format!("f{}", siwe_msg_sig_hex)).unwrap(); let sig = BasicSignature { s: sig.try_into().unwrap(), }; - let cacao = CACAO::::new(message, sig); + let cacao = CACAO::::new(payload, sig); let zcap = cacao_to_zcap(&cacao).unwrap(); let zcap_json = serde_json::to_value(&zcap).unwrap(); let zcap_json_expected: Value = diff --git a/tests/delegation0-zcap.jsonld b/tests/delegation0-zcap.jsonld index 8f2b65e..53089e2 100644 --- a/tests/delegation0-zcap.jsonld +++ b/tests/delegation0-zcap.jsonld @@ -7,7 +7,7 @@ "id": "urn:uuid:5fa2f8cc-fea0-4280-b98c-99f0acecf0e1", "parentCapability": "urn:zcap:root:kepler%3A%2F%2Fmy_orbit", "invocationTarget": "kepler://my_orbit", - "invoker": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp", + "invoker": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp", "expires": "2022-03-14T13:32:42.763Z", "validFrom": "2022-03-14T13:31:42.763Z", "cacaoPayloadType": "eip4361", @@ -19,7 +19,7 @@ "nonce": "h8yQTdScwt9pTyaQa", "cacaoSignatureType": "eip191", "proofValue": "f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "verificationMethod": "did:pkh:eip155:1:0x98626187d3b8e1f7c5b246ee443a07579b5923ac#blockchainAccountId", + "verificationMethod": "did:pkh:eip155:1:0x98626187D3B8e1F7C5b246eE443a07579b5923Ac#blockchainAccountId", "created": "2022-03-14T13:30:42.763Z", "capabilityChain": [ "urn:zcap:root:kepler%3A%2F%2Fmy_orbit" diff --git a/tests/delegation0.siwe b/tests/delegation0.siwe index 28e4b5f..b1ccda1 100644 --- a/tests/delegation0.siwe +++ b/tests/delegation0.siwe @@ -3,7 +3,7 @@ app.domain.com wants you to sign in with your Ethereum account: Allow access to your Kepler orbit -URI: did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp +URI: did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp Version: 1 Chain ID: 1 Nonce: h8yQTdScwt9pTyaQa From 4869e2823b5fbdd96aabe2ac5f7a8bf66d171a11 Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Fri, 25 Mar 2022 15:13:54 -0400 Subject: [PATCH 2/7] Remove unused test --- src/lib.rs | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 33b0c27..907e25d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -877,42 +877,26 @@ Issued At: 2021-12-07T18:28:18.807Z"#, serde_json::from_str(include_str!("../tests/delegation0-zcap.jsonld")).unwrap(); assert_eq!(zcap_json, zcap_json_expected); + let resolver = ExampleDIDPKH; // Verify cacao as zcap /* Can't call zcap.verify yet because that depends on ssi * having this proof type. use ssi::vc::Check; - let resolver = ExampleDIDPKH; let res = zcap.verify(None, &resolver).await; assert_eq!(res.errors, Vec::::new()); assert!(res.checks.iter().any(|c| c == &Check::Proof)); + */ let proof = zcap.proof.as_ref().unwrap(); let warnings = CacaoZcapProof2022 .verify(proof, &zcap, &resolver) .await .unwrap(); dbg!(warnings); - */ // Convert back let cacao = zcap_to_cacao::(&zcap).unwrap(); let msg: Message = cacao.payload().clone().try_into().unwrap(); assert_eq!(msg.to_string(), siwe_msg_str); - - // TODO: test this other one - let siwe_msg_str_2 = r#"app.domain.com wants you to sign in with your Ethereum account: -0x98626187D3B8e1F7C5b246eE443a07579b5923Ac - -Authorize an action on your Kepler Orbit - -URI: kepler://my_orbit -Version: 1 -Chain ID: 1 -Nonce: oSBq10JvMogdg5xmZ -Issued At: 2022-03-14T13:30:42.764Z -Expiration Time: 2022-03-14T13:30:52.764Z -Resources: -- kepler://my_orbit/s3#post -- kepler://my_orbit/s3#get"#; } } From be64353610b223a71b20522d81dafe95ed6b2c66 Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Fri, 25 Mar 2022 15:23:38 -0400 Subject: [PATCH 3/7] Fix context filename --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63f8744..9fc8ab3 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Licensed under either of at your option. -The [specification draft](./index.html) and [context files](contexts/v1.json) are additionally licensed under +The [specification draft](./index.html) and [context files](context/v1.json) are additionally licensed under * Creative Commons Attribution 4.0 International License ([LICENSE-CC-BY-4.0](LICENSE-CC-BY-4.0) or https://creativecommons.org/licenses/by/4.0/) From 12f4059d7f1c62f2deae46c5e85e4edd8bcaad6c Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Fri, 25 Mar 2022 15:59:01 -0400 Subject: [PATCH 4/7] Don't call verify yet --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 907e25d..0056548 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -885,14 +885,16 @@ Issued At: 2021-12-07T18:28:18.807Z"#, let res = zcap.verify(None, &resolver).await; assert_eq!(res.errors, Vec::::new()); assert!(res.checks.iter().any(|c| c == &Check::Proof)); - */ + + /* Can't verify because signature is not real let proof = zcap.proof.as_ref().unwrap(); let warnings = CacaoZcapProof2022 .verify(proof, &zcap, &resolver) .await .unwrap(); dbg!(warnings); + */ // Convert back let cacao = zcap_to_cacao::(&zcap).unwrap(); From 65261c49c3f05852d976da2779b9f79cd8f52057 Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Fri, 25 Mar 2022 16:18:30 -0400 Subject: [PATCH 5/7] Follow cargo clippy advice --- src/lib.rs | 103 ++++++++++++++++++++++++----------------------------- 1 file changed, 46 insertions(+), 57 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0056548..08bc214 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,16 @@ use async_trait::async_trait; use cacao::siwe_cacao::SignInWithEthereum; -use cacao::{BasicSignature, Header, Payload, SignatureScheme, Version as CacaoVersion, CACAO}; +use cacao::{Header, Payload, SignatureScheme, Version as CacaoVersion, CACAO}; use chrono::prelude::DateTime; use iri_string::types::UriString; -use serde::{ser::Error, Deserialize, Serialize, Serializer}; -use serde_json::{json, Value}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; use siwe::TimeStamp; use ssi::did_resolve::DIDResolver; use ssi::error::Error as SSIError; use ssi::jsonld::SECURITY_V2_CONTEXT; use ssi::jwk::JWK; -use ssi::ldp::{ - resolve_vm, LinkedDataDocument, ProofPreparation, ProofSuite, VerificationWarnings, -}; +use ssi::ldp::{LinkedDataDocument, ProofPreparation, ProofSuite, VerificationWarnings}; use ssi::vc::{LinkedDataProofOptions, Proof, ProofPurpose, URI}; use ssi::zcap::{Context, Contexts, Delegation}; use std::collections::HashMap; @@ -20,8 +18,8 @@ use std::fmt::{Display, Formatter}; use std::str::FromStr; use thiserror::Error; -pub const PROOF_TYPE_2022: &'static str = "CacaoZcapProof2022"; -pub const CONTEXT_URL_V1: &'static str = "https://demo.didkit.dev/2022/cacao-zcap/context/v1.json"; +pub const PROOF_TYPE_2022: &str = "CacaoZcapProof2022"; +pub const CONTEXT_URL_V1: &str = "https://demo.didkit.dev/2022/cacao-zcap/context/v1.json"; #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] @@ -175,8 +173,8 @@ fn get_header_and_signature_type(header: &Header) -> Result<(String, String), Ca .collect::>() .as_slice() { - [t1, t2] => return Ok((t1.to_string(), t2.to_string())), - _ => return Err(CacaoToZcapError::CombinedTypeParse), + [t1, t2] => Ok((t1.to_string(), t2.to_string())), + _ => Err(CacaoToZcapError::CombinedTypeParse), } } @@ -207,24 +205,18 @@ where _ => return Err(CacaoToZcapError::UnknownCacaoVersion), } let signature = cacao.signature(); - let valid_from_opt = match nbf_opt { - Some(nbf) => Some(nbf.to_string()), - None => None, - }; - let exp_string_opt = match exp_opt { - Some(ts) => Some(ts.to_string()), - None => None, - }; + let valid_from_opt = nbf_opt.as_ref().map(|nbf| nbf.to_string()); + let exp_string_opt = exp_opt.as_ref().map(|ts| ts.to_string()); - let (header_type, signature_type) = get_header_and_signature_type(&header)?; + let (header_type, signature_type) = get_header_and_signature_type(header)?; let request_id = req_id_opt.as_ref().ok_or(CacaoToZcapError::MissingId)?; let id = URI::String(request_id.to_string()); - let (first_resource, secondary_resources) = match resources.into_iter() { - mut iter => ( - iter.next().ok_or(CacaoToZcapError::MissingFirstResource)?, - iter, - ), - }; + let mut iter = resources.iter(); + let (first_resource, secondary_resources) = ( + iter.next().ok_or(CacaoToZcapError::MissingFirstResource)?, + iter, + ); + let invocation_target = first_resource; let root_cap_urn = ZcapRootURN { target: first_resource.clone(), @@ -254,7 +246,7 @@ where let proof_value_string = multibase::encode(multibase::Base::Base16Lower, signature); let proof_extraprops = CacaoZcapProofExtraProps { capability_chain, - cacao_signature_type: signature_type.to_string(), + cacao_signature_type: signature_type, } .into_property_set_opt() .map_err(CacaoToZcapError::ConvertProofExtraProps)?; @@ -270,10 +262,10 @@ where }; let delegation_extraprops = CacaoZcapExtraProps { r#type: String::from("CacaoZcap2022"), - expires: exp_string_opt.clone(), + expires: exp_string_opt, valid_from: valid_from_opt, invocation_target: invocation_target.to_string(), - cacao_payload_type: header_type.to_string(), + cacao_payload_type: header_type, cacao_statement: statement_opt.clone(), }; let mut delegation = Delegation { @@ -282,11 +274,7 @@ where Context::URI(URI::String(CONTEXT_URL_V1.into())), ]), invoker: Some(invoker_uri), - ..Delegation::new( - id, - URI::String(parent_capability_id.to_string()), - delegation_extraprops, - ) + ..Delegation::new(id, URI::String(parent_capability_id), delegation_extraprops) }; delegation.proof = Some(proof); Ok(delegation) @@ -529,8 +517,7 @@ where .ok_or(ZcapToCacaoError::MissingInvoker)? .to_string(); - let sig_mb = proof - .proof_value + let sig_mb = proof_value .as_ref() .ok_or(ZcapToCacaoError::MissingProofValue)?; let (_base, sig) = @@ -545,7 +532,7 @@ where let created = created_opt .as_ref() .ok_or(ZcapToCacaoError::MissingProofCreated)?; - let iat = TimeStamp::from(created.clone()); + let iat = TimeStamp::from(*created); let nbf_opt = match valid_from_opt { Some(valid_from) => Some( TimeStamp::from_str(valid_from) @@ -554,23 +541,23 @@ where None => None, }; let exp_opt = match expires_opt { - Some(vcdt) => Some( - TimeStamp::from_str(&String::from(vcdt.clone())) + Some(expires) => Some( + TimeStamp::from_str(expires) .map_err(ZcapToCacaoError::UnableToParseExpiresTimestamp)?, ), None => None, }; - /// First value of capability chain is the root capability; that is decoded to get the - /// invocation target which becomes the first value of the resources array. - /// Remaining values of the capability chain are delegation capability ids, that are passed - /// through into the resources array. - let (first_cap, secondary_caps) = match capability_chain.into_iter() { - mut iter => ( - iter.next() - .ok_or(ZcapToCacaoError::ExpectedNonEmptyCapabilityChain)?, - iter, - ), - }; + // First value of capability chain is the root capability; that is decoded to get the + // invocation target which becomes the first value of the resources array. + // Remaining values of the capability chain are delegation capability ids, that are passed + // through into the resources array. + let mut iter = capability_chain.into_iter(); + let (first_cap, secondary_caps) = ( + iter.next() + .ok_or(ZcapToCacaoError::ExpectedNonEmptyCapabilityChain)?, + iter, + ); + let root_cap_urn = ZcapRootURN::from_str(&first_cap).map_err(ZcapToCacaoError::RootURIParse)?; let root_target = root_cap_urn.target; let resources = vec![Ok(root_target.clone())] @@ -612,9 +599,9 @@ where .map_err(ZcapToCacaoError::InvokerParseAud)?, version: CacaoVersion::V1, nonce: nonce.to_string(), - iat: iat, - exp: exp_opt.clone(), - nbf: nbf_opt.clone(), + iat, + exp: exp_opt, + nbf: nbf_opt, request_id: Some(id.to_string()), resources, }; @@ -628,11 +615,11 @@ pub struct CacaoZcapProof2022; impl ProofSuite for CacaoZcapProof2022 { async fn sign( &self, - document: &(dyn LinkedDataDocument + Sync), - options: &LinkedDataProofOptions, + _document: &(dyn LinkedDataDocument + Sync), + _options: &LinkedDataProofOptions, _resolver: &dyn DIDResolver, - key: &JWK, - extra_proof_properties: Option>, + _key: &JWK, + _extra_proof_properties: Option>, ) -> Result { Err(SSIError::NotImplemented) /* @@ -756,8 +743,10 @@ impl ProofSuite for CacaoZcapProof2022 { #[cfg(test)] mod tests { use super::*; + use cacao::BasicSignature; use pretty_assertions::assert_eq; use siwe::Message; + use ssi::ldp::resolve_vm; pub struct ExampleDIDPKH; use async_trait::async_trait; @@ -877,7 +866,7 @@ Issued At: 2021-12-07T18:28:18.807Z"#, serde_json::from_str(include_str!("../tests/delegation0-zcap.jsonld")).unwrap(); assert_eq!(zcap_json, zcap_json_expected); - let resolver = ExampleDIDPKH; + let _resolver = ExampleDIDPKH; // Verify cacao as zcap /* Can't call zcap.verify yet because that depends on ssi * having this proof type. From 098faf56b5f037219e713b9e1ed04fa3e8c6392a Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Fri, 25 Mar 2022 16:35:29 -0400 Subject: [PATCH 6/7] Use source more --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 08bc214..e00a10e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -391,11 +391,11 @@ pub enum ZcapToCacaoError { /// Unable to parse validFrom timestamp #[error("Unable to parse validFrom timestamp")] - UnableToParseValidFromTimestamp(chrono::format::ParseError), + UnableToParseValidFromTimestamp(#[source] chrono::format::ParseError), /// Unable to parse expires timestamp #[error("Unable to parse expires timestamp")] - UnableToParseExpiresTimestamp(chrono::format::ParseError), + UnableToParseExpiresTimestamp(#[source] chrono::format::ParseError), } /// Root URN for authorization capability From fa332ff065ee4d03a5304b1b1177a3613742b35e Mon Sep 17 00:00:00 2001 From: "Charles E. Lehner" Date: Fri, 25 Mar 2022 16:29:24 -0400 Subject: [PATCH 7/7] Check more during CACAO to ZCAP conversion - Check proof type - Check proofPurpose - Check CACAO payload and signature types - Disallow additional properties --- src/lib.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index e00a10e..a33a931 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ pub const CONTEXT_URL_V1: &str = "https://demo.didkit.dev/2022/cacao-zcap/contex #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct CacaoZcapExtraProps { /// Type of Delegation pub r#type: String, @@ -69,6 +70,7 @@ pub struct CacaoZcapExtraProps { #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct CacaoZcapProofExtraProps { /// Capability chain /// @@ -389,6 +391,10 @@ pub enum ZcapToCacaoError { #[error("Unknown delegation type")] UnknownDelegationType, + /// Unknown delegation proof type + #[error("Unknown delegation proof type")] + UnknownDelegationProofType, + /// Unable to parse validFrom timestamp #[error("Unable to parse validFrom timestamp")] UnableToParseValidFromTimestamp(#[source] chrono::format::ParseError), @@ -396,6 +402,21 @@ pub enum ZcapToCacaoError { /// Unable to parse expires timestamp #[error("Unable to parse expires timestamp")] UnableToParseExpiresTimestamp(#[source] chrono::format::ParseError), + + /// Expected capabilityDelegation proof purpose but found something else + /// + /// [CacaoZcapProof2022] can only be used with proof purpose [CapabilityDelegation](ProofPurpose::CapabilityDelegation) + #[error("Expected capabilityDelegation proof purpose but found '{0:?}'")] + ExpectedCapabilityDelegationProofPurpose(Option), + + /// Unexpected CACAO types. + #[error("Unexpected CACAO types. Expected '{expected}' but found '{found}'")] + UnexpectedCACAOTypes { + /// The id pair for the [SignatureScheme] generic argument to [zcap_to_cacao] + expected: String, + /// The id pair found in the ZCAP properties ([CacaoZcapProofExtraProps::cacao_signature_type and [CacaoZcapExtraProps::cacao_payload_type]) + found: String, + }, } /// Root URN for authorization capability @@ -491,6 +512,7 @@ where cacao_signature_type, } = proof_extraprops; let Proof { + type_: proof_type, proof_purpose, proof_value, verification_method: vm_opt, @@ -502,6 +524,17 @@ where if zcap_type != "CacaoZcap2022" { return Err(ZcapToCacaoError::UnknownDelegationType); } + if proof_type != "CacaoZcapProof2022" { + return Err(ZcapToCacaoError::UnknownDelegationProofType); + } + let combined_type = format!("{}-{}", cacao_payload_type, cacao_signature_type); + let s_id = S::id(); + if combined_type != s_id { + return Err(ZcapToCacaoError::UnexpectedCACAOTypes { + expected: s_id, + found: combined_type, + }); + } match contexts { Contexts::Many(contexts) => match contexts.as_slice() { @@ -517,6 +550,12 @@ where .ok_or(ZcapToCacaoError::MissingInvoker)? .to_string(); + if proof_purpose.as_ref() != Some(&ProofPurpose::CapabilityDelegation) { + return Err(ZcapToCacaoError::ExpectedCapabilityDelegationProofPurpose( + proof_purpose.clone(), + )); + } + let sig_mb = proof_value .as_ref() .ok_or(ZcapToCacaoError::MissingProofValue)?;