Skip to content

Commit

Permalink
Add fuzz test for appending delegations
Browse files Browse the repository at this point in the history
  • Loading branch information
sgwilym committed Aug 1, 2024
1 parent 727d340 commit a4c6105
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 15 deletions.
7 changes: 7 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,10 @@ path = "fuzz_targets/mc_capability_delegation.rs"
test = false
doc = false
bench = false

[[bin]]
name = "mc_capability_append_delegation"
path = "fuzz_targets/mc_capability_append_delegation.rs"
test = false
doc = false
bench = false
92 changes: 92 additions & 0 deletions fuzz/fuzz_targets/mc_capability_append_delegation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#![no_main]

use libfuzzer_sys::fuzz_target;
use meadowcap::{mc_capability::McCapability, Delegation, InvalidDelegationError};
use willow_data_model_fuzz::silly_sigs::{SillyPublicKey, SillySig};

fuzz_target!(|data: (
Delegation<16, 16, 16, SillyPublicKey, SillySig>,
McCapability<16, 16, 16, SillyPublicKey, SillySig, SillyPublicKey, SillySig>,
Vec<SillyPublicKey>
)| {
let (delegation, mc_cap, delegees) = data;

let mut mut_cap = mc_cap.clone();

let mut last_receiver = mut_cap.receiver().clone();
let granted_area = mut_cap.granted_area();

for delegee in delegees {
mut_cap = mut_cap
.delegate(
&last_receiver.corresponding_secret_key(),
&delegee,
&granted_area,
)
.unwrap();
last_receiver = delegee;
}

let claimed_area = delegation.area().clone();
let delegation_user = delegation.user().clone();
let delegation_sig = delegation.signature().clone();

let granted_area_includes_delegation = mut_cap.granted_area().includes_area(delegation.area());

let actual_receiver_secret = mc_cap.receiver().corresponding_secret_key();

match mut_cap.append_existing_delegation(delegation) {
Ok(_) => {
println!("yay");
assert!(granted_area_includes_delegation);

// Because there is only one user who can delegate a given capability, we know what it should look like given the same new_area and new_user.
let expected_cap =
mc_cap.delegate(&actual_receiver_secret, &delegation_user, &claimed_area);

match expected_cap {
Ok(cap) => {
assert_eq!(cap, mut_cap);
}
Err(_) => {
panic!("The delegation should not have been possible")
}
}
}
Err(err) => match err {
InvalidDelegationError::AreaNotIncluded {
excluded_area,
claimed_receiver: _,
} => {
assert!(!granted_area_includes_delegation);
assert_eq!(excluded_area, claimed_area);
}
InvalidDelegationError::InvalidSignature {
expected_signatory,
claimed_receiver,
signature: _,
} => {
assert_eq!(&expected_signatory, mut_cap.receiver());
assert_eq!(delegation_user, claimed_receiver);

// Because there is only one user who can delegate a given capability, we know what it should look like given the same new_area and new_user.
let expected_cap =
mc_cap.delegate(&actual_receiver_secret, &delegation_user, &claimed_area);

match expected_cap {
Ok(cap) => match cap.delegations().last() {
Some(valid_delegation) => {
assert!(valid_delegation.signature() != &delegation_sig)
}
None => {
unreachable!(
"We just made a delegation, this really should not happen!"
)
}
},
Err(_) => panic!("The expected cap should have been fine..."),
}
}
},
}
});
28 changes: 22 additions & 6 deletions fuzz/fuzz_targets/mc_capability_delegation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,30 @@ fuzz_target!(|data: (
SillySecret,
SillyPublicKey,
Area<16, 16, 16, SillyPublicKey>,
McCapability<16, 16, 16, SillyPublicKey, SillySig, SillyPublicKey, SillySig>
McCapability<16, 16, 16, SillyPublicKey, SillySig, SillyPublicKey, SillySig>,
Vec<SillyPublicKey>
)| {
let (secret, new_user, area, mc_cap) = data;
let (secret, new_user, area, mc_cap, delegees) = data;

let area_is_included = mc_cap.granted_area().includes_area(&area);
let is_correct_secret = mc_cap.receiver() == &secret.corresponding_public_key();
let mut cap_with_delegees = mc_cap.clone();
let mut last_receiver = mc_cap.receiver().clone();
let granted_area = mc_cap.granted_area();

match mc_cap.delegate(&secret, &new_user, &area) {
for delegee in delegees {
cap_with_delegees = cap_with_delegees
.delegate(
&last_receiver.corresponding_secret_key(),
&delegee,
&granted_area,
)
.unwrap();
last_receiver = delegee;
}

let area_is_included = cap_with_delegees.granted_area().includes_area(&area);
let is_correct_secret = cap_with_delegees.receiver() == &secret.corresponding_public_key();

match cap_with_delegees.delegate(&secret, &new_user, &area) {
Ok(delegated_cap) => {
assert!(area_is_included);
assert!(is_correct_secret);
Expand All @@ -33,7 +49,7 @@ fuzz_target!(|data: (
assert_eq!(excluded_area, area);
}
FailedDelegationError::WrongSecretForUser(pub_key) => {
assert_eq!(&pub_key, mc_cap.receiver());
assert_eq!(&pub_key, cap_with_delegees.receiver());
assert!(secret.corresponding_public_key() != pub_key)
}
},
Expand Down
6 changes: 6 additions & 0 deletions fuzz/src/silly_sigs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ use willow_data_model::{
#[derive(PartialEq, Eq, Debug, Arbitrary, Clone, Default, PartialOrd, Ord)]
pub struct SillyPublicKey(u8);

impl SillyPublicKey {
pub fn corresponding_secret_key(&self) -> SillySecret {
SillySecret(self.0)
}
}

/// A silly, trivial, insecure secret key for fuzz testing.
/// The corresponding [`SillyPublicKey`] is the identity of the secret.
#[derive(PartialEq, Eq, Debug, Arbitrary)]
Expand Down
7 changes: 6 additions & 1 deletion meadowcap/src/communal_capability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub struct NamespaceIsNotCommunalError<NamespacePublicKey>(NamespacePublicKey);
/// A capability that implements [communal namespaces](https://willowprotocol.org/specs/meadowcap/index.html#communal_namespace).
///
/// [Definition](https://willowprotocol.org/specs/meadowcap/index.html#communal_capabilities).
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CommunalCapability<
const MCL: usize,
const MCC: usize,
Expand Down Expand Up @@ -185,6 +185,11 @@ where
last_delegation.area().clone()
}

/// Return a slice of all [`Delegation`]s made to this capability.
pub fn delegations(&self) -> &[Delegation<MCL, MCC, MPL, UserPublicKey, UserSignature>] {
&self.delegations
}

/// A bytestring to be signed for a new [`Delegation`].
///
/// [Definition](https://willowprotocol.org/specs/meadowcap/index.html#communal_handover)
Expand Down
31 changes: 26 additions & 5 deletions meadowcap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub trait IsCommunal {
}

/// A delegation of access rights to a user for a given area.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Delegation<
const MCL: usize,
const MCC: usize,
Expand Down Expand Up @@ -54,16 +54,36 @@ where
}
}

#[cfg(feature = "dev")]
use arbitrary::Arbitrary;

#[cfg(feature = "dev")]
impl<'a, const MCL: usize, const MCC: usize, const MPL: usize, UserPublicKey, UserSignature>
Arbitrary<'a> for Delegation<MCL, MCC, MPL, UserPublicKey, UserSignature>
where
UserSignature: Arbitrary<'a>,
UserPublicKey: SubspaceId + Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let area: Area<MCL, MCC, MPL, UserPublicKey> = Arbitrary::arbitrary(u)?;
let user: UserPublicKey = Arbitrary::arbitrary(u)?;
let signature: UserSignature = Arbitrary::arbitrary(u)?;

Ok(Self {
area,
signature,
user,
})
}
}

/// A mode granting read or write access to some [`Area`].
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AccessMode {
Read,
Write,
}

#[cfg(feature = "dev")]
use arbitrary::Arbitrary;

#[cfg(feature = "dev")]
impl<'a> Arbitrary<'a> for AccessMode {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
Expand All @@ -78,6 +98,7 @@ impl<'a> Arbitrary<'a> for AccessMode {
}

/// Returned when an attempt to delegate a capability failed.
#[derive(Debug)]
pub enum FailedDelegationError<
const MCL: usize,
const MCC: usize,
Expand Down
23 changes: 21 additions & 2 deletions meadowcap/src/mc_capability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ use crate::{
communal_capability::{CommunalCapability, NamespaceIsNotCommunalError},
mc_authorisation_token::McAuthorisationToken,
owned_capability::{OwnedCapability, OwnedCapabilityCreationError},
AccessMode, FailedDelegationError, IsCommunal,
AccessMode, Delegation, FailedDelegationError, InvalidDelegationError, IsCommunal,
};

/// A Meadowcap capability.
///
/// [Definition](https://willowprotocol.org/specs/meadowcap/index.html#Capability)
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum McCapability<
const MCL: usize,
const MCC: usize,
Expand Down Expand Up @@ -127,6 +127,14 @@ where
}
}

