Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable standalone ACCP Ed25519 #438

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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);

Expand Down
22 changes: 14 additions & 8 deletions src/com/amazon/corretto/crypto/provider/EdGen.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,31 @@
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;
import java.security.spec.PKCS8EncodedKeySpec;
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;
}
}

Expand All @@ -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 support conditions as described in the ctor
return new KeyPair(publicKey, privateKey);
}
try {
final PKCS8EncodedKeySpec privateKeyPkcs8 = new PKCS8EncodedKeySpec(privateKey.getEncoded());
final X509EncodedKeySpec publicKeyX509 = new X509EncodedKeySpec(publicKey.getEncoded());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bit of code smell here, I'm paranoid that matching on startsWith("Ed") is a bit too general.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very fair. Since it's a maintainability issue and not a correctness issue, I'll do a follow-up PR to match algorithm more precisely.

// 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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Provider> 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[0];
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));
}
}