diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index d1bd6b089..7993751b4 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::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"); + 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,9 @@ static jlong evpDigestSignVerifyInit(JNIEnv* env, } JNI_TRACE("%s(%p, %p, %p) <- ptr", jniName, mdCtx, md, pkey); - if (md == nullptr) { + // 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"); return 0; @@ -3037,6 +3075,137 @@ 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 +11291,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 +11310,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"), @@ -11179,8 +11351,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/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. */ diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java index 8ec45290e..127a02e5b 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..d381b78b5 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java @@ -26,20 +26,35 @@ 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; 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; @@ -74,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 { @@ -2716,6 +2721,79 @@ 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);