Skip to content

Commit

Permalink
fix: allow proofs to have unbounded lifetimes in subsumption checking
Browse files Browse the repository at this point in the history
  • Loading branch information
QuinnWilton committed Nov 29, 2023
1 parent d8e5039 commit edfefb1
Showing 1 changed file with 224 additions and 11 deletions.
235 changes: 224 additions & 11 deletions src/ucan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,7 @@ where
),
})?;

if ucan.expires_at() > proof_ucan.expires_at() {
continue;
}

if ucan.not_before() < proof_ucan.not_before() {
if !proof_ucan.lifetime_encompasses(&ucan) {
continue;
}

Expand Down Expand Up @@ -294,9 +290,6 @@ where
}

/// Returns true if this UCAN's lifetime begins no later than the other
/// Note that if a UCAN specifies an NBF but the other does not, the
/// other has an unbounded start time and this function will return
/// false.
pub fn lifetime_begins_before<F2, C2>(&self, other: &Ucan<F2, C2>) -> bool
where
F2: DeserializeOwned,
Expand Down Expand Up @@ -713,7 +706,77 @@ mod tests {
}

#[test]
fn test_capabilities_for_invocation() -> Result<(), anyhow::Error> {
fn test_capabilities_for_invocation_no_lifetime() -> Result<(), anyhow::Error> {
let mut store = InMemoryStore::<RawCodec>::default();
let did_verifier_map = DidVerifierMap::default();

let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng);
let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng);

let root_ucan: Ucan<DefaultFact, DefaultCapabilityParser> = UcanBuilder::default()
.for_audience(aud_key.did()?)
.claiming_capability(Capability::new(
WnfsResource::PublicPath {
user: "alice".to_string(),
path: vec!["photos".to_string()],
},
TopAbility,
EmptyCaveat,
))
.sign(&iss_key)?;

store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?;

let invocation: Ucan = UcanBuilder::default()
.for_audience("did:web:fission.codes")
.claiming_capability(Capability::new(
WnfsResource::PublicPath {
user: "alice".to_string(),
path: vec!["photos".to_string()],
},
WnfsAbility::Revise,
EmptyCaveat,
))
.witnessed_by(&root_ucan, None)
.sign(&aud_key)?;

let capabilities = invocation.capabilities_for(
iss_key.did()?,
WnfsResource::PublicPath {
user: "alice".to_string(),
path: vec!["photos".to_string()],
},
WnfsAbility::Revise,
time::now(),
&did_verifier_map,
&store,
)?;

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

assert_eq!(
capabilities[0].resource().downcast_ref::<WnfsResource>(),
Some(&WnfsResource::PublicPath {
user: "alice".to_string(),
path: vec!["photos".to_string()],
})
);

assert_eq!(
capabilities[0].ability().downcast_ref::<WnfsAbility>(),
Some(&WnfsAbility::Revise)
);

assert_eq!(
capabilities[0].caveat().downcast_ref::<EmptyCaveat>(),
Some(&EmptyCaveat)
);

Ok(())
}

#[test]
fn test_capabilities_for_invocation_lifetime_encompassed() -> Result<(), anyhow::Error> {
let mut store = InMemoryStore::<RawCodec>::default();
let did_verifier_map = DidVerifierMap::default();

Expand Down Expand Up @@ -745,6 +808,7 @@ mod tests {
WnfsAbility::Revise,
EmptyCaveat,
))
.with_lifetime(30)
.witnessed_by(&root_ucan, None)
.sign(&aud_key)?;

Expand Down Expand Up @@ -780,15 +844,164 @@ mod tests {
Some(&EmptyCaveat)
);

Ok(())
}

#[test]
fn test_capabilities_for_invocation_nbf_exposed() -> Result<(), anyhow::Error> {
let mut store = InMemoryStore::<RawCodec>::default();
let did_verifier_map = DidVerifierMap::default();

let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng);
let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng);

