Skip to content
This repository was archived by the owner on Jul 15, 2025. It is now read-only.

Commit c4df990

Browse files
itstheceoGeal
andauthored
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 <[email protected]>
1 parent 984132f commit c4df990

File tree

16 files changed

+645
-92
lines changed

16 files changed

+645
-92
lines changed

src/main/java/org/biscuitsec/biscuit/crypto/KeyPair.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22

33

44
import biscuit.format.schema.Schema;
5+
import biscuit.format.schema.Schema.PublicKey.Algorithm;
6+
import net.i2p.crypto.eddsa.EdDSAEngine;
57
import org.biscuitsec.biscuit.token.builder.Utils;
68
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
79
import net.i2p.crypto.eddsa.EdDSAPublicKey;
810
import net.i2p.crypto.eddsa.spec.*;
911

12+
import java.security.MessageDigest;
13+
import java.security.NoSuchAlgorithmException;
1014
import java.security.SecureRandom;
15+
import java.security.Signature;
1116

1217
/**
1318
* Private and public key
@@ -39,6 +44,26 @@ public KeyPair(final SecureRandom rng) {
3944
this.public_key = pubKey;
4045
}
4146

47+
public static KeyPair generate(Algorithm algorithm) {
48+
return generate(algorithm, new SecureRandom());
49+
}
50+
51+
public static KeyPair generate(Algorithm algorithm, SecureRandom rng) {
52+
if (algorithm == Algorithm.Ed25519) {
53+
return new KeyPair(rng);
54+
} else {
55+
throw new IllegalArgumentException("Unsupported algorithm");
56+
}
57+
}
58+
59+
public static Signature generateSignature(Algorithm algorithm) throws NoSuchAlgorithmException {
60+
if (algorithm == Algorithm.Ed25519) {
61+
return KeyPair.getSignature();
62+
} else {
63+
throw new NoSuchAlgorithmException("Unsupported algorithm");
64+
}
65+
}
66+
4267
public byte[] toBytes() {
4368
return this.private_key.getSeed();
4469
}
@@ -71,6 +96,10 @@ public KeyPair(String hex) {
7196
this.public_key = pubKey;
7297
}
7398

99+
public static Signature getSignature() throws NoSuchAlgorithmException {
100+
return new EdDSAEngine(MessageDigest.getInstance(ed25519.getHashAlgorithm()));
101+
}
102+
74103
public PublicKey public_key() {
75104
return new PublicKey(Schema.PublicKey.Algorithm.Ed25519, this.public_key);
76105
}

src/main/java/org/biscuitsec/biscuit/datalog/SymbolTable.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ public String print_scope(final Scope scope) {
180180
return pk.get().toString();
181181
}
182182
}
183-
return "?";
183+
return "<"+ scope.publicKey+"?>";
184184
}
185185

186186
public String print_predicate(final Predicate p) {

src/main/java/org/biscuitsec/biscuit/token/Authorizer.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,8 +614,13 @@ public String print_world() {
614614
for (int i = 0; i < this.token.blocks.size(); i++) {
615615
Block b = this.token.blocks.get(i);
616616

617+
SymbolTable blockSymbols = token.symbols;
618+
if(b.externalKey.isDefined()) {
619+
blockSymbols = new SymbolTable(b.symbols.symbols, token.symbols.publicKeys());
620+
}
621+
617622
for (int j = 0; j < b.checks.size(); j++) {
618-
checks.add("Block[" + i + "][" + j + "]: " + this.symbols.print_check(b.checks.get(j)));
623+
checks.add("Block[" + (i+1) + "][" + j + "]: " + blockSymbols.print_check(b.checks.get(j)));
619624
}
620625
}
621626
}

src/main/java/org/biscuitsec/biscuit/token/Biscuit.java

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.biscuitsec.biscuit.token;
22

3+
import biscuit.format.schema.Schema;
34
import org.biscuitsec.biscuit.crypto.KeyDelegate;
45
import org.biscuitsec.biscuit.crypto.KeyPair;
56
import org.biscuitsec.biscuit.crypto.PublicKey;
@@ -10,10 +11,7 @@
1011
import io.vavr.control.Either;
1112
import io.vavr.control.Option;
1213

13-
import java.security.InvalidKeyException;
14-
import java.security.NoSuchAlgorithmException;
15-
import java.security.SecureRandom;
16-
import java.security.SignatureException;
14+
import java.security.*;
1715
import java.util.*;
1816

1917
/**
@@ -91,7 +89,11 @@ public static Biscuit make(final SecureRandom rng, final KeyPair root, final Int
9189
static private Biscuit make(final SecureRandom rng, final KeyPair root, final Option<Integer> root_key_id, final Block authority) throws Error.FormatError {
9290
ArrayList<Block> blocks = new ArrayList<>();
9391

94-
KeyPair next = new KeyPair(rng);
92+
KeyPair next = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng);
93+
94+
for(PublicKey pk: authority.publicKeys) {
95+
authority.symbols.insert(pk);
96+
}
9597

9698
Either<Error.FormatError, SerializedBiscuit> container = SerializedBiscuit.make(root, root_key_id, authority, next);
9799
if (container.isLeft()) {
@@ -304,7 +306,7 @@ public String serialize_b64url() throws Error.FormatError.SerializationError {
304306
*/
305307
public Biscuit attenuate(org.biscuitsec.biscuit.token.builder.Block block) throws Error {
306308
SecureRandom rng = new SecureRandom();
307-
KeyPair keypair = new KeyPair(rng);
309+
KeyPair keypair = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng);
308310
SymbolTable builderSymbols = new SymbolTable(this.symbols);
309311
return attenuate(rng, keypair, block.build(builderSymbols));
310312
}
@@ -329,7 +331,7 @@ public Biscuit attenuate(final SecureRandom rng, final KeyPair keypair, Block bl
329331
throw new Error.SymbolTableOverlap();
330332
}
331333

