Skip to content

Commit 134ba27

Browse files
Ed25519 signer implementations (#596)
Signed-off-by: Mark S. Lewis <[email protected]>
1 parent f379584 commit 134ba27

File tree

15 files changed

+270
-151
lines changed

15 files changed

+270
-151
lines changed

java/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ A suitable gRPC channel service provider must also be specified (as described in
3838
<dependency>
3939
<groupId>io.grpc</groupId>
4040
<artifactId>grpc-netty-shaded</artifactId>
41-
<version>1.54.1</version>
41+
<version>1.55.1</version>
4242
<scope>runtime</scope>
4343
</dependency>
4444
```
@@ -54,7 +54,7 @@ implementation 'org.hyperledger.fabric:fabric-gateway:1.2.2'
5454
A suitable gRPC channel service provider must also be specified (as described in the [gRPC security documentation](https://github.com/grpc/grpc-java/blob/master/SECURITY.md#transport-security-tls)), such as:
5555

5656
```groovy
57-
runtimeOnly 'io.grpc:grpc-netty-shaded:1.54.1'
57+
runtimeOnly 'io.grpc:grpc-netty-shaded:1.55.1'
5858
```
5959

6060
## Compatibility

java/pom.xml

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@
3838
<properties>
3939
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
4040
<javaVersion>8</javaVersion>
41-
<grpcVersion>1.54.1</grpcVersion>
42-
<protobufVersion>3.21.12</protobufVersion> <!-- keep major/minor version in step with one used by io.grpc:grpc-protobuf -->
41+
<grpcVersion>1.55.1</grpcVersion>
4342
<enforceJavaVersion>1.8</enforceJavaVersion> <!-- this is overridden in the release profile -->
4443
<bouncyCastleVersion>1.73</bouncyCastleVersion>
4544
<skipUnitTests>${skipTests}</skipUnitTests>
@@ -68,13 +67,6 @@
6867
<type>pom</type>
6968
<scope>import</scope>
7069
</dependency>
71-
<dependency>
72-
<groupId>com.google.protobuf</groupId>
73-
<artifactId>protobuf-bom</artifactId>
74-
<version>${protobufVersion}</version>
75-
<type>pom</type>
76-
<scope>import</scope>
77-
</dependency>
7870
</dependencies>
7971
</dependencyManagement>
8072

@@ -121,12 +113,6 @@
121113
<artifactId>bcprov-jdk18on</artifactId>
122114
<version>${bouncyCastleVersion}</version>
123115
</dependency>
124-
<dependency> <!-- Necessary for Java 9+, according to https://github.com/grpc/grpc-java#readme -->
125-
<groupId>org.apache.tomcat</groupId>
126-
<artifactId>annotations-api</artifactId>
127-
<version>6.0.53</version>
128-
<scope>provided</scope>
129-
</dependency>
130116
<dependency>
131117
<groupId>io.grpc</groupId>
132118
<artifactId>grpc-netty-shaded</artifactId>
@@ -140,10 +126,6 @@
140126
<groupId>io.grpc</groupId>
141127
<artifactId>grpc-stub</artifactId>
142128
</dependency>
143-
<dependency>
144-
<groupId>com.google.protobuf</groupId>
145-
<artifactId>protobuf-java</artifactId>
146-
</dependency>
147129
<dependency>
148130
<groupId>com.google.code.gson</groupId>
149131
<artifactId>gson</artifactId>
@@ -180,6 +162,9 @@
180162
<requireJavaVersion>
181163
<version>${enforceJavaVersion}</version>
182164
</requireJavaVersion>
165+
<requireMavenVersion>
166+
<version>3.6.3</version>
167+
</requireMavenVersion>
183168
</rules>
184169
</configuration>
185170
</execution>
@@ -216,12 +201,12 @@
216201
</plugin>
217202
<plugin>
218203
<artifactId>maven-surefire-plugin</artifactId>
219-
<version>3.0.0</version>
204+
<version>3.1.0</version>
220205
<dependencies>
221206
<dependency>
222207
<groupId>me.fabriciorby</groupId>
223208
<artifactId>maven-surefire-junit5-tree-reporter</artifactId>
224-
<version>1.1.0</version>
209+
<version>1.2.1</version>
225210
</dependency>
226211
</dependencies>
227212
<configuration>
@@ -295,20 +280,10 @@
295280
</execution>
296281
</executions>
297282
</plugin>
298-
<plugin>
299-
<groupId>org.apache.maven.plugins</groupId>
300-
<artifactId>maven-dependency-plugin</artifactId>
301-
<version>3.6.0</version>
302-
</plugin>
303-
<plugin>
304-
<groupId>org.apache.maven.plugins</groupId>
305-
<artifactId>maven-resources-plugin</artifactId>
306-
<version>3.3.1</version>
307-
</plugin>
308283
<plugin>
309284
<groupId>org.apache.maven.plugins</groupId>
310285
<artifactId>maven-source-plugin</artifactId>
311-
<version>3.2.1</version>
286+
<version>3.3.0</version>
312287
<executions>
313288
<execution>
314289
<id>attach-sources</id>
@@ -348,6 +323,15 @@
348323
<additionalJavadocOpts>--no-module-directories</additionalJavadocOpts>
349324
</properties>
350325
</profile>
326+
<profile>
327+
<id>maven-compiler-release</id>
328+
<activation>
329+
<jdk>[9,)</jdk>
330+
</activation>
331+
<properties>
332+
<maven.compiler.release>${javaVersion}</maven.compiler.release>
333+
</properties>
334+
</profile>
351335
<profile>
352336
<id>checkstyle</id>
353337
<activation>
@@ -378,7 +362,7 @@
378362
<dependency>
379363
<groupId>com.puppycrawl.tools</groupId>
380364
<artifactId>checkstyle</artifactId>
381-
<version>10.10.0</version>
365+
<version>10.12.0</version>
382366
</dependency>
383367
</dependencies>
384368
</plugin>

java/src/main/java/org/hyperledger/fabric/client/identity/ECPrivateKeySigner.java

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,33 @@
66

77
package org.hyperledger.fabric.client.identity;
88

9+
import org.bouncycastle.asn1.ASN1Integer;
10+
911
import java.math.BigInteger;
1012
import java.security.GeneralSecurityException;
11-
import java.security.Provider;
12-
import java.security.Signature;
1313
import java.security.interfaces.ECPrivateKey;
1414

15-
import org.bouncycastle.asn1.ASN1Integer;
16-
import org.bouncycastle.jce.provider.BouncyCastleProvider;
17-
1815
final class ECPrivateKeySigner implements Signer {
19-
private static final Provider PROVIDER = new BouncyCastleProvider();
2016
private static final String ALGORITHM_NAME = "NONEwithECDSA";
2117

22-
private final ECPrivateKey privateKey;
18+
private final Signer signer;
2319
private final BigInteger curveN;
2420
private final BigInteger halfCurveN;
2521

2622
ECPrivateKeySigner(final ECPrivateKey privateKey) {
27-
this.privateKey = privateKey;
23+
signer = new PrivateKeySigner(privateKey, ALGORITHM_NAME);
2824
curveN = privateKey.getParams().getOrder();
2925
halfCurveN = curveN.divide(BigInteger.valueOf(2));
3026
}
3127

3228
@Override
3329
public byte[] sign(final byte[] digest) throws GeneralSecurityException {
34-
byte[] rawSignature = generateSignature(digest);
30+
byte[] rawSignature = signer.sign(digest);
3531
ECSignature signature = ECSignature.fromBytes(rawSignature);
3632
signature = preventMalleability(signature);
3733
return signature.getBytes();
3834
}
3935

40-
private byte[] generateSignature(final byte[] digest) throws GeneralSecurityException {
41-
Signature signer = Signature.getInstance(ALGORITHM_NAME, PROVIDER);
42-
signer.initSign(privateKey);
43-
signer.update(digest);
44-
return signer.sign();
45-
}
46-
4736
private ECSignature preventMalleability(final ECSignature signature) {
4837
BigInteger s = signature.getS().getValue();
4938
if (s.compareTo(halfCurveN) > 0) {
@@ -52,5 +41,4 @@ private ECSignature preventMalleability(final ECSignature signature) {
5241
}
5342
return signature;
5443
}
55-
5644
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2023 IBM All Rights Reserved.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package org.hyperledger.fabric.client.identity;
8+
9+
import org.bouncycastle.jce.provider.BouncyCastleProvider;
10+
11+
import java.security.GeneralSecurityException;
12+
import java.security.PrivateKey;
13+
import java.security.Provider;
14+
import java.security.Signature;
15+
16+
final class PrivateKeySigner implements Signer {
17+
private static final Provider PROVIDER = new BouncyCastleProvider();
18+
19+
private final PrivateKey privateKey;
20+
private final String algorithm;
21+
22+
PrivateKeySigner(final PrivateKey privateKey, final String algorithm) {
23+
this.privateKey = privateKey;
24+
this.algorithm = algorithm;
25+
}
26+
27+
@Override
28+
public byte[] sign(final byte[] digest) throws GeneralSecurityException {
29+
Signature signer = Signature.getInstance(algorithm, PROVIDER);
30+
signer.initSign(privateKey);
31+
signer.update(digest);
32+
return signer.sign();
33+
}
34+
}

java/src/main/java/org/hyperledger/fabric/client/identity/Signers.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,40 @@
1313
* Factory methods to create standard signing implementations.
1414
*/
1515
public final class Signers {
16+
private static final String ED25519_ALGORITHM = "Ed25519";
17+
1618
/**
1719
* Create a new signer that uses the supplied private key for signing. The {@link Identities} class provides static
1820
* methods to create a {@code PrivateKey} object from PEM-format data.
21+
*
22+
* <p>Currently supported private key types are:</p>
23+
* <ul>
24+
* <li>ECDSA.</li>
25+
* <li>Ed25519.</li>
26+
* </ul>
27+
*
28+
* <p>Note that the Sign implementations have different expectations on the input data supplied to them.</p>
29+
*
30+
* <p>The ECDSA signers operate on a pre-computed message digest, and should be combined with an appropriate hash
31+
* algorithm. P-256 is typically used with a SHA-256 hash, and P-384 is typically used with a SHA-384 hash.</p>
32+
*
33+
* <p>The Ed25519 signer operates on the full message content, and should be combined with a
34+
* {@link org.hyperledger.fabric.client.Hash#NONE NONE} (or no-op) hash implementation to ensure the complete
35+
* message is passed to the signer.</p>
1936
* @param privateKey A private key.
2037
* @return A signer implementation.
2138
*/
2239
public static Signer newPrivateKeySigner(final PrivateKey privateKey) {
2340
if (privateKey instanceof ECPrivateKey) {
2441
return new ECPrivateKeySigner((ECPrivateKey) privateKey);
25-
} else {
26-
throw new IllegalArgumentException("Unsupported private key type: " + privateKey.getClass().getTypeName());
2742
}
43+
44+
if (ED25519_ALGORITHM.equals(privateKey.getAlgorithm())) {
45+
return new PrivateKeySigner(privateKey, ED25519_ALGORITHM);
46+
}
47+
48+
throw new IllegalArgumentException("Unsupported private key type: " + privateKey.getClass().getTypeName()
49+
+ " (" + privateKey.getAlgorithm() + ")");
2850
}
2951

3052
// Private constructor to prevent instantiation

java/src/test/java/org/hyperledger/fabric/client/identity/SignerTest.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2525

2626
public final class SignerTest {
27-
private static final X509Credentials CREDENTIALS = new X509Credentials();
2827
private static final Provider PROVIDER = new BouncyCastleProvider();
2928
private static final byte[] MESSAGE = "MESSAGE".getBytes(StandardCharsets.UTF_8);
3029

@@ -48,28 +47,40 @@ void new_signer_from_unsupported_private_key_type_throws_IllegalArgumentExceptio
4847

4948
@Test
5049
void sign_with_P256_key() throws GeneralSecurityException {
51-
Signer signer = Signers.newPrivateKeySigner(CREDENTIALS.getPrivateKey());
50+
X509Credentials credentials = new X509Credentials();
51+
Signer signer = Signers.newPrivateKeySigner(credentials.getPrivateKey());
5252
byte[] signature = signer.sign(Hash.SHA256.apply(MESSAGE));
5353

5454
Signature verifier = Signature.getInstance("SHA256withECDSA", PROVIDER);
55-
assertValidSignature(verifier, CREDENTIALS.getCertificate(), signature);
55+
assertValidSignature(verifier, credentials.getCertificate(), signature);
5656
}
5757

5858
@Test
5959
void sign_null_digest_throws_NullPointerException() {
60-
Signer signer = Signers.newPrivateKeySigner(CREDENTIALS.getPrivateKey());
60+
X509Credentials credentials = new X509Credentials();
61+
Signer signer = Signers.newPrivateKeySigner(credentials.getPrivateKey());
6162

6263
assertThatThrownBy(() -> signer.sign(null))
6364
.isInstanceOf(NullPointerException.class);
6465
}
6566

6667
@Test
6768
void sign_with_P384_key() throws GeneralSecurityException {
68-
X509Credentials credentials = new X509Credentials("P-384");
69+
X509Credentials credentials = new X509Credentials(X509Credentials.Curve.P384);
6970
Signer signer = Signers.newPrivateKeySigner(credentials.getPrivateKey());
7071
byte[] signature = signer.sign(Hash.SHA384.apply(MESSAGE));
7172

7273
Signature verifier = Signature.getInstance("SHA384withECDSA", PROVIDER);
7374
assertValidSignature(verifier, credentials.getCertificate(), signature);
7475
}
76+
77+
@Test
78+
void sign_with_Ed25519_key() throws GeneralSecurityException {
79+
X509Credentials credentials = new X509Credentials(X509Credentials.Curve.Ed25519);
80+
Signer signer = Signers.newPrivateKeySigner(credentials.getPrivateKey());
81+
byte[] signature = signer.sign(MESSAGE);
82+
83+
Signature verifier = Signature.getInstance(credentials.getPrivateKey().getAlgorithm(), PROVIDER);
84+
assertValidSignature(verifier, credentials.getCertificate(), signature);
85+
}
7586
}

0 commit comments

Comments
 (0)