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

Add Ed25519 DSA #394

Merged
merged 14 commits into from
Aug 28, 2024
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ set(C_SRC
csrc/buffer.cpp
csrc/ec_gen.cpp
csrc/ec_utils.cpp
csrc/ed_gen.cpp
csrc/env.cpp
csrc/hkdf.cpp
csrc/hmac.cpp
Expand Down Expand Up @@ -873,4 +874,4 @@ add_custom_target(check-integration
set_target_properties(check-integration PROPERTIES EXCLUDE_FROM_ALL 1)

# Do this at the end, after we finish all our feature tests, or it'll be missing flags
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/csrc/config.h.in ${JNI_HEADER_DIR}/config.h)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/csrc/config.h.in ${JNI_HEADER_DIR}/config.h)
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,12 @@ Signature algorithms:
* SHA512withECDSA
* SHA512withECDSAinP1363Format
* RSASSA-PSS
* ED25519 (JDK 15+)

KeyPairGenerator:
* EC
* RSA
* ED25519 (JDK 15+)

KeyGenerator:
* AES
Expand All @@ -92,6 +94,7 @@ SecureRandom:
KeyFactory:
* EC
* RSA
* ED25519 (JDK 15+)

AlgorithmParameters:
* EC. Please refer to [system properties](https://github.com/corretto/amazon-corretto-crypto-provider#other-system-properties) for more information.
Expand Down
29 changes: 29 additions & 0 deletions csrc/ed_gen.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#include "auto_free.h"
geedo0 marked this conversation as resolved.
Show resolved Hide resolved
#include "env.h"
#include "generated-headers.h"
#include <openssl/evp.h>

using namespace AmazonCorrettoCryptoProvider;

void generateEdKey(EVP_PKEY_auto& key)
{
EVP_PKEY_CTX_auto ctx = EVP_PKEY_CTX_auto::from(EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr));
CHECK_OPENSSL(ctx.isInitialized());
CHECK_OPENSSL(EVP_PKEY_keygen_init(ctx) == 1);
CHECK_OPENSSL(EVP_PKEY_keygen(ctx, key.getAddressOfPtr()));
}

