From c4df990aa9c4fb48c4acb47df4003edfd3bfb2e4 Mon Sep 17 00:00:00 2001 From: Colin Date: Tue, 25 Jun 2024 15:01:35 +1200 Subject: [PATCH] Implement 3rd party block creation (#104) (#4) This implements 3rd party block generation and appending to existing tokens. It also fixes existing issues with public key interning which made deserialization of tokens with 3rd party blocks incorrect in some cases. Co-authored-by: Geoffroy Couprie --- .../biscuitsec/biscuit/crypto/KeyPair.java | 29 +++ .../biscuit/datalog/SymbolTable.java | 2 +- .../biscuitsec/biscuit/token/Authorizer.java | 7 +- .../org/biscuitsec/biscuit/token/Biscuit.java | 33 ++- .../org/biscuitsec/biscuit/token/Block.java | 34 ++- .../token/ThirdPartyBlockContents.java | 85 +++++++ .../biscuit/token/ThirdPartyBlockRequest.java | 122 ++++++++++ .../biscuit/token/UnverifiedBiscuit.java | 105 ++++++++- .../biscuit/token/builder/Block.java | 9 +- .../biscuit/token/builder/parser/Parser.java | 2 +- .../token/format/SerializedBiscuit.java | 56 +---- .../biscuit/builder/BuilderTest.java | 2 +- .../biscuit/builder/parser/ParserTest.java | 6 +- .../biscuitsec/biscuit/token/BiscuitTest.java | 8 +- .../biscuitsec/biscuit/token/SamplesTest.java | 17 +- .../biscuit/token/ThirdPartyTest.java | 220 ++++++++++++++++++ 16 files changed, 645 insertions(+), 92 deletions(-) create mode 100644 src/main/java/org/biscuitsec/biscuit/token/ThirdPartyBlockContents.java create mode 100644 src/main/java/org/biscuitsec/biscuit/token/ThirdPartyBlockRequest.java create mode 100644 src/test/java/org/biscuitsec/biscuit/token/ThirdPartyTest.java diff --git a/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java b/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java index a12c3de..37dfd14 100644 --- a/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java +++ b/src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java @@ -2,12 +2,17 @@ import biscuit.format.schema.Schema; +import biscuit.format.schema.Schema.PublicKey.Algorithm; +import net.i2p.crypto.eddsa.EdDSAEngine; import org.biscuitsec.biscuit.token.builder.Utils; import net.i2p.crypto.eddsa.EdDSAPrivateKey; import net.i2p.crypto.eddsa.EdDSAPublicKey; import net.i2p.crypto.eddsa.spec.*; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.security.Signature; /** * Private and public key @@ -39,6 +44,26 @@ public KeyPair(final SecureRandom rng) { this.public_key = pubKey; } + public static KeyPair generate(Algorithm algorithm) { + return generate(algorithm, new SecureRandom()); + } + + public static KeyPair generate(Algorithm algorithm, SecureRandom rng) { + if (algorithm == Algorithm.Ed25519) { + return new KeyPair(rng); + } else { + throw new IllegalArgumentException("Unsupported algorithm"); + } + } + + public static Signature generateSignature(Algorithm algorithm) throws NoSuchAlgorithmException { + if (algorithm == Algorithm.Ed25519) { + return KeyPair.getSignature(); + } else { + throw new NoSuchAlgorithmException("Unsupported algorithm"); + } + } + public byte[] toBytes() { return this.private_key.getSeed(); } @@ -71,6 +96,10 @@ public KeyPair(String hex) { this.public_key = pubKey; } + public static Signature getSignature() throws NoSuchAlgorithmException { + return new EdDSAEngine(MessageDigest.getInstance(ed25519.getHashAlgorithm())); + } + public PublicKey public_key() { return new PublicKey(Schema.PublicKey.Algorithm.Ed25519, this.public_key); } diff --git a/src/main/java/org/biscuitsec/biscuit/datalog/SymbolTable.java b/src/main/java/org/biscuitsec/biscuit/datalog/SymbolTable.java index 3b4a9e5..e7960d9 100644 --- a/src/main/java/org/biscuitsec/biscuit/datalog/SymbolTable.java +++ b/src/main/java/org/biscuitsec/biscuit/datalog/SymbolTable.java @@ -180,7 +180,7 @@ public String print_scope(final Scope scope) { return pk.get().toString(); } } - return "?"; + return "<"+ scope.publicKey+"?>"; } public String print_predicate(final Predicate p) { diff --git a/src/main/java/org/biscuitsec/biscuit/token/Authorizer.java b/src/main/java/org/biscuitsec/biscuit/token/Authorizer.java index e018e90..f593a6d 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/Authorizer.java +++ b/src/main/java/org/biscuitsec/biscuit/token/Authorizer.java @@ -614,8 +614,13 @@ public String print_world() { for (int i = 0; i < this.token.blocks.size(); i++) { Block b = this.token.blocks.get(i); + SymbolTable blockSymbols = token.symbols; + if(b.externalKey.isDefined()) { + blockSymbols = new SymbolTable(b.symbols.symbols, token.symbols.publicKeys()); + } + for (int j = 0; j < b.checks.size(); j++) { - checks.add("Block[" + i + "][" + j + "]: " + this.symbols.print_check(b.checks.get(j))); + checks.add("Block[" + (i+1) + "][" + j + "]: " + blockSymbols.print_check(b.checks.get(j))); } } } diff --git a/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java b/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java index 7f71296..662612b 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java +++ b/src/main/java/org/biscuitsec/biscuit/token/Biscuit.java @@ -1,5 +1,6 @@ package org.biscuitsec.biscuit.token; +import biscuit.format.schema.Schema; import org.biscuitsec.biscuit.crypto.KeyDelegate; import org.biscuitsec.biscuit.crypto.KeyPair; import org.biscuitsec.biscuit.crypto.PublicKey; @@ -10,10 +11,7 @@ import io.vavr.control.Either; import io.vavr.control.Option; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.SignatureException; +import java.security.*; import java.util.*; /** @@ -91,7 +89,11 @@ public static Biscuit make(final SecureRandom rng, final KeyPair root, final Int static private Biscuit make(final SecureRandom rng, final KeyPair root, final Option root_key_id, final Block authority) throws Error.FormatError { ArrayList blocks = new ArrayList<>(); - KeyPair next = new KeyPair(rng); + KeyPair next = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng); + + for(PublicKey pk: authority.publicKeys) { + authority.symbols.insert(pk); + } Either container = SerializedBiscuit.make(root, root_key_id, authority, next); if (container.isLeft()) { @@ -304,7 +306,7 @@ public String serialize_b64url() throws Error.FormatError.SerializationError { */ public Biscuit attenuate(org.biscuitsec.biscuit.token.builder.Block block) throws Error { SecureRandom rng = new SecureRandom(); - KeyPair keypair = new KeyPair(rng); + KeyPair keypair = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng); SymbolTable builderSymbols = new SymbolTable(this.symbols); return attenuate(rng, keypair, block.build(builderSymbols)); } @@ -329,7 +331,7 @@ public Biscuit attenuate(final SecureRandom rng, final KeyPair keypair, Block bl throw new Error.SymbolTableOverlap(); } - Either containerRes = copiedBiscuit.serializedBiscuit.append(keypair, block); + Either containerRes = copiedBiscuit.serializedBiscuit.append(keypair, block, Option.none()); if (containerRes.isLeft()) { throw containerRes.getLeft(); } @@ -340,6 +342,10 @@ public Biscuit attenuate(final SecureRandom rng, final KeyPair keypair, Block bl symbols.add(s); } + for(PublicKey pk: block.publicKeys) { + symbols.insert(pk); + } + ArrayList blocks = new ArrayList<>(); for (Block b : copiedBiscuit.blocks) { blocks.add(b); @@ -354,6 +360,17 @@ public Biscuit attenuate(final SecureRandom rng, final KeyPair keypair, Block bl return new Biscuit(copiedBiscuit.authority, blocks, symbols, container, publicKeyToBlockId, revocation_ids); } + /** + * Generates a third party block request from a token + */ + public Biscuit appendThirdPartyBlock(PublicKey externalKey, ThirdPartyBlockContents blockResponse) + throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error { + UnverifiedBiscuit b = super.appendThirdPartyBlock(externalKey, blockResponse); + + // no need to verify again, we are already working from a verified token + return Biscuit.from_serialized_biscuit(b.serializedBiscuit, b.symbols); + } + /** * Prints a token's content */ @@ -361,6 +378,8 @@ public String print() { StringBuilder s = new StringBuilder(); s.append("Biscuit {\n\tsymbols: "); s.append(this.symbols.getAllSymbols()); + s.append("\n\tpublic keys: "); + s.append(this.symbols.publicKeys()); s.append("\n\tauthority: "); s.append(this.authority.print(this.symbols)); s.append("\n\tblocks: [\n"); diff --git a/src/main/java/org/biscuitsec/biscuit/token/Block.java b/src/main/java/org/biscuitsec/biscuit/token/Block.java index 9e8fed8..975a006 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/Block.java +++ b/src/main/java/org/biscuitsec/biscuit/token/Block.java @@ -31,8 +31,8 @@ public class Block { final List checks; final List scopes; final List publicKeys; - final Option externalKey; - final long version; + Option externalKey; + long version; /** * creates a new block @@ -48,7 +48,6 @@ public Block(SymbolTable base_symbols) { this.scopes = new ArrayList<>(); this.publicKeys = new ArrayList<>(); this.externalKey = Option.none(); - this.version = SerializedBiscuit.MAX_SCHEMA_VERSION; } /** @@ -66,7 +65,6 @@ public Block(SymbolTable base_symbols, String context, List facts, List publicKeys() { return publicKeys; } + public void setExternalKey(PublicKey externalKey) { + this.externalKey = Option.some(externalKey); + } + /** * pretty printing for a block * @@ -88,9 +90,22 @@ public List publicKeys() { public String print(SymbolTable symbol_table) { StringBuilder s = new StringBuilder(); + SymbolTable local_symbols; + if(this.externalKey.isDefined()) { + local_symbols = new SymbolTable(this.symbols); + for(PublicKey pk: symbol_table.publicKeys()) { + local_symbols.insert(pk); + } + } else { + local_symbols = symbol_table; + } s.append("Block"); s.append(" {\n\t\tsymbols: "); s.append(this.symbols.symbols); + s.append("\n\t\tpublic keys: "); + s.append(this.publicKeys); + s.append("\n\t\tsymbol public keys: "); + s.append(this.symbols.publicKeys()); s.append("\n\t\tcontext: "); s.append(this.context); if(this.externalKey.isDefined()) { @@ -105,17 +120,17 @@ public String print(SymbolTable symbol_table) { s.append("\n\t\t]\n\t\tfacts: ["); for (Fact f : this.facts) { s.append("\n\t\t\t"); - s.append(symbol_table.print_fact(f)); + s.append(local_symbols.print_fact(f)); } s.append("\n\t\t]\n\t\trules: ["); for (Rule r : this.rules) { s.append("\n\t\t\t"); - s.append(symbol_table.print_rule(r)); + s.append(local_symbols.print_rule(r)); } s.append("\n\t\t]\n\t\tchecks: ["); for (Check c : this.checks) { s.append("\n\t\t\t"); - s.append(symbol_table.print_check(c)); + s.append(local_symbols.print_check(c)); } s.append("\n\t\t]\n\t}"); @@ -184,7 +199,7 @@ int getSchemaVersion() { } } - if(containsScopes || containsCheckAll || containsV4) { + if(containsScopes || containsCheckAll || containsV4 || this.externalKey.isDefined()) { return SerializedBiscuit.MAX_SCHEMA_VERSION; } else { return SerializedBiscuit.MIN_SCHEMA_VERSION; @@ -321,7 +336,6 @@ public boolean equals(Object o) { Block block = (Block) o; - if (version != block.version) return false; if (!Objects.equals(symbols, block.symbols)) return false; if (!Objects.equals(context, block.context)) return false; if (!Objects.equals(facts, block.facts)) return false; @@ -342,7 +356,6 @@ public int hashCode() { result = 31 * result + (scopes != null ? scopes.hashCode() : 0); result = 31 * result + (publicKeys != null ? publicKeys.hashCode() : 0); result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0); - result = 31 * result + (int) (version ^ (version >>> 32)); return result; } @@ -357,7 +370,6 @@ public String toString() { ", scopes=" + scopes + ", publicKeys=" + publicKeys + ", externalKey=" + externalKey + - ", version=" + version + '}'; } } diff --git a/src/main/java/org/biscuitsec/biscuit/token/ThirdPartyBlockContents.java b/src/main/java/org/biscuitsec/biscuit/token/ThirdPartyBlockContents.java new file mode 100644 index 0000000..812589c --- /dev/null +++ b/src/main/java/org/biscuitsec/biscuit/token/ThirdPartyBlockContents.java @@ -0,0 +1,85 @@ +package org.biscuitsec.biscuit.token; + +import biscuit.format.schema.Schema; +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import org.biscuitsec.biscuit.crypto.PublicKey; +import org.biscuitsec.biscuit.error.Error; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class ThirdPartyBlockContents { + byte[] payload; + byte[] signature; + PublicKey publicKey; + + ThirdPartyBlockContents(byte[] payload, byte[] signature, PublicKey publicKey) { + this.payload = payload; + this.signature = signature; + this.publicKey = publicKey; + } + + public Schema.ThirdPartyBlockContents serialize() throws Error.FormatError.SerializationError { + Schema.ThirdPartyBlockContents.Builder b = Schema.ThirdPartyBlockContents.newBuilder(); + b.setPayload(ByteString.copyFrom(this.payload)); + b.setExternalSignature(b.getExternalSignatureBuilder() + .setSignature(ByteString.copyFrom(this.signature)) + .setPublicKey(this.publicKey.serialize()) + .build()); + + return b.build(); + } + + static public ThirdPartyBlockContents deserialize(Schema.ThirdPartyBlockContents b) throws Error.FormatError.DeserializationError { + byte[] payload = b.getPayload().toByteArray(); + byte[] signature = b.getExternalSignature().getSignature().toByteArray(); + PublicKey publicKey = PublicKey.deserialize(b.getExternalSignature().getPublicKey()); + + return new ThirdPartyBlockContents(payload, signature, publicKey); + } + + static public ThirdPartyBlockContents fromBytes(byte[] slice) throws InvalidProtocolBufferException, Error.FormatError.DeserializationError { + return ThirdPartyBlockContents.deserialize(Schema.ThirdPartyBlockContents.parseFrom(slice)); + } + + public byte[] toBytes() throws IOException, Error.FormatError.SerializationError { + Schema.ThirdPartyBlockContents b = this.serialize(); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + b.writeTo(stream); + return stream.toByteArray(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ThirdPartyBlockContents that = (ThirdPartyBlockContents) o; + + if (!Arrays.equals(payload, that.payload)) return false; + if (!Arrays.equals(signature, that.signature)) return false; + return Objects.equals(publicKey, that.publicKey); + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(payload); + result = 31 * result + Arrays.hashCode(signature); + result = 31 * result + (publicKey != null ? publicKey.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "ThirdPartyBlockContents{" + + "payload=" + Arrays.toString(payload) + + ", signature=" + Arrays.toString(signature) + + ", publicKey=" + publicKey + + '}'; + } +} diff --git a/src/main/java/org/biscuitsec/biscuit/token/ThirdPartyBlockRequest.java b/src/main/java/org/biscuitsec/biscuit/token/ThirdPartyBlockRequest.java new file mode 100644 index 0000000..50b1298 --- /dev/null +++ b/src/main/java/org/biscuitsec/biscuit/token/ThirdPartyBlockRequest.java @@ -0,0 +1,122 @@ +package org.biscuitsec.biscuit.token; + +import biscuit.format.schema.Schema; +import com.google.protobuf.InvalidProtocolBufferException; +import io.vavr.control.Either; +import io.vavr.control.Option; +import net.i2p.crypto.eddsa.EdDSAEngine; +import org.biscuitsec.biscuit.crypto.KeyPair; +import org.biscuitsec.biscuit.crypto.PublicKey; +import org.biscuitsec.biscuit.datalog.SymbolTable; +import org.biscuitsec.biscuit.error.Error; +import org.biscuitsec.biscuit.token.builder.Block; +import org.biscuitsec.biscuit.token.format.SerializedBiscuit; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.*; +import java.util.ArrayList; +import java.util.List; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; + +public class ThirdPartyBlockRequest { + PublicKey previousKey; + List publicKeys; + + ThirdPartyBlockRequest(PublicKey previousKey, List publicKeys) { + this.previousKey = previousKey; + this.publicKeys = publicKeys; + } + + public Either createBlock(KeyPair keyPair, Block blockBuilder) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + SymbolTable symbols = new SymbolTable(); + for(PublicKey pk: this.publicKeys) { + symbols.insert(pk); + } + + org.biscuitsec.biscuit.token.Block block = blockBuilder.build(symbols, Option.some(keyPair.public_key())); + + Either res = block.to_bytes(); + if(res.isLeft()) { + return Either.left(res.getLeft()); + } + + byte[] serializedBlock = res.get(); + + Signature sgr = KeyPair.generateSignature(keyPair.public_key().algorithm); + + sgr.initSign(keyPair.private_key); + sgr.update(serializedBlock); + + ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + algo_buf.putInt(Integer.valueOf(Schema.PublicKey.Algorithm.Ed25519.getNumber())); + algo_buf.flip(); + sgr.update(algo_buf); + sgr.update(previousKey.toBytes()); + byte[] signature = sgr.sign(); + + PublicKey publicKey = keyPair.public_key(); + + return Either.right(new ThirdPartyBlockContents(serializedBlock, signature, publicKey)); + } + + public Schema.ThirdPartyBlockRequest serialize() throws Error.FormatError.SerializationError { + Schema.ThirdPartyBlockRequest.Builder b = Schema.ThirdPartyBlockRequest.newBuilder(); + b.setPreviousKey(this.previousKey.serialize()); + + for(PublicKey pk: this.publicKeys) { + b.addPublicKeys(pk.serialize()); + } + + return b.build(); + } + + static public ThirdPartyBlockRequest deserialize(Schema.ThirdPartyBlockRequest b) throws Error.FormatError.DeserializationError { + PublicKey previousKey = PublicKey.deserialize(b.getPreviousKey()); + List publicKeys = new ArrayList<>(); + for(Schema.PublicKey pk: b.getPublicKeysList()) { + publicKeys.add(PublicKey.deserialize(pk)); + } + return new ThirdPartyBlockRequest(previousKey, publicKeys); + } + + static public ThirdPartyBlockRequest fromBytes(byte[] slice) throws InvalidProtocolBufferException, Error.FormatError.DeserializationError { + return ThirdPartyBlockRequest.deserialize(Schema.ThirdPartyBlockRequest.parseFrom(slice)); + } + + public byte[] toBytes() throws IOException, Error.FormatError.SerializationError { + Schema.ThirdPartyBlockRequest b = this.serialize(); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + b.writeTo(stream); + return stream.toByteArray(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ThirdPartyBlockRequest that = (ThirdPartyBlockRequest) o; + + if (!Objects.equals(previousKey, that.previousKey)) return false; + return Objects.equals(publicKeys, that.publicKeys); + } + + @Override + public int hashCode() { + int result = previousKey != null ? previousKey.hashCode() : 0; + result = 31 * result + (publicKeys != null ? publicKeys.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "ThirdPartyBlockRequest{" + + "previousKey=" + previousKey + + ", publicKeys=" + publicKeys + + '}'; + } +} + diff --git a/src/main/java/org/biscuitsec/biscuit/token/UnverifiedBiscuit.java b/src/main/java/org/biscuitsec/biscuit/token/UnverifiedBiscuit.java index 06592a9..07ac2e9 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/UnverifiedBiscuit.java +++ b/src/main/java/org/biscuitsec/biscuit/token/UnverifiedBiscuit.java @@ -1,9 +1,12 @@ package org.biscuitsec.biscuit.token; +import biscuit.format.schema.Schema; +import net.i2p.crypto.eddsa.EdDSAEngine; import org.biscuitsec.biscuit.crypto.KeyDelegate; import org.biscuitsec.biscuit.crypto.KeyPair; import org.biscuitsec.biscuit.crypto.PublicKey; import org.biscuitsec.biscuit.error.Error; +import org.biscuitsec.biscuit.token.format.ExternalSignature; import org.biscuitsec.biscuit.token.format.SerializedBiscuit; import io.vavr.Tuple3; import io.vavr.control.Either; @@ -11,10 +14,9 @@ import org.biscuitsec.biscuit.datalog.Check; import org.biscuitsec.biscuit.datalog.SymbolTable; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.SignatureException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.*; import java.util.*; import java.util.stream.Collectors; @@ -129,7 +131,7 @@ public String serialize_b64url() throws Error.FormatError.SerializationError { * @return */ public org.biscuitsec.biscuit.token.builder.Block create_block() { - return new org.biscuitsec.biscuit.token.builder.Block(1 + this.blocks.size()); + return new org.biscuitsec.biscuit.token.builder.Block(); } /** @@ -140,7 +142,7 @@ public org.biscuitsec.biscuit.token.builder.Block create_block() { */ public UnverifiedBiscuit attenuate(org.biscuitsec.biscuit.token.builder.Block block) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error { SecureRandom rng = new SecureRandom(); - KeyPair keypair = new KeyPair(rng); + KeyPair keypair = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng); SymbolTable builderSymbols = new SymbolTable(this.symbols); return attenuate(rng, keypair, block.build(builderSymbols)); } @@ -165,7 +167,7 @@ public UnverifiedBiscuit attenuate(final SecureRandom rng, final KeyPair keypair throw new Error.SymbolTableOverlap(); } - Either containerRes = copiedBiscuit.serializedBiscuit.append(keypair, block); + Either containerRes = copiedBiscuit.serializedBiscuit.append(keypair, block, Option.none()); if (containerRes.isLeft()) { throw containerRes.getLeft(); } @@ -232,6 +234,95 @@ public Option root_key_id() { return this.root_key_id; } + /** + * Generates a third party block request from a token + */ + public ThirdPartyBlockRequest thirdPartyRequest() { + PublicKey previousKey; + if(this.serializedBiscuit.blocks.isEmpty()) { + previousKey = this.serializedBiscuit.authority.key; + } else { + previousKey = this.serializedBiscuit.blocks.get(this.serializedBiscuit.blocks.size() - 1).key; + } + + List publicKeys = new ArrayList<>(this.symbols.publicKeys()); + return new ThirdPartyBlockRequest(previousKey, publicKeys); + } + + + /** + * Generates a third party block request from a token + */ + public UnverifiedBiscuit appendThirdPartyBlock(PublicKey externalKey, ThirdPartyBlockContents blockResponse) + throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error { + KeyPair nextKeyPair = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519); + + Signature sgr = KeyPair.generateSignature(externalKey.algorithm); + sgr.initVerify(externalKey.key); + + sgr.update(blockResponse.payload); + ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); + algo_buf.putInt(Integer.valueOf(Schema.PublicKey.Algorithm.Ed25519.getNumber())); + algo_buf.flip(); + sgr.update(algo_buf); + + PublicKey previousKey; + if(this.serializedBiscuit.blocks.isEmpty()) { + previousKey = this.serializedBiscuit.authority.key; + } else { + previousKey = this.serializedBiscuit.blocks.get(this.serializedBiscuit.blocks.size() - 1).key; + } + sgr.update(previousKey.toBytes()); + if (!sgr.verify(blockResponse.signature)) { + throw new Error.FormatError.Signature.InvalidSignature("signature error: Verification equation was not satisfied"); + } + + Either res = Block.from_bytes(blockResponse.payload, Option.some(externalKey)); + if(res.isLeft()) { + throw res.getLeft(); + } + + Block block = res.get(); + + ExternalSignature externalSignature = new ExternalSignature(externalKey, blockResponse.signature); + + UnverifiedBiscuit copiedBiscuit = this.copy(); + + Either containerRes = copiedBiscuit.serializedBiscuit.append(nextKeyPair, block, Option.some(externalSignature)); + if (containerRes.isLeft()) { + throw containerRes.getLeft(); + } + + SerializedBiscuit container = containerRes.get(); + + SymbolTable symbols = new SymbolTable(copiedBiscuit.symbols); + + ArrayList blocks = new ArrayList<>(); + for (Block b : copiedBiscuit.blocks) { + blocks.add(b); + } + blocks.add(block); + + for(PublicKey pk: block.publicKeys) { + symbols.insert(pk); + } + + long pkIndex = symbols.insert(externalKey); + + HashMap> publicKeyToBlockId = new HashMap<>(); + publicKeyToBlockId.putAll(this.publicKeyToBlockId); + if(publicKeyToBlockId.containsKey(pkIndex)) { + publicKeyToBlockId.get(pkIndex).add((long)this.blocks.size()+1); + } else { + List list = new ArrayList<>(); + list.add((long)this.blocks.size()+1); + publicKeyToBlockId.put(pkIndex, list); + } + + List revocation_ids = container.revocation_identifiers(); + + return new UnverifiedBiscuit(copiedBiscuit.authority, blocks, symbols, container, publicKeyToBlockId, revocation_ids); + } /** * Prints a token's content diff --git a/src/main/java/org/biscuitsec/biscuit/token/builder/Block.java b/src/main/java/org/biscuitsec/biscuit/token/builder/Block.java index dde4a24..972a694 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/builder/Block.java +++ b/src/main/java/org/biscuitsec/biscuit/token/builder/Block.java @@ -18,15 +18,13 @@ import java.util.*; public class Block { - long index; String context; List facts; List rules; List checks; List scopes; - public Block(long index) { - this.index = index; + public Block() { this.context = ""; this.facts = new ArrayList<>(); this.rules = new ArrayList<>(); @@ -138,7 +136,6 @@ public org.biscuitsec.biscuit.token.Block build(SymbolTable symbols, final Optio block_symbols.add(symbols.symbols.get(i)); } - List publicKeys = new ArrayList<>(); for (int i = publicKeyStart; i < symbols.currentPublicKeyOffset(); i++) { publicKeys.add(symbols.publicKeys().get(i)); @@ -155,7 +152,6 @@ public boolean equals(Object o) { Block block = (Block) o; - if (index != block.index) return false; if (!Objects.equals(context, block.context)) return false; if (!Objects.equals(facts, block.facts)) return false; if (!Objects.equals(rules, block.rules)) return false; @@ -165,8 +161,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = (int) (index ^ (index >>> 32)); - result = 31 * result + (context != null ? context.hashCode() : 0); + int result = context != null ? context.hashCode() : 0; result = 31 * result + (facts != null ? facts.hashCode() : 0); result = 31 * result + (rules != null ? rules.hashCode() : 0); result = 31 * result + (checks != null ? checks.hashCode() : 0); diff --git a/src/main/java/org/biscuitsec/biscuit/token/builder/parser/Parser.java b/src/main/java/org/biscuitsec/biscuit/token/builder/parser/Parser.java index 0fe529f..1e85eec 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/builder/parser/Parser.java +++ b/src/main/java/org/biscuitsec/biscuit/token/builder/parser/Parser.java @@ -28,7 +28,7 @@ public class Parser { * @return Either>, Block> */ public static Either>, Block> datalog(long index, String s) { - Block blockBuilder = new Block(index); + Block blockBuilder = new Block(); // empty block code if (s.isEmpty()) { diff --git a/src/main/java/org/biscuitsec/biscuit/token/format/SerializedBiscuit.java b/src/main/java/org/biscuitsec/biscuit/token/format/SerializedBiscuit.java index a68419e..9f9609c 100644 --- a/src/main/java/org/biscuitsec/biscuit/token/format/SerializedBiscuit.java +++ b/src/main/java/org/biscuitsec/biscuit/token/format/SerializedBiscuit.java @@ -242,7 +242,7 @@ static public Either make(final org.biscui algo_buf.putInt(Integer.valueOf(next_key.algorithm.getNumber())); algo_buf.flip(); - Signature sgr = new EdDSAEngine(MessageDigest.getInstance(org.biscuitsec.biscuit.crypto.KeyPair.ed25519.getHashAlgorithm())); + Signature sgr = KeyPair.generateSignature(root.public_key().algorithm); sgr.initSign(root.private_key); sgr.update(block); sgr.update(algo_buf); @@ -259,7 +259,7 @@ static public Either make(final org.biscui } public Either append(final org.biscuitsec.biscuit.crypto.KeyPair next, - final Block newBlock) { + final Block newBlock, Option externalSignature) { if (this.proof.secretKey.isEmpty()) { return Left(new Error.FormatError.SerializationError("the token is sealed")); } @@ -278,51 +278,14 @@ public Either append(final org.biscuitsec. Signature sgr = new EdDSAEngine(MessageDigest.getInstance(org.biscuitsec.biscuit.crypto.KeyPair.ed25519.getHashAlgorithm())); sgr.initSign(this.proof.secretKey.get().private_key); sgr.update(block); - sgr.update(algo_buf); - sgr.update(next_key.toBytes()); - byte[] signature = sgr.sign(); - - SignedBlock signedBlock = new SignedBlock(block, next_key, signature, Option.none()); - - ArrayList blocks = new ArrayList<>(); - for (SignedBlock bl : this.blocks) { - blocks.add(bl); + if(externalSignature.isDefined()) { + sgr.update(externalSignature.get().signature); } - blocks.add(signedBlock); - - Proof proof = new Proof(next); - - return Right(new SerializedBiscuit(this.authority, blocks, proof, root_key_id)); - } catch (IOException | NoSuchAlgorithmException | SignatureException | InvalidKeyException e) { - return Left(new Error.FormatError.SerializationError(e.toString())); - } - } - - public Either appendThirdParty(final org.biscuitsec.biscuit.crypto.KeyPair next, - final Block newBlock) { - /*if (this.proof.secretKey.isEmpty()) { - return Left(new Error.FormatError.SerializationError("the token is sealed")); - } - - Schema.Block b = newBlock.serialize(); - try { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - b.writeTo(stream); - - byte[] block = stream.toByteArray(); - PublicKey next_key = next.public_key(); - ByteBuffer algo_buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); - algo_buf.putInt(Integer.valueOf(next_key.algorithm.getNumber())); - algo_buf.flip(); - - Signature sgr = new EdDSAEngine(MessageDigest.getInstance(ed25519.getHashAlgorithm())); - sgr.initSign(this.proof.secretKey.get().private_key); - sgr.update(block); sgr.update(algo_buf); sgr.update(next_key.toBytes()); byte[] signature = sgr.sign(); - SignedBlock signedBlock = new SignedBlock(block, next_key, signature); + SignedBlock signedBlock = new SignedBlock(block, next_key, signature, externalSignature); ArrayList blocks = new ArrayList<>(); for (SignedBlock bl : this.blocks) { @@ -335,8 +298,7 @@ public Either appendThirdParty(final org.b return Right(new SerializedBiscuit(this.authority, blocks, proof, root_key_id)); } catch (IOException | NoSuchAlgorithmException | SignatureException | InvalidKeyException e) { return Left(new Error.FormatError.SerializationError(e.toString())); - }*/ - throw new RuntimeException("todo"); + } } public Either verify(org.biscuitsec.biscuit.crypto.PublicKey root) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { @@ -424,7 +386,7 @@ static Either verifyBlockSignatu algo_buf.putInt(Integer.valueOf(next_key.algorithm.getNumber())); algo_buf.flip(); - Signature sgr = new EdDSAEngine(MessageDigest.getInstance(org.biscuitsec.biscuit.crypto.KeyPair.ed25519.getHashAlgorithm())); + Signature sgr = KeyPair.generateSignature(publicKey.algorithm); sgr.initVerify(publicKey.key); sgr.update(block); @@ -442,11 +404,13 @@ static Either verifyBlockSignatu algo_buf2.putInt(Integer.valueOf(publicKey.algorithm.getNumber())); algo_buf2.flip(); - Signature sgr2 = new EdDSAEngine(MessageDigest.getInstance(org.biscuitsec.biscuit.crypto.KeyPair.ed25519.getHashAlgorithm())); + Signature sgr2 = new EdDSAEngine(MessageDigest.getInstance(KeyPair.ed25519.getHashAlgorithm())); sgr2.initVerify(signedBlock.externalSignature.get().key.key); sgr2.update(block); sgr2.update(algo_buf2); sgr2.update(publicKey.toBytes()); + Either authRes = Block.from_bytes(block, Option.none()); + if (!sgr2.verify(signedBlock.externalSignature.get().signature)) { return Left(new Error.FormatError.Signature.InvalidSignature("external signature error: Verification equation was not satisfied")); } diff --git a/src/test/java/org/biscuitsec/biscuit/builder/BuilderTest.java b/src/test/java/org/biscuitsec/biscuit/builder/BuilderTest.java index b981c58..dbb8ff0 100644 --- a/src/test/java/org/biscuitsec/biscuit/builder/BuilderTest.java +++ b/src/test/java/org/biscuitsec/biscuit/builder/BuilderTest.java @@ -28,7 +28,7 @@ public void testBuild() throws Error.Language, Error.SymbolTableOverlap, Error.F KeyPair root = new KeyPair(rng); SymbolTable symbols = Biscuit.default_symbol_table(); - Block authority_builder = new Block(0); + Block authority_builder = new Block(); authority_builder.add_fact(Utils.fact("revocation_id", Arrays.asList(Utils.date(Date.from(Instant.now()))))); authority_builder.add_fact(Utils.fact("right", Arrays.asList(Utils.s("admin")))); authority_builder.add_rule(Utils.constrained_rule("right", diff --git a/src/test/java/org/biscuitsec/biscuit/builder/parser/ParserTest.java b/src/test/java/org/biscuitsec/biscuit/builder/parser/ParserTest.java index fe7504a..38ecbe3 100644 --- a/src/test/java/org/biscuitsec/biscuit/builder/parser/ParserTest.java +++ b/src/test/java/org/biscuitsec/biscuit/builder/parser/ParserTest.java @@ -434,7 +434,7 @@ void testDatalogSucceeds() throws org.biscuitsec.biscuit.error.Error.Parser { Either>, Block> output = Parser.datalog(1, toParse); assertTrue(output.isRight()); - Block validBlock = new Block(1); + Block validBlock = new Block(); validBlock.add_fact(l1); validBlock.add_fact(l2); validBlock.add_rule(l3); @@ -455,7 +455,7 @@ void testDatalogSucceedsArrays() throws org.biscuitsec.biscuit.error.Error.Parse Either>, Block> output = Parser.datalog(1, toParse); assertTrue(output.isRight()); - Block validBlock = new Block(1); + Block validBlock = new Block(); validBlock.add_check(l1); output.forEach(block -> @@ -473,7 +473,7 @@ void testDatalogSucceedsArraysContains() throws org.biscuitsec.biscuit.error.Err Either>, Block> output = Parser.datalog(1, toParse); assertTrue(output.isRight()); - Block validBlock = new Block(1); + Block validBlock = new Block(); validBlock.add_check(l1); output.forEach(block -> diff --git a/src/test/java/org/biscuitsec/biscuit/token/BiscuitTest.java b/src/test/java/org/biscuitsec/biscuit/token/BiscuitTest.java index 17ac83d..00a2471 100644 --- a/src/test/java/org/biscuitsec/biscuit/token/BiscuitTest.java +++ b/src/test/java/org/biscuitsec/biscuit/token/BiscuitTest.java @@ -38,7 +38,7 @@ public void testBasic() throws NoSuchAlgorithmException, SignatureException, Inv KeyPair root = new KeyPair(rng); - Block authority_builder = new Block(0); + Block authority_builder = new Block(); authority_builder.add_fact(fact("right", Arrays.asList(s("file1"), s("read")))); authority_builder.add_fact(fact("right", Arrays.asList(s("file2"), s("read")))); @@ -226,7 +226,7 @@ public void testMultipleAttenuation() throws NoSuchAlgorithmException, Signature SecureRandom rng = new SecureRandom(); KeyPair root = new KeyPair(rng); - Block authority_builder = new Block(0); + Block authority_builder = new Block(); Date date = Date.from(Instant.now()); authority_builder.add_fact(fact("revocation_id", Arrays.asList(date(date)))); @@ -369,7 +369,7 @@ public void testBasicWithNamespaces() throws NoSuchAlgorithmException, Signature KeyPair root = new KeyPair(rng); - Block authority_builder = new Block(0); + Block authority_builder = new Block(); authority_builder.add_fact(fact("namespace:right", Arrays.asList(s("file1"), s("read")))); authority_builder.add_fact(fact("namespace:right", Arrays.asList(s("file1"), s("write")))); @@ -616,7 +616,7 @@ public void testRootKeyId() throws NoSuchAlgorithmException, SignatureException, KeyPair root = new KeyPair(rng); - Block authority_builder = new Block(0); + Block authority_builder = new Block(); authority_builder.add_fact(fact("right", Arrays.asList(s("file1"), s("read")))); authority_builder.add_fact(fact("right", Arrays.asList(s("file2"), s("read")))); diff --git a/src/test/java/org/biscuitsec/biscuit/token/SamplesTest.java b/src/test/java/org/biscuitsec/biscuit/token/SamplesTest.java index 30547d7..a7dffc7 100644 --- a/src/test/java/org/biscuitsec/biscuit/token/SamplesTest.java +++ b/src/test/java/org/biscuitsec/biscuit/token/SamplesTest.java @@ -18,6 +18,7 @@ import org.biscuitsec.biscuit.token.builder.Expression; import org.biscuitsec.biscuit.token.builder.parser.ExpressionParser; import org.biscuitsec.biscuit.token.builder.parser.Parser; +import org.biscuitsec.biscuit.token.format.SerializedBiscuit; import org.biscuitsec.biscuit.token.format.SignedBlock; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; @@ -78,14 +79,24 @@ void compareBlock(SymbolTable baseSymbols, long sampleBlockIndex, Block sampleBl sampleSymbols = new SymbolTable(baseSymbols); } else { sampleSymbols = new SymbolTable(); + + for(PublicKey pk: baseSymbols.publicKeys()) { + sampleSymbols.insert(pk); + } } org.biscuitsec.biscuit.token.Block generatedSampleBlock = outputSample.get().build(sampleSymbols); - System.out.println(generatedSampleBlock.symbols.symbols); - System.out.println(block.symbols.symbols); - System.out.println(sampleSymbols.symbols); + if(!block.externalKey.isDefined()) { generatedSampleBlock.symbols.symbols.forEach(baseSymbols::add); + } else { + generatedSampleBlock.setExternalKey(block.externalKey.get()); + generatedSampleBlock.version = SerializedBiscuit.MAX_SCHEMA_VERSION; + baseSymbols.insert(block.externalKey.get()); + } + + for(PublicKey pk: generatedSampleBlock.publicKeys) { + baseSymbols.insert(pk); } System.out.println(baseSymbols.symbols); diff --git a/src/test/java/org/biscuitsec/biscuit/token/ThirdPartyTest.java b/src/test/java/org/biscuitsec/biscuit/token/ThirdPartyTest.java new file mode 100644 index 0000000..aca09ad --- /dev/null +++ b/src/test/java/org/biscuitsec/biscuit/token/ThirdPartyTest.java @@ -0,0 +1,220 @@ +package org.biscuitsec.biscuit.token; + +import org.biscuitsec.biscuit.crypto.KeyPair; +import org.biscuitsec.biscuit.datalog.RunLimits; +import org.biscuitsec.biscuit.error.Error; +import org.biscuitsec.biscuit.error.FailedCheck; +import org.biscuitsec.biscuit.error.LogicError; +import org.biscuitsec.biscuit.token.builder.Block; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.SignatureException; +import java.time.Duration; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class ThirdPartyTest { + @Test + public void testRoundTrip() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, CloneNotSupportedException, Error, IOException { + byte[] seed = {0, 0, 0, 0}; + SecureRandom rng = new SecureRandom(seed); + + System.out.println("preparing the authority block"); + + KeyPair root = new KeyPair(rng); + KeyPair external = new KeyPair(rng); + System.out.println("external: ed25519/"+external.public_key().toHex()); + + Block authority_builder = new Block(); + authority_builder.add_fact("right(\"read\")"); + authority_builder.add_check("check if group(\"admin\") trusting ed25519/"+external.public_key().toHex()); + + Biscuit b1 = Biscuit.make(rng, root, authority_builder.build()); + ThirdPartyBlockRequest request = b1.thirdPartyRequest(); + byte[] reqb = request.toBytes(); + ThirdPartyBlockRequest reqdeser = ThirdPartyBlockRequest.fromBytes(reqb); + assertEquals(request, reqdeser); + + Block builder = new Block(); + builder.add_fact("group(\"admin\")"); + builder.add_check("check if resource(\"file1\")"); + + ThirdPartyBlockContents blockResponse = request.createBlock(external, builder).get(); + byte[] resb = blockResponse.toBytes(); + ThirdPartyBlockContents resdeser = ThirdPartyBlockContents.fromBytes(resb); + assertEquals(blockResponse, resdeser); + + Biscuit b2 = b1.appendThirdPartyBlock(external.public_key(), blockResponse); + + byte[] data = b2.serialize(); + Biscuit deser = Biscuit.from_bytes(data, root.public_key()); + assertEquals(b2.print(), deser.print()); + + System.out.println("will check the token for resource=file1"); + Authorizer authorizer = deser.authorizer(); + authorizer.add_fact("resource(\"file1\")"); + authorizer.add_policy("allow if true"); + authorizer.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); + + System.out.println("will check the token for resource=file2"); + Authorizer authorizer2 = deser.authorizer(); + authorizer2.add_fact("resource(\"file2\")"); + authorizer2.add_policy("allow if true"); + + try { + authorizer2.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); + } catch (Error e) { + System.out.println(e); + assertEquals( + new Error.FailedLogic(new LogicError.Unauthorized(new LogicError.MatchedPolicy.Allow(0), Arrays.asList( + new FailedCheck.FailedBlock(1, 0, "check if resource(\"file1\")") + ))), + e); + } + } + + @Test + public void testPublicKeyInterning() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, CloneNotSupportedException, Error { + byte[] seed = {0, 0, 0, 0}; + SecureRandom rng = new SecureRandom(seed); + + System.out.println("preparing the authority block"); + + KeyPair root = new KeyPair(rng); + KeyPair external1 = new KeyPair(rng); + KeyPair external2 = new KeyPair(rng); + KeyPair external3 = new KeyPair(rng); + //System.out.println("external: ed25519/"+external.public_key().toHex()); + + Block authority_builder = new Block(); + authority_builder.add_fact("right(\"read\")"); + authority_builder.add_check("check if first(\"admin\") trusting ed25519/"+external1.public_key().toHex()); + + org.biscuitsec.biscuit.token.Block authority_block = authority_builder.build(); + System.out.println(authority_block); + Biscuit b1 = Biscuit.make(rng, root, authority_block); + System.out.println("TOKEN: "+b1.print()); + + ThirdPartyBlockRequest request1 = b1.thirdPartyRequest(); + Block builder = new Block(); + builder.add_fact("first(\"admin\")"); + builder.add_fact("second(\"A\")"); + builder.add_check("check if third(3) trusting ed25519/"+external2.public_key().toHex()); + ThirdPartyBlockContents blockResponse = request1.createBlock(external1, builder).get(); + Biscuit b2 = b1.appendThirdPartyBlock(external1.public_key(), blockResponse); + byte[] data = b2.serialize(); + Biscuit deser2 = Biscuit.from_bytes(data, root.public_key()); + assertEquals(b2.print(), deser2.print()); + System.out.println("TOKEN: "+deser2.print()); + + ThirdPartyBlockRequest request2 = deser2.thirdPartyRequest(); + Block builder2 = new Block(); + builder2.add_fact("third(3)"); + builder2.add_check("check if fourth(1) trusting ed25519/"+external3.public_key().toHex()+", ed25519/"+external1.public_key().toHex()); + ThirdPartyBlockContents blockResponse2 = request2.createBlock(external2, builder2).get(); + Biscuit b3 = deser2.appendThirdPartyBlock(external2.public_key(), blockResponse2); + byte[] data2 = b3.serialize(); + Biscuit deser3 = Biscuit.from_bytes(data2, root.public_key()); + assertEquals(b3.print(), deser3.print()); + System.out.println("TOKEN: "+deser3.print()); + + + ThirdPartyBlockRequest request3 = deser3.thirdPartyRequest(); + Block builder3 = new Block(); + builder3.add_fact("fourth(1)"); + builder3.add_check("check if resource(\"file1\")"); + ThirdPartyBlockContents blockResponse3 = request3.createBlock(external1, builder3).get(); + Biscuit b4 = deser3.appendThirdPartyBlock(external1.public_key(), blockResponse3); + byte[] data3 = b4.serialize(); + Biscuit deser4 = Biscuit.from_bytes(data3, root.public_key()); + assertEquals(b4.print(), deser4.print()); + System.out.println("TOKEN: "+deser4.print()); + + + System.out.println("will check the token for resource=file1"); + Authorizer authorizer = deser4.authorizer(); + authorizer.add_fact("resource(\"file1\")"); + authorizer.add_policy("allow if true"); + System.out.println("Authorizer world:\n"+authorizer.print_world()); + authorizer.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); + + System.out.println("will check the token for resource=file2"); + Authorizer authorizer2 = deser4.authorizer(); + authorizer2.add_fact("resource(\"file2\")"); + authorizer2.add_policy("allow if true"); + System.out.println("Authorizer world 2:\n"+authorizer2.print_world()); + + try { + authorizer2.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); + } catch (Error e) { + System.out.println(e); + assertEquals( + new Error.FailedLogic(new LogicError.Unauthorized(new LogicError.MatchedPolicy.Allow(0), Arrays.asList( + new FailedCheck.FailedBlock(3, 0, "check if resource(\"file1\")") + ))), + e); + } + } + + @Test + public void testReusedSymbols() throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, CloneNotSupportedException, Error { + byte[] seed = {0, 0, 0, 0}; + SecureRandom rng = new SecureRandom(seed); + + System.out.println("preparing the authority block"); + + KeyPair root = new KeyPair(rng); + KeyPair external = new KeyPair(rng); + System.out.println("external: ed25519/"+external.public_key().toHex()); + + Block authority_builder = new Block(); + authority_builder.add_fact("right(\"read\")"); + authority_builder.add_check("check if group(\"admin\") trusting ed25519/"+external.public_key().toHex()); + + Biscuit b1 = Biscuit.make(rng, root, authority_builder.build()); + ThirdPartyBlockRequest request = b1.thirdPartyRequest(); + Block builder = new Block(); + builder.add_fact("group(\"admin\")"); + builder.add_fact("resource(\"file2\")"); + builder.add_check("check if resource(\"file1\")"); + builder.add_check("check if right(\"read\")"); + + ThirdPartyBlockContents blockResponse = request.createBlock(external, builder).get(); + Biscuit b2 = b1.appendThirdPartyBlock(external.public_key(), blockResponse); + + byte[] data = b2.serialize(); + Biscuit deser = Biscuit.from_bytes(data, root.public_key()); + assertEquals(b2.print(), deser.print()); + + System.out.println("will check the token for resource=file1"); + Authorizer authorizer = deser.authorizer(); + authorizer.add_fact("resource(\"file1\")"); + authorizer.add_policy("allow if true"); + authorizer.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); + System.out.println("Authorizer world:\n"+authorizer.print_world()); + + + System.out.println("will check the token for resource=file2"); + Authorizer authorizer2 = deser.authorizer(); + authorizer2.add_fact("resource(\"file2\")"); + authorizer2.add_policy("allow if true"); + + try { + authorizer2.authorize(new RunLimits(500, 100, Duration.ofMillis(500))); + } catch (Error e) { + System.out.println(e); + assertEquals( + new Error.FailedLogic(new LogicError.Unauthorized(new LogicError.MatchedPolicy.Allow(0), Arrays.asList( + new FailedCheck.FailedBlock(1, 0, "check if resource(\"file1\")") + ))), + e); + } + } +} +