From 3d8d79c86b96b3c7575258aac28e0b8ca969a7ac Mon Sep 17 00:00:00 2001 From: chunningham Date: Fri, 6 Oct 2023 14:33:51 +0200 Subject: [PATCH] make common mod code much cleaner with better types --- src/v3/common.rs | 374 +++++++++++++++++++++++++++-------------------- 1 file changed, 216 insertions(+), 158 deletions(-) diff --git a/src/v3/common.rs b/src/v3/common.rs index 4f9352f..fe2a53b 100644 --- a/src/v3/common.rs +++ b/src/v3/common.rs @@ -3,38 +3,162 @@ use super::{ version::SiweVersion, Error as RecapError, RecapCacao, RecapFacts, RecapSignature, RecapVerify, }, - ucan_cacao::{Error as UcanError, UcanCacao, UcanFacts, UcanSignature}, - Cacao, CacaoVerifier, + ucan_cacao::{Error as UcanError, UcanCacao, UcanSignature}, + webauthn::{WebauthnCacao, WebauthnSignature, WebauthnVersion}, + CacaoVerifier, Flattener, }; use async_trait::async_trait; +use libipld::{cid::Cid, Ipld}; +use multidid::MultiDid; use serde::{Deserialize, Serialize}; -use serde_json::Value; use siwe::Message; use ssi_ucan::{ - jose::{self, Signature, VerificationError}, + jose::{self, VerificationError}, version::SemanticVersion, Ucan, }; -use varsig::{either::EitherSignature, VarSig}; +use std::collections::BTreeMap; +use ucan_capabilities_object::Capabilities; +use varsig::{VarSig, VarSigTrait}; -type CommonSignature = EitherSignature; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash)] +pub struct CommonCacao, NB = Ipld, W = U> { + #[serde(rename = "iss")] + issuer: MultiDid, + #[serde(rename = "aud")] + audience: MultiDid, + #[serde(rename = "att")] + attenuations: Capabilities, + #[serde(rename = "nnc", skip_serializing_if = "Option::is_none", default)] + nonce: Option, + #[serde(rename = "prf", skip_serializing_if = "Option::is_none", default)] + proof: Option>, + #[serde(rename = "iat", skip_serializing_if = "Option::is_none", default)] + issued_at: Option, + #[serde(rename = "nbf", skip_serializing_if = "Option::is_none", default)] + not_before: Option, + #[serde(rename = "exp", skip_serializing_if = "Option::is_none", default)] + expiration: Option, + #[serde(flatten)] + typ: Types, +} -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, PartialOrd, Hash)] -#[serde(untagged)] -pub enum CommonFacts { - Recap(RecapFacts), - Ucan(UcanFacts), +impl CommonCacao { + pub fn issuer(&self) -> &MultiDid { + &self.issuer + } + + pub fn audience(&self) -> &MultiDid { + &self.audience + } + + pub fn capabilities(&self) -> &Capabilities { + &self.attenuations + } + + pub fn nonce(&self) -> Option<&str> { + self.nonce.as_deref() + } + + pub fn proof(&self) -> Option<&[Cid]> { + self.proof.as_deref() + } + + pub fn issued_at(&self) -> Option { + self.issued_at + } + + pub fn not_before(&self) -> Option { + self.not_before + } + + pub fn expiration(&self) -> Option { + self.expiration + } + + pub fn facts(&self) -> Option> { + self.typ.facts() + } + + pub fn signature(&self) -> Signature<'_> { + self.typ.signature() + } + + pub fn valid_at_time(&self, time: u64, skew: Option) -> bool { + self.expiration + .map_or(true, |exp| time < exp + skew.unwrap_or(0)) + && self + .not_before + .map_or(true, |nbf| time >= nbf - skew.unwrap_or(0)) + && self.issued_at.map_or(true, |iat| { + self.not_before.map_or(true, |nbf| nbf < iat) + && self.expiration.map_or(true, |exp| iat < exp) + }) + } + + pub async fn verify(&self, verifier: &V) -> Result<(), V::Error> + where + V: CacaoVerifier, + NB: Send + Sync, + { + verifier.verify(self).await + } } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, PartialOrd, Hash)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash)] +#[serde(deny_unknown_fields)] +struct Type { + #[serde(rename = "v")] + version: V, + #[serde( + rename = "fct", + skip_serializing_if = "Option::is_none", + default = "Option::default" + )] + facts: Option>, + #[serde(rename = "s", bound = "S: VarSigTrait")] + signature: VarSig, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, Hash)] #[serde(untagged)] -pub enum CommonVersions { - Recap(SiweVersion), - Ucan(SemanticVersion), +enum Types { + Ucan(Type), + Recap(Type), + Webauthn(Type), +} + +impl Types { + pub fn facts(&self) -> Option> { + match self { + Types::Ucan(ref ucan) => ucan.facts.as_ref().map(|f| Facts::Ucan(&f.f)), + Types::Recap(ref recap) => recap.facts.as_ref().map(|f| Facts::Recap(&f.f)), + Types::Webauthn(ref webauthn) => webauthn.facts.as_ref().map(|f| Facts::Webauthn(&f.f)), + } + } + + pub fn signature(&self) -> Signature<'_> { + match self { + Types::Ucan(ref ucan) => Signature::Ucan(&ucan.signature), + Types::Recap(ref recap) => Signature::Recap(&recap.signature), + Types::Webauthn(ref webauthn) => Signature::Webauthn(&webauthn.signature), + } + } } -pub type CommonCacao = - Cacao, NB>; +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Facts<'a, U, W = U> { + Recap(&'a RecapFacts), + Ucan(&'a U), + Webauthn(&'a W), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Signature<'a> { + Recap(&'a VarSig), + Ucan(&'a VarSig), + Webauthn(&'a VarSig), +} #[derive(Debug, Clone, PartialEq, Default)] pub struct CommonVerifier(T); @@ -45,20 +169,18 @@ impl CommonVerifier { } } -impl CommonCacao +impl CommonCacao, NB, W> where - F: Clone + Serialize, + U: Clone + Serialize, NB: Clone + Serialize, + W: Clone, { - pub fn serialize_jwt(&self) -> Result, Error> { - Ok(match self.signature.sig() { - CommonSignature::A(_) => None, - CommonSignature::B(_) => UcanCacao::::try_from(self.clone()) - .ok() - .map(|uc| Ucan::::from(uc).encode()) - .transpose() - .map_err(|e| Error::Ucan(e.into()))?, - }) + pub fn serialize_jwt(&self) -> Result, UcanError> { + Ok(UcanCacao::try_from(self.clone()) + .ok() + .map(Ucan::from) + .map(|ucan| ucan.encode()) + .transpose()?) } } @@ -86,16 +208,16 @@ impl From> for Error { #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl<'r, NB, R, F> CacaoVerifier, NB> - for CommonVerifier +impl<'r, U, NB, W, R> CacaoVerifier> for CommonVerifier where - R: 'r + Send + Sync + CacaoVerifier, NB>, - F: Send + Sync + for<'a> Deserialize<'a> + Serialize + Clone, + R: 'r + Send + Sync + CacaoVerifier>, + U: Send + Sync + for<'a> Deserialize<'a> + Serialize + Clone, NB: Send + Sync + for<'a> Deserialize<'a> + Serialize + Clone, + W: Send + Sync + for<'a> Deserialize<'a> + Serialize + Clone, { type Error = Error; - async fn verify(&self, cacao: &CommonCacao) -> Result<(), Self::Error> { + async fn verify(&self, cacao: &CommonCacao) -> Result<(), Self::Error> { match RecapCacao::try_from(cacao.clone()) { Ok(recap) => self.verify(&recap).await?, Err(c) => match UcanCacao::try_from(c) { @@ -111,7 +233,7 @@ where #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl CacaoVerifier for CommonVerifier +impl CacaoVerifier> for CommonVerifier where R: Send + Sync, NB: Send + Sync + for<'a> Deserialize<'a> + Serialize + Clone, @@ -125,10 +247,9 @@ where #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl<'r, NB, R, F> CacaoVerifier, NB> - for CommonVerifier +impl<'r, NB, R, F> CacaoVerifier> for CommonVerifier where - R: 'r + Send + Sync + CacaoVerifier, NB>, + R: 'r + Send + Sync + CacaoVerifier>, F: Send + Sync + for<'a> Deserialize<'a> + Serialize + Clone, NB: Send + Sync + for<'a> Deserialize<'a> + Serialize + Clone, { @@ -139,134 +260,71 @@ where } } -impl From> for CommonCacao { - fn from(recap: RecapCacao) -> Self { - CommonCacao { - issuer: recap.issuer, - audience: recap.audience, - version: CommonVersions::Recap(recap.version), - attenuations: recap.attenuations, - nonce: recap.nonce, - proof: recap.proof, - issued_at: recap.issued_at, - not_before: recap.not_before, - expiration: recap.expiration, - facts: recap.facts.map(CommonFacts::Recap), - signature: VarSig::new(EitherSignature::A(recap.signature.into_inner())), +macro_rules! impl_from { + ($from:ty, $typ:tt) => { + impl From<$from> for CommonCacao { + fn from(from: $from) -> Self { + CommonCacao { + issuer: from.issuer, + audience: from.audience, + attenuations: from.attenuations, + nonce: from.nonce, + proof: from.proof, + issued_at: from.issued_at, + not_before: from.not_before, + expiration: from.expiration, + typ: Types::$typ(Type { + facts: from.facts, + signature: from.signature, + version: from.version, + }), + } + } } - } + }; } -impl From> for CommonCacao { - fn from(ucan: UcanCacao) -> Self { - CommonCacao { - issuer: ucan.issuer, - audience: ucan.audience, - version: CommonVersions::Ucan(ucan.version), - attenuations: ucan.attenuations, - nonce: ucan.nonce, - proof: ucan.proof, - issued_at: ucan.issued_at, - not_before: ucan.not_before, - expiration: ucan.expiration, - facts: ucan.facts.map(CommonFacts::Ucan), - signature: VarSig::new(EitherSignature::B(ucan.signature.into_inner())), - } - } -} +impl_from!(RecapCacao, Recap); +impl_from!(UcanCacao, Ucan); +impl_from!(WebauthnCacao, Webauthn); -impl TryFrom> for RecapCacao { - type Error = CommonCacao; - fn try_from(cacao: CommonCacao) -> Result { - match (cacao.facts, cacao.signature.into_inner(), cacao.version) { - ( - Some(CommonFacts::Recap(facts)), - EitherSignature::A(sig), - CommonVersions::Recap(version), - ) => Ok(RecapCacao { - issuer: cacao.issuer, - audience: cacao.audience, - version, - attenuations: cacao.attenuations, - nonce: cacao.nonce, - proof: cacao.proof, - issued_at: cacao.issued_at, - not_before: cacao.not_before, - expiration: cacao.expiration, - facts: Some(facts), - signature: VarSig::new(sig), - }), - (facts, sig, version) => Err(CommonCacao { - issuer: cacao.issuer, - audience: cacao.audience, - version, - attenuations: cacao.attenuations, - nonce: cacao.nonce, - proof: cacao.proof, - issued_at: cacao.issued_at, - not_before: cacao.not_before, - expiration: cacao.expiration, - facts, - signature: VarSig::new(sig), - }), +macro_rules! impl_tryfrom { + ($into:ty, $typ:tt) => { + impl TryFrom> for $into { + type Error = CommonCacao; + fn try_from(cacao: CommonCacao) -> Result { + match cacao.typ { + Types::$typ(Type { + version, + facts, + signature, + }) => Ok(Self { + issuer: cacao.issuer, + audience: cacao.audience, + version, + attenuations: cacao.attenuations, + nonce: cacao.nonce, + proof: cacao.proof, + issued_at: cacao.issued_at, + not_before: cacao.not_before, + expiration: cacao.expiration, + facts, + signature, + }), + _ => Err(cacao), + } + } } - } + }; } -impl TryFrom> for UcanCacao { - type Error = CommonCacao; - fn try_from(cacao: CommonCacao) -> Result { - match (cacao.facts, cacao.signature.into_inner(), cacao.version) { - ( - Some(CommonFacts::Ucan(facts)), - EitherSignature::B(sig), - CommonVersions::Ucan(version), - ) => Ok(UcanCacao { - issuer: cacao.issuer, - audience: cacao.audience, - version, - attenuations: cacao.attenuations, - nonce: cacao.nonce, - proof: cacao.proof, - issued_at: cacao.issued_at, - not_before: cacao.not_before, - expiration: cacao.expiration, - facts: Some(facts), - signature: VarSig::new(sig), - }), - (None, EitherSignature::B(sig), CommonVersions::Ucan(version)) => Ok(UcanCacao { - issuer: cacao.issuer, - audience: cacao.audience, - version, - attenuations: cacao.attenuations, - nonce: cacao.nonce, - proof: cacao.proof, - issued_at: cacao.issued_at, - not_before: cacao.not_before, - expiration: cacao.expiration, - facts: None, - signature: VarSig::new(sig), - }), - (facts, sig, version) => Err(CommonCacao { - issuer: cacao.issuer, - audience: cacao.audience, - version, - attenuations: cacao.attenuations, - nonce: cacao.nonce, - proof: cacao.proof, - issued_at: cacao.issued_at, - not_before: cacao.not_before, - expiration: cacao.expiration, - facts, - signature: VarSig::new(sig), - }), - } - } -} +impl_tryfrom!(RecapCacao, Recap); +impl_tryfrom!(UcanCacao, Ucan); +impl_tryfrom!(WebauthnCacao, Webauthn); -impl TryFrom> for CommonCacao { - type Error = Error; - fn try_from(ucan: Ucan) -> Result { +impl TryFrom> for CommonCacao, NB, W> { + type Error = UcanError; + fn try_from(ucan: Ucan) -> Result { Ok(UcanCacao::try_from(ucan)?.into()) } } @@ -275,7 +333,7 @@ impl TryFrom<(Message, [u8; 65])> for CommonCacao where NB: for<'d> Deserialize<'d>, { - type Error = Error; + type Error = RecapError; fn try_from(siwe: (Message, [u8; 65])) -> Result { Ok(RecapCacao::try_from(siwe)?.into()) }