Skip to content
This repository has been archived by the owner on Apr 29, 2024. It is now read-only.

Commit

Permalink
Implementing actions/substatement
Browse files Browse the repository at this point in the history
  • Loading branch information
clehner committed Apr 14, 2022
1 parent d2acb70 commit 547103c
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 16 deletions.
149 changes: 143 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use ssi::error::Error as SSIError;
use ssi::jsonld::SECURITY_V2_CONTEXT;
use ssi::jwk::JWK;
use ssi::ldp::{LinkedDataDocument, ProofPreparation, ProofSuite, VerificationWarnings};
use ssi::one_or_many::OneOrMany;
use ssi::vc::{LinkedDataProofOptions, Proof, ProofPurpose, URI};
use ssi::zcap::{Context, Contexts, Delegation};
use std::collections::HashMap;
Expand Down Expand Up @@ -61,16 +62,27 @@ pub struct CacaoZcapExtraProps {
/// CACAO header "t" value
pub cacao_payload_type: String,

/// CACAO statement
/// zCap allowed actions
///
/// [CACAO] payload "statement" value
/// <https://w3id.org/security#allowedAction>
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_action: Option<OneOrMany<String>>,

/// CACAO-ZCAP substatement
///
/// Part of a [CACAO] payload "statement" value
///
/// In [EIP-4361], statement is defined as a "human-readable ASCII assertion that the user will sign".
///
/// CACAO-ZCAP requires the CACAO statement to match a format containing an optional a list of
/// [allowed actions](CacaoZcapExtraProps::allowed_action) and an optional
/// [substatement string](CacaoZcapExtraProps::cacao_zcap_substatement).
///
/// [CACAO-ZCAP]: https://demo.didkit.dev/2022/cacao-zcap/
/// [CACAO]: https://github.com/ChainAgnostic/CAIPs/blob/8fdb5bfd1bdf15c9daf8aacfbcc423533764dfe9/CAIPs/caip-draft_cacao.md#container-format
/// [EIP-4361]: https://eips.ethereum.org/EIPS/eip-4361#message-field-descriptions
#[serde(skip_serializing_if = "Option::is_none")]
pub cacao_statement: Option<String>,
pub cacao_zcap_substatement: Option<String>,

/// CACAO request ID.
///
Expand Down Expand Up @@ -206,6 +218,110 @@ impl CacaoZcapProofExtraProps {
}
}

#[derive(Clone, Debug)]
struct CacaoZcapStatement {
/// zCap [allowedAction](CacaoZcapExtraProps::allowed_action) values
pub actions: Option<OneOrMany<String>>,

/// CACAO-ZCAP [substatement](CacaoZcapExtraProps::cacao_zcap_substatement)
pub substatement: Option<String>,
}
impl CacaoZcapStatement {
/// Construct cacao-zcap statement
pub fn from_actions_and_substatement_opt(
substmt: Option<&str>,
actions: Option<&OneOrMany<String>>,
) -> Self {
Self {
actions: actions.cloned(),
substatement: substmt.map(|s| s.to_string()),
}
}

/// Serialize to a CACAO statement string, or None if there is no actions or substatement
pub fn to_string_opt(&self) -> Option<String> {
if self.actions.is_some() && self.substatement.is_some() {
Some(format!("{}", self))
} else {
None
}
}
}

impl Display for CacaoZcapStatement {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "Authorize action")?;
if let Some(actions) = self.actions.as_ref() {
write!(f, " (")?;
let mut actions_iter = actions.into_iter();
if let Some(action) = actions_iter.next() {
write!(f, "{}", action)?;
}
for action in actions_iter {
write!(f, ", {}", action)?;
}
write!(f, ")")?;
}
if let Some(substatement) = self.substatement.as_ref() {
write!(f, ": {}", substatement)?;
}
Ok(())
}
}

/// Error from attempting to parse a [CacaoZcapStatement]
#[derive(Error, Debug)]
pub enum CacaoZcapStatementParseError {
/// Unexpected statement prefix
#[error("Unexpected statement prefix")]
UnexpectedPrefix,

/// Expected separator
#[error("Expected separator before substatement")]
ExpectedSeparatorBeforeSubstatement,

/// Expected separator after actions
#[error("Expected separator after actions")]
ExpectedSeparatorAfterActions,
}