let root_ucan: Ucan<DefaultFact, DefaultCapabilityParser> = UcanBuilder::default()
.for_audience(aud_key.did()?)
.claiming_capability(Capability::new(
WnfsResource::PublicPath {
user: "alice".to_string(),
path: vec!["photos".to_string()],
},
TopAbility,
EmptyCaveat,
))
.not_before(1)
.sign(&iss_key)?;

store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?;

let invocation: Ucan = UcanBuilder::default()
.for_audience("did:web:fission.codes")
.claiming_capability(Capability::new(
WnfsResource::PublicPath {
user: "alice".to_string(),
path: vec!["photos".to_string()],
},
WnfsAbility::Revise,
EmptyCaveat,
))
.not_before(0)
.witnessed_by(&root_ucan, None)
.sign(&aud_key)?;

let capabilities = invocation.capabilities_for(
iss_key.did()?,
WnfsResource::PublicPath {
user: "alice".to_string(),
path: vec!["photos".to_string()],
},
WnfsAbility::Revise,
0,
&did_verifier_map,
&store,
)?;

assert_eq!(capabilities.len(), 0);

Ok(())
}

#[test]
fn test_capabilities_for_invocation_exp_exposed() -> Result<(), anyhow::Error> {
let mut store = InMemoryStore::<RawCodec>::default();
let did_verifier_map = DidVerifierMap::default();

let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng);
let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng);

let root_ucan: Ucan<DefaultFact, DefaultCapabilityParser> = UcanBuilder::default()
.for_audience(aud_key.did()?)
.claiming_capability(Capability::new(
WnfsResource::PublicPath {
user: "alice".to_string(),
path: vec!["photos".to_string()],
},
TopAbility,
EmptyCaveat,
))
.with_expiration(0)
.sign(&iss_key)?;

store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?;

let invocation: Ucan = UcanBuilder::default()
.for_audience("did:web:fission.codes")
.claiming_capability(Capability::new(
WnfsResource::PublicPath {
user: "alice".to_string(),
path: vec!["photos".to_string()],
},
WnfsAbility::Revise,
EmptyCaveat,
))
.with_expiration(1)
.witnessed_by(&root_ucan, None)
.sign(&aud_key)?;

let capabilities = invocation.capabilities_for(
iss_key.did()?,
WnfsResource::PublicPath {
user: "alice".to_string(),
path: vec!["photos".to_string()],
},
WnfsAbility::Revise,
0,
&did_verifier_map,
&store,
)?;

assert_eq!(capabilities.len(), 0);

Ok(())
}

#[test]
fn test_capabilities_for_invocation_lifetime_disjoint() -> Result<(), anyhow::Error> {
let mut store = InMemoryStore::<RawCodec>::default();
let did_verifier_map = DidVerifierMap::default();

let iss_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng);
let aud_key = ed25519_dalek::SigningKey::generate(&mut rand_core::OsRng);

let root_ucan: Ucan<DefaultFact, DefaultCapabilityParser> = UcanBuilder::default()
.for_audience(aud_key.did()?)
.claiming_capability(Capability::new(
WnfsResource::PublicPath {
user: "alice".to_string(),
path: vec!["photos".to_string()],
},
TopAbility,
EmptyCaveat,
))
.not_before(0)
.with_expiration(1)
.sign(&iss_key)?;

store.write(Ipld::Bytes(root_ucan.encode()?.as_bytes().to_vec()), None)?;

let invocation: Ucan = UcanBuilder::default()
.for_audience("did:web:fission.codes")
.claiming_capability(Capability::new(
WnfsResource::PublicPath {
user: "alice".to_string(),
path: vec!["photos".to_string()],
},
WnfsAbility::Revise,
EmptyCaveat,
))
.not_before(2)
.with_expiration(3)
.witnessed_by(&root_ucan, None)
.sign(&aud_key)?;

let capabilities = invocation.capabilities_for(
iss_key.did()?,
WnfsResource::PublicPath {
user: "alice".to_string(),
path: vec!["photos".to_string()],
},
WnfsAbility::Revise,
// Past the lifetime of the root UCAN
time::now() + 61,
2,
&did_verifier_map,
&store,
)?;
Expand Down

0 comments on commit edfefb1

Please sign in to comment.