From c1709e7d7c05051c071af5a6d9178c4dfb4418fc Mon Sep 17 00:00:00 2001 From: Amir Vakili <114409643+amirhosv@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:36:42 -0500 Subject: [PATCH] Make Ed25519 KeyFactory registration Opt-in (#410) + The keys generated by KeyFactory of ACCP for Ed25519 do not implemet EdEcKey interface from JCA since this interface is not present to JDKs prior to 15. --- CMakeLists.txt | 15 ++++++- README.md | 10 ++++- .../AmazonCorrettoCryptoProvider.java | 26 +++++++++-- .../crypto/provider/test/EdDSATest.java | 44 +++++++++++++++++++ .../crypto/provider/test/TestUtil.java | 5 +++ 5 files changed, 95 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fadc0ff7..c3f58cda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -824,6 +824,18 @@ add_custom_target(check-install-via-properties-with-debug DEPENDS accp-jar tests-jar) +add_custom_target(check-junit-edKeyFactory + COMMAND ${TEST_JAVA_EXECUTABLE} + -Dcom.amazon.corretto.crypto.provider.registerEdKeyFactory=true + ${TEST_RUNNER_ARGUMENTS} + --select-class=com.amazon.corretto.crypto.provider.test.EdDSATest + --select-class=com.amazon.corretto.crypto.provider.test.EvpKeyFactoryTest + --select-class=com.amazon.corretto.crypto.provider.test.EvpSignatureSpecificTest + --select-class=com.amazon.corretto.crypto.provider.test.EvpSignatureTest + --select-class=com.amazon.corretto.crypto.provider.test.KeyReuseThreadStormTest + + DEPENDS accp-jar tests-jar) + set(check_targets check-recursive-init check-install-via-properties check-install-via-properties-with-debug @@ -832,7 +844,8 @@ set(check_targets check-recursive-init check-external-lib check-junit-AesLazy check-junit-AesEager - check-junit-DifferentTempDir) + check-junit-DifferentTempDir + check-junit-edKeyFactory) if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") set(check_targets ${check_targets} check-with-jni-flag) diff --git a/README.md b/README.md index c54b3f3c..ae966ecb 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ SecureRandom: KeyFactory: * EC * RSA -* ED25519 (JDK 15+) +* ED25519 (JDK 15+). Please refer to [system properties](https://github.com/corretto/amazon-corretto-crypto-provider#other-system-properties) for more information. AlgorithmParameters: * EC. Please refer to [system properties](https://github.com/corretto/amazon-corretto-crypto-provider#other-system-properties) for more information. @@ -384,6 +384,14 @@ Thus, these should all be set on the JVM command line using `-D`. * `com.amazon.corretto.crypto.provider.tmpdir` Allows one to set the temporary directory used by ACCP when loading native libraries. If this system property is not defined, the system property `java.io.tmpdir` is used. +* `com.amazon.corretto.crypto.provider.registerEdKeyFactory` + Takes in `true` or `false` (defaults to `false`). + If `true` and JDK version is 15+, then ACCP will register its Ed25519 related KeyFactory classes. + The keys produced by ACCP's KeyFactory services for Ed25519 do not implement [EdECKey](https://docs.oracle.com/en/java/javase/17/docs//api/java.base/java/security/interfaces/EdECKey.html) + interface, and as a result, they cannot be used by other providers. Consider setting this property + to `true` if the keys are only used by other ACCP services AND they are not type cast to `EdECKey`. + It is worth noting that the key generated by KeyFactory service of SunEC can be used by ACCP services + such as Signature. # License This library is licensed under the Apache 2.0 license although portions of this diff --git a/src/com/amazon/corretto/crypto/provider/AmazonCorrettoCryptoProvider.java b/src/com/amazon/corretto/crypto/provider/AmazonCorrettoCryptoProvider.java index a3259670..dc1106c0 100644 --- a/src/com/amazon/corretto/crypto/provider/AmazonCorrettoCryptoProvider.java +++ b/src/com/amazon/corretto/crypto/provider/AmazonCorrettoCryptoProvider.java @@ -49,6 +49,7 @@ public final class AmazonCorrettoCryptoProvider extends java.security.Provider { private static final String PROPERTY_CACHE_SELF_TEST_RESULTS = "cacheselftestresults"; private static final String PROPERTY_REGISTER_EC_PARAMS = "registerEcParams"; private static final String PROPERTY_REGISTER_SECURE_RANDOM = "registerSecureRandom"; + private static final String PROPERTY_REGISTER_ED_KEYFACTORY = "registerEdKeyFactory"; private static final long serialVersionUID = 1L; @@ -61,6 +62,7 @@ public final class AmazonCorrettoCryptoProvider extends java.security.Provider { private final boolean shouldRegisterEcParams; private final boolean shouldRegisterSecureRandom; private final boolean shouldRegisterEdDSA; + private final boolean shouldRegisterEdKeyFactory; private final Utils.NativeContextReleaseStrategy nativeContextReleaseStrategy; private transient SelfTestSuite selfTestSuite = new SelfTestSuite(); @@ -91,8 +93,15 @@ private void buildServiceMap() { addService("KeyFactory", "EC", "EvpKeyFactory$EC"); if (shouldRegisterEdDSA) { - addService("KeyFactory", "EdDSA", "EvpKeyFactory$EdDSA"); - addService("KeyFactory", "Ed25519", "EvpKeyFactory$EdDSA"); + // 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"); } @@ -492,6 +501,9 @@ public AmazonCorrettoCryptoProvider() { // 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); + this.nativeContextReleaseStrategy = Utils.getNativeContextReleaseStrategyProperty(); Utils.optionsFromProperty(ExtraCheck.class, extraChecks, "extrachecks"); @@ -721,7 +733,7 @@ KeyFactory getKeyFactory(EvpKeyType keyType) { return ecFactory; case EdDSA: if (edFactory == null) { - edFactory = KeyFactory.getInstance(keyType.jceName, this); + edFactory = new EdKeyFactory(this); } return edFactory; default: @@ -745,4 +757,12 @@ EvpKey translateKey(Key key, EvpKeyType keyType) throws InvalidKeyException { return (EvpKey) getKeyFactory(keyType).translateKey(key); } } + + // In case the user does not register Ed25519 KeyFactories by ACCP, we still need one to be used + // internally. + private static class EdKeyFactory extends KeyFactory { + EdKeyFactory(final AmazonCorrettoCryptoProvider provider) { + super(new EvpKeyFactory.EdDSA(provider), provider, "Ed25519"); + } + } } diff --git a/tst/com/amazon/corretto/crypto/provider/test/EdDSATest.java b/tst/com/amazon/corretto/crypto/provider/test/EdDSATest.java index 91a9a196..a331e413 100644 --- a/tst/com/amazon/corretto/crypto/provider/test/EdDSATest.java +++ b/tst/com/amazon/corretto/crypto/provider/test/EdDSATest.java @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.security.*; import java.security.spec.InvalidKeySpecException; @@ -96,6 +97,7 @@ public void keyGenValidation() throws GeneralSecurityException { @Test public void keyFactoryValidation() throws GeneralSecurityException { + assumeTrue(TestUtil.edKeyFactoryRegistered()); final KeyPair keyPair = jceGen.generateKeyPair(); final byte[] privateKeyJCE = keyPair.getPrivate().getEncoded(); @@ -210,6 +212,47 @@ public void bcKeyValidation() throws GeneralSecurityException { assertArrayEquals(pbkACCP, pbkBC); } + @Test + public void jceKeyValidation() throws Exception { + // Generate keys with ACCP and use JCE KeyFactory to get equivalent Keys + final KeyPair kp = nativeGen.generateKeyPair(); + final Class edPrivateKeyCls = Class.forName("java.security.interfaces.EdECPrivateKey"); + final Class edPPublicKeyCls = Class.forName("java.security.interfaces.EdECPublicKey"); + assertTrue(edPrivateKeyCls.isAssignableFrom(kp.getPrivate().getClass())); + assertTrue(edPPublicKeyCls.isAssignableFrom(kp.getPublic().getClass())); + final byte[] privateKeyAccpEncoding = kp.getPrivate().getEncoded(); + final byte[] publicKeyAccpEncoding = kp.getPublic().getEncoded(); + + final PKCS8EncodedKeySpec privateKeyPkcs8 = new PKCS8EncodedKeySpec(privateKeyAccpEncoding); + final X509EncodedKeySpec publicKeyX509 = new X509EncodedKeySpec(publicKeyAccpEncoding); + + final KeyFactory kf = KeyFactory.getInstance("Ed25519", "SunEC"); + + final PrivateKey privateKeyJce = kf.generatePrivate(privateKeyPkcs8); + final PublicKey publicKeyJce = kf.generatePublic(publicKeyX509); + + // Confirm that ACCP & SunJCE keys are equivalent + assertArrayEquals(privateKeyAccpEncoding, privateKeyJce.getEncoded()); + assertArrayEquals(publicKeyAccpEncoding, publicKeyJce.getEncoded()); + + // SunEC keys produced by its KeyFactory should be usable by EdDSA from ACCP + final Signature sigService = Signature.getInstance("EdDSA", NATIVE_PROVIDER); + + for (int messageLength = 1; messageLength <= 1024; messageLength++) { + final byte[] message = new byte[messageLength]; + final Random rand = new Random(messageLength); + rand.nextBytes(message); + + sigService.initSign(privateKeyJce); + sigService.update(message); + final byte[] signature = sigService.sign(); + + sigService.initVerify(publicKeyJce); + sigService.update(message); + assertTrue(sigService.verify(signature)); + } + } + @Test public void eddsaValidation() throws GeneralSecurityException { // Generate keys, sign, & verify with ACCP @@ -251,6 +294,7 @@ public void mismatchSignature() throws GeneralSecurityException { @Test public void testInvalidKey() throws GeneralSecurityException { + assumeTrue(TestUtil.edKeyFactoryRegistered()); byte[] invalidKeyBytes = new byte[] {}; PKCS8EncodedKeySpec invalidPrivateKeySpec = new PKCS8EncodedKeySpec(invalidKeyBytes); X509EncodedKeySpec invalidPublicKeySpec = new X509EncodedKeySpec(invalidKeyBytes); diff --git a/tst/com/amazon/corretto/crypto/provider/test/TestUtil.java b/tst/com/amazon/corretto/crypto/provider/test/TestUtil.java index bafeaf5b..30f3c6db 100644 --- a/tst/com/amazon/corretto/crypto/provider/test/TestUtil.java +++ b/tst/com/amazon/corretto/crypto/provider/test/TestUtil.java @@ -840,4 +840,9 @@ static Digest bcDigest(final String digest) { return null; } } + + static boolean edKeyFactoryRegistered() { + return "true" + .equals(System.getProperty("com.amazon.corretto.crypto.provider.registerEdKeyFactory")); + } }