/// Return a slice of all [`Delegation`]s made to this capability.
pub fn delegations(&self) -> &[Delegation<MCL, MCC, MPL, UserPublicKey, UserSignature>] {
match self {
McCapability::Communal(cap) => cap.delegations(),
McCapability::Owned(cap) => cap.delegations(),
}
}

/// Delegate this capability to a new [`UserPublicKey`] for a given [`Area`].
/// Will fail if the area is not included by this capability's granted area, or if the given secret key does not correspond to the capability's receiver.
pub fn delegate<UserSecretKey>(
Expand Down Expand Up @@ -154,6 +162,17 @@ where
Ok(delegated)
}

/// Append an existing delegation to an existing capability, or return an error if the delegation is invalid.
pub fn append_existing_delegation(
&mut self,
delegation: Delegation<MCL, MCC, MPL, UserPublicKey, UserSignature>,
) -> Result<(), InvalidDelegationError<MCL, MCC, MPL, UserPublicKey, UserSignature>> {
match self {
McCapability::Communal(cap) => cap.append_existing_delegation(delegation),
McCapability::Owned(cap) => cap.append_existing_delegation(delegation),
}
}

/// Return a new AuthorisationToken without checking if the resulting signature is correct (e.g. because you are going to immediately do that by constructing an [`willow_data_model::AuthorisedEntry`]).
pub fn authorisation_token<UserSecret, PD>(
&self,
Expand Down
7 changes: 6 additions & 1 deletion meadowcap/src/owned_capability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub enum OwnedCapabilityCreationError<NamespacePublicKey> {
/// A capability that implements [owned namespaces](https://willowprotocol.org/specs/meadowcap/index.html#owned_namespace).
///
/// [Definition](https://willowprotocol.org/specs/meadowcap/index.html#owned_capabilities).
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OwnedCapability<
const MCL: usize,
const MCC: usize,
Expand Down Expand Up @@ -230,6 +230,11 @@ where
last_delegation.area().clone()
}

/// Return a slice of all [`Delegation`]s made to this capability.
pub fn delegations(&self) -> &[Delegation<MCL, MCC, MPL, UserPublicKey, UserSignature>] {
&self.delegations
}

/// A bytestring to be signed for a new [`Delegation`].
///
/// [Definition](https://willowprotocol.org/specs/meadowcap/index.html#owned_handover)
Expand Down

0 comments on commit a4c6105

Please sign in to comment.