JNIEXPORT jlong JNICALL Java_com_amazon_corretto_crypto_provider_EdGen_generateEvpEdKey(JNIEnv* pEnv, jclass)
{
try {
raii_env env(pEnv);
EVP_PKEY_auto key;
generateEdKey(key);
return reinterpret_cast<jlong>(key.take());
} catch (java_ex& ex) {
ex.throw_to_java(pEnv);
}
return 0;
}
41 changes: 36 additions & 5 deletions csrc/java_evp_keys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_amazon_corretto_crypto_provider_EvpKey_enc
* Signature: ([BI)J
*/
JNIEXPORT jlong JNICALL Java_com_amazon_corretto_crypto_provider_EvpKeyFactory_pkcs82Evp(
JNIEnv* pEnv, jclass, jbyteArray pkcs8der, jint nativeValue, jboolean shouldCheckPrivate)
JNIEnv* pEnv, jclass, jbyteArray pkcs8der, jint evpType, jboolean shouldCheckPrivate)
{
try {
raii_env env(pEnv);
Expand All @@ -104,8 +104,8 @@ JNIEXPORT jlong JNICALL Java_com_amazon_corretto_crypto_provider_EvpKeyFactory_p

{
jni_borrow borrow = jni_borrow(env, pkcs8Buff, "pkcs8Buff");
result.set(der2EvpPrivateKey(borrow, derLen, shouldCheckPrivate, EX_INVALID_KEY_SPEC));
if (EVP_PKEY_base_id(result) != nativeValue) {
result.set(der2EvpPrivateKey(borrow, derLen, evpType, shouldCheckPrivate, EX_INVALID_KEY_SPEC));
if (EVP_PKEY_base_id(result) != evpType) {
throw_java_ex(EX_INVALID_KEY_SPEC, "Incorrect key type");
}
}
Expand All @@ -122,7 +122,7 @@ JNIEXPORT jlong JNICALL Java_com_amazon_corretto_crypto_provider_EvpKeyFactory_p
* Signature: ([BI)J
*/
JNIEXPORT jlong JNICALL Java_com_amazon_corretto_crypto_provider_EvpKeyFactory_x5092Evp(
JNIEnv* pEnv, jclass, jbyteArray x509der, jint nativeValue)
JNIEnv* pEnv, jclass, jbyteArray x509der, jint evpType)
{
try {
raii_env env(pEnv);
Expand All @@ -134,7 +134,7 @@ JNIEXPORT jlong JNICALL Java_com_amazon_corretto_crypto_provider_EvpKeyFactory_x
{
jni_borrow borrow = jni_borrow(env, x509Buff, "x509Buff");
result.set(der2EvpPublicKey(borrow, derLen, EX_INVALID_KEY_SPEC));
if (EVP_PKEY_base_id(result) != nativeValue) {
if (EVP_PKEY_base_id(result) != evpType) {
throw_java_ex(EX_INVALID_KEY_SPEC, "Incorrect key type");
}
}
Expand Down Expand Up @@ -328,6 +328,37 @@ JNIEXPORT jbyteArray JNICALL Java_com_amazon_corretto_crypto_provider_EvpEcPriva
}
}

/*
* Class: com_amazon_corretto_crypto_provider_EvpEdPrivateKey
* Method: getPrivateKey
*/
JNIEXPORT jbyteArray JNICALL Java_com_amazon_corretto_crypto_provider_EvpEdPrivateKey_getPrivateKey(
JNIEnv* pEnv, jclass, jlong keyHandle)
{
jbyteArray result = NULL;

try {
raii_env env(pEnv);

EVP_PKEY* key = reinterpret_cast<EVP_PKEY*>(keyHandle);

size_t bufSize;

CHECK_OPENSSL(EVP_PKEY_get_raw_private_key(key, NULL, &bufSize) == 1);
Copy link
Contributor

Choose a reason for hiding this comment

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

Does EVP_PKEY_get_raw_private_key(key, NULL, &bufSize) always return the exact size? If yes, please document it here.

Copy link
Contributor Author

@sp717 sp717 Aug 27, 2024

Choose a reason for hiding this comment

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

It sets bufSize to the size and returns 1 on success. The == 1 check here is just to confirm that the method was successful.

SimpleBuffer privateKeyBuffer(bufSize);
CHECK_OPENSSL(EVP_PKEY_get_raw_private_key(key, privateKeyBuffer.get_buffer(), &bufSize) == 1);

result = env->NewByteArray(bufSize);
sp717 marked this conversation as resolved.
Show resolved Hide resolved
if (!result) {
throw_java_ex(EX_OOM, "Unable to allocate private key array");
}
env->SetByteArrayRegion(result, 0, bufSize, (jbyte*)privateKeyBuffer.get_buffer());
} catch (java_ex& ex) {
ex.throw_to_java(pEnv);
}
return result;
}

JNIEXPORT jbyteArray JNICALL Java_com_amazon_corretto_crypto_provider_EvpRsaKey_getModulus(
JNIEnv* pEnv, jclass, jlong keyHandle)
{
Expand Down
20 changes: 10 additions & 10 deletions csrc/keyutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@

namespace AmazonCorrettoCryptoProvider {

EVP_PKEY* der2EvpPrivateKey(
const unsigned char* der, const int derLen, bool shouldCheckPrivate, const char* javaExceptionClass)
EVP_PKEY* der2EvpPrivateKey(const unsigned char* der,
const int derLen,
const int evpType,
bool shouldCheckPrivate,
const char* javaExceptionClass)
{
const unsigned char* der_mutable_ptr = der; // openssl modifies the input pointer

PKCS8_PRIV_KEY_INFO* pkcs8Key = d2i_PKCS8_PRIV_KEY_INFO(NULL, &der_mutable_ptr, derLen);
EVP_PKEY* result = d2i_PrivateKey(evpType, NULL, &der_mutable_ptr, derLen);

if (der + derLen != der_mutable_ptr) {
if (pkcs8Key) {
PKCS8_PRIV_KEY_INFO_free(pkcs8Key);
if (result) {
EVP_PKEY_free(result);
}
throw_openssl(javaExceptionClass, "Extra key information");
}
if (!pkcs8Key) {
throw_openssl(javaExceptionClass, "Unable to parse DER key into PKCS8_PRIV_KEY_INFO");
}
EVP_PKEY* result = EVP_PKCS82PKEY(pkcs8Key);
PKCS8_PRIV_KEY_INFO_free(pkcs8Key);

if (!result) {
throw_openssl(javaExceptionClass, "Unable to convert PKCS8_PRIV_KEY_INFO to EVP_PKEY");
}
Expand Down
7 changes: 5 additions & 2 deletions csrc/keyutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,11 @@ class EvpKeyContext {
EvpKeyContext& operator=(const EvpKeyContext&) DELETE_IMPLICIT;
};

EVP_PKEY* der2EvpPrivateKey(
const unsigned char* der, const int derLen, const bool checkPrivateKey, const char* javaExceptionClass);
EVP_PKEY* der2EvpPrivateKey(const unsigned char* der,
const int derLen,
const int evpType,
const bool checkPrivateKey,
const char* javaExceptionClass);
EVP_PKEY* der2EvpPublicKey(const unsigned char* der, const int derLen, const char* javaExceptionClass);
bool checkKey(const EVP_PKEY* key);
static bool inline BN_null_or_zero(const BIGNUM* bn) { return nullptr == bn || BN_is_zero(bn); }
Expand Down
38 changes: 31 additions & 7 deletions csrc/sign.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ bool initializeContext(raii_env& env,
EVP_PKEY_up_ref(pKey);
ctx->setKey(pKey);

if (md != nullptr) {
if (md != nullptr || EVP_PKEY_id(pKey) == EVP_PKEY_ED25519) {
geedo0 marked this conversation as resolved.
Show resolved Hide resolved
if (!ctx->setDigestCtx(EVP_MD_CTX_create())) {
throw_openssl("Unable to create MD_CTX");
}
Expand Down Expand Up @@ -168,8 +168,8 @@ JNIEXPORT jlong JNICALL Java_com_amazon_corretto_crypto_provider_EvpSignature_si
true, // true->sign
reinterpret_cast<EVP_PKEY*>(pKey), reinterpret_cast<const EVP_MD*>(mdPtr), paddingType,
reinterpret_cast<const EVP_MD*>(mgfMdPtr), pssSaltLen);
update(env, &ctx, digestSignUpdate, java_buffer::from_array(env, message, offset, length));

update(env, &ctx, digestSignUpdate, java_buffer::from_array(env, message, offset, length));
return reinterpret_cast<jlong>(ctx.moveToHeap());
} catch (java_ex& ex) {
ex.throw_to_java(pEnv);
Expand Down Expand Up @@ -452,8 +452,25 @@ JNIEXPORT jbyteArray JNICALL Java_com_amazon_corretto_crypto_provider_EvpSignatu
paddingType, reinterpret_cast<const EVP_MD*>(mgfMdPtr), pssSaltLen);

std::vector<uint8_t, SecureAlloc<uint8_t> > signature;
{
size_t sigLength;
size_t sigLength;

int keyType = EVP_PKEY_id(reinterpret_cast<EVP_PKEY*>(pKey));

if (keyType == EVP_PKEY_ED25519) {
jni_borrow message(env, messageBuf, "message");

if (!EVP_DigestSign(ctx.getDigestCtx(), NULL, &sigLength, message.data(), message.len())) {
sp717 marked this conversation as resolved.
Show resolved Hide resolved
throw_openssl("Signature failed");
}

signature.resize(sigLength);

if (!EVP_DigestSign(ctx.getDigestCtx(), &signature[0], &sigLength, message.data(), message.len())) {
throw_openssl("Signature failed");
}

signature.resize(sigLength);
} else {
jni_borrow message(env, messageBuf, "message");

if (EVP_PKEY_sign(ctx.getKeyCtx(), NULL, &sigLength, message.data(), message.len()) <= 0) {
Expand All @@ -472,7 +489,6 @@ JNIEXPORT jbyteArray JNICALL Java_com_amazon_corretto_crypto_provider_EvpSignatu

signature.resize(sigLength);
}

return vecToArray(env, signature);
} catch (java_ex& ex) {
ex.throw_to_java(pEnv);
Expand Down Expand Up @@ -508,7 +524,14 @@ JNIEXPORT jboolean JNICALL Java_com_amazon_corretto_crypto_provider_EvpSignature
jni_borrow message(env, messageBuf, "message");
jni_borrow signature(env, signatureBuf, "signature");

int ret = EVP_PKEY_verify(ctx.getKeyCtx(), signature.data(), signature.len(), message.data(), message.len());
int ret;
int keyType = EVP_PKEY_id(reinterpret_cast<EVP_PKEY*>(pKey));
if (keyType == EVP_PKEY_ED25519) {
ret = EVP_DigestVerify(
ctx.getDigestCtx(), signature.data(), signature.len(), message.data(), message.len());
} else {
ret = EVP_PKEY_verify(ctx.getKeyCtx(), signature.data(), signature.len(), message.data(), message.len());
}

if (likely(ret == 1)) {
return true;
Expand All @@ -518,7 +541,8 @@ JNIEXPORT jboolean JNICALL Java_com_amazon_corretto_crypto_provider_EvpSignature
// Mismatched signatures are not an error case, so return false
// instead of throwing per JCA convention.
if (ECDSA_R_MISMATCHED_SIGNATURE == (errorCode & ECDSA_R_MISMATCHED_SIGNATURE)
|| RSA_R_MISMATCHED_SIGNATURE == (errorCode & RSA_R_MISMATCHED_SIGNATURE)) {
|| RSA_R_MISMATCHED_SIGNATURE == (errorCode & RSA_R_MISMATCHED_SIGNATURE)
|| EVP_R_INVALID_SIGNATURE == (errorCode & EVP_R_INVALID_SIGNATURE)) {
return false;
}

Expand Down
8 changes: 4 additions & 4 deletions csrc/test_keyutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ static const uint8_t validPKCS8ECKey[DER_LENGTH] = {

void test_deserialize_valid_key()
{
EVP_PKEY* result = der2EvpPrivateKey(validPKCS8ECKey, DER_LENGTH, false, EX_INVALID_KEY);
EVP_PKEY* result = der2EvpPrivateKey(validPKCS8ECKey, DER_LENGTH, EVP_PKEY_EC, false, EX_INVALID_KEY);
TEST_ASSERT(result);
EVP_PKEY_free(result);
}
Expand All @@ -178,7 +178,7 @@ void test_deserialize_invalid_der()
// This makes the private key invalid
invalidKey[34] = 0;
try {
der2EvpPrivateKey(invalidKey, DER_LENGTH, false, EX_INVALID_KEY);
der2EvpPrivateKey(invalidKey, DER_LENGTH, EVP_PKEY_EC, false, EX_INVALID_KEY);
FAIL();
} catch (...) {
// Expected
Expand All @@ -190,7 +190,7 @@ void test_deserialize_empty()
// This makes the DER encoding invalid for a PKCS8 key but still the right length
uint8_t emptyKey[1] = { 0x00 };
try {
der2EvpPrivateKey(emptyKey, 0, false, EX_INVALID_KEY);
der2EvpPrivateKey(emptyKey, 0, EVP_PKEY_EC, false, EX_INVALID_KEY);
FAIL();
} catch (...) {
// Expected
Expand All @@ -205,7 +205,7 @@ void test_deserialize_extra_data()
// This sets the der to have 0 elements but still has data and will hit the extra key info
invalidKey[0] = 0;
try {
der2EvpPrivateKey(invalidKey, DER_LENGTH, false, EX_INVALID_KEY);
der2EvpPrivateKey(invalidKey, DER_LENGTH, EVP_PKEY_EC, false, EX_INVALID_KEY);
FAIL();
} catch (...) {
// Expected
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ 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 Utils.NativeContextReleaseStrategy nativeContextReleaseStrategy;

private transient SelfTestSuite selfTestSuite = new SelfTestSuite();
Expand Down Expand Up @@ -86,6 +87,13 @@ private void buildServiceMap() {
addService("KeyFactory", "RSA", "EvpKeyFactory$RSA");
addService("KeyFactory", "EC", "EvpKeyFactory$EC");

if (shouldRegisterEdDSA) {
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);
addService("SecretKeyFactory", HKDF_WITH_SHA256, hkdfSpi, false);
Expand Down Expand Up @@ -187,6 +195,10 @@ 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");
}
}

private ACCPService addService(
Expand Down Expand Up @@ -462,6 +474,10 @@ 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.nativeContextReleaseStrategy = Utils.getNativeContextReleaseStrategyProperty();

Utils.optionsFromProperty(ExtraCheck.class, extraChecks, "extrachecks");
Expand Down Expand Up @@ -663,6 +679,7 @@ private void readObjectNoData() {
// Provider.getService logic.
private transient volatile KeyFactory rsaFactory;
private transient volatile KeyFactory ecFactory;
private transient volatile KeyFactory edFactory;

KeyFactory getKeyFactory(EvpKeyType keyType) {
try {
Expand All @@ -677,6 +694,11 @@ KeyFactory getKeyFactory(EvpKeyType keyType) {
ecFactory = KeyFactory.getInstance(keyType.jceName, this);
}
return ecFactory;
case EdDSA:
if (edFactory == null) {
edFactory = KeyFactory.getInstance(keyType.jceName, this);
}
return edFactory;
default:
throw new AssertionError("Unsupported key type");
}
Expand Down
Loading
Loading