Skip to content

Commit 8675b8a

Browse files
authored
Merge pull request #135 from preuss-adam/apreuss/deterministic-ecdsa
Add support for deterministic ECDSA
2 parents 3ad37d7 + c639e59 commit 8675b8a

10 files changed

Lines changed: 212 additions & 59 deletions

File tree

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,25 @@ public KeyPair generate(SecureRandom rng) {
3535
new Factory() {
3636
@Override
3737
public KeyPair generate(byte[] bytes) throws Error.FormatError.InvalidKeySize {
38-
return new SECP256R1KeyPair(bytes);
38+
return new SECP256R1KeyPair(bytes, true);
3939
}
4040

4141
@Override
4242
public KeyPair generate(SecureRandom rng) {
43-
return new SECP256R1KeyPair(rng);
43+
return new SECP256R1KeyPair(rng, true);
44+
}
45+
};
46+
47+
public static final Factory DEFAULT_NONDETERMINISTIC_SECP256R1_FACTORY =
48+
new Factory() {
49+
@Override
50+
public KeyPair generate(byte[] bytes) throws Error.FormatError.InvalidKeySize {
51+
return new SECP256R1KeyPair(bytes, false);
52+
}
53+
54+
@Override
55+
public KeyPair generate(SecureRandom rng) {
56+
return new SECP256R1KeyPair(rng, false);
4457
}
4558
};
4659

@@ -69,9 +82,9 @@ public static KeyPair generate(Algorithm algorithm, byte[] bytes)
6982

7083
public static KeyPair generate(Algorithm algorithm, SecureRandom rng) {
7184
if (algorithm == Algorithm.Ed25519) {
72-
return ed25519Factory != null ? ed25519Factory.generate(rng) : new Ed25519KeyPair(rng);
85+
return ed25519Factory.generate(rng);
7386
} else if (algorithm == Algorithm.SECP256R1) {
74-
return secp256r1Factory != null ? secp256r1Factory.generate(rng) : new SECP256R1KeyPair(rng);
87+
return secp256r1Factory.generate(rng);
7588
} else {
7689
throw new IllegalArgumentException("Unsupported algorithm");
7790
}

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

Lines changed: 24 additions & 3 deletions
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;
@@ -30,12 +31,16 @@ final class SECP256R1KeyPair extends KeyPair {
3031

3132
private final BCECPrivateKey privateKey;
3233
private final BCECPublicKey publicKey;
34+
private final boolean deterministicNonce;
3335

3436
static final String ALGORITHM = "ECDSA";
3537
static final String CURVE = "secp256r1";
3638
static final ECNamedCurveParameterSpec SECP256R1 = ECNamedCurveTable.getParameterSpec(CURVE);
3739

38-
SECP256R1KeyPair(byte[] bytes) throws Error.FormatError.InvalidKeySize {
40+
SECP256R1KeyPair(byte[] bytes, boolean deterministicNonce)
41+
throws Error.FormatError.InvalidKeySize {
42+
this.deterministicNonce = deterministicNonce;
43+
3944
if (bytes.length != BUFFER_SIZE) {
4045
throw new Error.FormatError.InvalidKeySize(bytes.length);
4146
}
@@ -51,7 +56,9 @@ final class SECP256R1KeyPair extends KeyPair {
5156
this.publicKey = publicKey;
5257
}
5358

54-
SECP256R1KeyPair(SecureRandom rng) {
59+
SECP256R1KeyPair(SecureRandom rng, boolean deterministicNonce) {
60+
this.deterministicNonce = deterministicNonce;
61+
5562
byte[] bytes = new byte[BUFFER_SIZE];
5663
rng.nextBytes(bytes);
5764

@@ -67,14 +74,28 @@ final class SECP256R1KeyPair extends KeyPair {
6774
this.publicKey = publicKey;
6875
}
6976

77+
/// By default sign message digests with a deterministic k
78+
/// computed using the algorithm described in [RFC6979 § 3.2].
79+
///
80+
/// [RFC6979 § 3.2]: https://tools.ietf.org/html/rfc6979#section-3
81+
///
82+
/// Although deterministic ECDSA signing is typically slower than
83+
/// signing with an RNG, it prevents accidental nonce-reuse due to
84+
/// a weak RNG.
7085
@Override
7186
public byte[] sign(byte[] data) {
7287
var digest = new SHA256Digest();
7388
digest.update(data, 0, data.length);
7489
var hash = new byte[digest.getDigestSize()];
7590
digest.doFinal(hash, 0);
7691

77-
var signer = new ECDSASigner();
92+
ECDSASigner signer;
93+
if (deterministicNonce) {
94+
signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
95+
} else {
96+
signer = new ECDSASigner();
97+
}
98+
7899
signer.init(true, privateKey.engineGetKeyParameters());
79100
var sig = signer.generateSignature(hash);
80101

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

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

358364
// no need to verify again, we are already working from a verified token
359365
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
@@ -272,14 +272,20 @@ public ThirdPartyBlockRequest thirdPartyRequest() {
272272
public UnverifiedBiscuit appendThirdPartyBlock(
273273
PublicKey externalKey, ThirdPartyBlockContents blockResponse)
274274
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
275+
return appendThirdPartyBlock(externalKey, blockResponse, new SecureRandom());
276+
}
277+
278+
public UnverifiedBiscuit appendThirdPartyBlock(
279+
PublicKey externalKey, ThirdPartyBlockContents blockResponse, SecureRandom rng)
280+
throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, Error {
275281
SignedBlock previousBlock;
276282
if (this.serializedBiscuit.getBlocks().isEmpty()) {
277283
previousBlock = this.serializedBiscuit.getAuthority();
278284
} else {
279285
previousBlock =
280286
this.serializedBiscuit.getBlocks().get(this.serializedBiscuit.getBlocks().size() - 1);
281287
}
282-
KeyPair nextKeyPair = KeyPair.generate(previousBlock.getKey().getAlgorithm());
288+
KeyPair nextKeyPair = KeyPair.generate(previousBlock.getKey().getAlgorithm(), rng);
283289
byte[] payload =
284290
BlockSignatureBuffer.generateExternalBlockSignaturePayloadV1(
285291
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);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) 2019 Geoffroy Couprie <contact@geoffroycouprie.com> and Contributors to the Eclipse Foundation.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.eclipse.biscuit.token;
7+
8+
import static org.eclipse.biscuit.token.builder.Utils.fact;
9+
import static org.eclipse.biscuit.token.builder.Utils.str;
10+
11+
import biscuit.format.schema.Schema;
12+
import java.security.InvalidKeyException;
13+
import java.security.NoSuchAlgorithmException;
14+
import java.security.SecureRandom;
15+
import java.security.SignatureException;
16+
import java.util.List;
17+
import org.eclipse.biscuit.crypto.KeyPair;
18+
import org.eclipse.biscuit.error.Error;
19+
import org.eclipse.biscuit.token.builder.Block;
20+
import org.junit.jupiter.api.AfterAll;
21+
import org.junit.jupiter.api.BeforeAll;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.parallel.Isolated;
24+
25+
/** Top-level test to ensure ECDSA with nondeterministic nonce also works. */
26+
@Isolated
27+
public class NondeterministicEcdsaTest {
28+
@BeforeAll
29+
static void beforeAll() {
30+
KeyPair.setSECP256R1Factory(KeyPair.DEFAULT_NONDETERMINISTIC_SECP256R1_FACTORY);
31+
}
32+
33+
@AfterAll
34+
static void afterAll() {
35+
KeyPair.setSECP256R1Factory(KeyPair.DEFAULT_SECP256R1_FACTORY);
36+
}
37+
38+
@Test
39+
public void simpleSigningTest()
40+
throws Error, NoSuchAlgorithmException, SignatureException, InvalidKeyException {
41+
var root = KeyPair.generate(Schema.PublicKey.Algorithm.SECP256R1);
42+
var b =
43+
Biscuit.make(
44+
new SecureRandom(),
45+
root,
46+
new Block().addFact(fact("foo", List.of(str("bar")))).build());
47+
Biscuit.fromBytes(b.serialize(), root.getPublicKey());
48+
}
49+
}

0 commit comments

Comments
 (0)