#![cfg(not(target_arch = "wasm32"))]
use libmacaroon::{Format, Macaroon, MacaroonKey, Verifier};
use proptest::collection::vec;
use proptest::prelude::*;
fn location_strategy() -> impl Strategy<Value = String> {
"[ -~]{0,64}".prop_map(|s| s)
}
fn bytes_strategy(max_len: usize) -> impl Strategy<Value = Vec<u8>> {
vec(any::<u8>(), 1..=max_len)
}
#[derive(Debug, Clone)]
enum CaveatSpec {
First(Vec<u8>),
Third {
location: String,
key_seed: Vec<u8>,
id: Vec<u8>,
},
}
fn caveat_spec_strategy() -> impl Strategy<Value = CaveatSpec> {
prop_oneof![
4 => bytes_strategy(128).prop_map(CaveatSpec::First),
1 => (location_strategy(), bytes_strategy(32), bytes_strategy(64)).prop_map(
|(location, key_seed, id)| CaveatSpec::Third { location, key_seed, id },
),
]
}
#[derive(Debug, Clone)]
struct MacaroonSpec {
location: Option<String>,
key_seed: Vec<u8>,
identifier: Vec<u8>,
caveats: Vec<CaveatSpec>,
}
fn macaroon_spec_strategy() -> impl Strategy<Value = MacaroonSpec> {
(
proptest::option::of(location_strategy()),
bytes_strategy(32),
bytes_strategy(64),
vec(caveat_spec_strategy(), 0..=8),
)
.prop_map(|(location, key_seed, identifier, caveats)| MacaroonSpec {
location,
key_seed,
identifier,
caveats,
})
}
fn build(spec: &MacaroonSpec) -> (MacaroonKey, Macaroon) {
let key = MacaroonKey::generate(&spec.key_seed);
let mut mac = Macaroon::create(spec.location.as_deref(), &key, spec.identifier.as_slice())
.expect("valid inputs");
for c in &spec.caveats {
match c {
CaveatSpec::First(predicate) => {
mac.add_first_party_caveat(predicate.as_slice())
.expect("valid first-party caveat");
}
CaveatSpec::Third {
location,
key_seed,
id,
} => {
let cav_key = MacaroonKey::generate(key_seed);
mac.add_third_party_caveat(location, &cav_key, id.as_slice())
.expect("valid third-party caveat");
}
}
}
(key, mac)
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn roundtrip_all_formats(spec in macaroon_spec_strategy()) {
let (_key, mac) = build(&spec);
#[cfg(feature = "v2json")]
let formats = [Format::V1, Format::V2, Format::V2JSON];
#[cfg(not(feature = "v2json"))]
let formats = [Format::V1, Format::V2];
for format in formats {
let encoded = mac.serialize(format).expect("serialize");
let decoded = Macaroon::deserialize(&encoded).expect("deserialize");
prop_assert_eq!(&decoded, &mac, "round-trip mismatch in one of the formats");
}
}
#[test]
fn verify_succeeds_when_all_predicates_exact_matched(spec in macaroon_spec_strategy()) {
prop_assume!(spec.caveats.iter().all(|c| matches!(c, CaveatSpec::First(_))));
let (key, mac) = build(&spec);
let mut verifier = Verifier::default();
for c in &spec.caveats {
if let CaveatSpec::First(pred) = c {
verifier.satisfy_exact(pred.as_slice());
}
}
prop_assert!(verifier.verify(&mac, &key, &[]).is_ok());
}
#[test]
fn verify_fails_with_wrong_key(spec in macaroon_spec_strategy(), wrong_seed in bytes_strategy(32)) {
prop_assume!(spec.caveats.iter().all(|c| matches!(c, CaveatSpec::First(_))));
prop_assume!(wrong_seed != spec.key_seed);
let (_key, mac) = build(&spec);
let wrong_key = MacaroonKey::generate(&wrong_seed);
let mut verifier = Verifier::default();
for c in &spec.caveats {
if let CaveatSpec::First(pred) = c {
verifier.satisfy_exact(pred.as_slice());
}
}
prop_assert!(verifier.verify(&mac, &wrong_key, &[]).is_err());
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(512))]
#[test]
fn deserialize_does_not_panic(input in vec(any::<u8>(), 0..=256)) {
let _ = Macaroon::deserialize(input.as_slice());
let _ = Macaroon::deserialize_binary(input.as_slice());
}
}