From 1fc51c85ea661270a236781a8f65e1d894bbae7d Mon Sep 17 00:00:00 2001 From: Charles Cunningham Date: Fri, 25 Mar 2022 11:42:54 +0100 Subject: [PATCH 1/4] update siwe deps and integration --- .gitignore | 0 Cargo.toml | 9 ++++++--- src/lib.rs | 34 +++++++++++++++------------------- src/siwe.rs | 5 +++-- 4 files changed, 24 insertions(+), 24 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 Cargo.toml mode change 100644 => 100755 src/siwe.rs diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/Cargo.toml b/Cargo.toml old mode 100644 new mode 100755 index 4c072d1..61b088a --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,12 @@ repository = "https://github.com/spruceid/cacao/" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -eip4361 = ["siwe", "hex"] -default = ["eip4361"] +eip4361 = ["hex", "ethers-core"] +zcap = ["ssi"] +default = ["eip4361", "zcap"] [dependencies] -siwe = { version = "0.1", optional = true } +siwe = { version = "0.2" } iri-string = { version = "0.4", features = ["serde", "serde-std"] } chrono = "0.4" thiserror = "1.0" @@ -24,6 +25,8 @@ serde = "1.0" serde_with = "1.11" http = "0.2.5" hex = { version = "0.4", optional = true } +ethers-core = { version = "0.6.2", optional = true } +ssi = { version = "0.4", optional = true } [dev-dependencies] async-std = { version = "1.10", features = ["attributes"] } diff --git a/src/lib.rs b/src/lib.rs index 87d79c3..fd60d33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ use async_trait::async_trait; use chrono::{DateTime, Utc}; use http::uri::Authority; use iri_string::types::{UriAbsoluteString, UriString}; +use siwe::TimeStamp; use std::str::FromStr; use thiserror::Error; @@ -10,9 +11,10 @@ pub mod generic; #[cfg(feature = "siwe")] pub mod siwe; -pub type TimeStamp = DateTime; - -pub struct CACAO { +pub struct CACAO +where + S: SignatureScheme, +{ h: Header, p: Payload, s: S::Signature, @@ -104,15 +106,15 @@ pub struct Payload { pub aud: UriAbsoluteString, pub version: Version, pub nonce: String, - pub iat: String, - pub exp: Option, - pub nbf: Option, + pub iat: TimeStamp, + pub exp: Option, + pub nbf: Option, pub request_id: Option, pub resources: Vec, } impl Payload { - pub fn sign(self, s: ::Signature) -> CACAO { + pub fn sign(self, s: S::Signature) -> CACAO { CACAO { h: S::header(), p: self, @@ -135,18 +137,12 @@ impl Payload { &self.iss.as_str() } + pub fn valid_at(&self, t: &DateTime) -> bool { + self.nbf.as_ref().map(|nbf| nbf < t).unwrap_or(true) + && self.exp.as_ref().map(|exp| exp >= t).unwrap_or(true) + } + pub fn valid_now(&self) -> bool { - let now = Utc::now(); - self.nbf - .as_ref() - .and_then(|s| TimeStamp::from_str(s).ok()) - .map(|nbf| now >= nbf) - .unwrap_or(true) - && self - .exp - .as_ref() - .and_then(|s| TimeStamp::from_str(s).ok()) - .map(|exp| now < exp) - .unwrap_or(true) + self.valid_at(&Utc::now()) } } diff --git a/src/siwe.rs b/src/siwe.rs old mode 100644 new mode 100755 index 76923bd..52ffc38 --- a/src/siwe.rs +++ b/src/siwe.rs @@ -1,7 +1,8 @@ use super::{BasicSignature, Payload, SignatureScheme, VerificationError, Version}; use async_trait::async_trait; +use ethers_core::{types::H160, utils::to_checksum}; use hex::FromHex; -use siwe::eip4361::{Message, VerificationError as SVE, Version as SVersion}; +use siwe::{Message, VerificationError as SVE, Version as SVersion}; impl Into for Version { fn into(self) -> SVersion { @@ -61,7 +62,7 @@ impl From for Payload { iss: format!( "did:pkh:eip155:{}:0x{}", m.chain_id, - hex::encode(&m.address) + to_checksum(&H160(&m.address), None) ) .parse() .unwrap(), From b02a163c8954728c6652bfdb541e2c74562686a8 Mon Sep 17 00:00:00 2001 From: Charles Cunningham Date: Fri, 25 Mar 2022 16:40:49 +0100 Subject: [PATCH 2/4] export TimeStamp --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index fd60d33..6d200f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use chrono::{DateTime, Utc}; use http::uri::Authority; use iri_string::types::{UriAbsoluteString, UriString}; -use siwe::TimeStamp; +pub use siwe::TimeStamp; use std::str::FromStr; use thiserror::Error; From c1c63fa98cb9764dd8d1a5674f0e5e86f43b1d24 Mon Sep 17 00:00:00 2001 From: Charles Cunningham Date: Fri, 25 Mar 2022 16:43:40 +0100 Subject: [PATCH 3/4] add siwe as default feature --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 61b088a..f29d9f9 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ repository = "https://github.com/spruceid/cacao/" [features] eip4361 = ["hex", "ethers-core"] zcap = ["ssi"] -default = ["eip4361", "zcap"] +default = ["eip4361", "zcap", "siwe"] [dependencies] siwe = { version = "0.2" } From 07b8a115b133ed4ffa869f430c363114cb3ba574 Mon Sep 17 00:00:00 2001 From: Charles Cunningham Date: Fri, 25 Mar 2022 17:09:07 +0100 Subject: [PATCH 4/4] remove siwe feature gate, fix siwe history issues and rename siwe mod --- Cargo.toml | 2 +- src/lib.rs | 8 +-- src/siwe.rs | 140 ------------------------------------------ src/siwe_cacao.rs | 151 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 146 deletions(-) mode change 100755 => 100644 src/siwe.rs create mode 100755 src/siwe_cacao.rs diff --git a/Cargo.toml b/Cargo.toml index f29d9f9..61b088a 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ repository = "https://github.com/spruceid/cacao/" [features] eip4361 = ["hex", "ethers-core"] zcap = ["ssi"] -default = ["eip4361", "zcap", "siwe"] +default = ["eip4361", "zcap"] [dependencies] siwe = { version = "0.2" } diff --git a/src/lib.rs b/src/lib.rs index 6d200f1..ea993cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,13 +3,11 @@ use chrono::{DateTime, Utc}; use http::uri::Authority; use iri_string::types::{UriAbsoluteString, UriString}; pub use siwe::TimeStamp; -use std::str::FromStr; use thiserror::Error; pub mod generic; -#[cfg(feature = "siwe")] -pub mod siwe; +pub mod siwe_cacao; pub struct CACAO where @@ -102,8 +100,8 @@ pub enum Version { pub struct Payload { pub domain: Authority, pub iss: UriAbsoluteString, - pub statement: String, - pub aud: UriAbsoluteString, + pub statement: Option, + pub aud: UriString, pub version: Version, pub nonce: String, pub iat: TimeStamp, diff --git a/src/siwe.rs b/src/siwe.rs old mode 100755 new mode 100644 index 52ffc38..e69de29 --- a/src/siwe.rs +++ b/src/siwe.rs @@ -1,140 +0,0 @@ -use super::{BasicSignature, Payload, SignatureScheme, VerificationError, Version}; -use async_trait::async_trait; -use ethers_core::{types::H160, utils::to_checksum}; -use hex::FromHex; -use siwe::{Message, VerificationError as SVE, Version as SVersion}; - -impl Into for Version { - fn into(self) -> SVersion { - match self { - Self::V1 => SVersion::V1, - } - } -} - -impl From for Version { - fn from(v: SVersion) -> Self { - match v { - SVersion::V1 => Self::V1, - } - } -} - -impl From for VerificationError { - fn from(e: SVE) -> Self { - match e { - SVE::Crypto(_) | SVE::Signer => Self::Crypto, - SVE::Serialization(_) => Self::Serialization, - } - } -} - -impl TryInto for Payload { - type Error = (); - fn try_into(self) -> Result { - let (chain_id, address) = match &self.iss.as_str().split(":").collect::>()[..] { - &["did", "pkh", "eip155", c, h] => { - (c.to_string(), FromHex::from_hex(&h[2..]).map_err(|_| ())?) - } - _ => return Err(()), - }; - Ok(Message { - domain: self.domain, - address, - chain_id, - statement: self.statement, - uri: self.aud, - version: self.version.into(), - nonce: self.nonce, - issued_at: self.iat, - not_before: self.nbf, - expiration_time: self.exp, - request_id: self.request_id, - resources: self.resources, - }) - } -} - -impl From for Payload { - fn from(m: Message) -> Self { - Self { - domain: m.domain, - iss: format!( - "did:pkh:eip155:{}:0x{}", - m.chain_id, - to_checksum(&H160(&m.address), None) - ) - .parse() - .unwrap(), - statement: m.statement, - aud: m.uri, - version: m.version.into(), - nonce: m.nonce, - iat: m.issued_at, - nbf: m.not_before, - exp: m.expiration_time, - request_id: m.request_id, - resources: m.resources, - } - } -} - -pub struct SignInWithEthereum; - -#[async_trait] -impl SignatureScheme for SignInWithEthereum { - type Signature = BasicSignature<[u8; 65]>; - fn id() -> String { - "eip4361-eip191".into() - } - async fn verify(payload: &Payload, sig: &Self::Signature) -> Result<(), VerificationError> { - if !payload.valid_now() { - return Err(VerificationError::NotCurrentlyValid); - }; - let m: Message = payload - .clone() - .try_into() - .map_err(|_| VerificationError::MissingVerificationMaterial)?; - m.verify_eip191(sig.s)?; - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::BasicSignature; - use hex::FromHex; - use siwe::eip4361::Message; - use std::str::FromStr; - - #[async_std::test] - async fn validation() { - // from https://github.com/blockdemy/eth_personal_sign - let message: Payload = Message::from_str( - r#"localhost:4361 wants you to sign in with your Ethereum account: -0x6Da01670d8fc844e736095918bbE11fE8D564163 - -SIWE Notepad Example - -URI: http://localhost:4361 -Version: 1 -Chain ID: 1 -Nonce: kEWepMt9knR6lWJ6A -Issued At: 2021-12-07T18:28:18.807Z"#, - ) - .unwrap() - .into(); - let correct = <[u8; 65]>::from_hex(r#"6228b3ecd7bf2df018183aeab6b6f1db1e9f4e3cbe24560404112e25363540eb679934908143224d746bbb5e1aa65ab435684081f4dbb74a0fec57f98f40f5051c"#).unwrap(); - SignInWithEthereum::verify(&message, &BasicSignature { s: correct }) - .await - .unwrap(); - - let incorrect = <[u8; 65]>::from_hex(r#"7228b3ecd7bf2df018183aeab6b6f1db1e9f4e3cbe24560404112e25363540eb679934908143224d746bbb5e1aa65ab435684081f4dbb74a0fec57f98f40f5051c"#).unwrap(); - assert!( - SignInWithEthereum::verify(&message, &BasicSignature { s: incorrect }) - .await - .is_err() - ); - } -} diff --git a/src/siwe_cacao.rs b/src/siwe_cacao.rs new file mode 100755 index 0000000..dd910ce --- /dev/null +++ b/src/siwe_cacao.rs @@ -0,0 +1,151 @@ +use super::{BasicSignature, Payload, SignatureScheme, VerificationError, Version}; +use async_trait::async_trait; +use ethers_core::{types::H160, utils::to_checksum}; +use hex::FromHex; +use siwe::{Message, VerificationError as SVE, Version as SVersion}; + +impl Into for Version { + fn into(self) -> SVersion { + match self { + Self::V1 => SVersion::V1, + } + } +} + +impl From for Version { + fn from(v: SVersion) -> Self { + match v { + SVersion::V1 => Self::V1, + } + } +} + +impl From for VerificationError { + fn from(e: SVE) -> Self { + match e { + SVE::Crypto(_) | SVE::Signer => Self::Crypto, + SVE::Serialization(_) => Self::Serialization, + SVE::Time => Self::NotCurrentlyValid, + } + } +} + +#[derive(thiserror::Error, Debug)] +pub enum SIWEPayloadConversionError { + #[error(transparent)] + InvalidAddress(#[from] hex::FromHexError), + #[error(transparent)] + InvalidChainId(#[from] std::num::ParseIntError), + #[error("Invalid DID, expected did:pkh")] + InvalidDID, +} + +impl TryInto for Payload { + type Error = SIWEPayloadConversionError; + fn try_into(self) -> Result { + let (chain_id, address) = match &self.iss.as_str().split(":").collect::>()[..] { + &["did", "pkh", "eip155", c, h] if h.get(..2) == Some("0x") => { + (c.parse()?, FromHex::from_hex(&h[2..])?) + } + _ => return Err(Self::Error::InvalidDID), + }; + Ok(Message { + domain: self.domain, + address, + chain_id, + statement: self.statement, + uri: self.aud, + version: self.version.into(), + nonce: self.nonce, + issued_at: self.iat, + not_before: self.nbf, + expiration_time: self.exp, + request_id: self.request_id, + resources: self.resources, + }) + } +} + +impl From for Payload { + fn from(m: Message) -> Self { + Self { + domain: m.domain, + iss: format!( + "did:pkh:eip155:{}:{}", + m.chain_id, + to_checksum(&H160(m.address), None) + ) + .parse() + .unwrap(), + statement: m.statement, + aud: m.uri, + version: m.version.into(), + nonce: m.nonce, + iat: m.issued_at, + nbf: m.not_before, + exp: m.expiration_time, + request_id: m.request_id, + resources: m.resources, + } + } +} + +pub struct SignInWithEthereum; + +#[async_trait] +impl SignatureScheme for SignInWithEthereum { + type Signature = BasicSignature<[u8; 65]>; + fn id() -> String { + "eip4361-eip191".into() + } + async fn verify(payload: &Payload, sig: &Self::Signature) -> Result<(), VerificationError> { + if !payload.valid_now() { + return Err(VerificationError::NotCurrentlyValid); + }; + let m: Message = payload + .clone() + .try_into() + .map_err(|e| VerificationError::MissingVerificationMaterial)?; + m.verify_eip191(&sig.s)?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::BasicSignature; + use hex::FromHex; + use siwe::Message; + use std::str::FromStr; + + #[async_std::test] + async fn validation() { + // from https://github.com/blockdemy/eth_personal_sign + let message: Payload = Message::from_str( + r#"localhost:4361 wants you to sign in with your Ethereum account: +0x6Da01670d8fc844e736095918bbE11fE8D564163 + +SIWE Notepad Example + +URI: http://localhost:4361 +Version: 1 +Chain ID: 1 +Nonce: kEWepMt9knR6lWJ6A +Issued At: 2021-12-07T18:28:18.807Z"#, + ) + .unwrap() + .into(); + let correct = <[u8; 65]>::from_hex(r#"6228b3ecd7bf2df018183aeab6b6f1db1e9f4e3cbe24560404112e25363540eb679934908143224d746bbb5e1aa65ab435684081f4dbb74a0fec57f98f40f5051c"#).unwrap(); + SignInWithEthereum::verify(&message, &BasicSignature { s: correct }) + .await + .unwrap(); + + let incorrect = <[u8; 65]>::from_hex(r#"7228b3ecd7bf2df018183aeab6b6f1db1e9f4e3cbe24560404112e25363540eb679934908143224d746bbb5e1aa65ab435684081f4dbb74a0fec57f98f40f5051c"#).unwrap(); + assert!( + SignInWithEthereum::verify(&message, &BasicSignature { s: incorrect }) + .await + .is_err() + ); + } +}