Skip to content

Commit ae54ea6

Browse files
committed
Add support for deterministic ECDSA
Use BeforeEach for a cleaner test setup.
1 parent 3ef87dd commit ae54ea6

8 files changed

Lines changed: 142 additions & 53 deletions

File tree

src/main/java/org/eclipse/biscuit/crypto/SECP256R1KeyPair.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.security.SecureRandom;
1010
import org.bouncycastle.crypto.digests.SHA256Digest;
1111
import org.bouncycastle.crypto.signers.ECDSASigner;
12+
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
1213
import org.bouncycastle.crypto.signers.StandardDSAEncoding;
1314
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
1415
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
@@ -67,14 +68,32 @@ final class SECP256R1KeyPair extends KeyPair {
6768
this.publicKey = publicKey;
6869
}
6970

71+
/// By default sign message digests with a deterministic k
72+
/// computed using the algorithm described in [RFC6979 § 3.2].
73+
///
74+
/// [RFC6979 § 3.2]: https://tools.ietf.org/html/rfc6979#section-3
75+
///
76+
/// Although deterministic ECDSA signing is typically slower than
77+
/// signing with an RNG, it prevents accidental nonce-reuse due to
78+
/// a weak RNG.
7079
@Override
7180
public byte[] sign(byte[] data) {
81+
return sign(data, true);
82+
}
83+
84+
public byte[] sign(byte[] data, boolean deterministicNonce) {
7285
var digest = new SHA256Digest();
7386
digest.update(data, 0, data.length);
7487
var hash = new byte[digest.getDigestSize()];
7588
digest.doFinal(hash, 0);
7689

77-
var signer = new ECDSASigner();
90+
ECDSASigner signer;
91+
if (deterministicNonce) {
92+
signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
93+
} else {
94+
signer = new ECDSASigner();
95+
}
96+
7897
signer.init(true, privateKey.engineGetKeyParameters());
7998
var sig = signer.generateSignature(hash);
8099

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,13 @@ public Biscuit attenuate(final SecureRandom rng, final KeyPair keypair, Block bl
356356
/** Generates a third party block request from a token */
357357
public Biscuit appendThirdPartyBlock(PublicKey externalKey, ThirdPartyBlockContents blockResponse)
358358
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
359-
UnverifiedBiscuit b = super.appendThirdPartyBlock(externalKey, blockResponse);
359+
return appendThirdPartyBlock(externalKey, blockResponse, new SecureRandom());
360+
}
361+
362+
public Biscuit appendThirdPartyBlock(
363+
PublicKey externalKey, ThirdPartyBlockContents blockResponse, SecureRandom rng)
364+
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
365+
UnverifiedBiscuit b = super.appendThirdPartyBlock(externalKey, blockResponse, rng);
360366

361367
// no need to verify again, we are already working from a verified token
362368
return Biscuit.fromSerializedBiscuit(b.serializedBiscuit, b.symbolTable);

src/main/java/org/eclipse/biscuit/token/UnverifiedBiscuit.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,14 +279,20 @@ public ThirdPartyBlockRequest thirdPartyRequest() {
279279
public UnverifiedBiscuit appendThirdPartyBlock(
280280
PublicKey externalKey, ThirdPartyBlockContents blockResponse)
281281
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
282+
return appendThirdPartyBlock(externalKey, blockResponse, new SecureRandom());
283+
}
284+
285+
public UnverifiedBiscuit appendThirdPartyBlock(
286+
PublicKey externalKey, ThirdPartyBlockContents blockResponse, SecureRandom rng)
287+
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
282288
SignedBlock previousBlock;
283289
if (this.serializedBiscuit.getBlocks().isEmpty()) {
284290
previousBlock = this.serializedBiscuit.getAuthority();
285291
} else {
286292
previousBlock =
287293
this.serializedBiscuit.getBlocks().get(this.serializedBiscuit.getBlocks().size() - 1);
288294
}
289-
KeyPair nextKeyPair = KeyPair.generate(previousBlock.getKey().getAlgorithm());
295+
KeyPair nextKeyPair = KeyPair.generate(previousBlock.getKey().getAlgorithm(), rng);
290296
byte[] payload =
291297
BlockSignatureBuffer.generateExternalBlockSignaturePayloadV1(
292298
blockResponse.getPayload(),

src/test/java/org/eclipse/biscuit/crypto/SignatureTest.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,24 @@
1818
import org.eclipse.biscuit.error.Error;
1919
import org.eclipse.biscuit.error.Result;
2020
import org.eclipse.biscuit.token.Biscuit;
21+
import org.junit.jupiter.api.BeforeEach;
2122
import org.junit.jupiter.api.Test;
2223

2324
/**
2425
* @serial exclude
2526
*/
2627
public class SignatureTest {
28+
private SecureRandom rng;
29+
30+
@BeforeEach
31+
public void setUp() throws NoSuchAlgorithmException {
32+
byte[] seed = {0, 0, 0, 0};
33+
rng = SecureRandom.getInstance("SHA1PRNG");
34+
rng.setSeed(seed);
35+
}
36+
2737
@Test
28-
public void testSerialize() throws Error.FormatError {
38+
public void testSerialize() throws Error.FormatError, NoSuchAlgorithmException {
2939
prTestSerialize(Schema.PublicKey.Algorithm.Ed25519, 32);
3040
prTestSerialize(
3141
// compressed - 0x02 or 0x03 prefix byte, 32 bytes for X coordinate
@@ -86,11 +96,8 @@ void testInvalidEd25519PublicKey() {
8696
() -> PublicKey.load(Schema.PublicKey.Algorithm.Ed25519, "badkey".getBytes()));
8797
}
8898

89-
private static void prTestSerialize(
90-
Schema.PublicKey.Algorithm algorithm, int expectedPublicKeyLength) throws Error.FormatError {
91-
byte[] seed = {1, 2, 3, 4};
92-
SecureRandom rng = new SecureRandom(seed);
93-
99+
private void prTestSerialize(Schema.PublicKey.Algorithm algorithm, int expectedPublicKeyLength)
100+
throws Error.FormatError, NoSuchAlgorithmException {
94101
KeyPair keypair = KeyPair.generate(algorithm, rng);
95102
PublicKey pubkey = keypair.getPublicKey();
96103

@@ -112,11 +119,8 @@ private static void prTestSerialize(
112119
assertEquals(pubkey.toHex(), deserializedPublicKey.toHex());
113120
}
114121

115-
private static void prTestThreeMessages(Schema.PublicKey.Algorithm algorithm)
122+
private void prTestThreeMessages(Schema.PublicKey.Algorithm algorithm)
116123
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
117-
byte[] seed = {0, 0, 0, 0};
118-
SecureRandom rng = new SecureRandom(seed);
119-
120124
String message1 = "hello";
121125
KeyPair root = KeyPair.generate(algorithm, rng);
122126
KeyPair keypair2 = KeyPair.generate(algorithm, rng);

src/test/java/org/eclipse/biscuit/token/AuthorizerTest.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,19 @@
2222
import org.eclipse.biscuit.error.Error.Parser;
2323
import org.eclipse.biscuit.token.builder.Expression;
2424
import org.eclipse.biscuit.token.builder.Term;
25+
import org.junit.jupiter.api.BeforeEach;
2526
import org.junit.jupiter.api.Test;
2627

2728
public class AuthorizerTest {
2829
final RunLimits runLimits = new RunLimits(500, 100, Duration.ofMillis(500));
30+
private SecureRandom rng;
31+
32+
@BeforeEach
33+
public void setUp() throws Exception {
34+
byte[] seed = {0, 0, 0, 0};
35+
rng = SecureRandom.getInstance("SHA1PRNG");
36+
rng.setSeed(seed);
37+
}
2938

3039
@Test
3140
public void testAuthorizerPolicy() throws Parser {
@@ -51,8 +60,7 @@ public void testAuthorizerPolicy() throws Parser {
5160

5261
@Test
5362
public void testPuttingSomeFactsInBiscuitAndGettingThemBackOutAgain() throws Exception {
54-
55-
KeyPair keypair = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, new SecureRandom());
63+
KeyPair keypair = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng);
5664

5765
Biscuit token =
5866
Biscuit.builder(keypair)
@@ -84,7 +92,7 @@ public void testPuttingSomeFactsInBiscuitAndGettingThemBackOutAgain() throws Exc
8492

8593
@Test
8694
public void testDatalogAuthorizer() throws Exception {
87-
KeyPair keypair = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, new SecureRandom());
95+
KeyPair keypair = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng);
8896

8997
Biscuit token =
9098
Biscuit.builder(keypair)

src/test/java/org/eclipse/biscuit/token/BiscuitTest.java

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,23 @@
3737
import org.eclipse.biscuit.error.LogicError;
3838
import org.eclipse.biscuit.token.builder.Block;
3939
import org.junit.jupiter.api.Assertions;
40+
import org.junit.jupiter.api.BeforeEach;
4041
import org.junit.jupiter.api.Test;
4142

4243
public class BiscuitTest {
4344

45+
private SecureRandom rng;
46+
47+
@BeforeEach
48+
public void setUp() throws NoSuchAlgorithmException {
49+
byte[] seed = {0, 0, 0, 0};
50+
rng = SecureRandom.getInstance("SHA1PRNG");
51+
rng.setSeed(seed);
52+
}
53+
4454
@Test
4555
public void testBasic()
4656
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
47-
byte[] seed = {0, 0, 0, 0};
48-
SecureRandom rng = new SecureRandom(seed);
4957

5058
System.out.println("preparing the authority block");
5159

@@ -177,9 +185,6 @@ public void testBasic()
177185

178186
@Test
179187
public void testFolders() throws NoSuchAlgorithmException, Error {
180-
byte[] seed = {0, 0, 0, 0};
181-
SecureRandom rng = new SecureRandom(seed);
182-
183188
System.out.println("preparing the authority block");
184189

185190
KeyPair root = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng);
@@ -288,10 +293,7 @@ public void testMultipleAttenuation()
288293
}
289294

290295
@Test
291-
public void testReset() throws Error {
292-
byte[] seed = {0, 0, 0, 0};
293-
SecureRandom rng = new SecureRandom(seed);
294-
296+
public void testReset() throws Error, NoSuchAlgorithmException {
295297
System.out.println("preparing the authority block");
296298

297299
KeyPair root = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng);
@@ -361,10 +363,7 @@ public void testReset() throws Error {
361363
}
362364

363365
@Test
364-
public void testEmptyAuthorizer() throws Error {
365-
byte[] seed = {0, 0, 0, 0};
366-
SecureRandom rng = new SecureRandom(seed);
367-
366+
public void testEmptyAuthorizer() throws Error, NoSuchAlgorithmException {
368367
System.out.println("preparing the authority block");
369368

370369
KeyPair root = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng);
@@ -405,9 +404,6 @@ public void testEmptyAuthorizer() throws Error {
405404
@Test
406405
public void testBasicWithNamespaces()
407406
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
408-
byte[] seed = {0, 0, 0, 0};
409-
SecureRandom rng = new SecureRandom(seed);
410-
411407
System.out.println("preparing the authority block");
412408

413409
KeyPair root = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng);
@@ -535,9 +531,6 @@ public void testBasicWithNamespaces()
535531
@Test
536532
public void testBasicWithNamespacesWithAddAuthorityFact()
537533
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
538-
byte[] seed = {0, 0, 0, 0};
539-
SecureRandom rng = new SecureRandom(seed);
540-
541534
System.out.println("preparing the authority block");
542535

543536
KeyPair root = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng);
@@ -664,9 +657,6 @@ public void testBasicWithNamespacesWithAddAuthorityFact()
664657
@Test
665658
public void testRootKeyId()
666659
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
667-
byte[] seed = {0, 0, 0, 0};
668-
SecureRandom rng = new SecureRandom(seed);
669-
670660
System.out.println("preparing the authority block");
671661

672662
KeyPair root = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng);
@@ -739,9 +729,6 @@ public Optional<PublicKey> getRootKey(Optional<Integer> keyId) {
739729
@Test
740730
public void testCheckAll()
741731
throws Error, NoSuchAlgorithmException, SignatureException, InvalidKeyException {
742-
byte[] seed = {0, 0, 0, 0};
743-
SecureRandom rng = new SecureRandom(seed);
744-
745732
System.out.println("preparing the authority block");
746733

747734
KeyPair root = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng);

src/test/java/org/eclipse/biscuit/token/ThirdPartyTest.java

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,19 @@
2323
import org.eclipse.biscuit.error.FailedCheck;
2424
import org.eclipse.biscuit.error.LogicError;
2525
import org.eclipse.biscuit.token.builder.Block;
26+
import org.junit.jupiter.api.BeforeEach;
2627
import org.junit.jupiter.api.Test;
2728

2829
public class ThirdPartyTest {
30+
private SecureRandom rng;
31+
32+
@BeforeEach
33+
public void setUp() throws NoSuchAlgorithmException {
34+
byte[] seed = {0, 0, 0, 0};
35+
rng = SecureRandom.getInstance("SHA1PRNG");
36+
rng.setSeed(seed);
37+
}
38+
2939
@Test
3040
public void testRoundTrip()
3141
throws NoSuchAlgorithmException,
@@ -34,8 +44,6 @@ public void testRoundTrip()
3444
CloneNotSupportedException,
3545
Error,
3646
IOException {
37-
byte[] seed = {0, 0, 0, 0};
38-
SecureRandom rng = new SecureRandom(seed);
3947

4048
System.out.println("preparing the authority block");
4149

@@ -105,11 +113,6 @@ public void testPublicKeyInterning()
105113
InvalidKeyException,
106114
CloneNotSupportedException,
107115
Error {
108-
// this makes a deterministic RNG
109-
SecureRandom rng = SecureRandom.getInstance("SHA1PRNG");
110-
byte[] seed = {0, 0, 0, 0};
111-
rng.setSeed(seed);
112-
113116
System.out.println("preparing the authority block");
114117

115118
final KeyPair root = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng);
@@ -200,9 +203,6 @@ public void testReusedSymbols()
200203
InvalidKeyException,
201204
CloneNotSupportedException,
202205
Error {
203-
byte[] seed = {0, 0, 0, 0};
204-
SecureRandom rng = new SecureRandom(seed);
205-
206206
System.out.println("preparing the authority block");
207207

208208
final KeyPair root = KeyPair.generate(Schema.PublicKey.Algorithm.Ed25519, rng);
@@ -254,4 +254,56 @@ public void testReusedSymbols()
254254
e);
255255
}
256256
}
257+
258+
private org.eclipse.biscuit.token.Biscuit generateDeterministicBiscuit(SecureRandom rng)
259+
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
260+
final KeyPair root = KeyPair.generate(Schema.PublicKey.Algorithm.SECP256R1, rng);
261+
final KeyPair external = KeyPair.generate(Schema.PublicKey.Algorithm.SECP256R1, rng);
262+
263+
Block authorityBuilder = new Block();
264+
Biscuit b1 = Biscuit.make(rng, root, authorityBuilder.build());
265+
266+
ThirdPartyBlockRequest request = b1.thirdPartyRequest();
267+
Block builder = new Block();
268+
ThirdPartyBlockContents blockResponse = request.createBlock(external, builder).getOk();
269+
Biscuit b2 = b1.appendThirdPartyBlock(external.getPublicKey(), blockResponse, rng);
270+
271+
return b2;
272+
}
273+
274+
@Test
275+
public void testDeterministicECDSA()
276+
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
277+
// Generate the same set of root and 3rd-party keys and confirm that they result in the same
278+
// ECDSA signatures.
279+
280+
// Create fresh RNG with same seed for first biscuit
281+
byte[] seed = {0, 0, 0, 0};
282+
SecureRandom rng1 = SecureRandom.getInstance("SHA1PRNG");
283+
rng1.setSeed(seed);
284+
Biscuit b1 = generateDeterministicBiscuit(rng1);
285+
286+
// Create fresh RNG with same seed for second biscuit to get identical signatures
287+
SecureRandom rng2 = SecureRandom.getInstance("SHA1PRNG");
288+
rng2.setSeed(seed);
289+
Biscuit b2 = generateDeterministicBiscuit(rng2);
290+
291+
assert (Arrays.equals(
292+
b1.serializedBiscuit.getAuthority().getSignature(),
293+
b2.serializedBiscuit.getAuthority().getSignature()));
294+
295+
assert (Arrays.equals(
296+
b1.serializedBiscuit.getBlocks().get(0).getSignature(),
297+
b2.serializedBiscuit.getBlocks().get(0).getSignature()));
298+
299+
byte[] data1 = b1.serialize();
300+
byte[] data2 = b2.serialize();
301+
assert (Arrays.equals(data1, data2));
302+
assertEquals(b1.print(), b2.print());
303+
304+
// Make sure that the token is still valid
305+
Authorizer authorizer = b1.authorizer();
306+
authorizer.addPolicy("allow if true");
307+
authorizer.authorize(new RunLimits(500, 100, Duration.ofMillis(500)));
308+
}
257309
}

0 commit comments

Comments
 (0)