From b2bfe91c42f74b5d1db2ba55b176d1a9d4cf6b52 Mon Sep 17 00:00:00 2001 From: juergw Date: Thu, 9 Jan 2025 15:47:30 +0000 Subject: [PATCH 1/6] Add support for Ed25519 to NativeCrypto BoringSSL doesn't support Ed25519 with EVP_DigestUpdate, only with EVP_DigestSign and EVP_DigestVerify. So we need to add wrappers of these functions to NativeCrypto. --- .../jni/main/cpp/conscrypt/native_crypto.cc | 181 +++++++++++++++++- .../main/java/org/conscrypt/NativeCrypto.java | 8 + constants/src/gen/cpp/generate_constants.cc | 1 + .../java/org/conscrypt/NativeCryptoTest.java | 77 ++++++++ 4 files changed, 266 insertions(+), 1 deletion(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index d1bd6b089..1005f42ae 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -2565,6 +2565,42 @@ static void NativeCrypto_X25519_keypair(JNIEnv* env, jclass, jbyteArray outPubli JNI_TRACE("X25519_keypair(%p, %p) => success", outPublicArray, outPrivateArray); } +static void NativeCrypto_ED25519_keypair(JNIEnv* env, jclass, jbyteArray outPublicArray, + jbyteArray outPrivateArray) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("ED25519_keypair(%p, %p)", outPublicArray, outPrivateArray); + + ScopedByteArrayRW outPublic(env, outPublicArray); + if (outPublic.get() == nullptr) { + JNI_TRACE("ED25519_keypair(%p, %p) can't get output public key buffer", outPublicArray, + outPrivateArray); + return; + } + + ScopedByteArrayRW outPrivate(env, outPrivateArray); + if (outPrivate.get() == nullptr) { + JNI_TRACE("ED25519_keypair(%p, %p) can't get output private key buffer", outPublicArray, + outPrivateArray); + return; + } + + if (outPublic.size() != ED25519_PUBLIC_KEY_LEN) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Output public key array length != 32"); + return; + } + + if (outPrivate.size() != ED25519_PRIVATE_KEY_LEN) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Output private key array length != 64"); + return; + } + + ED25519_keypair(reinterpret_cast(outPublic.get()), + reinterpret_cast(outPrivate.get())); + JNI_TRACE("ED25519_keypair(%p, %p) => success", outPublicArray, outPrivateArray); +} + static jlong NativeCrypto_EVP_MD_CTX_create(JNIEnv* env, jclass) { CHECK_ERROR_QUEUE_ON_RETURN; JNI_TRACE_MD("EVP_MD_CTX_create()"); @@ -2763,7 +2799,7 @@ static jlong evpDigestSignVerifyInit(JNIEnv* env, } JNI_TRACE("%s(%p, %p, %p) <- ptr", jniName, mdCtx, md, pkey); - if (md == nullptr) { + if (md == nullptr && (EVP_PKEY_id(pkey) != EVP_PKEY_ED25519)) { JNI_TRACE("ctx=%p %s => md == null", mdCtx, jniName); conscrypt::jniutil::throwNullPointerException(env, "md == null"); return 0; @@ -3037,6 +3073,146 @@ static jboolean NativeCrypto_EVP_DigestVerifyFinal(JNIEnv* env, jclass, jobject return result; } +static jbyteArray NativeCrypto_EVP_DigestSign(JNIEnv* env, jclass, + jobject evpMdCtxRef, + jbyteArray inJavaBytes, + jint inOffset, jint inLength) { + CHECK_ERROR_QUEUE_ON_RETURN; + + EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); + JNI_TRACE_MD("%s(%p, %p, %d, %d)", "EVP_DigestSign", mdCtx, inJavaBytes, + inOffset, inLength); + + if (mdCtx == nullptr) { + return nullptr; + } + + if (inJavaBytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "inBytes"); + return nullptr; + } + + size_t array_size = static_cast(env->GetArrayLength(inJavaBytes)); + if (ARRAY_CHUNK_INVALID(array_size, inOffset, inLength)) { + conscrypt::jniutil::throwException( + env, "java/lang/ArrayIndexOutOfBoundsException", "inBytes"); + return nullptr; + } + + jint in_offset = inOffset; + jint in_size = inLength; + + jbyte* array_elements = env->GetByteArrayElements(inJavaBytes, nullptr); + if (array_elements == nullptr) { + conscrypt::jniutil::throwOutOfMemory( + env, "Unable to obtain elements of inBytes"); + return nullptr; + } + const unsigned char* buf = + reinterpret_cast(array_elements); + const unsigned char* inStart = buf + in_offset; + size_t inLen = static_cast(in_size); + + size_t maxLen; + if (EVP_DigestSign(mdCtx, nullptr, &maxLen, inStart, inLen) != 1) { + JNI_TRACE("ctx=%p EVP_DigestSign => threw exception", mdCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSign"); + return nullptr; + } + + std::unique_ptr buffer(new unsigned char[maxLen]); + if (buffer.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, + "Unable to allocate signature buffer"); + return nullptr; + } + size_t actualLen(maxLen); + if (EVP_DigestSign(mdCtx, buffer.get(), &actualLen, inStart, inLen) != 1) { + JNI_TRACE("ctx=%p EVP_DigestSign => threw exception", mdCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSign"); + return nullptr; + } + if (actualLen > maxLen) { + JNI_TRACE("ctx=%p EVP_DigestSign => signature too long: %zd vs %zd", mdCtx, + actualLen, maxLen); + conscrypt::jniutil::throwRuntimeException( + env, "EVP_DigestSign signature too long"); + return nullptr; + } + + ScopedLocalRef sigJavaBytes( + env, env->NewByteArray(static_cast(actualLen))); + if (sigJavaBytes.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, + "Failed to allocate signature byte[]"); + return nullptr; + } + env->SetByteArrayRegion(sigJavaBytes.get(), 0, static_cast(actualLen), + reinterpret_cast(buffer.get())); + + JNI_TRACE("EVP_DigestSign(%p) => %p", mdCtx, sigJavaBytes.get()); + return sigJavaBytes.release(); +} + +static jboolean NativeCrypto_EVP_DigestVerify(JNIEnv* env, jclass, jobject evpMdCtxRef, + jbyteArray signature, jint sigOffset, jint sigLen, + jbyteArray data, jint dataOffset, jint dataLen) { + CHECK_ERROR_QUEUE_ON_RETURN; + EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); + JNI_TRACE("EVP_DigestVerify(%p)", mdCtx); + + if (mdCtx == nullptr) { + return 0; + } + + ScopedByteArrayRO sigBytes(env, signature); + if (sigBytes.get() == nullptr) { + return 0; + } + + if (ARRAY_OFFSET_LENGTH_INVALID(sigBytes, sigOffset, sigLen)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "signature"); + return 0; + } + + ScopedByteArrayRO dataBytes(env, data); + if (dataBytes.get() == nullptr) { + return 0; + } + + if (ARRAY_OFFSET_LENGTH_INVALID(dataBytes, dataOffset, dataLen)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "data"); + return 0; + } + + const unsigned char* sigBuf = reinterpret_cast(sigBytes.get()); + const unsigned char* dataBuf = reinterpret_cast(dataBytes.get()); + int err = EVP_DigestVerify(mdCtx, sigBuf + sigOffset, static_cast(sigLen), + dataBuf + dataOffset, static_cast(dataLen)); + jboolean result; + if (err == 1) { + // Signature verified + result = 1; + } else if (err == 0) { + // Signature did not verify + result = 0; + } else { + // Error while verifying signature + JNI_TRACE("ctx=%p EVP_DigestVerify => threw exception", mdCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestVerify"); + return 0; + } + + // If the signature did not verify, BoringSSL error queue contains an error (BAD_SIGNATURE). + // Clear the error queue to prevent its state from affecting future operations. + ERR_clear_error(); + + JNI_TRACE("EVP_DigestVerify(%p) => %d", mdCtx, result); + return result; +} + static jint evpPkeyEncryptDecrypt(JNIEnv* env, int (*encrypt_decrypt_func)(EVP_PKEY_CTX*, uint8_t*, size_t*, const uint8_t*, size_t), @@ -11122,6 +11298,7 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(ECDSA_verify, "([B[B" REF_EVP_PKEY ")I"), CONSCRYPT_NATIVE_METHOD(X25519, "([B[B[B)Z"), CONSCRYPT_NATIVE_METHOD(X25519_keypair, "([B[B)V"), + CONSCRYPT_NATIVE_METHOD(ED25519_keypair, "([B[B)V"), CONSCRYPT_NATIVE_METHOD(EVP_MD_CTX_create, "()J"), CONSCRYPT_NATIVE_METHOD(EVP_MD_CTX_cleanup, "(" REF_EVP_MD_CTX ")V"), CONSCRYPT_NATIVE_METHOD(EVP_MD_CTX_destroy, "(J)V"), @@ -11140,6 +11317,8 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(EVP_DigestVerifyUpdate, "(" REF_EVP_MD_CTX "[BII)V"), CONSCRYPT_NATIVE_METHOD(EVP_DigestVerifyUpdateDirect, "(" REF_EVP_MD_CTX "JI)V"), CONSCRYPT_NATIVE_METHOD(EVP_DigestVerifyFinal, "(" REF_EVP_MD_CTX "[BII)Z"), + CONSCRYPT_NATIVE_METHOD(EVP_DigestSign, "(" REF_EVP_MD_CTX "[BII)[B"), + CONSCRYPT_NATIVE_METHOD(EVP_DigestVerify, "(" REF_EVP_MD_CTX "[BII[BII)Z"), CONSCRYPT_NATIVE_METHOD(EVP_PKEY_encrypt_init, "(" REF_EVP_PKEY ")J"), CONSCRYPT_NATIVE_METHOD(EVP_PKEY_encrypt, "(" REF_EVP_PKEY_CTX "[BI[BII)I"), CONSCRYPT_NATIVE_METHOD(EVP_PKEY_decrypt_init, "(" REF_EVP_PKEY ")J"), diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java index f33acbce4..82a080441 100644 --- a/common/src/main/java/org/conscrypt/NativeCrypto.java +++ b/common/src/main/java/org/conscrypt/NativeCrypto.java @@ -212,6 +212,8 @@ static native int ECDH_compute_key(byte[] out, int outOffset, NativeRef.EVP_PKEY static native void X25519_keypair(byte[] outPublicKey, byte[] outPrivateKey); + static native void ED25519_keypair(byte[] outPublicKey, byte[] outPrivateKey); + // --- Message digest functions -------------- // These return const references @@ -264,6 +266,12 @@ static native void EVP_DigestVerifyUpdate( static native boolean EVP_DigestVerifyFinal(NativeRef.EVP_MD_CTX ctx, byte[] signature, int offset, int length) throws IndexOutOfBoundsException; + static native byte[] EVP_DigestSign( + NativeRef.EVP_MD_CTX ctx, byte[] buffer, int offset, int length); + + static native boolean EVP_DigestVerify(NativeRef.EVP_MD_CTX ctx, byte[] sigBuffer, + int sigOffset, int sigLen, byte[] dataBuffer, int dataOffset, int dataLen); + static native long EVP_PKEY_encrypt_init(NativeRef.EVP_PKEY pkey) throws InvalidKeyException; static native int EVP_PKEY_encrypt(NativeRef.EVP_PKEY_CTX ctx, byte[] out, int outOffset, diff --git a/constants/src/gen/cpp/generate_constants.cc b/constants/src/gen/cpp/generate_constants.cc index 60fe93d8b..8624e37f5 100644 --- a/constants/src/gen/cpp/generate_constants.cc +++ b/constants/src/gen/cpp/generate_constants.cc @@ -55,6 +55,7 @@ int main(int /* argc */, char ** /* argv */) { CONST(EVP_PKEY_RSA); CONST(EVP_PKEY_EC); + CONST(EVP_PKEY_ED25519); CONST(RSA_PKCS1_PADDING); CONST(RSA_NO_PADDING); diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java index af2e9ca70..b3f04b83e 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java @@ -26,14 +26,17 @@ import static org.conscrypt.NativeConstants.TLS1_1_VERSION; import static org.conscrypt.NativeConstants.TLS1_2_VERSION; import static org.conscrypt.NativeConstants.TLS1_VERSION; +import static org.conscrypt.TestUtils.decodeHex; import static org.conscrypt.TestUtils.isWindows; import static org.conscrypt.TestUtils.openTestFile; import static org.conscrypt.TestUtils.readTestFile; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; @@ -2716,6 +2719,80 @@ public void test_EVP_DigestSignInit() throws Exception { } } + + @Test + public void test_ED25519_keypair_works() throws Exception { + byte[] publicKeyBytes = new byte[32]; + byte[] privateKeyBytes = new byte[64]; + NativeCrypto.ED25519_keypair(publicKeyBytes, privateKeyBytes); + + byte[] publicKeyBytes2 = new byte[32]; + byte[] privateKeyBytes2 = new byte[64]; + NativeCrypto.ED25519_keypair(publicKeyBytes2, privateKeyBytes2); + + // keys must be random + assertNotEquals(publicKeyBytes, publicKeyBytes2); + assertNotEquals(privateKeyBytes, privateKeyBytes2); + } + + @Test + public void test_ED25519_keypair_32BytePrivateKey_throws() throws Exception { + byte[] publicKeyBytes = new byte[32]; + byte[] privateKeyBytes = new byte[32]; + assertThrows(IllegalArgumentException.class, + () -> NativeCrypto.ED25519_keypair(publicKeyBytes, privateKeyBytes)); + } + + @Test + public void test_EVP_DigestSign_Ed25519_works() throws Exception { + // Test vectors from https://datatracker.ietf.org/doc/html/rfc8032#section-7 + // PKCS#8 encoding for Ed25519 is defined in https://datatracker.ietf.org/doc/html/rfc8410 + byte[] pkcs8EncodedPrivateKey = decodeHex( + // PKCS#8 header + "302e020100300506032b657004220420" + // raw private key + + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"); + byte[] data = decodeHex(""); + byte[] expectedSig = + decodeHex("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155" + + "5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"); + + NativeRef.EVP_PKEY privateKey = + new NativeRef.EVP_PKEY(NativeCrypto.EVP_parse_private_key(pkcs8EncodedPrivateKey)); + + NativeRef.EVP_MD_CTX ctx = new NativeRef.EVP_MD_CTX(NativeCrypto.EVP_MD_CTX_create()); + + NativeCrypto.EVP_DigestSignInit(ctx, 0, privateKey); + byte[] sig = NativeCrypto.EVP_DigestSign(ctx, data, 0, data.length); + + assertArrayEquals(expectedSig, sig); + } + + @Test + public void test_EVP_DigestVerify_Ed25519_works() throws Exception { + // Test vectors from https://datatracker.ietf.org/doc/html/rfc8032#section-7 + // X.509 encoding for Ed25519 is defined in https://datatracker.ietf.org/doc/html/rfc8410 + byte[] x509EncodedPublicKey = decodeHex( + // X.509 header + "302a300506032b6570032100" + // raw public key + + "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"); + byte[] data = decodeHex(""); + byte[] sig = decodeHex("e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155" + + "5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"); + + NativeRef.EVP_MD_CTX ctx = new NativeRef.EVP_MD_CTX(NativeCrypto.EVP_MD_CTX_create()); + + NativeRef.EVP_PKEY publicKey = + new NativeRef.EVP_PKEY(NativeCrypto.EVP_parse_public_key(x509EncodedPublicKey)); + + NativeCrypto.EVP_DigestVerifyInit(ctx, 0, publicKey); + boolean result = + NativeCrypto.EVP_DigestVerify(ctx, sig, 0, sig.length, data, 0, data.length); + + assertTrue(result); + } + @Test(expected = NullPointerException.class) public void get_RSA_private_params_NullArgument() throws Exception { NativeCrypto.get_RSA_private_params(null); From 290d9e90a6045d4189a5b94cf0015566fa1afe99 Mon Sep 17 00:00:00 2001 From: juergw Date: Thu, 9 Jan 2025 16:07:16 +0000 Subject: [PATCH 2/6] Fix some formatting. --- .../jni/main/cpp/conscrypt/native_crypto.cc | 145 +++++++++--------- .../java/org/conscrypt/NativeCryptoTest.java | 25 +-- 2 files changed, 82 insertions(+), 88 deletions(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index 1005f42ae..2316b3e31 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -3073,90 +3073,82 @@ static jboolean NativeCrypto_EVP_DigestVerifyFinal(JNIEnv* env, jclass, jobject return result; } -static jbyteArray NativeCrypto_EVP_DigestSign(JNIEnv* env, jclass, - jobject evpMdCtxRef, - jbyteArray inJavaBytes, - jint inOffset, jint inLength) { - CHECK_ERROR_QUEUE_ON_RETURN; +static jbyteArray NativeCrypto_EVP_DigestSign(JNIEnv* env, jclass, jobject evpMdCtxRef, + jbyteArray inJavaBytes, jint inOffset, + jint inLength) { + CHECK_ERROR_QUEUE_ON_RETURN; - EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); - JNI_TRACE_MD("%s(%p, %p, %d, %d)", "EVP_DigestSign", mdCtx, inJavaBytes, - inOffset, inLength); + EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); + JNI_TRACE_MD("%s(%p, %p, %d, %d)", "EVP_DigestSign", mdCtx, inJavaBytes, inOffset, inLength); - if (mdCtx == nullptr) { - return nullptr; - } + if (mdCtx == nullptr) { + return nullptr; + } - if (inJavaBytes == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "inBytes"); - return nullptr; - } + if (inJavaBytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "inBytes"); + return nullptr; + } - size_t array_size = static_cast(env->GetArrayLength(inJavaBytes)); - if (ARRAY_CHUNK_INVALID(array_size, inOffset, inLength)) { - conscrypt::jniutil::throwException( - env, "java/lang/ArrayIndexOutOfBoundsException", "inBytes"); - return nullptr; - } + size_t array_size = static_cast(env->GetArrayLength(inJavaBytes)); + if (ARRAY_CHUNK_INVALID(array_size, inOffset, inLength)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "inBytes"); + return nullptr; + } - jint in_offset = inOffset; - jint in_size = inLength; + jint in_offset = inOffset; + jint in_size = inLength; - jbyte* array_elements = env->GetByteArrayElements(inJavaBytes, nullptr); - if (array_elements == nullptr) { - conscrypt::jniutil::throwOutOfMemory( - env, "Unable to obtain elements of inBytes"); - return nullptr; - } - const unsigned char* buf = - reinterpret_cast(array_elements); - const unsigned char* inStart = buf + in_offset; - size_t inLen = static_cast(in_size); - - size_t maxLen; - if (EVP_DigestSign(mdCtx, nullptr, &maxLen, inStart, inLen) != 1) { - JNI_TRACE("ctx=%p EVP_DigestSign => threw exception", mdCtx); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSign"); - return nullptr; - } + jbyte* array_elements = env->GetByteArrayElements(inJavaBytes, nullptr); + if (array_elements == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to obtain elements of inBytes"); + return nullptr; + } + const unsigned char* buf = reinterpret_cast(array_elements); + const unsigned char* inStart = buf + in_offset; + size_t inLen = static_cast(in_size); - std::unique_ptr buffer(new unsigned char[maxLen]); - if (buffer.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, - "Unable to allocate signature buffer"); - return nullptr; - } - size_t actualLen(maxLen); - if (EVP_DigestSign(mdCtx, buffer.get(), &actualLen, inStart, inLen) != 1) { - JNI_TRACE("ctx=%p EVP_DigestSign => threw exception", mdCtx); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSign"); - return nullptr; - } - if (actualLen > maxLen) { - JNI_TRACE("ctx=%p EVP_DigestSign => signature too long: %zd vs %zd", mdCtx, - actualLen, maxLen); - conscrypt::jniutil::throwRuntimeException( - env, "EVP_DigestSign signature too long"); - return nullptr; - } + size_t maxLen; + if (EVP_DigestSign(mdCtx, nullptr, &maxLen, inStart, inLen) != 1) { + JNI_TRACE("ctx=%p EVP_DigestSign => threw exception", mdCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSign"); + return nullptr; + } - ScopedLocalRef sigJavaBytes( - env, env->NewByteArray(static_cast(actualLen))); - if (sigJavaBytes.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, - "Failed to allocate signature byte[]"); - return nullptr; - } - env->SetByteArrayRegion(sigJavaBytes.get(), 0, static_cast(actualLen), - reinterpret_cast(buffer.get())); + std::unique_ptr buffer(new unsigned char[maxLen]); + if (buffer.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate signature buffer"); + return nullptr; + } + size_t actualLen(maxLen); + if (EVP_DigestSign(mdCtx, buffer.get(), &actualLen, inStart, inLen) != 1) { + JNI_TRACE("ctx=%p EVP_DigestSign => threw exception", mdCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSign"); + return nullptr; + } + if (actualLen > maxLen) { + JNI_TRACE("ctx=%p EVP_DigestSign => signature too long: %zd vs %zd", mdCtx, actualLen, + maxLen); + conscrypt::jniutil::throwRuntimeException(env, "EVP_DigestSign signature too long"); + return nullptr; + } - JNI_TRACE("EVP_DigestSign(%p) => %p", mdCtx, sigJavaBytes.get()); - return sigJavaBytes.release(); + ScopedLocalRef sigJavaBytes(env, env->NewByteArray(static_cast(actualLen))); + if (sigJavaBytes.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Failed to allocate signature byte[]"); + return nullptr; + } + env->SetByteArrayRegion(sigJavaBytes.get(), 0, static_cast(actualLen), + reinterpret_cast(buffer.get())); + + JNI_TRACE("EVP_DigestSign(%p) => %p", mdCtx, sigJavaBytes.get()); + return sigJavaBytes.release(); } static jboolean NativeCrypto_EVP_DigestVerify(JNIEnv* env, jclass, jobject evpMdCtxRef, - jbyteArray signature, jint sigOffset, jint sigLen, - jbyteArray data, jint dataOffset, jint dataLen) { + jbyteArray signature, jint sigOffset, jint sigLen, + jbyteArray data, jint dataOffset, jint dataLen) { CHECK_ERROR_QUEUE_ON_RETURN; EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); JNI_TRACE("EVP_DigestVerify(%p)", mdCtx); @@ -3182,8 +3174,7 @@ static jboolean NativeCrypto_EVP_DigestVerify(JNIEnv* env, jclass, jobject evpMd } if (ARRAY_OFFSET_LENGTH_INVALID(dataBytes, dataOffset, dataLen)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", - "data"); + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", "data"); return 0; } @@ -11358,8 +11349,10 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_free, "(J)V"), CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_open, "(" REF_EVP_HPKE_CTX "[B[B)[B"), CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_seal, "(" REF_EVP_HPKE_CTX "[B[B)[B"), - CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_setup_base_mode_recipient, "(III[B[B[B)Ljava/lang/Object;"), - CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_setup_base_mode_sender, "(III[B[B)[Ljava/lang/Object;"), + CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_setup_base_mode_recipient, + "(III[B[B[B)Ljava/lang/Object;"), + CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_setup_base_mode_sender, + "(III[B[B)[Ljava/lang/Object;"), CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_setup_base_mode_sender_with_seed_for_testing, "(III[B[B[B)[Ljava/lang/Object;"), CONSCRYPT_NATIVE_METHOD(HMAC_CTX_new, "()J"), diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java index b3f04b83e..d381b78b5 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java @@ -43,6 +43,18 @@ import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.when; +import org.conscrypt.NativeCrypto.SSLHandshakeCallbacks; +import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException; +import org.conscrypt.io.IoUtils; +import org.conscrypt.java.security.StandardNames; +import org.conscrypt.java.security.TestKeyStore; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; @@ -77,22 +89,12 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; + import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLProtocolException; import javax.security.auth.x500.X500Principal; -import org.conscrypt.NativeCrypto.SSLHandshakeCallbacks; -import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException; -import org.conscrypt.io.IoUtils; -import org.conscrypt.java.security.StandardNames; -import org.conscrypt.java.security.TestKeyStore; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; @RunWith(JUnit4.class) public class NativeCryptoTest { @@ -2719,7 +2721,6 @@ public void test_EVP_DigestSignInit() throws Exception { } } - @Test public void test_ED25519_keypair_works() throws Exception { byte[] publicKeyBytes = new byte[32]; From 8a5edd2bb83e29aa2444a34696d7b656b779d911 Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 13 Jan 2025 12:39:40 +0000 Subject: [PATCH 3/6] Add comment for md = null. And test that it fails for md != null. --- common/src/jni/main/cpp/conscrypt/native_crypto.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index 2316b3e31..c9ffd3de2 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -2799,6 +2799,8 @@ static jlong evpDigestSignVerifyInit(JNIEnv* env, } JNI_TRACE("%s(%p, %p, %p) <- ptr", jniName, mdCtx, md, pkey); + // For ED25519, md must be null, see + // https://github.com/google/boringssl/blob/master/include/openssl/evp.h if (md == nullptr && (EVP_PKEY_id(pkey) != EVP_PKEY_ED25519)) { JNI_TRACE("ctx=%p %s => md == null", mdCtx, jniName); conscrypt::jniutil::throwNullPointerException(env, "md == null"); From 000147bac18bd85baa7eb577b6f4d64cd6ad7ef3 Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 13 Jan 2025 12:56:28 +0000 Subject: [PATCH 4/6] Use throwIllegalArgumentException. --- common/src/jni/main/cpp/conscrypt/native_crypto.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index c9ffd3de2..c008d67cb 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -2585,14 +2585,14 @@ static void NativeCrypto_ED25519_keypair(JNIEnv* env, jclass, jbyteArray outPubl } if (outPublic.size() != ED25519_PUBLIC_KEY_LEN) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "Output public key array length != 32"); + conscrypt::jniutil::throwIllegalArgumentException( + env, "Output public key array length != 32"); return; } if (outPrivate.size() != ED25519_PRIVATE_KEY_LEN) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "Output private key array length != 64"); + conscrypt::jniutil::throwIllegalArgumentException( + env, "Output private key array length != 64"); return; } From 846a9a954f78fd3b4deaa491914718c10ad70a4c Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 13 Jan 2025 12:59:34 +0000 Subject: [PATCH 5/6] Fix formatting --- common/src/jni/main/cpp/conscrypt/native_crypto.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index c008d67cb..7993751b4 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -2585,14 +2585,14 @@ static void NativeCrypto_ED25519_keypair(JNIEnv* env, jclass, jbyteArray outPubl } if (outPublic.size() != ED25519_PUBLIC_KEY_LEN) { - conscrypt::jniutil::throwIllegalArgumentException( - env, "Output public key array length != 32"); + conscrypt::jniutil::throwIllegalArgumentException(env, + "Output public key array length != 32"); return; } if (outPrivate.size() != ED25519_PRIVATE_KEY_LEN) { - conscrypt::jniutil::throwIllegalArgumentException( - env, "Output private key array length != 64"); + conscrypt::jniutil::throwIllegalArgumentException(env, + "Output private key array length != 64"); return; } From 8067d687dd11437e05ec02c21f4cd00605060a5f Mon Sep 17 00:00:00 2001 From: juergw Date: Mon, 13 Jan 2025 13:20:39 +0000 Subject: [PATCH 6/6] Add throwIllegalArgumentException to jniutil.h. --- common/src/jni/main/include/conscrypt/jniutil.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/src/jni/main/include/conscrypt/jniutil.h b/common/src/jni/main/include/conscrypt/jniutil.h index 68ce128d9..7ae567dbc 100644 --- a/common/src/jni/main/include/conscrypt/jniutil.h +++ b/common/src/jni/main/include/conscrypt/jniutil.h @@ -195,6 +195,11 @@ extern int throwNullPointerException(JNIEnv* env, const char* msg); */ extern int throwOutOfMemory(JNIEnv* env, const char* message); +/** + * Throws an IllegalArgumentException with the given string as a message. + */ +extern int throwIllegalArgumentException(JNIEnv* env, const char* message); + /** * Throws a BadPaddingException with the given string as a message. */