impl FromStr for CacaoZcapStatement {
type Err = CacaoZcapStatementParseError;
fn from_str(stmt: &str) -> Result<Self, Self::Err> {
let mut s = stmt
.strip_prefix("Authorize action")
.ok_or(CacaoZcapStatementParseError::UnexpectedPrefix)?;

let actions = if let Some(after_paren) = s.strip_prefix(" (") {
let (actions_to_split, after_actions) = after_paren
.split_once(')')
.ok_or(CacaoZcapStatementParseError::ExpectedSeparatorAfterActions)?;
s = after_actions;
Some(OneOrMany::Many(
actions_to_split
.split(", ")
.map(String::from)
.collect::<Vec<String>>(),
))
} else {
None
};
let substatement = if s.is_empty() {
None
} else {
Some(
s.strip_prefix(": ")
.ok_or(CacaoZcapStatementParseError::ExpectedSeparatorBeforeSubstatement)?
.to_string(),
)
};
Ok(Self {
actions,
substatement,
})
}
}

/// Error from converting to [CACAO to a Zcap](cacao_to_zcap)
#[derive(Error, Debug)]
pub enum CacaoToZcapError {
Expand Down Expand Up @@ -256,6 +372,10 @@ pub enum CacaoToZcapError {
/// Unable to parse root capability id as URI
#[error("Unable to parse root capability id as URI")]
RootCapUriParse(#[source] iri_string::validate::Error),

/// Unable to parse CACAO-ZCAP statement string
#[error("Unable to parse CACAO-ZCAP statement string")]
StatementParse(#[source] CacaoZcapStatementParseError),
}

fn get_header_and_signature_type(header: &Header) -> Result<(String, String), CacaoToZcapError> {
Expand Down Expand Up @@ -323,6 +443,15 @@ where
_ => return Err(CacaoToZcapError::UnknownCacaoVersion),
}
let signature = cacao.signature();

let (substatement_opt, allowed_action_opt) = if let Some(statement) = statement_opt {
let cacao_zcap_stmt =
CacaoZcapStatement::from_str(statement).map_err(CacaoToZcapError::StatementParse)?;
(cacao_zcap_stmt.substatement, cacao_zcap_stmt.actions)
} else {
(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());

Expand Down Expand Up @@ -395,7 +524,8 @@ where
valid_from: valid_from_opt,
invocation_target: invocation_target.to_string(),
cacao_payload_type: header_type,
cacao_statement: statement_opt.clone(),
allowed_action: allowed_action_opt,
cacao_zcap_substatement: substatement_opt,
cacao_request_id: request_id_opt.clone(),
};
let mut delegation = Delegation {
Expand Down Expand Up @@ -643,9 +773,16 @@ where
expires: expires_opt,
valid_from: valid_from_opt,
cacao_payload_type,
cacao_statement: cacao_statement_opt,
cacao_zcap_substatement: cacao_zcap_substatement_opt,
allowed_action: allowed_action_opt,
cacao_request_id,
} = zcap_extraprops;

let stmt = CacaoZcapStatement::from_actions_and_substatement_opt(
cacao_zcap_substatement_opt.as_ref().map(|s| s.as_str()),
allowed_action_opt.as_ref(),
);

let proof = zcap.proof.as_ref().ok_or(ZcapToCacaoError::MissingProof)?;
let proof_extraprops =
CacaoZcapProofExtraProps::from_property_set_opt(proof.property_set.clone())
Expand Down Expand Up @@ -785,7 +922,7 @@ where
let payload = Payload {
domain: domain.to_string().try_into().unwrap(),
iss: issuer.try_into().map_err(ZcapToCacaoError::IssuerParse)?,
statement: cacao_statement_opt.clone(),
statement: stmt.to_string_opt(),
aud: invoker
.as_str()
.try_into()
Expand Down
8 changes: 6 additions & 2 deletions tests/delegation0-zcap.jsonld
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
"https://w3id.org/security/v2",
"https://demo.didkit.dev/2022/cacao-zcap/context/v1.json"
],
"allowedAction": [
"read",
"write"
],
"cacaoPayloadType": "eip4361",
"cacaoRequestId": "https://example.org/delegations/981871674",
"cacaoStatement": "Allow access to your Kepler orbit",
"cacaoZcapSubstatement": "Allow access to your Kepler orbit",
"expires": "2022-03-14T13:32:42.763Z",
"id": "urn:uuid:f6076745-00e9-475b-bd97-53245c9be61a",
"id": "urn:uuid:0807b7fa-0ca6-445c-99f6-f9c6d015b675",
"invocationTarget": "kepler://my_orbit",
"invoker": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"parentCapability": "urn:zcap:root:kepler%3A%2F%2Fmy_orbit",
Expand Down
2 changes: 1 addition & 1 deletion tests/delegation0.siwe
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
app.domain.com wants you to sign in with your Ethereum account:
0x98626187D3B8e1F7C5b246eE443a07579b5923Ac

Allow access to your Kepler orbit
Authorize action (read, write): Allow access to your Kepler orbit

URI: did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp
Version: 1
Expand Down
14 changes: 9 additions & 5 deletions tests/delegation1-zcap.jsonld
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
],
"cacaoPayloadType": "eip4361",
"cacaoRequestId": "https://example.org/siwe/123123213",
"cacaoStatement": "Allow access to your Kepler orbit",
"cacaoZcapSubstatement": "Allow access to your Kepler orbit",
"expires": "2049-01-01T00:00:00Z",
"id": "urn:uuid:076d7929-6c3f-4596-9853-c32d4c412204",
"id": "urn:uuid:db74a464-4292-4202-b781-c43d71c26760",
"invocationTarget": "kepler://my_orbit",
"invoker": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"parentCapability": "urn:uuid:f6076745-00e9-475b-bd97-53245c9be61a",
"parentCapability": "urn:uuid:0807b7fa-0ca6-445c-99f6-f9c6d015b675",
"proof": {
"cacaoSignatureType": "eip191",
"capabilityChain": [
Expand All @@ -20,11 +20,15 @@
"https://w3id.org/security/v2",
"https://demo.didkit.dev/2022/cacao-zcap/context/v1.json"
],
"allowedAction": [
"read",
"write"
],
"cacaoPayloadType": "eip4361",
"cacaoRequestId": "https://example.org/delegations/981871674",
"cacaoStatement": "Allow access to your Kepler orbit",
"cacaoZcapSubstatement": "Allow access to your Kepler orbit",
"expires": "2022-03-14T13:32:42.763Z",
"id": "urn:uuid:f6076745-00e9-475b-bd97-53245c9be61a",
"id": "urn:uuid:0807b7fa-0ca6-445c-99f6-f9c6d015b675",
"invocationTarget": "kepler://my_orbit",
"invoker": "did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
"parentCapability": "urn:zcap:root:kepler%3A%2F%2Fmy_orbit",
Expand Down
4 changes: 2 additions & 2 deletions tests/delegation1.siwe
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
app.domain.com wants you to sign in with your Ethereum account:
0x98626187D3B8e1F7C5b246eE443a07579b5923Ac

Allow access to your Kepler orbit
Authorize action: Allow access to your Kepler orbit

URI: did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp#z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp
Version: 1
Expand All @@ -13,4 +13,4 @@ Not Before: 2022-03-29T14:40:00Z
Request ID: https://example.org/siwe/123123213
Resources:
- kepler://my_orbit
- data:application/json;base64,eyJAY29udGV4dCI6WyJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L3YyIiwiaHR0cHM6Ly9kZW1vLmRpZGtpdC5kZXYvMjAyMi9jYWNhby16Y2FwL2NvbnRleHQvdjEuanNvbiJdLCJjYWNhb1BheWxvYWRUeXBlIjoiZWlwNDM2MSIsImNhY2FvUmVxdWVzdElkIjoiaHR0cHM6Ly9leGFtcGxlLm9yZy9kZWxlZ2F0aW9ucy85ODE4NzE2NzQiLCJjYWNhb1N0YXRlbWVudCI6IkFsbG93IGFjY2VzcyB0byB5b3VyIEtlcGxlciBvcmJpdCIsImV4cGlyZXMiOiIyMDIyLTAzLTE0VDEzOjMyOjQyLjc2M1oiLCJpZCI6InVybjp1dWlkOmY2MDc2NzQ1LTAwZTktNDc1Yi1iZDk3LTUzMjQ1YzliZTYxYSIsImludm9jYXRpb25UYXJnZXQiOiJrZXBsZXI6Ly9teV9vcmJpdCIsImludm9rZXIiOiJkaWQ6a2V5Ono2TWtpVEJ6MXltdWVwQVE0SEVIWVNGMUg4cXVHNUdMVlZRUjNkamRYM21Eb29XcCN6Nk1raVRCejF5bXVlcEFRNEhFSFlTRjFIOHF1RzVHTFZWUVIzZGpkWDNtRG9vV3AiLCJwYXJlbnRDYXBhYmlsaXR5IjoidXJuOnpjYXA6cm9vdDprZXBsZXIlM0ElMkYlMkZteV9vcmJpdCIsInByb29mIjp7ImNhY2FvU2lnbmF0dXJlVHlwZSI6ImVpcDE5MSIsImNhcGFiaWxpdHlDaGFpbiI6WyJ1cm46emNhcDpyb290OmtlcGxlciUzQSUyRiUyRm15X29yYml0Il0sImNyZWF0ZWQiOiIyMDIyLTAzLTE0VDEzOjMwOjQyLjc2M1oiLCJkb21haW4iOiJhcHAuZG9tYWluLmNvbSIsIm5vbmNlIjoiaDh5UVRkU2N3dDlwVHlhUWEiLCJwcm9vZlB1cnBvc2UiOiJjYXBhYmlsaXR5RGVsZWdhdGlvbiIsInByb29mVmFsdWUiOiJmMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsInR5cGUiOiJDYWNhb1pjYXBQcm9vZjIwMjIiLCJ2ZXJpZmljYXRpb25NZXRob2QiOiJkaWQ6cGtoOmVpcDE1NToxOjB4OTg2MjYxODdEM0I4ZTFGN0M1YjI0NmVFNDQzYTA3NTc5YjU5MjNBYyNibG9ja2NoYWluQWNjb3VudElkIn0sInR5cGUiOiJDYWNhb1pjYXAyMDIyIiwidmFsaWRGcm9tIjoiMjAyMi0wMy0xNFQxMzozMTo0Mi43NjNaIn0=
- data:application/json;base64,eyJAY29udGV4dCI6WyJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L3YyIiwiaHR0cHM6Ly9kZW1vLmRpZGtpdC5kZXYvMjAyMi9jYWNhby16Y2FwL2NvbnRleHQvdjEuanNvbiJdLCJhbGxvd2VkQWN0aW9uIjpbInJlYWQiLCJ3cml0ZSJdLCJjYWNhb1BheWxvYWRUeXBlIjoiZWlwNDM2MSIsImNhY2FvUmVxdWVzdElkIjoiaHR0cHM6Ly9leGFtcGxlLm9yZy9kZWxlZ2F0aW9ucy85ODE4NzE2NzQiLCJjYWNhb1pjYXBTdWJzdGF0ZW1lbnQiOiJBbGxvdyBhY2Nlc3MgdG8geW91ciBLZXBsZXIgb3JiaXQiLCJleHBpcmVzIjoiMjAyMi0wMy0xNFQxMzozMjo0Mi43NjNaIiwiaWQiOiJ1cm46dXVpZDowODA3YjdmYS0wY2E2LTQ0NWMtOTlmNi1mOWM2ZDAxNWI2NzUiLCJpbnZvY2F0aW9uVGFyZ2V0Ijoia2VwbGVyOi8vbXlfb3JiaXQiLCJpbnZva2VyIjoiZGlkOmtleTp6Nk1raVRCejF5bXVlcEFRNEhFSFlTRjFIOHF1RzVHTFZWUVIzZGpkWDNtRG9vV3AjejZNa2lUQnoxeW11ZXBBUTRIRUhZU0YxSDhxdUc1R0xWVlFSM2RqZFgzbURvb1dwIiwicGFyZW50Q2FwYWJpbGl0eSI6InVybjp6Y2FwOnJvb3Q6a2VwbGVyJTNBJTJGJTJGbXlfb3JiaXQiLCJwcm9vZiI6eyJjYWNhb1NpZ25hdHVyZVR5cGUiOiJlaXAxOTEiLCJjYXBhYmlsaXR5Q2hhaW4iOlsidXJuOnpjYXA6cm9vdDprZXBsZXIlM0ElMkYlMkZteV9vcmJpdCJdLCJjcmVhdGVkIjoiMjAyMi0wMy0xNFQxMzozMDo0Mi43NjNaIiwiZG9tYWluIjoiYXBwLmRvbWFpbi5jb20iLCJub25jZSI6Img4eVFUZFNjd3Q5cFR5YVFhIiwicHJvb2ZQdXJwb3NlIjoiY2FwYWJpbGl0eURlbGVnYXRpb24iLCJwcm9vZlZhbHVlIjoiZjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAiLCJ0eXBlIjoiQ2FjYW9aY2FwUHJvb2YyMDIyIiwidmVyaWZpY2F0aW9uTWV0aG9kIjoiZGlkOnBraDplaXAxNTU6MToweDk4NjI2MTg3RDNCOGUxRjdDNWIyNDZlRTQ0M2EwNzU3OWI1OTIzQWMjYmxvY2tjaGFpbkFjY291bnRJZCJ9LCJ0eXBlIjoiQ2FjYW9aY2FwMjAyMiIsInZhbGlkRnJvbSI6IjIwMjItMDMtMTRUMTM6MzE6NDIuNzYzWiJ9

0 comments on commit 547103c

Please sign in to comment.