use super::crypto::{KeyPair, PublicKey};
use super::datalog::{Fact, Rule, Caveat, SymbolTable, World, ID};
use super::error;
use super::format::SerializedBiscuit;
use builder::{BiscuitBuilder, BlockBuilder};
use prost::Message;
use rand_core::{CryptoRng, RngCore};
use std::collections::HashSet;
#[cfg(test)]
use std::collections::HashMap;
use crate::format::{convert::proto_block_to_token_block, schema};
use verifier::Verifier;
pub mod builder;
pub mod sealed;
pub mod verifier;
pub const MAX_SCHEMA_VERSION: u32 = 0;
pub fn default_symbol_table() -> SymbolTable {
let mut syms = SymbolTable::new();
syms.insert("authority");
syms.insert("ambient");
syms.insert("resource");
syms.insert("operation");
syms.insert("right");
syms.insert("current_time");
syms.insert("revocation_id");
syms
}
#[derive(Clone, Debug)]
pub struct Biscuit {
pub(crate) authority: Block,
pub(crate) blocks: Vec<Block>,
pub(crate) symbols: SymbolTable,
container: Option<SerializedBiscuit>,
}
impl Biscuit {
pub fn new(
root: &KeyPair,
symbols: SymbolTable,
authority: Block,
) -> Result<Biscuit, error::Token> {
Self::new_with_rng(&mut rand::rngs::OsRng, root, symbols, authority)
}
pub fn new_with_rng<T: RngCore + CryptoRng>(
rng: &mut T,
root: &KeyPair,
mut symbols: SymbolTable,
authority: Block,
) -> Result<Biscuit, error::Token> {
let h1 = symbols.symbols.iter().collect::<HashSet<_>>();
let h2 = authority.symbols.symbols.iter().collect::<HashSet<_>>();
if !h1.is_disjoint(&h2) {
return Err(error::Token::SymbolTableOverlap);
}
if authority.index as usize != 0 {
return Err(error::Token::InvalidAuthorityIndex(authority.index));
}
symbols
.symbols
.extend(authority.symbols.symbols.iter().cloned());
let blocks = vec![];
let container =
SerializedBiscuit::new(rng, root, &authority).map_err(error::Token::Format)?;
Ok(Biscuit {
authority,
blocks,
symbols,
container: Some(container),
})
}
pub fn from(slice: &[u8]) -> Result<Self, error::Token> {
Biscuit::from_with_symbols(slice, default_symbol_table())
}
pub fn from_with_symbols(slice: &[u8], mut symbols: SymbolTable) -> Result<Self, error::Token> {
let container = SerializedBiscuit::from_slice(slice).map_err(error::Token::Format)?;
let authority: Block = schema::Block::decode(&container.authority[..])
.map_err(|e| {
error::Token::Format(error::Format::BlockDeserializationError(format!(
"error deserializing authority block: {:?}",
e
)))
})
.and_then(|b| proto_block_to_token_block(&b).map_err(error::Token::Format))?;
if authority.index != 0 {
return Err(error::Token::InvalidAuthorityIndex(authority.index));
}
let mut blocks = vec![];
let mut index = 1;
for block in container.blocks.iter() {
let deser: Block = schema::Block::decode(&block[..])
.map_err(|e| {
error::Token::Format(error::Format::BlockDeserializationError(format!(
"error deserializing block: {:?}",
e
)))
})
.and_then(|b| proto_block_to_token_block(&b).map_err(error::Token::Format))?;
if deser.index != index {
return Err(error::Token::InvalidBlockIndex(error::InvalidBlockIndex {
expected: index,
found: deser.index,
}));
}
blocks.push(deser);
index += 1;
}
symbols
.symbols
.extend(authority.symbols.symbols.iter().cloned());
for block in blocks.iter() {
symbols
.symbols
.extend(block.symbols.symbols.iter().cloned());
}
let container = Some(container);
Ok(Biscuit {
authority,
blocks,
symbols,
container,
})
}
pub fn from_sealed(slice: &[u8], secret: &[u8]) -> Result<Self, error::Token> {
Biscuit::from_sealed_with_symbols(slice, secret, default_symbol_table())
}
pub fn from_sealed_with_symbols(slice: &[u8], secret: &[u8], mut symbols: SymbolTable) -> Result<Self, error::Token> {
let container =
sealed::SealedBiscuit::from_slice(slice, secret).map_err(error::Token::Format)?;
let authority: Block = schema::Block::decode(&container.authority[..])
.map_err(|e| {
error::Token::Format(error::Format::BlockDeserializationError(format!(
"error deserializing authority block: {:?}",
e
)))
})
.and_then(|b| proto_block_to_token_block(&b).map_err(error::Token::Format))?;
if authority.index != 0 {
return Err(error::Token::InvalidAuthorityIndex(authority.index));
}
let mut blocks = vec![];
let mut index = 1;
for block in container.blocks.iter() {
let deser: Block = schema::Block::decode(&block[..])
.map_err(|e| {
error::Token::Format(error::Format::BlockDeserializationError(format!(
"error deserializing block: {:?}",
e
)))
})
.and_then(|b| proto_block_to_token_block(&b).map_err(error::Token::Format))?;
if deser.index != index {
return Err(error::Token::InvalidBlockIndex(error::InvalidBlockIndex {
expected: index,
found: deser.index,
}));
}
blocks.push(deser);
index += 1;
}
symbols
.symbols
.extend(authority.symbols.symbols.iter().cloned());
for block in blocks.iter() {
symbols
.symbols
.extend(block.symbols.symbols.iter().cloned());
}
let container = None;
Ok(Biscuit {
authority,
blocks,
symbols,
container,
})
}
pub fn to_vec(&self) -> Result<Vec<u8>, error::Token> {
match self.container.as_ref() {
None => Err(error::Token::InternalError),
Some(c) => c.to_vec().map_err(error::Token::Format),
}
}
pub fn serialized_size(&self) -> Result<usize, error::Token> {
match self.container.as_ref() {
None => Err(error::Token::InternalError),
Some(c) => Ok(c.serialized_size()),
}
}
pub fn sealed_size(&self) -> Result<usize, error::Token> {
let sealed =
sealed::SealedBiscuit::from_token(self, &b"ABCD"[..]).map_err(error::Token::Format)?;
Ok(sealed.serialized_size())
}
pub fn seal(&self, secret: &[u8]) -> Result<Vec<u8>, error::Token> {
let sealed =
sealed::SealedBiscuit::from_token(self, secret).map_err(error::Token::Format)?;
sealed.to_vec().map_err(error::Token::Format)
}
pub fn container(&self) -> Option<&SerializedBiscuit> {
self.container.as_ref()
}
pub fn check_root_key(&self, root: PublicKey) -> Result<(), error::Token> {
self.container
.as_ref()
.map(|c| c.check_root_key(root).map_err(error::Token::Format))
.unwrap_or(Err(error::Token::Sealed))?;
Ok(())
}
pub fn verify(&self, root: PublicKey) -> Result<Verifier, error::Token> {
self.check_root_key(root)?;
Verifier::from_token(self).map_err(error::Token::FailedLogic)
}
pub fn verify_sealed(&self) -> Result<Verifier, error::Token> {
if self.container.is_some() {
Err(error::Token::InternalError)
} else {
Verifier::from_token(self).map_err(error::Token::FailedLogic)
}
}
pub(crate) fn generate_world(&self, symbols: &SymbolTable) -> Result<World, error::Logic> {
let mut world = World::new();
let authority_index = symbols.get("authority").unwrap();
let ambient_index = symbols.get("ambient").unwrap();
for fact in self.authority.facts.iter().cloned() {
if fact.predicate.ids[0] == ID::Symbol(ambient_index) {
return Err(error::Logic::InvalidAuthorityFact(
symbols.print_fact(&fact),
));
}
world.facts.insert(fact);
}
for rule in self.authority.rules.iter().cloned() {
world.rules.push(rule);
}
for (i, block) in self.blocks.iter().enumerate() {
for fact in block.facts.iter().cloned() {
if fact.predicate.ids[0] == ID::Symbol(authority_index)
|| fact.predicate.ids[0] == ID::Symbol(ambient_index)
{
return Err(error::Logic::InvalidBlockFact(
i as u32,
symbols.print_fact(&fact),
));
}
world.facts.insert(fact);
}
for rule in block.rules.iter().cloned() {
if rule.head.ids[0] == ID::Symbol(authority_index)
|| rule.head.ids[0] == ID::Symbol(ambient_index)
{
return Err(error::Logic::InvalidBlockRule(
i as u32,
symbols.print_rule(&rule),
));
}
world.rules.push(rule);
}
}
Ok(world)
}
pub(crate) fn caveats(&self) -> Vec<Vec<Caveat>> {
let mut result = Vec::new();
let v = self.authority.caveats.to_vec();
result.push(v);
for block in self.blocks.iter() {
let v = block.caveats.to_vec();
result.push(v);
}
result
}
#[cfg(test)]
pub(crate) fn check(
&self,
symbols: &SymbolTable,
mut ambient_facts: Vec<Fact>,
ambient_rules: Vec<Rule>,
verifier_caveats: Vec<Caveat>,
queries: HashMap<String, Rule>,
) -> Result<HashMap<String, Vec<Fact>>, error::Token> {
let mut world = self.generate_world(symbols).map_err(error::Token::FailedLogic)?;
for fact in ambient_facts.drain(..) {
world.facts.insert(fact);
}
for rule in ambient_rules.iter().cloned() {
world.rules.push(rule);
}
world.run().map_err(error::Token::RunLimit)?;
let mut errors = vec![];
for (i, caveat) in self.authority.caveats.iter().enumerate() {
let mut successful = false;
for query in caveat.queries.iter() {
let res = world.query_rule(query.clone());
if !res.is_empty() {
successful = true;
break;
}
}
if !successful {
errors.push(error::FailedCaveat::Block(error::FailedBlockCaveat {
block_id: 0,
caveat_id: i as u32,
rule: symbols.print_caveat(caveat),
}));
}
}
for (i, caveat) in verifier_caveats.iter().enumerate() {
let mut successful = false;
for query in caveat.queries.iter() {
let res = world.query_rule(query.clone());
if !res.is_empty() {
successful = true;
break;
}
}
if !successful {
errors.push(error::FailedCaveat::Verifier(error::FailedVerifierCaveat {
caveat_id: i as u32,
rule: symbols.print_caveat(caveat),
}));
}
}
for (i, block) in self.blocks.iter().enumerate() {
for (j, caveat) in block.caveats.iter().enumerate() {
let mut successful = false;
for query in caveat.queries.iter() {
let res = world.query_rule(query.clone());
if !res.is_empty() {
successful = true;
break;
}
}
if !successful {
errors.push(error::FailedCaveat::Block(error::FailedBlockCaveat {
block_id: i as u32,
caveat_id: j as u32,
rule: symbols.print_caveat(caveat),
}));
}
}
}
let mut query_results = HashMap::new();
for (name, rule) in queries.iter() {
let res = world.query_rule(rule.clone());
query_results.insert(name.clone(), res);
}
if errors.is_empty() {
Ok(query_results)
} else {
Err(error::Token::FailedLogic(error::Logic::FailedCaveats(errors)))
}
}
pub fn builder<'a>(
root: &'a KeyPair,
) -> BiscuitBuilder<'a> {
Biscuit::builder_with_symbols(root, default_symbol_table())
}
pub fn builder_with_symbols<'a>(
root: &'a KeyPair,
symbols: SymbolTable,
) -> BiscuitBuilder<'a> {
BiscuitBuilder::new(root, symbols)
}
pub fn create_block(&self) -> BlockBuilder {
BlockBuilder::new((1 + self.blocks.len()) as u32)
}
pub fn append(
&self,
keypair: &KeyPair,
block_builder: BlockBuilder,
) -> Result<Self, error::Token> {
self.append_with_rng(&mut rand::rngs::OsRng, keypair, block_builder)
}
pub fn append_with_rng<T: RngCore + CryptoRng>(
&self,
rng: &mut T,
keypair: &KeyPair,
block_builder: BlockBuilder,
) -> Result<Self, error::Token> {
if self.container.is_none() {
return Err(error::Token::Sealed);
}
let block = block_builder.build(self.symbols.clone());
let h1 = self.symbols.symbols.iter().collect::<HashSet<_>>();
let h2 = block.symbols.symbols.iter().collect::<HashSet<_>>();
if !h1.is_disjoint(&h2) {
return Err(error::Token::SymbolTableOverlap);
}
if block.index as usize != 1 + self.blocks.len() {
return Err(error::Token::InvalidBlockIndex(error::InvalidBlockIndex {
expected: 1 + self.blocks.len() as u32,
found: block.index,
}));
}
let authority = self.authority.clone();
let mut blocks = self.blocks.clone();
let mut symbols = self.symbols.clone();
let container = match self.container.as_ref() {
None => return Err(error::Token::Sealed),
Some(c) => c
.append(rng, keypair, &block)
.map_err(error::Token::Format)?,
};
symbols
.symbols
.extend(block.symbols.symbols.iter().cloned());
blocks.push(block);
Ok(Biscuit {
authority,
blocks,
symbols,
container: Some(container),
})
}
pub fn context(&self) -> Vec<Option<String>> {
let mut res = vec![];
res.push(self.authority.context.clone());
for b in self.blocks.iter() {
res.push(b.context.clone());
}
res
}
pub fn print(&self) -> String {
let authority = print_block(&self.symbols, &self.authority);
let blocks: Vec<_> = self
.blocks
.iter()
.map(|b| print_block(&self.symbols, b))
.collect();
format!(
"Biscuit {{\n symbols: {:?}\n authority: {}\n blocks: [\n {}\n ]\n}}",
self.symbols.symbols,
authority,
blocks.join(",\n\t")
)
}
}
fn print_block(symbols: &SymbolTable, block: &Block) -> String {
let facts: Vec<_> = block.facts.iter().map(|f| symbols.print_fact(f)).collect();
let rules: Vec<_> = block.rules.iter().map(|r| symbols.print_rule(r)).collect();
let caveats: Vec<_> = block
.caveats
.iter()
.map(|r| symbols.print_caveat(r))
.collect();
let facts = if facts.is_empty() {
String::new()
} else {
format!("\n {}\n ", facts.join(",\n "))
};
let rules = if rules.is_empty() {
String::new()
} else {
format!("\n {}\n ", rules.join(",\n "))
};
let caveats = if caveats.is_empty() {
String::new()
} else {
format!("\n {}\n ", caveats.join(",\n "))
};
format!(
"Block[{}] {{\n symbols: {:?}\n version: {}\n context: \"{}\"\n facts: [{}]\n rules: [{}]\n caveats: [{}]\n }}",
block.index,
block.symbols.symbols,
block.version,
block.context.as_deref().unwrap_or(""),
facts,
rules,
caveats,
)
}
#[derive(Clone, Debug)]
pub struct Block {
pub index: u32,
pub symbols: SymbolTable,
pub facts: Vec<Fact>,
pub rules: Vec<Rule>,
pub caveats: Vec<Caveat>,
pub context: Option<String>,
pub version: u32,
}
impl Block {
pub fn new(index: u32, base_symbols: SymbolTable) -> Block {
Block {
index,
symbols: base_symbols,
facts: vec![],
rules: vec![],
caveats: vec![],
context: None,
version: MAX_SCHEMA_VERSION,
}
}
pub fn symbol_add(&mut self, s: &str) -> ID {
self.symbols.add(s)
}
pub fn symbol_insert(&mut self, s: &str) -> u64 {
self.symbols.insert(s)
}
}
#[cfg(test)]
mod tests {
use super::builder::{fact, pred, rule, s, var, int};
use super::*;
use crate::crypto::KeyPair;
use crate::error::*;
use rand::prelude::*;
use std::time::{Duration, SystemTime};
#[test]
fn basic() {
let mut rng: StdRng = SeedableRng::seed_from_u64(0);
let root = KeyPair::new_with_rng(&mut rng);
let serialized1 = {
let mut builder = Biscuit::builder(&root);
builder.add_authority_fact("right(#authority, #file1, #read)").unwrap();
builder.add_authority_fact("right(#authority, #file2, #read)").unwrap();
builder.add_authority_fact("right(#authority, #file1, #write)").unwrap();
let biscuit1 = builder.build_with_rng(&mut rng).unwrap();
println!("biscuit1 (authority): {}", biscuit1.print());
biscuit1.to_vec().unwrap()
};
println!("generated biscuit token: {} bytes", serialized1.len());
let serialized2 = {
let biscuit1_deser = Biscuit::from(&serialized1).unwrap();
let mut block2 = biscuit1_deser.create_block();
block2.add_caveat(rule(
"caveat1",
&[var("resource")],
&[
pred("resource", &[s("ambient"), var("resource")]),
pred("operation", &[s("ambient"), s("read")]),
pred("right", &[s("authority"), var("resource"), s("read")]),
],
)).unwrap();
let keypair2 = KeyPair::new_with_rng(&mut rng);
let biscuit2 = biscuit1_deser
.append_with_rng(&mut rng, &keypair2, block2)
.unwrap();
println!("biscuit2 (1 caveat): {}", biscuit2.print());
biscuit2.to_vec().unwrap()
};
println!("generated biscuit token 2: {} bytes", serialized2.len());
let serialized3 = {
let biscuit2_deser = Biscuit::from(&serialized2).unwrap();
let mut block3 = biscuit2_deser.create_block();
block3.add_caveat(rule(
"caveat2",
&[s("file1")],
&[pred("resource", &[s("ambient"), s("file1")])],
)).unwrap();
let keypair3 = KeyPair::new_with_rng(&mut rng);
let biscuit3 = biscuit2_deser
.append_with_rng(&mut rng, &keypair3, block3)
.unwrap();
biscuit3.to_vec().unwrap()
};
println!("generated biscuit token 3: {} bytes", serialized3.len());
let final_token = Biscuit::from(&serialized3).unwrap();
final_token.check_root_key(root.public()).unwrap();
println!("final token:\n{}", final_token.print());
{
let mut symbols = final_token.symbols.clone();
let facts = vec![
fact("resource", &[s("ambient"), s("file1")]),
fact("operation", &[s("ambient"), s("read")]),
];
let mut ambient_facts = vec![];
for fact in facts.iter() {
ambient_facts.push(fact.convert(&mut symbols));
}
let res = final_token.check(&symbols, ambient_facts, vec![], vec![], HashMap::new());
println!("res1: {:?}", res);
res.unwrap();
}
{
let mut symbols = final_token.symbols.clone();
let facts = vec![
fact("resource", &[s("ambient"), s("file2")]),
fact("operation", &[s("ambient"), s("write")]),
];
let mut ambient_facts = vec![];
for fact in facts.iter() {
ambient_facts.push(fact.convert(&mut symbols));
}
let res = final_token.check(&symbols, ambient_facts, vec![], vec![], HashMap::new());
println!("res2: {:#?}", res);
assert_eq!(res,
Err(Token::FailedLogic(Logic::FailedCaveats(vec![
FailedCaveat::Block(FailedBlockCaveat { block_id: 0, caveat_id: 0, rule: String::from("caveat1($resource) <- resource(#ambient, $resource), operation(#ambient, #read), right(#authority, $resource, #read)") }),
FailedCaveat::Block(FailedBlockCaveat { block_id: 1, caveat_id: 0, rule: String::from("caveat2(#file1) <- resource(#ambient, #file1)") })
]))));
}
}
#[test]
fn folders() {
let mut rng: StdRng = SeedableRng::seed_from_u64(0);
let root = KeyPair::new_with_rng(&mut rng);
let mut builder = Biscuit::builder(&root);
builder.add_right("/folder1/file1", "read");
builder.add_right("/folder1/file1", "write");
builder.add_right("/folder1/file2", "read");
builder.add_right("/folder1/file2", "write");
builder.add_right("/folder2/file3", "read");
let biscuit1 = builder.build_with_rng(&mut rng).unwrap();
println!("biscuit1 (authority): {}", biscuit1.print());
let mut block2 = biscuit1.create_block();
block2.resource_prefix("/folder1/");
block2.check_right("read");
let keypair2 = KeyPair::new_with_rng(&mut rng);
let biscuit2 = biscuit1
.append_with_rng(&mut rng, &keypair2, block2)
.unwrap();
{
let mut verifier = biscuit2.verify(root.public()).unwrap();
verifier.add_resource("/folder1/file1");
verifier.add_operation("read");
let res = verifier.verify();
println!("res1: {:?}", res);
res.unwrap();
}
{
let mut verifier = biscuit2.verify(root.public()).unwrap();
verifier.add_resource("/folder2/file3");
verifier.add_operation("read");
let res = verifier.verify();
println!("res2: {:?}", res);
assert_eq!(
res,
Err(Token::FailedLogic(Logic::FailedCaveats(vec![
FailedCaveat::Block(FailedBlockCaveat {
block_id: 1,
caveat_id: 0,
rule: String::from(
"prefix($resource) <- resource(#ambient, $resource) @ $resource matches /folder1/*"
)
}),
])))
);
}
{
let mut verifier = biscuit2.verify(root.public()).unwrap();
verifier.add_resource("/folder2/file1");
verifier.add_operation("write");
let res = verifier.verify();
println!("res3: {:?}", res);
assert_eq!(res,
Err(Token::FailedLogic(Logic::FailedCaveats(vec![
FailedCaveat::Block(FailedBlockCaveat { block_id: 1, caveat_id: 0, rule: String::from("prefix($resource) <- resource(#ambient, $resource) @ $resource matches /folder1/*") }),
FailedCaveat::Block(FailedBlockCaveat { block_id: 1, caveat_id: 1, rule: String::from("check_right(#read) <- resource(#ambient, $resource_name), operation(#ambient, #read), right(#authority, $resource_name, #read)") }),
]))));
}
}
#[test]
fn constraints() {
let mut rng: StdRng = SeedableRng::seed_from_u64(0);
let root = KeyPair::new_with_rng(&mut rng);
let mut builder = Biscuit::builder(&root);
builder.add_right("file1", "read");
builder.add_right("file2", "read");
let biscuit1 = builder.build_with_rng(&mut rng).unwrap();
println!("biscuit1 (authority): {}", biscuit1.print());
let mut block2 = biscuit1.create_block();
block2.expiration_date(SystemTime::now() + Duration::from_secs(30));
block2.revocation_id(1234);
let keypair2 = KeyPair::new_with_rng(&mut rng);
let biscuit2 = biscuit1
.append_with_rng(&mut rng, &keypair2, block2)
.unwrap();
{
let mut verifier = biscuit2.verify(root.public()).unwrap();
verifier.add_resource("file1");
verifier.add_operation("read");
verifier.set_time();
let res = verifier.verify();
println!("res1: {:?}", res);
res.unwrap();
}
{
println!("biscuit2: {}", biscuit2.print());
let mut verifier = biscuit2.verify(root.public()).unwrap();
verifier.add_resource("file1");
verifier.add_operation("read");
verifier.set_time();
verifier.revocation_check(&[0, 1, 2, 5, 1234]);
let res = verifier.verify();
println!("res3: {:?}", res);
assert!(res.is_err());
}
}
#[test]
fn sealed_token() {
let mut rng: StdRng = SeedableRng::seed_from_u64(0);
let root = KeyPair::new_with_rng(&mut rng);
let mut builder = Biscuit::builder(&root);
builder.add_right("/folder1/file1", "read");
builder.add_right("/folder1/file1", "write");
builder.add_right("/folder1/file2", "read");
builder.add_right("/folder1/file2", "write");
builder.add_right("/folder2/file3", "read");
let biscuit1 = builder.build_with_rng(&mut rng).unwrap();
println!("biscuit1 (authority): {}", biscuit1.print());
let mut block2 = biscuit1.create_block();
block2.resource_prefix("/folder1/");
block2.check_right("read");
let keypair2 = KeyPair::new_with_rng(&mut rng);
let biscuit2 = biscuit1
.append_with_rng(&mut rng, &keypair2, block2)
.unwrap();
{
let mut verifier = biscuit2.verify(root.public()).unwrap();
verifier.add_resource("/folder1/file1");
verifier.add_operation("read");
let res = verifier.verify();
println!("res1: {:?}", res);
res.unwrap();
}
let _serialized = biscuit2.to_vec().unwrap();
let secret = b"secret key";
let sealed = biscuit2.seal(&secret[..]).unwrap();
let biscuit3 = Biscuit::from_sealed(&sealed, &secret[..]).unwrap();
{
let mut verifier = biscuit3.verify_sealed().unwrap();
verifier.add_resource("/folder1/file1");
verifier.add_operation("read");
let res = verifier.verify();
println!("res1: {:?}", res);
res.unwrap();
}
}
#[test]
fn verif_no_blocks() {
use crate::token::builder::*;
let mut rng: StdRng = SeedableRng::seed_from_u64(1234);
let root = KeyPair::new_with_rng(&mut rng);
let mut builder = Biscuit::builder(&root);
builder.add_authority_fact(fact("right", &[s("authority"), string("file1"), s("read")])).unwrap();
builder.add_authority_fact(fact("right", &[s("authority"), string("file2"), s("read")])).unwrap();
builder.add_authority_fact(fact("right", &[s("authority"), string("file1"), s("write")])).unwrap();
let biscuit1 = builder.build_with_rng(&mut rng).unwrap();
println!("{}", biscuit1.print());
let mut v = biscuit1.verify(root.public()).expect("omg verifier");
v.add_caveat(rule("right",
&[s("right")],
&[pred("right", &[s("authority"), string("file2"), s("write")])]
)).unwrap();
let res = v.verify();
println!("res: {:?}", res);
assert_eq!(res,
Err(Token::FailedLogic(Logic::FailedCaveats(vec![
FailedCaveat::Verifier(FailedVerifierCaveat { caveat_id: 0, rule: String::from("right(#right) <- right(#authority, \"file2\", #write)") }),
]))));
}
#[test]
fn verifier_queries() {
let mut rng: StdRng = SeedableRng::seed_from_u64(0);
let root = KeyPair::new_with_rng(&mut rng);
let mut builder = Biscuit::builder(&root);
builder.add_right("file1", "read");
builder.add_right("file2", "read");
let biscuit1 = builder.build_with_rng(&mut rng).unwrap();
println!("biscuit1 (authority): {}", biscuit1.print());
let mut block2 = biscuit1.create_block();
block2.expiration_date(SystemTime::now() + Duration::from_secs(30));
block2.revocation_id(1234);
let keypair2 = KeyPair::new_with_rng(&mut rng);
let biscuit2 = biscuit1
.append_with_rng(&mut rng, &keypair2, block2)
.unwrap();
let mut block3 = biscuit2.create_block();
block3.expiration_date(SystemTime::now() + Duration::from_secs(10));
block3.revocation_id(5678);
let keypair3 = KeyPair::new_with_rng(&mut rng);
let biscuit3 = biscuit2
.append_with_rng(&mut rng, &keypair3, block3)
.unwrap();
{
let mut verifier = biscuit3.verify(root.public()).unwrap();
verifier.add_resource("file1");
verifier.add_operation("read");
verifier.set_time();
let res = verifier.verify();
println!("res1: {:?}", res);
let res2 = verifier.query(rule(
"revocation_id_verif",
&[builder::Term::Variable("id".to_string())],
&[pred("revocation_id", &[builder::Term::Variable("id".to_string())])]
));
println!("res2: {:?}", res2);
assert_eq!(
&res2.unwrap().iter().collect::<HashSet<_>>(),
&[fact("revocation_id_verif", &[int(1234)]), fact("revocation_id_verif", &[int(5678)])].iter().collect::<HashSet<_>>()
);
}
}
#[test]
fn caveat_head_name() {
let mut rng: StdRng = SeedableRng::seed_from_u64(0);
let root = KeyPair::new_with_rng(&mut rng);
let mut builder = Biscuit::builder(&root);
let empty: &[builder::Term] = &[];
builder.add_authority_caveat(rule(
"caveat1",
empty,
&[
pred("resource", &[s("ambient"), s("hello")]),
],
)).unwrap();
let biscuit1 = builder.build_with_rng(&mut rng).unwrap();
println!("biscuit1 (authority): {}", biscuit1.print());
let mut block2 = biscuit1.create_block();
block2.add_fact(fact("caveat1", &[s("test")])).unwrap();
let keypair2 = KeyPair::new_with_rng(&mut rng);
let biscuit2 = biscuit1
.append_with_rng(&mut rng, &keypair2, block2)
.unwrap();
println!("biscuit2: {}", biscuit2.print());
{
let mut verifier = biscuit2.verify(root.public()).unwrap();
verifier.add_resource("file1");
verifier.add_operation("read");
verifier.set_time();
println!("world:\n{}", verifier.print_world());
let res = verifier.verify();
println!("res1: {:?}", res);
assert_eq!(
res,
Err(Token::FailedLogic(Logic::FailedCaveats(vec![
FailedCaveat::Block(FailedBlockCaveat {
block_id: 0,
caveat_id: 0,
rule: String::from("caveat1() <- resource(#ambient, #hello)"),
}),
]))));
}
}
#[test]
fn caveat_requires_fact_in_future_block() {
let mut rng: StdRng = SeedableRng::seed_from_u64(0);
let root = KeyPair::new_with_rng(&mut rng);
let mut builder = Biscuit::builder(&root);
builder.add_authority_caveat(rule(
"requires_name",
&[var("name")],
&[
pred("name", &[var("name")]),
],
)).unwrap();
let biscuit1 = builder.build_with_rng(&mut rng).unwrap();
println!("biscuit1 (authority): {}", biscuit1.print());
let mut verifier1 = biscuit1.verify(root.public()).unwrap();
let res1 = verifier1.verify();
println!("res1: {:?}", res1);
assert_eq!(
res1,
Err(Token::FailedLogic(Logic::FailedCaveats(vec![
FailedCaveat::Block(FailedBlockCaveat {
block_id: 0,
caveat_id: 0,
rule: String::from("requires_name($name) <- name($name)"),
}),
]))));
let mut block2 = biscuit1.create_block();
block2.add_fact(fact("name", &[s("test")])).unwrap();
let keypair2 = KeyPair::new_with_rng(&mut rng);
let biscuit2 = biscuit1
.append_with_rng(&mut rng, &keypair2, block2)
.unwrap();
println!("biscuit2 (with name fact): {}", biscuit2.print());
let mut verifier2 = biscuit2.verify(root.public()).unwrap();
let res2 = verifier2.verify();
assert_eq!(res2, Ok(()));
}
#[test]
fn bytes_constraints() {
let mut rng: StdRng = SeedableRng::seed_from_u64(0);
let root = KeyPair::new_with_rng(&mut rng);
let mut builder = Biscuit::builder(&root);
builder.add_authority_fact("bytes(#authority, hex:0102AB)").unwrap();
let biscuit1 = builder.build_with_rng(&mut rng).unwrap();
println!("biscuit1 (authority): {}", biscuit1.print());
let mut block2 = biscuit1.create_block();
block2.add_rule("has_bytes($0) <- bytes(#authority, $0) @ $0 in [ hex:00000000, hex:0102AB ]").unwrap();
let keypair2 = KeyPair::new_with_rng(&mut rng);
let biscuit2 = biscuit1
.append_with_rng(&mut rng, &keypair2, block2)
.unwrap();
let mut verifier = biscuit2.verify(root.public()).unwrap();
verifier.add_caveat("ok(0) <- has_bytes($0)").unwrap();
let res = verifier.verify();
println!("res1: {:?}", res);
res.unwrap();
let res = verifier.query("data($0) <- bytes(#authority, $0)").unwrap();
println!("query result: {:x?}", res);
println!("query result: {}", res[0]);
}
}