diff --git a/src/com/amazon/corretto/crypto/provider/AmazonCorrettoCryptoProvider.java b/src/com/amazon/corretto/crypto/provider/AmazonCorrettoCryptoProvider.java index 11f7b78c..132d6e70 100644 --- a/src/com/amazon/corretto/crypto/provider/AmazonCorrettoCryptoProvider.java +++ b/src/com/amazon/corretto/crypto/provider/AmazonCorrettoCryptoProvider.java @@ -64,7 +64,6 @@ public final class AmazonCorrettoCryptoProvider extends java.security.Provider { private final boolean relyOnCachedSelfTestResults; private final boolean shouldRegisterEcParams; private final boolean shouldRegisterSecureRandom; - private final boolean shouldRegisterEdDSA; private final boolean shouldRegisterEdKeyFactory; private final boolean shouldRegisterMLDSA; private final Utils.NativeContextReleaseStrategy nativeContextReleaseStrategy; @@ -102,19 +101,16 @@ private void buildServiceMap() { addService("KeyFactory", "ML-DSA-87", "EvpKeyFactory$MLDSA"); } - if (shouldRegisterEdDSA) { - // KeyFactories are used to convert key encodings to Java Key objects. ACCP's KeyFactory for - // Ed25519 returns keys of type EvpEdPublicKey and EvpEdPrivateKey that do not implement - // EdECKey interface. One should register the KeyFactories from ACCP if they only use the - // output of the factories with ACCP's services. - // Once ACCP supports multi-release jar, then this option can be removed. - if (shouldRegisterEdKeyFactory) { - addService("KeyFactory", "EdDSA", "EvpKeyFactory$EdDSA"); - addService("KeyFactory", "Ed25519", "EvpKeyFactory$EdDSA"); - } - addService("KeyPairGenerator", "EdDSA", "EdGen"); - addService("KeyPairGenerator", "Ed25519", "EdGen"); + // KeyFactories are used to convert key encodings to Java Key objects. ACCP's KeyFactory for + // Ed25519 returns keys of type EvpEdPublicKey and EvpEdPrivateKey that do not implement + // EdECKey interface. One should register the KeyFactories from ACCP if they only use the + // output of the factories with ACCP's services. + if (shouldRegisterEdKeyFactory) { + addService("KeyFactory", "EdDSA", "EvpKeyFactory$EdDSA"); + addService("KeyFactory", "Ed25519", "EvpKeyFactory$EdDSA"); } + addService("KeyPairGenerator", "EdDSA", "EdGen"); + addService("KeyPairGenerator", "Ed25519", "EdGen"); final String hkdfSpi = "HkdfSecretKeyFactorySpi"; addService("SecretKeyFactory", HKDF_WITH_SHA1, hkdfSpi, false); @@ -216,11 +212,9 @@ private void addSignatures() { addService("Signature", "RSASSA-PSS", "EvpSignature$RSASSA_PSS"); addService("Signature", "NONEwithECDSA", "EvpSignatureRaw$NONEwithECDSA"); - if (shouldRegisterEdDSA) { - addService("Signature", "EdDSA", "EvpSignatureRaw$Ed25519"); - addService("Signature", "Ed25519", "EvpSignatureRaw$Ed25519"); - addService("Signature", "Ed25519ph", "EvpSignature$Ed25519ph"); - } + addService("Signature", "EdDSA", "EvpSignatureRaw$Ed25519"); + addService("Signature", "Ed25519", "EvpSignatureRaw$Ed25519"); + addService("Signature", "Ed25519ph", "EvpSignature$Ed25519ph"); if (shouldRegisterMLDSA) { addService("Signature", "ML-DSA", "EvpSignatureRaw$MLDSA"); @@ -511,10 +505,6 @@ public AmazonCorrettoCryptoProvider() { this.shouldRegisterSecureRandom = Utils.getBooleanProperty(PROPERTY_REGISTER_SECURE_RANDOM, true); - // The Java classes necessary for EdDSA are not included in Java versions < 15, so to compile - // successfully on older versions of Java we can only register EdDSA if JDK version >= 15. - this.shouldRegisterEdDSA = Utils.getJavaVersion() >= 15; - this.shouldRegisterEdKeyFactory = Utils.getBooleanProperty(PROPERTY_REGISTER_ED_KEYFACTORY, false); diff --git a/src/com/amazon/corretto/crypto/provider/EdGen.java b/src/com/amazon/corretto/crypto/provider/EdGen.java index cce43dc6..6a911880 100644 --- a/src/com/amazon/corretto/crypto/provider/EdGen.java +++ b/src/com/amazon/corretto/crypto/provider/EdGen.java @@ -6,6 +6,8 @@ import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGeneratorSpi; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; @@ -13,19 +15,22 @@ import java.security.spec.X509EncodedKeySpec; class EdGen extends KeyPairGeneratorSpi { - /** Generates a new Ed25519 key and returns a pointer to it. */ + // Generates a new Ed25519 key and returns a pointer to it encoded as a java |long| private static native long generateEvpEdKey(); private final AmazonCorrettoCryptoProvider provider_; - private final KeyFactory kf; + private KeyFactory kf; EdGen(AmazonCorrettoCryptoProvider provider) { Loader.checkNativeLibraryAvailability(); provider_ = provider; try { kf = KeyFactory.getInstance("EdDSA", "SunEC"); - } catch (final GeneralSecurityException e) { - throw new RuntimeException("Error setting up KeyPairGenerator", e); + } catch (final NoSuchAlgorithmException | NoSuchProviderException e) { + // This case indicates that either: + // 1.) The current JDK runtime version does not support EdDSA (i.e. JDK version <15) or + // 2.) No SunEC is registered with JCA + kf = null; } } @@ -35,10 +40,11 @@ public void initialize(final int keysize, final SecureRandom random) { @Override public KeyPair generateKeyPair() { - final EvpEdPrivateKey privateKey; - final EvpEdPublicKey publicKey; - privateKey = new EvpEdPrivateKey(generateEvpEdKey()); - publicKey = privateKey.getPublicKey(); + final EvpEdPrivateKey privateKey = new EvpEdPrivateKey(generateEvpEdKey()); + final EvpEdPublicKey publicKey = privateKey.getPublicKey(); + if (kf == null) { // This case indicates JDK EdDSA conditions as described in the constructor + return new KeyPair(publicKey, privateKey); + } try { final PKCS8EncodedKeySpec privateKeyPkcs8 = new PKCS8EncodedKeySpec(privateKey.getEncoded()); final X509EncodedKeySpec publicKeyX509 = new X509EncodedKeySpec(publicKey.getEncoded()); diff --git a/tst/com/amazon/corretto/crypto/provider/test/EvpKeyFactoryTest.java b/tst/com/amazon/corretto/crypto/provider/test/EvpKeyFactoryTest.java index 189eec6d..0c18536b 100644 --- a/tst/com/amazon/corretto/crypto/provider/test/EvpKeyFactoryTest.java +++ b/tst/com/amazon/corretto/crypto/provider/test/EvpKeyFactoryTest.java @@ -87,10 +87,12 @@ public static void setupParameters() throws Exception { for (String algorithm : ALGORITHMS) { KeyPairGenerator kpg; - if (algorithm.startsWith("ML-DSA")) { + if (algorithm.startsWith("ML-DSA") + || (algorithm.startsWith("Ed") && TestUtil.getJavaVersion() < 15)) { // JCE doesn't support ML-DSA until JDK24, and BouncyCastle currently - // serializes ML-DSA private keys via seeds. TODO: switch to - // BouncyCastle once BC supports CHOICE-encoded private keys + // serializes ML-DSA private keys via seeds. + // TODO: switch to BouncyCastle once BC supports CHOICE-encoded private keys + // Similarly, JDK doesn't support EdDSA/Ed25519 until JDK15 kpg = KeyPairGenerator.getInstance(algorithm, NATIVE_PROVIDER); } else { kpg = KeyPairGenerator.getInstance(algorithm); @@ -235,10 +237,12 @@ public void testX509Encoding(final KeyPair keyPair, final String testName) throw final KeyFactory nativeFactory = KeyFactory.getInstance(algorithm, NATIVE_PROVIDER); final KeyFactory jceFactory; - if (algorithm.startsWith("ML-DSA")) { + if (algorithm.startsWith("ML-DSA") + || (algorithm.startsWith("Ed") && TestUtil.getJavaVersion() < 15)) { // JCE doesn't support ML-DSA until JDK24, and BouncyCastle currently - // serializes ML-DSA private keys via seeds. TODO: switch to - // BouncyCastle once BC supports CHOICE-encoded private keys + // serializes ML-DSA private keys via seeds. + // TODO: switch to BouncyCastle once BC supports CHOICE-encoded private keys + // Similarly, JDK doesn't support EdDSA/Ed25519 until JDK15 jceFactory = KeyFactory.getInstance(algorithm, NATIVE_PROVIDER); } else { jceFactory = KeyFactory.getInstance(algorithm); @@ -312,10 +316,12 @@ public void testPKCS8Encoding(final KeyPair keyPair, final String testName) thro final KeyFactory nativeFactory = KeyFactory.getInstance(algorithm, NATIVE_PROVIDER); final KeyFactory jceFactory; - if (algorithm.startsWith("ML-DSA")) { + if (algorithm.startsWith("ML-DSA") + || (algorithm.startsWith("Ed") && TestUtil.getJavaVersion() < 15)) { // JCE doesn't support ML-DSA until JDK24, and BouncyCastle currently - // serializes ML-DSA private keys via seeds. TODO: switch to - // BouncyCastle once BC supports CHOICE-encoded private keys + // serializes ML-DSA private keys via seeds. + // TODO: switch to BouncyCastle once BC supports CHOICE-encoded private keys + // Similarly, JDK doesn't support EdDSA/Ed25519 until JDK15 jceFactory = KeyFactory.getInstance(algorithm, NATIVE_PROVIDER); } else { jceFactory = KeyFactory.getInstance(algorithm); diff --git a/tst/com/amazon/corretto/crypto/provider/test/RemoveDefaultProvidersTest.java b/tst/com/amazon/corretto/crypto/provider/test/RemoveDefaultProvidersTest.java new file mode 100644 index 00000000..706e1b79 --- /dev/null +++ b/tst/com/amazon/corretto/crypto/provider/test/RemoveDefaultProvidersTest.java @@ -0,0 +1,64 @@ +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package com.amazon.corretto.crypto.provider.test; + +import static com.amazon.corretto.crypto.provider.test.TestUtil.NATIVE_PROVIDER; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.Provider; +import java.security.Security; +import java.security.Signature; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; + +@Execution(ExecutionMode.CONCURRENT) +@ExtendWith(TestResultLogger.class) +@ResourceLock(value = TestUtil.RESOURCE_GLOBAL, mode = ResourceAccessMode.READ_WRITE) +// This test is used to prove that functionality tested within it is independent of +// default-registered JDK providers (SunEC, SunJCE, etc.). We take a global resource +// lock because other tests rely on default providers and JCA state is global. +public class RemoveDefaultProvidersTest { + private static List defaultProviders; + + @BeforeAll + static void removeAndStoreProviders() { + defaultProviders = new ArrayList<>(); + for (Provider provider : Security.getProviders()) { + defaultProviders.add(provider); + Security.removeProvider(provider.getName()); + } + assertTrue(Security.getProviders().length == 0); + } + + @AfterAll + static void restoreProviders() { + assertTrue(Security.getProviders().length == 0); + for (Provider provider : defaultProviders) { + Security.addProvider(provider); + } + } + + @Test + void testEdDSASignature() throws Exception { + final byte[] message = new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + final KeyPair keyPair = + KeyPairGenerator.getInstance("EdDSA", NATIVE_PROVIDER).generateKeyPair(); + final Signature signature = Signature.getInstance("EdDSA", NATIVE_PROVIDER); + signature.initSign(keyPair.getPrivate()); + signature.update(message); + final byte[] signatureBytes = signature.sign(); + signature.initVerify(keyPair.getPublic()); + signature.update(message); + assertTrue(signature.verify(signatureBytes)); + } +}