From 7832a939f7410bc8e25bf35503d5b2152a9c0217 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Tue, 23 Mar 2021 09:56:51 +0100 Subject: [PATCH] implement Verifier.add_token an empty verifier can be created, adding data, checks, etc. Then when a request come, we clone the verifier, add the token and test the policies with that request and token --- .../biscuit/datalog/expressions/Op.java | 12 ++ .../clevercloud/biscuit/error/LogicError.java | 22 +++ .../clevercloud/biscuit/token/Biscuit.java | 11 ++ .../clevercloud/biscuit/token/Verifier.java | 142 ++++++++++++------ .../biscuit/token/builder/Check.java | 10 ++ .../biscuit/token/builder/Expression.java | 94 +++++++++++- .../biscuit/token/builder/Rule.java | 17 +++ .../biscuit/token/builder/Term.java | 3 + .../biscuit/token/BiscuitTest.java | 46 ++++++ 9 files changed, 308 insertions(+), 49 deletions(-) diff --git a/src/main/java/com/clevercloud/biscuit/datalog/expressions/Op.java b/src/main/java/com/clevercloud/biscuit/datalog/expressions/Op.java index c73f3b26..d9a78366 100644 --- a/src/main/java/com/clevercloud/biscuit/datalog/expressions/Op.java +++ b/src/main/java/com/clevercloud/biscuit/datalog/expressions/Op.java @@ -38,6 +38,10 @@ public Value(ID value) { this.value = value; } + public ID getValue() { + return value; + } + @Override public boolean evaluate(Deque stack, Map variables) { if (value instanceof ID.Variable) { @@ -106,6 +110,10 @@ public Unary(UnaryOp op) { this.op = op; } + public UnaryOp getOp() { + return op; + } + @Override public boolean evaluate(Deque stack, Map variables) { ID value = stack.pop(); @@ -236,6 +244,10 @@ public Binary(BinaryOp value) { this.op = value; } + public BinaryOp getOp() { + return op; + } + @Override public boolean evaluate(Deque stack, Map variables) { ID right = stack.pop(); diff --git a/src/main/java/com/clevercloud/biscuit/error/LogicError.java b/src/main/java/com/clevercloud/biscuit/error/LogicError.java index 8d0b5ca8..63167fc5 100644 --- a/src/main/java/com/clevercloud/biscuit/error/LogicError.java +++ b/src/main/java/com/clevercloud/biscuit/error/LogicError.java @@ -169,4 +169,26 @@ public String toString() { return "Denied("+id+")"; } } + + public static class VerifierNotEmpty extends LogicError { + + public VerifierNotEmpty() { + + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + @Override + public String toString() { + return "VerifierNotEmpty"; + } + } } \ No newline at end of file diff --git a/src/main/java/com/clevercloud/biscuit/token/Biscuit.java b/src/main/java/com/clevercloud/biscuit/token/Biscuit.java index 4f423ccf..0699008a 100644 --- a/src/main/java/com/clevercloud/biscuit/token/Biscuit.java +++ b/src/main/java/com/clevercloud/biscuit/token/Biscuit.java @@ -452,6 +452,17 @@ public Either attenuate(final SecureRandom rng, final KeyPair ke return Right(new Biscuit(copiedBiscuit.authority, blocks, symbols, Option.some(container))); } + public List> checks() { + ArrayList> l = new ArrayList(); + l.add(new ArrayList(this.authority.checks)); + + for(Block b: this.blocks) { + l.add(new ArrayList<>(b.checks)); + } + + return l; + } + public List> context() { ArrayList res = new ArrayList(); if(this.authority.context.isEmpty()) { diff --git a/src/main/java/com/clevercloud/biscuit/token/Verifier.java b/src/main/java/com/clevercloud/biscuit/token/Verifier.java index b8949a25..e44f9090 100644 --- a/src/main/java/com/clevercloud/biscuit/token/Verifier.java +++ b/src/main/java/com/clevercloud/biscuit/token/Verifier.java @@ -1,6 +1,7 @@ package com.clevercloud.biscuit.token; import com.clevercloud.biscuit.crypto.PublicKey; +import com.clevercloud.biscuit.datalog.ID; import com.clevercloud.biscuit.datalog.RunLimits; import com.clevercloud.biscuit.datalog.SymbolTable; import com.clevercloud.biscuit.datalog.World; @@ -26,6 +27,7 @@ public class Verifier { Biscuit token; List checks; + List> token_checks; List policies; World world; SymbolTable symbols; @@ -36,6 +38,7 @@ private Verifier(Biscuit token, World w) { this.symbols = new SymbolTable(this.token.symbols); this.checks = new ArrayList<>(); this.policies = new ArrayList<>(); + this.token_checks = this.token.checks(); } /** @@ -49,12 +52,15 @@ public Verifier() { this.symbols = Biscuit.default_symbol_table(); this.checks = new ArrayList<>(); this.policies = new ArrayList<>(); + this.token_checks = new ArrayList<>(); } - Verifier(Biscuit token, List checks, List policies, World world, SymbolTable symbols) { + Verifier(Biscuit token, List checks, List policies, + List> token_checks, World world, SymbolTable symbols) { this.token = token; this.checks = checks; this.policies = policies; + this.token_checks = token_checks; this.world = world; this.symbols = symbols; } @@ -79,7 +85,6 @@ static public Either make(Biscuit token, Option root Either res = token.generate_world(); if (res.isLeft()) { Error e = res.getLeft(); - System.out.println(e); return Left(e); } @@ -87,8 +92,77 @@ static public Either make(Biscuit token, Option root } public Verifier clone() { - return new Verifier(this.token, new ArrayList<>(this.checks), new ArrayList<>(this.policies), new World(this.world), - new SymbolTable(this.symbols)); + return new Verifier(this.token, new ArrayList<>(this.checks), new ArrayList<>(this.policies), + new ArrayList<>(this.token_checks), new World(this.world), new SymbolTable(this.symbols)); + } + + public Either add_token(Biscuit token, Option root) { + if(!token.is_sealed()) { + Either res = token.check_root_key(root.get()); + if (res.isLeft()) { + Error e = res.getLeft(); + return Left(e); + } + } + + if(this.token != null) { + return Either.left(new Error.FailedLogic(new LogicError.VerifierNotEmpty())); + } + + long authority_index = symbols.get("authority").get(); + long ambient_index = symbols.get("ambient").get(); + + for(com.clevercloud.biscuit.datalog.Fact fact: token.authority.facts) { + if (fact.predicate().ids().get(0).equals(new ID.Symbol(ambient_index))) { + return Left(new Error.FailedLogic(new LogicError.InvalidAuthorityFact(symbols.print_fact(fact)))); + } + + com.clevercloud.biscuit.datalog.Fact converted_fact = Fact.convert_from(fact, token.symbols).convert(this.symbols); + world.add_fact(converted_fact); + } + + for(com.clevercloud.biscuit.datalog.Rule rule: token.authority.rules) { + com.clevercloud.biscuit.datalog.Rule converted_rule = Rule.convert_from(rule, token.symbols).convert(this.symbols); + world.add_rule(converted_rule); + } + + List authority_checks = new ArrayList<>(); + for(com.clevercloud.biscuit.datalog.Check check: token.authority.checks) { + com.clevercloud.biscuit.datalog.Check converted_check = Check.convert_from(check, token.symbols).convert(this.symbols); + authority_checks.add(converted_check); + } + token_checks.add(authority_checks); + + for(int i = 0; i < token.blocks.size(); i++) { + Block b = token.blocks.get(i); + if (b.index != i + 1) { + return Left(new Error.InvalidBlockIndex(1 + token.blocks.size(), token.blocks.get(i).index)); + } + + for (com.clevercloud.biscuit.datalog.Fact fact : b.facts) { + if (fact.predicate().ids().get(0).equals(new ID.Symbol(authority_index)) || + fact.predicate().ids().get(0).equals(new ID.Symbol(ambient_index))) { + return Left(new Error.FailedLogic(new LogicError.InvalidBlockFact(i, symbols.print_fact(fact)))); + } + + com.clevercloud.biscuit.datalog.Fact converted_fact = Fact.convert_from(fact, token.symbols).convert(this.symbols); + world.add_fact(converted_fact); + } + + for (com.clevercloud.biscuit.datalog.Rule rule : b.rules) { + com.clevercloud.biscuit.datalog.Rule converted_rule = Rule.convert_from(rule, token.symbols).convert(this.symbols); + world.add_rule(converted_rule); + } + + List block_checks = new ArrayList<>(); + for(com.clevercloud.biscuit.datalog.Check check: b.checks) { + com.clevercloud.biscuit.datalog.Check converted_check = Check.convert_from(check, token.symbols).convert(this.symbols); + block_checks.add(converted_check); + } + token_checks.add(block_checks); + } + + return Right(null); } public void add_fact(Fact fact) { @@ -196,7 +270,6 @@ public Either> get_revocation_ids() { Either> queryRes = this.query(getRevocationIds); if (queryRes.isLeft()) { Error e = queryRes.getLeft(); - System.out.println(e); return Left(e); } @@ -272,7 +345,6 @@ public Either> query(Rule query, RunLimits limits) { Either runRes = world.run(limits); if (runRes.isLeft()) { Error e = runRes.getLeft(); - System.out.println(e); return Left(e); } @@ -313,36 +385,12 @@ public Either verify(RunLimits limits) { Either runRes = world.run(limits); if (runRes.isLeft()) { Error e = runRes.getLeft(); - System.out.println(e); return Left(e); } SymbolTable symbols = new SymbolTable(this.symbols); ArrayList errors = new ArrayList<>(); - if(this.token != null) { - for (int j = 0; j < this.token.authority.checks.size(); j++) { - boolean successful = false; - com.clevercloud.biscuit.datalog.Check c = this.token.authority.checks.get(j); - - for (int k = 0; k < c.queries().size(); k++) { - boolean res = world.test_rule(c.queries().get(k)); - - if (Instant.now().compareTo(timeLimit) >= 0) { - return Left(new Error.Timeout()); - } - - if (res) { - successful = true; - break; - } - } - - if (!successful) { - errors.add(new FailedCheck.FailedBlock(0, j, symbols.print_check(this.token.authority.checks.get(j)))); - } - } - } for (int j = 0; j < this.checks.size(); j++) { com.clevercloud.biscuit.datalog.Check c = this.checks.get(j).convert(symbols); @@ -366,31 +414,30 @@ public Either verify(RunLimits limits) { } } - if(this.token != null) { - for (int i = 0; i < this.token.blocks.size(); i++) { - Block b = this.token.blocks.get(i); - for (int j = 0; j < b.checks.size(); j++) { - boolean successful = false; - com.clevercloud.biscuit.datalog.Check c = b.checks.get(j); + for (int i = 0; i < this.token_checks.size(); i++) { + List checks = this.token_checks.get(i); - for (int k = 0; k < c.queries().size(); k++) { - boolean res = world.test_rule(c.queries().get(k)); + for (int j = 0; j < checks.size(); j++) { + boolean successful = false; + com.clevercloud.biscuit.datalog.Check c = checks.get(j); - if (Instant.now().compareTo(timeLimit) >= 0) { - return Left(new Error.Timeout()); - } + for (int k = 0; k < c.queries().size(); k++) { + boolean res = world.test_rule(c.queries().get(k)); - if (res) { - successful = true; - break; - } + if (Instant.now().compareTo(timeLimit) >= 0) { + return Left(new Error.Timeout()); } - if (!successful) { - errors.add(new FailedCheck.FailedBlock(b.index, j, symbols.print_check(b.checks.get(j)))); + if (res) { + successful = true; + break; } } + + if (!successful) { + errors.add(new FailedCheck.FailedBlock(i, j, symbols.print_check(checks.get(j)))); + } } } @@ -418,7 +465,6 @@ public Either verify(RunLimits limits) { return Left(new Error.FailedLogic(new LogicError.NoMatchingPolicy())); } else { - System.out.println(errors); return Left(new Error.FailedLogic(new LogicError.FailedChecks(errors))); } } diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Check.java b/src/main/java/com/clevercloud/biscuit/token/builder/Check.java index 47c811d1..7086c9d0 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Check.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Check.java @@ -27,6 +27,16 @@ public com.clevercloud.biscuit.datalog.Check convert(SymbolTable symbols) { return new com.clevercloud.biscuit.datalog.Check(queries); } + public static Check convert_from(com.clevercloud.biscuit.datalog.Check r, SymbolTable symbols) { + ArrayList queries = new ArrayList<>(); + + for(com.clevercloud.biscuit.datalog.Rule q: r.queries()) { + queries.add(Rule.convert_from(q, symbols)); + } + + return new Check(queries); + } + @Override public String toString() { return "check if "+queries; diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Expression.java b/src/main/java/com/clevercloud/biscuit/token/builder/Expression.java index 11c7b6a0..2e3c784a 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Expression.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Expression.java @@ -1,9 +1,12 @@ package com.clevercloud.biscuit.token.builder; +import com.clevercloud.biscuit.datalog.ID; import com.clevercloud.biscuit.datalog.SymbolTable; import com.clevercloud.biscuit.datalog.expressions.Op; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.List; public abstract class Expression { @@ -14,8 +17,97 @@ public com.clevercloud.biscuit.datalog.expressions.Expression convert(SymbolTabl return new com.clevercloud.biscuit.datalog.expressions.Expression(ops); } - public abstract void toOpcodes(SymbolTable symbols, List ops); + public static Expression convert_from(com.clevercloud.biscuit.datalog.expressions.Expression e, SymbolTable symbols) { + ArrayList ops = new ArrayList<>(); + Deque stack = new ArrayDeque(16); + for(com.clevercloud.biscuit.datalog.expressions.Op op: e.getOps()){ + if(op instanceof com.clevercloud.biscuit.datalog.expressions.Op.Value) { + com.clevercloud.biscuit.datalog.expressions.Op.Value v = (com.clevercloud.biscuit.datalog.expressions.Op.Value) op; + stack.push(new Expression.Value(Term.convert_from(v.getValue(), symbols))); + } else if(op instanceof com.clevercloud.biscuit.datalog.expressions.Op.Unary) { + com.clevercloud.biscuit.datalog.expressions.Op.Unary v = (com.clevercloud.biscuit.datalog.expressions.Op.Unary) op; + Expression e1 = stack.pop(); + + switch (v.getOp()) { + case Length: + stack.push(new Expression.Unary(Op.Length, e1)); + break; + case Negate: + stack.push(new Expression.Unary(Op.Negate, e1)); + break; + case Parens: + stack.push(new Expression.Unary(Op.Parens, e1)); + break; + default: + return null; + } + } else if (op instanceof com.clevercloud.biscuit.datalog.expressions.Op.Binary) { + com.clevercloud.biscuit.datalog.expressions.Op.Binary v = (com.clevercloud.biscuit.datalog.expressions.Op.Binary) op; + Expression e1 = stack.pop(); + Expression e2 = stack.pop(); + + switch (v.getOp()) { + case LessThan: + stack.push(new Expression.Binary(Op.LessThan, e1, e2)); + break; + case GreaterThan: + stack.push(new Expression.Binary(Op.GreaterThan, e1, e2)); + break; + case LessOrEqual: + stack.push(new Expression.Binary(Op.LessOrEqual, e1, e2)); + break; + case GreaterOrEqual: + stack.push(new Expression.Binary(Op.GreaterOrEqual, e1, e2)); + break; + case Equal: + stack.push(new Expression.Binary(Op.Equal, e1, e2)); + break; + case Contains: + stack.push(new Expression.Binary(Op.Contains, e1, e2)); + break; + case Prefix: + stack.push(new Expression.Binary(Op.Prefix, e1, e2)); + break; + case Suffix: + stack.push(new Expression.Binary(Op.Suffix, e1, e2)); + break; + case Regex: + stack.push(new Expression.Binary(Op.Regex, e1, e2)); + break; + case Add: + stack.push(new Expression.Binary(Op.Add, e1, e2)); + break; + case Sub: + stack.push(new Expression.Binary(Op.Sub, e1, e2)); + break; + case Mul: + stack.push(new Expression.Binary(Op.Mul, e1, e2)); + break; + case Div: + stack.push(new Expression.Binary(Op.Div, e1, e2)); + break; + case And: + stack.push(new Expression.Binary(Op.And, e1, e2)); + break; + case Or: + stack.push(new Expression.Binary(Op.Or, e1, e2)); + break; + case Intersection: + stack.push(new Expression.Binary(Op.Intersection, e1, e2)); + break; + case Union: + stack.push(new Expression.Binary(Op.Union, e1, e2)); + break; + default: + return null; + } + } + } + return stack.pop(); + } + + public abstract void toOpcodes(SymbolTable symbols, List ops); public enum Op { Negate, diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Rule.java b/src/main/java/com/clevercloud/biscuit/token/builder/Rule.java index 64870ade..6a1b6e0f 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Rule.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Rule.java @@ -32,6 +32,23 @@ public com.clevercloud.biscuit.datalog.Rule convert(SymbolTable symbols) { return new com.clevercloud.biscuit.datalog.Rule(head, body, expressions); } + public static Rule convert_from(com.clevercloud.biscuit.datalog.Rule r, SymbolTable symbols) { + Predicate head = Predicate.convert_from(r.head(), symbols); + + ArrayList body = new ArrayList<>(); + ArrayList expressions = new ArrayList<>(); + + for(com.clevercloud.biscuit.datalog.Predicate p: r.body()) { + body.add(Predicate.convert_from(p, symbols)); + } + + for(com.clevercloud.biscuit.datalog.expressions.Expression e: r.expressions()) { + expressions.add(Expression.convert_from(e, symbols)); + } + + return new Rule(head, body, expressions); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/com/clevercloud/biscuit/token/builder/Term.java b/src/main/java/com/clevercloud/biscuit/token/builder/Term.java index fa91fd91..fc5a6a8d 100644 --- a/src/main/java/com/clevercloud/biscuit/token/builder/Term.java +++ b/src/main/java/com/clevercloud/biscuit/token/builder/Term.java @@ -9,6 +9,9 @@ public abstract class Term { abstract public ID convert(SymbolTable symbols); + static public Term convert_from(ID id, SymbolTable symbols) { + return id.toTerm(symbols); + } public static class Symbol extends Term { String value; diff --git a/src/test/java/com/clevercloud/biscuit/token/BiscuitTest.java b/src/test/java/com/clevercloud/biscuit/token/BiscuitTest.java index bbc3477e..b6abe21b 100644 --- a/src/test/java/com/clevercloud/biscuit/token/BiscuitTest.java +++ b/src/test/java/com/clevercloud/biscuit/token/BiscuitTest.java @@ -8,6 +8,7 @@ import com.clevercloud.biscuit.error.LogicError; import com.clevercloud.biscuit.token.builder.Block; import io.vavr.control.Either; +import io.vavr.control.Option; import junit.framework.Assert; import junit.framework.Test; import junit.framework.TestCase; @@ -433,4 +434,49 @@ public void testReset() { ))), e); } + + public void testEmptyVerifier() { + byte[] seed = {0, 0, 0, 0}; + SecureRandom rng = new SecureRandom(seed); + + System.out.println("preparing the authority block"); + + KeyPair root = new KeyPair(rng); + + com.clevercloud.biscuit.token.builder.Biscuit builder = Biscuit.builder(rng, 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"); + + System.out.println(builder.build()); + Biscuit b = builder.build().get(); + + System.out.println(b.print()); + + Block block2 = b.create_block(); + block2.resource_prefix("/folder1/"); + block2.check_right("read"); + + KeyPair keypair2 = new KeyPair(rng); + Biscuit b2 = b.attenuate(rng, keypair2, block2.build()).get(); + + Verifier v1 = new Verifier(); + v1.allow(); + + Either res = v1.verify(); + Assert.assertTrue(res.isRight()); + + v1.add_token(b2, Option.some(root.public_key())).get(); + + v1.add_resource("/folder2/file1"); + v1.add_operation("write"); + + res = v1.verify(); + + Error e = res.getLeft(); + Assert.assertTrue(res.isLeft()); + } }