Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update siwe deps and integration #6

Merged
merged 4 commits into from
Mar 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified .gitignore
100644 → 100755
Empty file.
9 changes: 6 additions & 3 deletions Cargo.toml
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"] }
42 changes: 18 additions & 24 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ use async_trait::async_trait;
use chrono::{DateTime, Utc};
use http::uri::Authority;
use iri_string::types::{UriAbsoluteString, UriString};
use std::str::FromStr;
pub use siwe::TimeStamp;
use thiserror::Error;

pub mod generic;

#[cfg(feature = "siwe")]
pub mod siwe;
pub mod siwe_cacao;

pub type TimeStamp = DateTime<Utc>;

pub struct CACAO<S: SignatureScheme> {
pub struct CACAO<S>
where
S: SignatureScheme,
{
h: Header,
p: Payload,
s: S::Signature,
Expand Down Expand Up @@ -100,19 +100,19 @@ pub enum Version {
pub struct Payload {
pub domain: Authority,
pub iss: UriAbsoluteString,
pub statement: String,
pub aud: UriAbsoluteString,
pub statement: Option<String>,
pub aud: UriString,
pub version: Version,
pub nonce: String,
pub iat: String,
pub exp: Option<String>,
pub nbf: Option<String>,
pub iat: TimeStamp,
pub exp: Option<TimeStamp>,
pub nbf: Option<TimeStamp>,
pub request_id: Option<String>,
pub resources: Vec<UriString>,
}

impl Payload {
pub fn sign<S: SignatureScheme>(self, s: <S as SignatureScheme>::Signature) -> CACAO<S> {
pub fn sign<S: SignatureScheme>(self, s: S::Signature) -> CACAO<S> {
CACAO {
h: S::header(),
p: self,
Expand All @@ -135,18 +135,12 @@ impl Payload {
&self.iss.as_str()
}

pub fn valid_at(&self, t: &DateTime<Utc>) -> 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())
}
}
139 changes: 0 additions & 139 deletions src/siwe.rs
Original file line number Diff line number Diff line change
@@ -1,139 +0,0 @@
use super::{BasicSignature, Payload, SignatureScheme, VerificationError, Version};
use async_trait::async_trait;
use hex::FromHex;
use siwe::eip4361::{Message, VerificationError as SVE, Version as SVersion};

impl Into<SVersion> for Version {
fn into(self) -> SVersion {
match self {
Self::V1 => SVersion::V1,
}
}
}

impl From<SVersion> for Version {
fn from(v: SVersion) -> Self {
match v {
SVersion::V1 => Self::V1,
}
}
}

impl From<SVE> for VerificationError {
fn from(e: SVE) -> Self {
match e {
SVE::Crypto(_) | SVE::Signer => Self::Crypto,
SVE::Serialization(_) => Self::Serialization,
}
}
}

impl TryInto<Message> for Payload {
type Error = ();
fn try_into(self) -> Result<Message, Self::Error> {
let (chain_id, address) = match &self.iss.as_str().split(":").collect::<Vec<&str>>()[..] {
&["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<Message> for Payload {
fn from(m: Message) -> Self {
Self {
domain: m.domain,
iss: format!(
"did:pkh:eip155:{}:0x{}",
m.chain_id,
hex::encode(&m.address)
)
.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()
);
}
}
151 changes: 151 additions & 0 deletions src/siwe_cacao.rs
Original file line number Diff line number Diff line change
@@ -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<SVersion> for Version {
fn into(self) -> SVersion {
match self {
Self::V1 => SVersion::V1,
}
}
}

impl From<SVersion> for Version {
fn from(v: SVersion) -> Self {
match v {
SVersion::V1 => Self::V1,
}
}
}

impl From<SVE> 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<Message> for Payload {
type Error = SIWEPayloadConversionError;
fn try_into(self) -> Result<Message, Self::Error> {
let (chain_id, address) = match &self.iss.as_str().split(":").collect::<Vec<&str>>()[..] {
&["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<Message> 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()
);
}
}