Skip to content

Commit

Permalink
Implement 3rd party block creation (#104)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Geal authored Jun 23, 2024
1 parent df93100 commit 942fbb9
Show file tree
Hide file tree
Showing 16 changed files with 645 additions and 92 deletions.
29 changes: 29 additions & 0 deletions src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/org/biscuitsec/biscuit/token/Authorizer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}
}
}
Expand Down
33 changes: 26 additions & 7 deletions src/main/java/org/biscuitsec/biscuit/token/Biscuit.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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.*;

/**
Expand Down Expand Up @@ -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<Integer> root_key_id, final Block authority) throws Error.FormatError {
ArrayList<Block> 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<Error.FormatError, SerializedBiscuit> container = SerializedBiscuit.make(root, root_key_id, authority, next);
if (container.isLeft()) {
Expand Down Expand Up @@ -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));
}
Expand All @@ -329,7 +331,7 @@ public Biscuit attenuate(final SecureRandom rng, final KeyPair keypair, Block bl
throw new Error.SymbolTableOverlap();
}

Either<Error.FormatError, SerializedBiscuit> containerRes = copiedBiscuit.serializedBiscuit.append(keypair, block);
Either<Error.FormatError, SerializedBiscuit> containerRes = copiedBiscuit.serializedBiscuit.append(keypair, block, Option.none());
if (containerRes.isLeft()) {
throw containerRes.getLeft();
}
Expand All @@ -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<Block> blocks = new ArrayList<>();
for (Block b : copiedBiscuit.blocks) {
blocks.add(b);
Expand All @@ -354,13 +360,26 @@ 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
*/
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");
Expand Down
34 changes: 23 additions & 11 deletions src/main/java/org/biscuitsec/biscuit/token/Block.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ public class Block {
final List<Check> checks;
final List<Scope> scopes;
final List<PublicKey> publicKeys;
final Option<PublicKey> externalKey;
final long version;
Option<PublicKey> externalKey;
long version;

/**
* creates a new block
Expand All @@ -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;
}

/**
Expand All @@ -66,7 +65,6 @@ public Block(SymbolTable base_symbols, String context, List<Fact> facts, List<Ru
this.rules = rules;
this.checks = checks;
this.scopes = scopes;
this.version = version;
this.publicKeys = publicKeys;
this.externalKey = externalKey;
}
Expand All @@ -79,6 +77,10 @@ public List<PublicKey> publicKeys() {
return publicKeys;
}

public void setExternalKey(PublicKey externalKey) {
this.externalKey = Option.some(externalKey);
}

/**
* pretty printing for a block
*
Expand All @@ -88,9 +90,22 @@ public List<PublicKey> 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()) {
Expand All @@ -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}");

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}

Expand All @@ -357,7 +370,6 @@ public String toString() {
", scopes=" + scopes +
", publicKeys=" + publicKeys +
", externalKey=" + externalKey +
", version=" + version +
'}';
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 +
'}';
}
}
Loading

0 comments on commit 942fbb9

Please sign in to comment.