diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 36e3a71..0313ab0 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -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 diff --git a/fuzz/fuzz_targets/mc_capability_append_delegation.rs b/fuzz/fuzz_targets/mc_capability_append_delegation.rs new file mode 100644 index 0000000..8ea693b --- /dev/null +++ b/fuzz/fuzz_targets/mc_capability_append_delegation.rs @@ -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 +)| { + 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..."), + } + } + }, + } +}); diff --git a/fuzz/fuzz_targets/mc_capability_delegation.rs b/fuzz/fuzz_targets/mc_capability_delegation.rs index a278385..025d6ce 100644 --- a/fuzz/fuzz_targets/mc_capability_delegation.rs +++ b/fuzz/fuzz_targets/mc_capability_delegation.rs @@ -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 )| { - 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); @@ -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) } }, diff --git a/fuzz/src/silly_sigs.rs b/fuzz/src/silly_sigs.rs index 2ade01f..c2faa9b 100644 --- a/fuzz/src/silly_sigs.rs +++ b/fuzz/src/silly_sigs.rs @@ -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)] diff --git a/meadowcap/src/communal_capability.rs b/meadowcap/src/communal_capability.rs index 21aa084..29379ca 100644 --- a/meadowcap/src/communal_capability.rs +++ b/meadowcap/src/communal_capability.rs @@ -14,7 +14,7 @@ pub struct NamespaceIsNotCommunalError(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, @@ -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] { + &self.delegations + } + /// A bytestring to be signed for a new [`Delegation`]. /// /// [Definition](https://willowprotocol.org/specs/meadowcap/index.html#communal_handover) diff --git a/meadowcap/src/lib.rs b/meadowcap/src/lib.rs index 0c9268a..47055a9 100644 --- a/meadowcap/src/lib.rs +++ b/meadowcap/src/lib.rs @@ -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, @@ -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 +where + UserSignature: Arbitrary<'a>, + UserPublicKey: SubspaceId + Arbitrary<'a>, +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let area: Area = 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 { @@ -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, diff --git a/meadowcap/src/mc_capability.rs b/meadowcap/src/mc_capability.rs index dff9118..dbf960a 100644 --- a/meadowcap/src/mc_capability.rs +++ b/meadowcap/src/mc_capability.rs @@ -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, @@ -127,6 +127,14 @@ where } } + /// Return a slice of all [`Delegation`]s made to this capability. + pub fn delegations(&self) -> &[Delegation] { + 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( @@ -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, + ) -> Result<(), InvalidDelegationError> { + 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( &self, diff --git a/meadowcap/src/owned_capability.rs b/meadowcap/src/owned_capability.rs index 4f56432..0181e82 100644 --- a/meadowcap/src/owned_capability.rs +++ b/meadowcap/src/owned_capability.rs @@ -19,7 +19,7 @@ pub enum OwnedCapabilityCreationError { /// 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, @@ -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] { + &self.delegations + } + /// A bytestring to be signed for a new [`Delegation`]. /// /// [Definition](https://willowprotocol.org/specs/meadowcap/index.html#owned_handover)