Skip to content

Commit

Permalink
feat!: Custom 'now' for proof chain validation (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
cdata authored May 4, 2023
1 parent 77ec26d commit 1732a89
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 22 deletions.
24 changes: 19 additions & 5 deletions ucan/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,22 @@ impl ProofChain {
#[cfg_attr(not(target_arch = "wasm32"), async_recursion)]
pub async fn from_ucan<S>(
ucan: Ucan,
now_time: Option<u64>,
did_parser: &mut DidParser,
store: &S,
) -> Result<ProofChain>
where
S: UcanJwtStore,
{
ucan.validate(did_parser).await?;
ucan.validate(now_time, did_parser).await?;

let mut proofs: Vec<ProofChain> = Vec::new();

for cid_string in ucan.proofs().iter() {
let cid = Cid::try_from(cid_string.as_str())?;
let ucan_token = store.require_token(&cid).await?;
let proof_chain = Self::try_from_token_string(&ucan_token, did_parser, store).await?;
let proof_chain =
Self::try_from_token_string(&ucan_token, now_time, did_parser, store).await?;
proof_chain.validate_link_to(&ucan)?;
proofs.push(proof_chain);
}
Expand Down Expand Up @@ -105,24 +107,36 @@ impl ProofChain {

/// Instantiate a [ProofChain] from a [Cid], given a [UcanJwtStore] and [DidParser]
/// The [Cid] must resolve to a JWT token string
pub async fn from_cid<S>(cid: &Cid, did_parser: &mut DidParser, store: &S) -> Result<ProofChain>
pub async fn from_cid<S>(
cid: &Cid,
now_time: Option<u64>,
did_parser: &mut DidParser,
store: &S,
) -> Result<ProofChain>
where
S: UcanJwtStore,
{
Self::try_from_token_string(&store.require_token(cid).await?, did_parser, store).await
Self::try_from_token_string(
&store.require_token(cid).await?,
now_time,
did_parser,
store,
)
.await
}

/// Instantiate a [ProofChain] from a JWT token string, given a [UcanJwtStore] and [DidParser]
pub async fn try_from_token_string<'a, S>(
ucan_token_string: &str,
now_time: Option<u64>,
did_parser: &mut DidParser,
store: &S,
) -> Result<ProofChain>
where
S: UcanJwtStore,
{
let ucan = Ucan::try_from(ucan_token_string)?;
Self::from_ucan(ucan, did_parser, store).await
Self::from_ucan(ucan, now_time, did_parser, store).await
}

fn validate_link_to(&self, ucan: &Ucan) -> Result<()> {
Expand Down
2 changes: 1 addition & 1 deletion ucan/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
//! {
//! let mut did_parser = DidParser::new(SUPPORTED_KEY_TYPES);
//!
//! Ok(ProofChain::try_from_token_string(ucan_token, &mut did_parser, store).await?
//! Ok(ProofChain::try_from_token_string(ucan_token, None, &mut did_parser, store).await?
//! .reduce_capabilities(semantics))
//! }
//! ```
Expand Down
15 changes: 8 additions & 7 deletions ucan/src/tests/attenuation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ pub async fn it_works_with_a_simple_example() {
.unwrap();

let chain =
ProofChain::try_from_token_string(attenuated_token.as_str(), &mut did_parser, &store)
ProofChain::try_from_token_string(attenuated_token.as_str(), None, &mut did_parser, &store)
.await
.unwrap();

Expand Down Expand Up @@ -115,10 +115,11 @@ pub async fn it_reports_the_first_issuer_in_the_chain_as_originator() {
.await
.unwrap();

let capability_infos = ProofChain::try_from_token_string(&ucan_token, &mut did_parser, &store)
.await
.unwrap()
.reduce_capabilities(&email_semantics);
let capability_infos =
ProofChain::try_from_token_string(&ucan_token, None, &mut did_parser, &store)
.await
.unwrap()
.reduce_capabilities(&email_semantics);

assert_eq!(capability_infos.len(), 1);

Expand Down Expand Up @@ -193,7 +194,7 @@ pub async fn it_finds_the_right_proof_chain_for_the_originator() {
.await
.unwrap();

let proof_chain = ProofChain::try_from_token_string(&ucan_token, &mut did_parser, &store)
let proof_chain = ProofChain::try_from_token_string(&ucan_token, None, &mut did_parser, &store)
.await
.unwrap();
let capability_infos = proof_chain.reduce_capabilities(&email_semantics);
Expand Down Expand Up @@ -282,7 +283,7 @@ pub async fn it_reports_all_chain_options() {
.await
.unwrap();

let proof_chain = ProofChain::try_from_token_string(&ucan_token, &mut did_parser, &store)
let proof_chain = ProofChain::try_from_token_string(&ucan_token, None, &mut did_parser, &store)
.await
.unwrap();
let capability_infos = proof_chain.reduce_capabilities(&email_semantics);
Expand Down
57 changes: 53 additions & 4 deletions ucan/src/tests/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
chain::ProofChain,
crypto::did::DidParser,
store::{MemoryStore, UcanJwtStore},
time::now,
};

#[cfg(target_arch = "wasm32")]
Expand Down Expand Up @@ -48,7 +49,7 @@ pub async fn it_decodes_deep_ucan_chains() {
.unwrap();

let chain =
ProofChain::try_from_token_string(delegated_token.as_str(), &mut did_parser, &store)
ProofChain::try_from_token_string(delegated_token.as_str(), None, &mut did_parser, &store)
.await
.unwrap();

Expand Down Expand Up @@ -95,7 +96,8 @@ pub async fn it_fails_with_incorrect_chaining() {
.unwrap();

let parse_token_result =
ProofChain::try_from_token_string(delegated_token.as_str(), &mut did_parser, &store).await;
ProofChain::try_from_token_string(delegated_token.as_str(), None, &mut did_parser, &store)
.await;

assert!(parse_token_result.is_err());
}
Expand Down Expand Up @@ -138,7 +140,7 @@ pub async fn it_can_be_instantiated_by_cid() {

let cid = store.write_token(&delegated_token).await.unwrap();

let chain = ProofChain::from_cid(&cid, &mut did_parser, &store)
let chain = ProofChain::from_cid(&cid, None, &mut did_parser, &store)
.await
.unwrap();

Expand Down Expand Up @@ -199,7 +201,54 @@ pub async fn it_can_handle_multiple_leaves() {
.await
.unwrap();

ProofChain::try_from_token_string(&delegated_token, &mut did_parser, &store)
ProofChain::try_from_token_string(&delegated_token, None, &mut did_parser, &store)
.await
.unwrap();
}

#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
pub async fn it_can_use_a_custom_timestamp_to_validate_a_ucan() {
let identities = Identities::new().await;
let mut did_parser = DidParser::new(SUPPORTED_KEYS);

let leaf_ucan = UcanBuilder::default()
.issued_by(&identities.alice_key)
.for_audience(identities.bob_did.as_str())
.with_lifetime(60)
.build()
.unwrap()
.sign()
.await
.unwrap();

let delegated_token = UcanBuilder::default()
.issued_by(&identities.bob_key)
.for_audience(identities.mallory_did.as_str())
.with_lifetime(50)
.witnessed_by(&leaf_ucan)
.build()
.unwrap()
.sign()
.await
.unwrap()
.encode()
.unwrap();

let mut store = MemoryStore::default();

store
.write_token(&leaf_ucan.encode().unwrap())
.await
.unwrap();

let cid = store.write_token(&delegated_token).await.unwrap();

let valid_chain = ProofChain::from_cid(&cid, Some(now()), &mut did_parser, &store).await;

assert!(valid_chain.is_ok());

let invalid_chain = ProofChain::from_cid(&cid, Some(now() + 61), &mut did_parser, &store).await;

assert!(invalid_chain.is_err());
}
2 changes: 1 addition & 1 deletion ucan/src/tests/ucan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ mod validate {
let encoded_ucan = ucan.encode().unwrap();
let decoded_ucan = Ucan::try_from(encoded_ucan.as_str()).unwrap();

decoded_ucan.validate(&mut did_parser).await.unwrap();
decoded_ucan.validate(None, &mut did_parser).await.unwrap();
}

#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
Expand Down
14 changes: 10 additions & 4 deletions ucan/src/ucan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,12 @@ impl Ucan {
}

/// Validate the UCAN's signature and timestamps
pub async fn validate<'a>(&self, did_parser: &mut DidParser) -> Result<()> {
if self.is_expired() {
pub async fn validate<'a>(
&self,
now_time: Option<u64>,
did_parser: &mut DidParser,
) -> Result<()> {
if self.is_expired(now_time) {
return Err(anyhow!("Expired"));
}

Expand Down Expand Up @@ -92,8 +96,10 @@ impl Ucan {
}

/// Returns true if the UCAN has past its expiration date
pub fn is_expired(&self) -> bool {
self.payload.exp < now()
pub fn is_expired(&self, now_time: Option<u64>) -> bool {
let now_time = now_time.unwrap_or_else(now);

self.payload.exp < now_time
}

/// Raw bytes of signed data for this UCAN
Expand Down

0 comments on commit 1732a89

Please sign in to comment.