Skip to content

Commit 0a05b47

Browse files
Enable standalone ACCP Ed25519 (#438)
PR #394 deliberately [coupled][1] ACCP's Ed25519 key generation to SunEC's KeyFactory in the hopes of increasing compatibility. However, this also prevents the use of Ed25519 in standalone ACCP. This PR provides a fallback mechanism to allow Ed25519 keys to be generated by standalone ACCP without relying on SunEC. If SunEC has a relevant KeyFactory registered, the old behavior is retained for backwards compatibility. Conveniently, this fallback mechanism also allows us to provide Ed25519 support on JDK versions < 15. We add a test that de- and re-registers default providers to prove Ed25519 signatures can be performed by standalone ACCP with an empty JCA registry. [1]: #394 (comment)
1 parent 3abb411 commit 0a05b47

File tree

4 files changed

+110
-39
lines changed

4 files changed

+110
-39
lines changed

src/com/amazon/corretto/crypto/provider/AmazonCorrettoCryptoProvider.java

+12-22
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ public final class AmazonCorrettoCryptoProvider extends java.security.Provider {
6464
private final boolean relyOnCachedSelfTestResults;
6565
private final boolean shouldRegisterEcParams;
6666
private final boolean shouldRegisterSecureRandom;
67-
private final boolean shouldRegisterEdDSA;
6867
private final boolean shouldRegisterEdKeyFactory;
6968
private final boolean shouldRegisterMLDSA;
7069
private final Utils.NativeContextReleaseStrategy nativeContextReleaseStrategy;
@@ -102,19 +101,16 @@ private void buildServiceMap() {
102101
addService("KeyFactory", "ML-DSA-87", "EvpKeyFactory$MLDSA");
103102
}
104103

105-
if (shouldRegisterEdDSA) {
106-
// KeyFactories are used to convert key encodings to Java Key objects. ACCP's KeyFactory for
107-
// Ed25519 returns keys of type EvpEdPublicKey and EvpEdPrivateKey that do not implement
108-
// EdECKey interface. One should register the KeyFactories from ACCP if they only use the
109-
// output of the factories with ACCP's services.
110-
// Once ACCP supports multi-release jar, then this option can be removed.
111-
if (shouldRegisterEdKeyFactory) {
112-
addService("KeyFactory", "EdDSA", "EvpKeyFactory$EdDSA");
113-
addService("KeyFactory", "Ed25519", "EvpKeyFactory$EdDSA");
114-
}
115-
addService("KeyPairGenerator", "EdDSA", "EdGen");
116-
addService("KeyPairGenerator", "Ed25519", "EdGen");
104+
// KeyFactories are used to convert key encodings to Java Key objects. ACCP's KeyFactory for
105+
// Ed25519 returns keys of type EvpEdPublicKey and EvpEdPrivateKey that do not implement
106+
// EdECKey interface. One should register the KeyFactories from ACCP if they only use the
107+
// output of the factories with ACCP's services.
108+
if (shouldRegisterEdKeyFactory) {
109+
addService("KeyFactory", "EdDSA", "EvpKeyFactory$EdDSA");
110+
addService("KeyFactory", "Ed25519", "EvpKeyFactory$EdDSA");
117111
}
112+
addService("KeyPairGenerator", "EdDSA", "EdGen");
113+
addService("KeyPairGenerator", "Ed25519", "EdGen");
118114

119115
final String hkdfSpi = "HkdfSecretKeyFactorySpi";
120116
addService("SecretKeyFactory", HKDF_WITH_SHA1, hkdfSpi, false);
@@ -216,11 +212,9 @@ private void addSignatures() {
216212

217213
addService("Signature", "RSASSA-PSS", "EvpSignature$RSASSA_PSS");
218214
addService("Signature", "NONEwithECDSA", "EvpSignatureRaw$NONEwithECDSA");
219-
if (shouldRegisterEdDSA) {
220-
addService("Signature", "EdDSA", "EvpSignatureRaw$Ed25519");
221-
addService("Signature", "Ed25519", "EvpSignatureRaw$Ed25519");
222-
addService("Signature", "Ed25519ph", "EvpSignature$Ed25519ph");
223-
}
215+
addService("Signature", "EdDSA", "EvpSignatureRaw$Ed25519");
216+
addService("Signature", "Ed25519", "EvpSignatureRaw$Ed25519");
217+
addService("Signature", "Ed25519ph", "EvpSignature$Ed25519ph");
224218

225219
if (shouldRegisterMLDSA) {
226220
addService("Signature", "ML-DSA", "EvpSignatureRaw$MLDSA");
@@ -516,10 +510,6 @@ public AmazonCorrettoCryptoProvider() {
516510
this.shouldRegisterSecureRandom =
517511
Utils.getBooleanProperty(PROPERTY_REGISTER_SECURE_RANDOM, true);
518512

519-
// The Java classes necessary for EdDSA are not included in Java versions < 15, so to compile
520-
// successfully on older versions of Java we can only register EdDSA if JDK version >= 15.
521-
this.shouldRegisterEdDSA = Utils.getJavaVersion() >= 15;
522-
523513
this.shouldRegisterEdKeyFactory =
524514
Utils.getBooleanProperty(PROPERTY_REGISTER_ED_KEYFACTORY, false);
525515

src/com/amazon/corretto/crypto/provider/EdGen.java

+14-8
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,31 @@
66
import java.security.KeyFactory;
77
import java.security.KeyPair;
88
import java.security.KeyPairGeneratorSpi;
9+
import java.security.NoSuchAlgorithmException;
10+
import java.security.NoSuchProviderException;
911
import java.security.PrivateKey;
1012
import java.security.PublicKey;
1113
import java.security.SecureRandom;
1214
import java.security.spec.PKCS8EncodedKeySpec;
1315
import java.security.spec.X509EncodedKeySpec;
1416

1517
class EdGen extends KeyPairGeneratorSpi {
16-
/** Generates a new Ed25519 key and returns a pointer to it. */
18+
// Generates a new Ed25519 key and returns a pointer to it encoded as a java |long|
1719
private static native long generateEvpEdKey();
1820

1921
private final AmazonCorrettoCryptoProvider provider_;
20-
private final KeyFactory kf;
22+
private KeyFactory kf;
2123

2224
EdGen(AmazonCorrettoCryptoProvider provider) {
2325
Loader.checkNativeLibraryAvailability();
2426
provider_ = provider;
2527
try {
2628
kf = KeyFactory.getInstance("EdDSA", "SunEC");
27-
} catch (final GeneralSecurityException e) {
28-
throw new RuntimeException("Error setting up KeyPairGenerator", e);
29+
} catch (final NoSuchAlgorithmException | NoSuchProviderException e) {
30+
// This case indicates that either:
31+
// 1.) The current JDK runtime version does not support EdDSA (i.e. JDK version <15) or
32+
// 2.) No SunEC is registered with JCA
33+
kf = null;
2934
}
3035
}
3136

@@ -35,10 +40,11 @@ public void initialize(final int keysize, final SecureRandom random) {
3540

3641
@Override
3742
public KeyPair generateKeyPair() {
38-
final EvpEdPrivateKey privateKey;
39-
final EvpEdPublicKey publicKey;
40-
privateKey = new EvpEdPrivateKey(generateEvpEdKey());
41-
publicKey = privateKey.getPublicKey();
43+
final EvpEdPrivateKey privateKey = new EvpEdPrivateKey(generateEvpEdKey());
44+
final EvpEdPublicKey publicKey = privateKey.getPublicKey();
45+
if (kf == null) { // This case indicates JDK EdDSA conditions as described in the constructor
46+
return new KeyPair(publicKey, privateKey);
47+
}
4248
try {
4349
final PKCS8EncodedKeySpec privateKeyPkcs8 = new PKCS8EncodedKeySpec(privateKey.getEncoded());
4450
final X509EncodedKeySpec publicKeyX509 = new X509EncodedKeySpec(publicKey.getEncoded());

tst/com/amazon/corretto/crypto/provider/test/EvpKeyFactoryTest.java

+15-9
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,12 @@ public static void setupParameters() throws Exception {
8787

8888
for (String algorithm : ALGORITHMS) {
8989
KeyPairGenerator kpg;
90-
if (algorithm.startsWith("ML-DSA")) {
90+
if (algorithm.startsWith("ML-DSA")
91+
|| (algorithm.startsWith("Ed") && TestUtil.getJavaVersion() < 15)) {
9192
// JCE doesn't support ML-DSA until JDK24, and BouncyCastle currently
92-
// serializes ML-DSA private keys via seeds. TODO: switch to
93-
// BouncyCastle once BC supports CHOICE-encoded private keys
93+
// serializes ML-DSA private keys via seeds.
94+
// TODO: switch to BouncyCastle once BC supports CHOICE-encoded private keys
95+
// Similarly, JDK doesn't support EdDSA/Ed25519 until JDK15
9496
kpg = KeyPairGenerator.getInstance(algorithm, NATIVE_PROVIDER);
9597
} else {
9698
kpg = KeyPairGenerator.getInstance(algorithm);
@@ -235,10 +237,12 @@ public void testX509Encoding(final KeyPair keyPair, final String testName) throw
235237

236238
final KeyFactory nativeFactory = KeyFactory.getInstance(algorithm, NATIVE_PROVIDER);
237239
final KeyFactory jceFactory;
238-
if (algorithm.startsWith("ML-DSA")) {
240+
if (algorithm.startsWith("ML-DSA")
241+
|| (algorithm.startsWith("Ed") && TestUtil.getJavaVersion() < 15)) {
239242
// JCE doesn't support ML-DSA until JDK24, and BouncyCastle currently
240-
// serializes ML-DSA private keys via seeds. TODO: switch to
241-
// BouncyCastle once BC supports CHOICE-encoded private keys
243+
// serializes ML-DSA private keys via seeds.
244+
// TODO: switch to BouncyCastle once BC supports CHOICE-encoded private keys
245+
// Similarly, JDK doesn't support EdDSA/Ed25519 until JDK15
242246
jceFactory = KeyFactory.getInstance(algorithm, NATIVE_PROVIDER);
243247
} else {
244248
jceFactory = KeyFactory.getInstance(algorithm);
@@ -312,10 +316,12 @@ public void testPKCS8Encoding(final KeyPair keyPair, final String testName) thro
312316

313317
final KeyFactory nativeFactory = KeyFactory.getInstance(algorithm, NATIVE_PROVIDER);
314318
final KeyFactory jceFactory;
315-
if (algorithm.startsWith("ML-DSA")) {
319+
if (algorithm.startsWith("ML-DSA")
320+
|| (algorithm.startsWith("Ed") && TestUtil.getJavaVersion() < 15)) {
316321
// JCE doesn't support ML-DSA until JDK24, and BouncyCastle currently
317-
// serializes ML-DSA private keys via seeds. TODO: switch to
318-
// BouncyCastle once BC supports CHOICE-encoded private keys
322+
// serializes ML-DSA private keys via seeds.
323+
// TODO: switch to BouncyCastle once BC supports CHOICE-encoded private keys
324+
// Similarly, JDK doesn't support EdDSA/Ed25519 until JDK15
319325
jceFactory = KeyFactory.getInstance(algorithm, NATIVE_PROVIDER);
320326
} else {
321327
jceFactory = KeyFactory.getInstance(algorithm);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package com.amazon.corretto.crypto.provider.test;
4+
5+
import static com.amazon.corretto.crypto.provider.test.TestUtil.NATIVE_PROVIDER;
6+
import static org.junit.jupiter.api.Assertions.assertEquals;
7+
import static org.junit.jupiter.api.Assertions.assertTrue;
8+
9+
import java.security.KeyPair;
10+
import java.security.KeyPairGenerator;
11+
import java.security.Provider;
12+
import java.security.Security;
13+
import java.security.Signature;
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
import org.junit.jupiter.api.AfterAll;
17+
import org.junit.jupiter.api.BeforeAll;
18+
import org.junit.jupiter.api.Test;
19+
import org.junit.jupiter.api.extension.ExtendWith;
20+
import org.junit.jupiter.api.parallel.Execution;
21+
import org.junit.jupiter.api.parallel.ExecutionMode;
22+
import org.junit.jupiter.api.parallel.Isolated;
23+
import org.junit.jupiter.api.parallel.ResourceAccessMode;
24+
import org.junit.jupiter.api.parallel.ResourceLock;
25+
26+
@ExtendWith(TestResultLogger.class)
27+
@Isolated("RemoveDefaultProvidersTest modifies global JCA state, so must be run in isolation.")
28+
@Execution(ExecutionMode.SAME_THREAD)
29+
@ResourceLock(value = TestUtil.RESOURCE_GLOBAL, mode = ResourceAccessMode.READ_WRITE)
30+
// This test is used to prove that functionality tested within it is independent of
31+
// default-registered JDK providers (SunEC, SunJCE, etc.). We take a global resource
32+
// lock because other tests rely on default providers and JCA state is global.
33+
public class RemoveDefaultProvidersTest {
34+
private static List<Provider> defaultProviders;
35+
36+
@BeforeAll
37+
static void removeAndStoreProviders() {
38+
defaultProviders = new ArrayList<>();
39+
for (Provider provider : Security.getProviders()) {
40+
defaultProviders.add(provider);
41+
Security.removeProvider(provider.getName());
42+
}
43+
assertTrue(Security.getProviders().length == 0);
44+
}
45+
46+
@AfterAll
47+
static void restoreProviders() {
48+
assertTrue(Security.getProviders().length == 0);
49+
int i = 1;
50+
for (Provider provider : defaultProviders) {
51+
assertEquals(i, Security.addProvider(provider));
52+
i++;
53+
}
54+
}
55+
56+
@Test
57+
void testEdDSASignature() throws Exception {
58+
final byte[] message = new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
59+
final KeyPair keyPair =
60+
KeyPairGenerator.getInstance("EdDSA", NATIVE_PROVIDER).generateKeyPair();
61+
final Signature signature = Signature.getInstance("EdDSA", NATIVE_PROVIDER);
62+
signature.initSign(keyPair.getPrivate());
63+
signature.update(message);
64+
final byte[] signatureBytes = signature.sign();
65+
signature.initVerify(keyPair.getPublic());
66+
signature.update(message);
67+
assertTrue(signature.verify(signatureBytes));
68+
}
69+
}

0 commit comments

Comments
 (0)