332-
Either<Error.FormatError, SerializedBiscuit> containerRes = copiedBiscuit.serializedBiscuit.append(keypair, block);
334+
Either<Error.FormatError, SerializedBiscuit> containerRes = copiedBiscuit.serializedBiscuit.append(keypair, block, Option.none());
333335
if (containerRes.isLeft()) {
334336
throw containerRes.getLeft();
335337
}
@@ -340,6 +342,10 @@ public Biscuit attenuate(final SecureRandom rng, final KeyPair keypair, Block bl
340342
symbols.add(s);
341343
}
342344

345+
for(PublicKey pk: block.publicKeys) {
346+
symbols.insert(pk);
347+
}
348+
343349
ArrayList<Block> blocks = new ArrayList<>();
344350
for (Block b : copiedBiscuit.blocks) {
345351
blocks.add(b);
@@ -354,13 +360,26 @@ public Biscuit attenuate(final SecureRandom rng, final KeyPair keypair, Block bl
354360
return new Biscuit(copiedBiscuit.authority, blocks, symbols, container, publicKeyToBlockId, revocation_ids);
355361
}
356362

363+
/**
364+
* Generates a third party block request from a token
365+
*/
366+
public Biscuit appendThirdPartyBlock(PublicKey externalKey, ThirdPartyBlockContents blockResponse)
367+
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
368+
UnverifiedBiscuit b = super.appendThirdPartyBlock(externalKey, blockResponse);
369+
370+
// no need to verify again, we are already working from a verified token
371+
return Biscuit.from_serialized_biscuit(b.serializedBiscuit, b.symbols);
372+
}
373+
357374
/**
358375
* Prints a token's content
359376
*/
360377
public String print() {
361378
StringBuilder s = new StringBuilder();
362379
s.append("Biscuit {\n\tsymbols: ");
363380
s.append(this.symbols.getAllSymbols());
381+
s.append("\n\tpublic keys: ");
382+
s.append(this.symbols.publicKeys());
364383
s.append("\n\tauthority: ");
365384
s.append(this.authority.print(this.symbols));
366385
s.append("\n\tblocks: [\n");

src/main/java/org/biscuitsec/biscuit/token/Block.java

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ public class Block {
3131
final List<Check> checks;
3232
final List<Scope> scopes;
3333
final List<PublicKey> publicKeys;
34-
final Option<PublicKey> externalKey;
35-
final long version;
34+
Option<PublicKey> externalKey;
35+
long version;
3636

3737
/**
3838
* creates a new block
@@ -48,7 +48,6 @@ public Block(SymbolTable base_symbols) {
4848
this.scopes = new ArrayList<>();
4949
this.publicKeys = new ArrayList<>();
5050
this.externalKey = Option.none();
51-
this.version = SerializedBiscuit.MAX_SCHEMA_VERSION;
5251
}
5352

5453
/**
@@ -66,7 +65,6 @@ public Block(SymbolTable base_symbols, String context, List<Fact> facts, List<Ru
6665
this.rules = rules;
6766
this.checks = checks;
6867
this.scopes = scopes;
69-
this.version = version;
7068
this.publicKeys = publicKeys;
7169
this.externalKey = externalKey;
7270
}
@@ -79,6 +77,10 @@ public List<PublicKey> publicKeys() {
7977
return publicKeys;
8078
}
8179

80+
public void setExternalKey(PublicKey externalKey) {
81+
this.externalKey = Option.some(externalKey);
82+
}
83+
8284
/**
8385
* pretty printing for a block
8486
*
@@ -88,9 +90,22 @@ public List<PublicKey> publicKeys() {
8890
public String print(SymbolTable symbol_table) {
8991
StringBuilder s = new StringBuilder();
9092

93+
SymbolTable local_symbols;
94+
if(this.externalKey.isDefined()) {
95+
local_symbols = new SymbolTable(this.symbols);
96+
for(PublicKey pk: symbol_table.publicKeys()) {
97+
local_symbols.insert(pk);
98+
}
99+
} else {
100+
local_symbols = symbol_table;
101+
}
91102
s.append("Block");
92103
s.append(" {\n\t\tsymbols: ");
93104
s.append(this.symbols.symbols);
105+
s.append("\n\t\tpublic keys: ");
106+
s.append(this.publicKeys);
107+
s.append("\n\t\tsymbol public keys: ");
108+
s.append(this.symbols.publicKeys());
94109
s.append("\n\t\tcontext: ");
95110
s.append(this.context);
96111
if(this.externalKey.isDefined()) {
@@ -105,17 +120,17 @@ public String print(SymbolTable symbol_table) {
105120
s.append("\n\t\t]\n\t\tfacts: [");
106121
for (Fact f : this.facts) {
107122
s.append("\n\t\t\t");
108-
s.append(symbol_table.print_fact(f));
123+
s.append(local_symbols.print_fact(f));
109124
}
110125
s.append("\n\t\t]\n\t\trules: [");
111126
for (Rule r : this.rules) {
112127
s.append("\n\t\t\t");
113-
s.append(symbol_table.print_rule(r));
128+
s.append(local_symbols.print_rule(r));
114129
}
115130
s.append("\n\t\t]\n\t\tchecks: [");
116131
for (Check c : this.checks) {
117132
s.append("\n\t\t\t");
118-
s.append(symbol_table.print_check(c));
133+
s.append(local_symbols.print_check(c));
119134
}
120135
s.append("\n\t\t]\n\t}");
121136

@@ -184,7 +199,7 @@ int getSchemaVersion() {
184199
}
185200
}
186201

187-
if(containsScopes || containsCheckAll || containsV4) {
202+
if(containsScopes || containsCheckAll || containsV4 || this.externalKey.isDefined()) {
188203
return SerializedBiscuit.MAX_SCHEMA_VERSION;
189204
} else {
190205
return SerializedBiscuit.MIN_SCHEMA_VERSION;
@@ -321,7 +336,6 @@ public boolean equals(Object o) {
321336

322337
Block block = (Block) o;
323338

324-
if (version != block.version) return false;
325339
if (!Objects.equals(symbols, block.symbols)) return false;
326340
if (!Objects.equals(context, block.context)) return false;
327341
if (!Objects.equals(facts, block.facts)) return false;
@@ -342,7 +356,6 @@ public int hashCode() {
342356
result = 31 * result + (scopes != null ? scopes.hashCode() : 0);
343357
result = 31 * result + (publicKeys != null ? publicKeys.hashCode() : 0);
344358
result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
345-
result = 31 * result + (int) (version ^ (version >>> 32));
346359
return result;
347360
}
348361

@@ -357,7 +370,6 @@ public String toString() {
357370
", scopes=" + scopes +
358371
", publicKeys=" + publicKeys +
359372
", externalKey=" + externalKey +
360-
", version=" + version +
361373
'}';
362374
}
363375
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package org.biscuitsec.biscuit.token;
2+
3+
import biscuit.format.schema.Schema;
4+
import com.google.protobuf.ByteString;
5+
import com.google.protobuf.InvalidProtocolBufferException;
6+
import org.biscuitsec.biscuit.crypto.PublicKey;
7+
import org.biscuitsec.biscuit.error.Error;
8+
9+
import java.io.ByteArrayOutputStream;
10+
import java.io.IOException;
11+
import java.util.ArrayList;
12+
import java.util.Arrays;
13+
import java.util.List;
14+
import java.util.Objects;
15+
16+
public class ThirdPartyBlockContents {
17+
byte[] payload;
18+
byte[] signature;
19+
PublicKey publicKey;
20+
21+
ThirdPartyBlockContents(byte[] payload, byte[] signature, PublicKey publicKey) {
22+
this.payload = payload;
23+
this.signature = signature;
24+
this.publicKey = publicKey;
25+
}
26+
27+
public Schema.ThirdPartyBlockContents serialize() throws Error.FormatError.SerializationError {
28+
Schema.ThirdPartyBlockContents.Builder b = Schema.ThirdPartyBlockContents.newBuilder();
29+
b.setPayload(ByteString.copyFrom(this.payload));
30+
b.setExternalSignature(b.getExternalSignatureBuilder()
31+
.setSignature(ByteString.copyFrom(this.signature))
32+
.setPublicKey(this.publicKey.serialize())
33+
.build());
34+
35+
return b.build();
36+
}
37+
38+
static public ThirdPartyBlockContents deserialize(Schema.ThirdPartyBlockContents b) throws Error.FormatError.DeserializationError {
39+
byte[] payload = b.getPayload().toByteArray();
40+
byte[] signature = b.getExternalSignature().getSignature().toByteArray();
41+
PublicKey publicKey = PublicKey.deserialize(b.getExternalSignature().getPublicKey());
42+
43+
return new ThirdPartyBlockContents(payload, signature, publicKey);
44+
}
45+
46+
static public ThirdPartyBlockContents fromBytes(byte[] slice) throws InvalidProtocolBufferException, Error.FormatError.DeserializationError {
47+
return ThirdPartyBlockContents.deserialize(Schema.ThirdPartyBlockContents.parseFrom(slice));
48+
}
49+
50+
public byte[] toBytes() throws IOException, Error.FormatError.SerializationError {
51+
Schema.ThirdPartyBlockContents b = this.serialize();
52+
ByteArrayOutputStream stream = new ByteArrayOutputStream();
53+
b.writeTo(stream);
54+
return stream.toByteArray();
55+
}
56+
57+
@Override
58+
public boolean equals(Object o) {
59+
if (this == o) return true;
60+
if (o == null || getClass() != o.getClass()) return false;
61+
62+
ThirdPartyBlockContents that = (ThirdPartyBlockContents) o;
63+
64+
if (!Arrays.equals(payload, that.payload)) return false;
65+
if (!Arrays.equals(signature, that.signature)) return false;
66+
return Objects.equals(publicKey, that.publicKey);
67+
}
68+
69+
@Override
70+
public int hashCode() {
71+
int result = Arrays.hashCode(payload);
72+
result = 31 * result + Arrays.hashCode(signature);
73+
result = 31 * result + (publicKey != null ? publicKey.hashCode() : 0);
74+
return result;
75+
}
76+
77+
@Override
78+
public String toString() {
79+
return "ThirdPartyBlockContents{" +
80+
"payload=" + Arrays.toString(payload) +
81+
", signature=" + Arrays.toString(signature) +
82+
", publicKey=" + publicKey +
83+
'}';
84+
}
85+
}

0 commit comments

Comments
 (0)