From 2a1683f403f37d3e4519e2c34e16f4ecc3fdd536 Mon Sep 17 00:00:00 2001 From: Juerg Wullschleger Date: Fri, 10 Apr 2026 08:12:54 +0000 Subject: [PATCH] Project import generated by Copybara. PiperOrigin-RevId: 897544682 --- README.md | 3 +- .../conscrypt/compatibility_close_monitor.cc | 3 +- common/src/jni/main/cpp/conscrypt/jniload.cc | 8 +- common/src/jni/main/cpp/conscrypt/jniutil.cc | 19 +- .../jni/main/cpp/conscrypt/native_crypto.cc | 666 +++++++++++----- common/src/jni/main/include/conscrypt/NetFd.h | 9 +- .../src/jni/main/include/conscrypt/app_data.h | 15 +- .../src/jni/main/include/conscrypt/compat.h | 7 +- .../conscrypt/compatibility_close_monitor.h | 16 +- .../src/jni/main/include/conscrypt/jniutil.h | 17 +- .../src/jni/main/include/conscrypt/macros.h | 10 +- .../main/include/conscrypt/scoped_ssl_bio.h | 5 +- common/src/jni/main/include/conscrypt/trace.h | 3 +- .../nativehelper/scoped_primitive_array.h | 18 +- .../java/org/conscrypt/ActiveSession.java | 21 + .../java/org/conscrypt/ConscryptEngine.java | 6 +- .../ConscryptFileDescriptorSocket.java | 6 +- .../java/org/conscrypt/ConscryptSession.java | 4 + .../java/org/conscrypt/ExternalSession.java | 10 + .../src/main/java/org/conscrypt/HpkeImpl.java | 68 ++ .../main/java/org/conscrypt/HpkeSuite.java | 19 +- .../conscrypt/Java7ExtendedSSLSession.java | 17 +- .../java/org/conscrypt/MlKemAlgorithm.java | 41 + .../main/java/org/conscrypt/NativeCrypto.java | 8 +- .../java/org/conscrypt/OpenSSLProvider.java | 22 + .../conscrypt/OpenSSLX25519PrivateKey.java | 15 +- .../org/conscrypt/OpenSSLX25519PublicKey.java | 12 +- .../org/conscrypt/OpenSslMlKemKeyFactory.java | 273 +++++++ .../OpenSslMlKemKeyPairGenerator.java | 85 ++ .../org/conscrypt/OpenSslMlKemPrivateKey.java | 116 +++ .../org/conscrypt/OpenSslMlKemPublicKey.java | 111 +++ .../java/org/conscrypt/SSLNullSession.java | 10 + .../src/main/java/org/conscrypt/SSLUtils.java | 63 ++ .../java/org/conscrypt/SessionSnapshot.java | 16 + .../test/java/org/conscrypt/MlKemTest.java | 746 ++++++++++++++++++ .../javax/net/ssl/KeyManagerFactoryTest.java | 18 +- .../javax/net/ssl/SSLSessionTest.java | 2 +- .../SSLSocketVersionCompatibilityTest.java | 1 - common/src/test/resources/crypto/mlkem.txt | 54 ++ .../src/main/java/org/conscrypt/Platform.java | 28 +- .../org/conscrypt/ConscryptAndroidSuite.java | 1 + .../org/conscrypt/ConscryptOpenJdkSuite.java | 1 + .../java/org/conscrypt/NativeCryptoTest.java | 202 ++++- .../conscrypt/java/security/TestKeyStore.java | 2 +- 44 files changed, 2453 insertions(+), 324 deletions(-) create mode 100644 common/src/main/java/org/conscrypt/MlKemAlgorithm.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslMlKemKeyFactory.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslMlKemKeyPairGenerator.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslMlKemPrivateKey.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslMlKemPublicKey.java create mode 100644 common/src/test/java/org/conscrypt/MlKemTest.java create mode 100644 common/src/test/resources/crypto/mlkem.txt diff --git a/README.md b/README.md index 44f163372..6f1d5575a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ + # Evolving Conscrypt's Open Source Approach Hello Conscrypt Developers, @@ -68,7 +69,7 @@ try { // Example: Listing providers to see what's available // val providers = Security.getProviders() // providers.forEach { provider -> - // println("Provider: ${provider.name}") + // println("Provider: " + provider.name) // } } catch (e: NoSuchAlgorithmException) { // Handle exception diff --git a/common/src/jni/main/cpp/conscrypt/compatibility_close_monitor.cc b/common/src/jni/main/cpp/conscrypt/compatibility_close_monitor.cc index a4d9a9e82..ca0d37770 100644 --- a/common/src/jni/main/cpp/conscrypt/compatibility_close_monitor.cc +++ b/common/src/jni/main/cpp/conscrypt/compatibility_close_monitor.cc @@ -43,7 +43,8 @@ void CompatibilityCloseMonitor::init() { return; } #ifdef CONSCRYPT_UNBUNDLED - // Only attempt to initialise the legacy C++ API if the C API symbols were not found. + // Only attempt to initialise the legacy C++ API if the C API symbols were not + // found. lib = dlopen("libjavacore.so", RTLD_NOW); if (lib != nullptr) { if (asyncCloseMonitorCreate == nullptr) { diff --git a/common/src/jni/main/cpp/conscrypt/jniload.cc b/common/src/jni/main/cpp/conscrypt/jniload.cc index 49a3bd24d..b54aa93b3 100644 --- a/common/src/jni/main/cpp/conscrypt/jniload.cc +++ b/common/src/jni/main/cpp/conscrypt/jniload.cc @@ -41,15 +41,17 @@ jint libconscrypt_JNI_OnLoad(JavaVM* vm, void*) { // Register all of the native JNI methods. NativeCrypto::registerNativeMethods(env); - // Perform static initialization of the close monitor (if required on this platform). + // Perform static initialization of the close monitor (if required on this + // platform). CompatibilityCloseMonitor::init(); return CONSCRYPT_JNI_VERSION; } #ifdef STATIC_LIB -// A version of OnLoad called when the Conscrypt library has been statically linked to the JVM (For -// Java >= 1.8). The manner in which the library is statically linked is implementation specific. +// A version of OnLoad called when the Conscrypt library has been statically +// linked to the JVM (For Java >= 1.8). The manner in which the library is +// statically linked is implementation specific. // // See http://openjdk.java.net/jeps/178 CONSCRYPT_PUBLIC jint JNI_OnLoad_conscrypt(JavaVM* vm, void* reserved) { diff --git a/common/src/jni/main/cpp/conscrypt/jniutil.cc b/common/src/jni/main/cpp/conscrypt/jniutil.cc index ae24377e5..a57c6d810 100644 --- a/common/src/jni/main/cpp/conscrypt/jniutil.cc +++ b/common/src/jni/main/cpp/conscrypt/jniutil.cc @@ -93,7 +93,10 @@ void init(JavaVM* vm, JNIEnv* env) { openSslInputStreamClass = getGlobalRefToClass( env, TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLBIOInputStream"); sslHandshakeCallbacksClass = getGlobalRefToClass( - env, TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/NativeCrypto$SSLHandshakeCallbacks"); + env, + TO_STRING( + JNI_JARJAR_PREFIX) "org/conscrypt/" + "NativeCrypto$SSLHandshakeCallbacks"); nativeRef_address = getFieldRef(env, nativeRefClass, "address", "J"); #if defined(ANDROID) && !defined(CONSCRYPT_OPENJDK) @@ -119,7 +122,7 @@ void init(JavaVM* vm, JNIEnv* env) { sslHandshakeCallbacks_clientCertificateRequested = getMethodRef( env, sslHandshakeCallbacksClass, "clientCertificateRequested", "([B[I[[B)V"); sslHandshakeCallbacks_serverCertificateRequested = - getMethodRef(env, sslHandshakeCallbacksClass, "serverCertificateRequested", "()V"); + getMethodRef(env, sslHandshakeCallbacksClass, "serverCertificateRequested", "([I)V"); sslHandshakeCallbacks_clientPSKKeyRequested = getMethodRef( env, sslHandshakeCallbacksClass, "clientPSKKeyRequested", "(Ljava/lang/String;[B[B)I"); sslHandshakeCallbacks_serverPSKKeyRequested = @@ -178,8 +181,8 @@ int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor) { } extern bool isDirectByteBufferInstance(JNIEnv* env, jobject buffer) { - // Some versions of ART do not check the buffer validity when handling GetDirectBufferAddress() - // and GetDirectBufferCapacity(). + // Some versions of ART do not check the buffer validity when handling + // GetDirectBufferAddress() and GetDirectBufferCapacity(). if (buffer == nullptr) { return false; } @@ -191,7 +194,8 @@ extern bool isDirectByteBufferInstance(JNIEnv* env, jobject buffer) { bool isGetByteArrayElementsLikelyToReturnACopy(size_t size) { #if defined(ANDROID) && !defined(CONSCRYPT_OPENJDK) - // ART's GetByteArrayElements creates copies only for arrays smaller than 12 kB. + // ART's GetByteArrayElements creates copies only for arrays smaller than 12 + // kB. return size <= 12 * 1024; #else (void)size; @@ -447,8 +451,9 @@ void throwExceptionFromBoringSSLError(JNIEnv* env, CONSCRYPT_UNUSED const char* return; } - // If there's an error from BoringSSL it may have been caused by an exception in Java code, so - // ensure there isn't a pending exception before we throw a new one. + // If there's an error from BoringSSL it may have been caused by an exception + // in Java code, so ensure there isn't a pending exception before we throw a + // new one. if (!env->ExceptionCheck()) { char message[256]; ERR_error_string_n(error, message, sizeof(message)); diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index eaea7717f..fd5e2100b 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -67,8 +68,8 @@ using conscrypt::NativeCrypto; using conscrypt::SslError; /** - * Helper function that grabs the casts an ssl pointer and then checks for nullness. - * If this function returns nullptr and throwIfNull is + * Helper function that grabs the casts an ssl pointer and then checks for + * nullness. If this function returns nullptr and throwIfNull is * passed as true, then this function will call * throwSSLExceptionStr before returning, so in this case of * nullptr, a caller of this function should simply return and allow JNI @@ -248,7 +249,8 @@ static jbyteArray bignumToArray(JNIEnv* env, const BIGNUM* source, const char* s return nullptr; } - // Set the sign and convert to two's complement if necessary for the Java code. + // Set the sign and convert to two's complement if necessary for the Java + // code. if (BN_is_negative(source)) { bool carry = true; for (ssize_t i = static_cast(numBytes - 1); i >= 0; i--) { @@ -825,14 +827,17 @@ void init_engine_globals() { #define THROWN_EXCEPTION (-4) /** - * private static native int EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q); + * private static native int EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, + * byte[] p, byte[] q); */ static jlong NativeCrypto_EVP_PKEY_new_RSA(JNIEnv* env, jclass, jbyteArray n, jbyteArray e, jbyteArray d, jbyteArray p, jbyteArray q, jbyteArray dmp1, jbyteArray dmq1, jbyteArray iqmp) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_PKEY_new_RSA(n=%p, e=%p, d=%p, p=%p, q=%p, dmp1=%p, dmq1=%p, iqmp=%p)", n, e, d, - p, q, dmp1, dmq1, iqmp); + JNI_TRACE( + "EVP_PKEY_new_RSA(n=%p, e=%p, d=%p, p=%p, q=%p, dmp1=%p, dmq1=%p, " + "iqmp=%p)", + n, e, d, p, q, dmp1, dmq1, iqmp); if (e == nullptr && d == nullptr) { conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", @@ -892,10 +897,11 @@ static jlong NativeCrypto_EVP_PKEY_new_RSA(JNIEnv* env, jclass, jbyteArray n, jb // Determine what kind of key this is. // - // TODO(davidben): The caller already knows what kind of key they expect. Ideally we would have - // separate APIs for the caller. However, we currently tolerate, say, an RSAPrivateCrtKeySpec - // where most fields are null and silently make a public key out of it. This is probably a - // mistake, but would need to be a breaking change. + // TODO(davidben): The caller already knows what kind of key they expect. + // Ideally we would have separate APIs for the caller. However, we currently + // tolerate, say, an RSAPrivateCrtKeySpec where most fields are null and + // silently make a public key out of it. This is probably a mistake, but would + // need to be a breaking change. bssl::UniquePtr rsa; if (!dBN) { rsa.reset(RSA_new_public_key(nBN.get(), eBN.get())); @@ -987,8 +993,10 @@ static jlong NativeCrypto_EVP_PKEY_new_RSA(JNIEnv* env, jclass, jbyteArray n, jb return 0; } OWNERSHIP_TRANSFERRED(rsa); - JNI_TRACE("EVP_PKEY_new_RSA(n=%p, e=%p, d=%p, p=%p, q=%p dmp1=%p, dmq1=%p, iqmp=%p) => %p", n, - e, d, p, q, dmp1, dmq1, iqmp, pkey.get()); + JNI_TRACE( + "EVP_PKEY_new_RSA(n=%p, e=%p, d=%p, p=%p, q=%p dmp1=%p, dmq1=%p, " + "iqmp=%p) => %p", + n, e, d, p, q, dmp1, dmq1, iqmp, pkey.get()); return reinterpret_cast(pkey.release()); } @@ -1208,9 +1216,9 @@ static jlong NativeCrypto_EVP_parse_private_key(JNIEnv* env, jclass, jbyteArray CBS cbs; CBS_init(&cbs, reinterpret_cast(bytes.get()), bytes.size()); bssl::UniquePtr pkey(EVP_parse_private_key(&cbs)); - // We intentionally do not check that cbs is exhausted, as JCA providers typically - // allow parsing keys from buffers that are larger than the contained key structure - // so we do the same for compatibility. + // We intentionally do not check that cbs is exhausted, as JCA providers + // typically allow parsing keys from buffers that are larger than the + // contained key structure so we do the same for compatibility. if (!pkey) { conscrypt::jniutil::throwParsingException(env, "Error parsing private key"); ERR_clear_error(); @@ -1657,9 +1665,9 @@ static jlong NativeCrypto_EVP_parse_public_key(JNIEnv* env, jclass, jbyteArray k CBS cbs; CBS_init(&cbs, reinterpret_cast(bytes.get()), bytes.size()); bssl::UniquePtr pkey(EVP_parse_public_key(&cbs)); - // We intentionally do not check that cbs is exhausted, as JCA providers typically - // allow parsing keys from buffers that are larger than the contained key structure - // so we do the same for compatibility. + // We intentionally do not check that cbs is exhausted, as JCA providers + // typically allow parsing keys from buffers that are larger than the + // contained key structure so we do the same for compatibility. if (!pkey) { conscrypt::jniutil::throwParsingException(env, "Error parsing public key"); ERR_clear_error(); @@ -1686,7 +1694,8 @@ static jlong NativeCrypto_getRSAPrivateKeyWrapper(JNIEnv* env, jclass, jobject j return 0; } - // TODO(crbug.com/boringssl/602): RSA_METHOD is not the ideal abstraction to use here. + // TODO(crbug.com/boringssl/602): RSA_METHOD is not the ideal abstraction to + // use here. bssl::UniquePtr rsa(RSA_new_method_no_e(g_engine, n.get())); if (rsa == nullptr) { conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate RSA key"); @@ -1782,7 +1791,8 @@ static jlong NativeCrypto_getECPrivateKeyWrapper(JNIEnv* env, jclass, jobject ja } /* - * public static native int RSA_generate_key(int modulusBits, byte[] publicExponent); + * public static native int RSA_generate_key(int modulusBits, byte[] + * publicExponent); */ static jlong NativeCrypto_RSA_generate_key_ex(JNIEnv* env, jclass, jint modulusBits, jbyteArray publicExponent) { @@ -2048,22 +2058,30 @@ static void NativeCrypto_chacha20_encrypt_decrypt(JNIEnv* env, jclass, jbyteArra JNI_TRACE("chacha20_encrypt_decrypt"); ScopedByteArrayRO in(env, inBytes); if (in.get() == nullptr) { - JNI_TRACE("chacha20_encrypt_decrypt => threw exception: could not read input bytes"); + JNI_TRACE( + "chacha20_encrypt_decrypt => threw exception: could not read input " + "bytes"); return; } ScopedByteArrayRW out(env, outBytes); if (out.get() == nullptr) { - JNI_TRACE("chacha20_encrypt_decrypt => threw exception: could not read output bytes"); + JNI_TRACE( + "chacha20_encrypt_decrypt => threw exception: could not read output " + "bytes"); return; } ScopedByteArrayRO key(env, keyBytes); if (key.get() == nullptr) { - JNI_TRACE("chacha20_encrypt_decrypt => threw exception: could not read key bytes"); + JNI_TRACE( + "chacha20_encrypt_decrypt => threw exception: could not read key " + "bytes"); return; } ScopedByteArrayRO nonce(env, nonceBytes); if (nonce.get() == nullptr) { - JNI_TRACE("chacha20_encrypt_decrypt => threw exception: could not read nonce bytes"); + JNI_TRACE( + "chacha20_encrypt_decrypt => threw exception: could not read nonce " + "bytes"); return; } @@ -2840,8 +2858,8 @@ static jint NativeCrypto_ECDSA_verify(JNIEnv* env, jclass, jbyteArray data, jint unsigned long error = ERR_peek_last_error(); if ((ERR_GET_LIB(error) == ERR_LIB_ECDSA) && (ERR_GET_REASON(error) == ECDSA_R_BAD_SIGNATURE)) { - // This error just means the signature didn't verify, so clear the error and return - // a failed verification + // This error just means the signature didn't verify, so clear the error + // and return a failed verification ERR_clear_error(); JNI_TRACE("ECDSA_verify(%p, %d, %p, %p) => %d", data, dataLen, sig, pkey, result); return 0; @@ -3240,6 +3258,108 @@ static jbyteArray NativeCrypto_XWING_public_key_from_seed(JNIEnv* env, jclass, return publicKeyRef.release(); } +static jbyteArray NativeCrypto_MLKEM768_public_key_from_seed(JNIEnv* env, jclass, + jbyteArray privateKeySeed) { + CHECK_ERROR_QUEUE_ON_RETURN; + + ScopedByteArrayRO seedArray(env, privateKeySeed); + if (seedArray.get() == nullptr) { + JNI_TRACE("MLKEM768_public_key_from_seed => privateKeySeed == null"); + return nullptr; + } + + if (seedArray.size() != MLKEM_SEED_BYTES) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "privateKeySeed length != 64"); + return nullptr; + } + + MLKEM768_private_key privateKey; + if (!MLKEM768_private_key_from_seed( + &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { + JNI_TRACE("MLKEM768_private_key_from_seed failed"); + conscrypt::jniutil::throwIllegalArgumentException(env, + "MLKEM768_private_key_from_seed failed"); + return nullptr; + } + + MLKEM768_public_key publicKey; + MLKEM768_public_from_private(&publicKey, &privateKey); + + ScopedLocalRef publicKeyRef( + env, env->NewByteArray(static_cast(MLKEM768_PUBLIC_KEY_BYTES))); + if (publicKeyRef.get() == nullptr) { + return nullptr; + } + ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); + if (publicKeyArray.get() == nullptr) { + return nullptr; + } + + CBB cbb; + size_t size; + if (!CBB_init_fixed(&cbb, reinterpret_cast(publicKeyArray.get()), + publicKeyArray.size()) || + !MLKEM768_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || + size != MLKEM768_PUBLIC_KEY_BYTES) { + JNI_TRACE("MLKEM768_marshal_public_key failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLKEM768_marshal_public_key"); + return nullptr; + } + return publicKeyRef.release(); +} + +static jbyteArray NativeCrypto_MLKEM1024_public_key_from_seed(JNIEnv* env, jclass, + jbyteArray privateKeySeed) { + CHECK_ERROR_QUEUE_ON_RETURN; + + ScopedByteArrayRO seedArray(env, privateKeySeed); + if (seedArray.get() == nullptr) { + JNI_TRACE("MLKEM1024_public_key_from_seed => privateKeySeed == null"); + return nullptr; + } + + if (seedArray.size() != MLKEM_SEED_BYTES) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "privateKeySeed length != 64"); + return nullptr; + } + + MLKEM1024_private_key privateKey; + if (!MLKEM1024_private_key_from_seed( + &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { + JNI_TRACE("MLKEM1024_private_key_from_seed failed"); + conscrypt::jniutil::throwIllegalArgumentException(env, + "MLKEM1024_private_key_from_seed failed"); + return nullptr; + } + + MLKEM1024_public_key publicKey; + MLKEM1024_public_from_private(&publicKey, &privateKey); + + ScopedLocalRef publicKeyRef( + env, env->NewByteArray(static_cast(MLKEM1024_PUBLIC_KEY_BYTES))); + if (publicKeyRef.get() == nullptr) { + return nullptr; + } + ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); + if (publicKeyArray.get() == nullptr) { + return nullptr; + } + + CBB cbb; + size_t size; + if (!CBB_init_fixed(&cbb, reinterpret_cast(publicKeyArray.get()), + publicKeyArray.size()) || + !MLKEM1024_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || + size != MLKEM1024_PUBLIC_KEY_BYTES) { + JNI_TRACE("MLKEM1024_marshal_public_key failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLKEM1024_marshal_public_key"); + return nullptr; + } + return publicKeyRef.release(); +} + static jlong NativeCrypto_EVP_MD_CTX_create(JNIEnv* env, jclass) { CHECK_ERROR_QUEUE_ON_RETURN; JNI_TRACE_MD("EVP_MD_CTX_create()"); @@ -3526,18 +3646,18 @@ static void evpUpdate(JNIEnv* env, jobject evpMdCtxRef, jbyteArray inJavaBytes, int update_func_result = -1; if (conscrypt::jniutil::isGetByteArrayElementsLikelyToReturnACopy(array_size)) { - // GetByteArrayElements is expected to return a copy. Use GetByteArrayRegion instead, to - // avoid copying the whole array. + // GetByteArrayElements is expected to return a copy. Use GetByteArrayRegion + // instead, to avoid copying the whole array. if (in_size <= 1024) { - // For small chunk, it's more efficient to use a bit more space on the stack instead of - // allocating a new buffer. + // For small chunk, it's more efficient to use a bit more space on the + // stack instead of allocating a new buffer. jbyte buf[1024]; env->GetByteArrayRegion(inJavaBytes, in_offset, in_size, buf); update_func_result = update_func(mdCtx, reinterpret_cast(buf), static_cast(in_size)); } else { - // For large chunk, allocate a 64 kB buffer and stream the chunk into update_func - // through the buffer, stopping as soon as update_func fails. + // For large chunk, allocate a 64 kB buffer and stream the chunk into + // update_func through the buffer, stopping as soon as update_func fails. jint remaining = in_size; jint buf_size = (remaining >= 65536) ? 65536 : remaining; std::unique_ptr buf(new jbyte[static_cast(buf_size)]); @@ -3560,9 +3680,10 @@ static void evpUpdate(JNIEnv* env, jobject evpMdCtxRef, jbyteArray inJavaBytes, } } } else { - // GetByteArrayElements is expected to not return a copy. Use GetByteArrayElements. - // We're not using ScopedByteArrayRO here because its an implementation detail whether it'll - // use GetByteArrayElements or another approach. + // GetByteArrayElements is expected to not return a copy. Use + // GetByteArrayElements. We're not using ScopedByteArrayRO here because its + // an implementation detail whether it'll use GetByteArrayElements or + // another approach. jbyte* array_elements = env->GetByteArrayElements(inJavaBytes, nullptr); if (array_elements == nullptr) { conscrypt::jniutil::throwOutOfMemory(env, "Unable to obtain elements of inBytes"); @@ -3708,8 +3829,9 @@ static jboolean NativeCrypto_EVP_DigestVerifyFinal(JNIEnv* env, jclass, jobject 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. + // 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_DigestVerifyFinal(%p) => %d", mdCtx, result); @@ -3839,8 +3961,9 @@ static jboolean NativeCrypto_EVP_DigestVerify(JNIEnv* env, jclass, jobject evpMd 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. + // 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); @@ -4180,8 +4303,8 @@ static void NativeCrypto_EVP_CipherInit_ex(JNIEnv* env, jclass, jobject ctxRef, } /* - * public static native int EVP_CipherUpdate(long ctx, byte[] out, int outOffset, byte[] in, - * int inOffset, int inLength); + * public static native int EVP_CipherUpdate(long ctx, byte[] out, int + * outOffset, byte[] in, int inOffset, int inLength); */ static jint NativeCrypto_EVP_CipherUpdate(JNIEnv* env, jclass, jobject ctxRef, jbyteArray outArray, jint outOffset, jbyteArray inArray, jint inOffset, @@ -4216,7 +4339,8 @@ static jint NativeCrypto_EVP_CipherUpdate(JNIEnv* env, jclass, jobject ctxRef, j } JNI_TRACE( - "ctx=%p EVP_CipherUpdate in=%p in.length=%zd inOffset=%d inLength=%d out=%p " + "ctx=%p EVP_CipherUpdate in=%p in.length=%zd inOffset=%d inLength=%d " + "out=%p " "out.length=%zd outOffset=%d", ctx, inBytes.get(), inBytes.size(), inOffset, inLength, outBytes.get(), outBytes.size(), outOffset); @@ -4354,7 +4478,8 @@ static void NativeCrypto_EVP_CIPHER_CTX_set_padding(JNIEnv* env, jclass, jobject return; } - EVP_CIPHER_CTX_set_padding(ctx, enablePadding); // Not void, but always returns 1. + EVP_CIPHER_CTX_set_padding(ctx, + enablePadding); // Not void, but always returns 1. JNI_TRACE("EVP_CIPHER_CTX_set_padding(%p, %d) => success", ctx, enablePadding); } @@ -4520,9 +4645,11 @@ static jint evp_aead_ctx_op(JNIEnv* env, jlong evpAeadRef, jbyteArray keyArray, } if (ARRAY_OFFSET_INVALID(outBytes, outOffset)) { - JNI_TRACE("evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p) => out offset invalid", - evpAead, keyArray, tagLen, outArray, outOffset, nonceArray, inArray, inOffset, - inLength, aadArray); + JNI_TRACE( + "evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p) => out offset " + "invalid", + evpAead, keyArray, tagLen, outArray, outOffset, nonceArray, inArray, inOffset, + inLength, aadArray); conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", "out"); return 0; } @@ -4534,7 +4661,8 @@ static jint evp_aead_ctx_op(JNIEnv* env, jlong evpAeadRef, jbyteArray keyArray, if (ARRAY_OFFSET_LENGTH_INVALID(inBytes, inOffset, inLength)) { JNI_TRACE( - "evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p) => in offset/length " + "evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p) => in " + "offset/length " "invalid", evpAead, keyArray, tagLen, outArray, outOffset, nonceArray, inArray, inOffset, inLength, aadArray); @@ -4741,14 +4869,15 @@ static jbyteArray NativeCrypto_EVP_HPKE_CTX_open(JNIEnv* env, jclass, jobject re size_t plaintextLen; std::vector plaintext(ciphertext.size()); - if (!EVP_HPKE_CTX_open(/* ctx= */ ctx, - /* out= */ plaintext.data(), - /* out_len= */ &plaintextLen, - /* max_out_len= */ plaintext.size(), - /* in= */ reinterpret_cast(ciphertext.get()), - /* in_len= */ ciphertext.size(), - /* aad= */ aad, - /* aad_len= */ aadLen)) { + if (!EVP_HPKE_CTX_open( + /* ctx= */ ctx, + /* out= */ plaintext.data(), + /* out_len= */ &plaintextLen, + /* max_out_len= */ plaintext.size(), + /* in= */ reinterpret_cast(ciphertext.get()), + /* in_len= */ ciphertext.size(), + /* aad= */ aad, + /* aad_len= */ aadLen)) { conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_open"); return {}; } @@ -4799,14 +4928,15 @@ static jbyteArray NativeCrypto_EVP_HPKE_CTX_seal(JNIEnv* env, jclass, jobject se std::vector encrypted(env->GetArrayLength(plaintextArray) + EVP_HPKE_CTX_max_overhead(ctx)); size_t encryptedLen; - if (!EVP_HPKE_CTX_seal(/* ctx= */ ctx, - /* out= */ encrypted.data(), - /* out_len= */ &encryptedLen, - /* max_out_len= */ encrypted.size(), - /* in= */ reinterpret_cast(plaintext.get()), - /* in_len= */ plaintext.size(), - /* aad= */ aad, - /* aad_len= */ aadLen)) { + if (!EVP_HPKE_CTX_seal( + /* ctx= */ ctx, + /* out= */ encrypted.data(), + /* out_len= */ &encryptedLen, + /* max_out_len= */ encrypted.size(), + /* in= */ reinterpret_cast(plaintext.get()), + /* in_len= */ plaintext.size(), + /* aad= */ aad, + /* aad_len= */ aadLen)) { conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_seal"); return {}; } @@ -4852,6 +4982,10 @@ const EVP_HPKE_KDF* getHpkeKdf(JNIEnv* env, jint kdfValue) { const EVP_HPKE_KEM* getHpkeKem(JNIEnv* env, jint kemValue) { if (kemValue == EVP_HPKE_DHKEM_X25519_HKDF_SHA256) { return EVP_hpke_x25519_hkdf_sha256(); + } else if (kemValue == EVP_HPKE_MLKEM768) { + return EVP_hpke_mlkem768(); + } else if (kemValue == EVP_HPKE_MLKEM1024) { + return EVP_hpke_mlkem1024(); } else if (kemValue == EVP_HPKE_XWING) { return EVP_hpke_xwing(); } else { @@ -4893,10 +5027,11 @@ static jobject NativeCrypto_EVP_HPKE_CTX_setup_base_mode_recipient( bssl::ScopedEVP_HPKE_KEY key; - if (!EVP_HPKE_KEY_init(/* key= */ key.get(), - /* kem= */ kem, - /* priv_key= */ reinterpret_cast(privateKey.get()), - /* priv_key_len= */ privateKey.size())) { + if (!EVP_HPKE_KEY_init( + /* key= */ key.get(), + /* kem= */ kem, + /* priv_key= */ reinterpret_cast(privateKey.get()), + /* priv_key_len= */ privateKey.size())) { conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_setup_recipient"); return nullptr; } @@ -4915,14 +5050,15 @@ static jobject NativeCrypto_EVP_HPKE_CTX_setup_base_mode_recipient( bssl::UniquePtr ctx(EVP_HPKE_CTX_new()); ScopedByteArrayRO enc(env, encArray); - if (!EVP_HPKE_CTX_setup_recipient(/* ctx= */ ctx.get(), - /* key= */ key.get(), - /* kdf= */ kdf, - /* aead= */ aead, - /* enc= */ reinterpret_cast(enc.get()), - /* enc_len= */ enc.size(), - /* info= */ info, - /* info_len= */ infoLen)) { + if (!EVP_HPKE_CTX_setup_recipient( + /* ctx= */ ctx.get(), + /* key= */ key.get(), + /* kdf= */ kdf, + /* aead= */ aead, + /* enc= */ reinterpret_cast(enc.get()), + /* enc_len= */ enc.size(), + /* info= */ info, + /* info_len= */ infoLen)) { conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_setup_recipient"); return nullptr; } @@ -4987,7 +5123,8 @@ static jobjectArray NativeCrypto_EVP_HPKE_CTX_setup_base_mode_sender(JNIEnv* env /* kem= */ kem, /* kdf= */ kdf, /* aead= */ aead, - /* peer_public_key= */ reinterpret_cast(peer_public_key.get()), + /* peer_public_key= */ + reinterpret_cast(peer_public_key.get()), /* peer_public_key_len= */ peer_public_key.size(), /* info= */ info, /* info_len= */ infoLen)) { @@ -5074,7 +5211,8 @@ static jobjectArray NativeCrypto_EVP_HPKE_CTX_setup_base_mode_sender_with_seed_f /* kem= */ kem, /* kdf= */ kdf, /* aead= */ aead, - /* peer_public_key= */ reinterpret_cast(peer_public_key.get()), + /* peer_public_key= */ + reinterpret_cast(peer_public_key.get()), /* peer_public_key_len= */ peer_public_key.size(), /* info= */ info, /* info_len= */ infoLen, @@ -5409,7 +5547,8 @@ static void NativeCrypto_HMAC_Reset(JNIEnv* env, jclass, jobject hmacCtxRef) { // HMAC_Init_ex with all nulls will reuse the existing key. This is slightly // more efficient than re-initializing the context with the key again. - if (!HMAC_Init_ex(hmacCtx, /*key=*/nullptr, /*key_len=*/0, /*md=*/nullptr, /*impl=*/nullptr)) { + if (!HMAC_Init_ex(hmacCtx, /*key=*/nullptr, /*key_len=*/0, /*md=*/nullptr, + /*impl=*/nullptr)) { JNI_TRACE("HMAC_Reset(%p) => threw exception", hmacCtx); conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "HMAC_Init_ex"); return; @@ -5669,8 +5808,10 @@ static jobjectArray NativeCrypto_get_X509_GENERAL_NAME_stack(JNIEnv* env, jclass GENERAL_NAME* gen = sk_GENERAL_NAME_value(gn_stack.get(), static_cast(i)); ScopedLocalRef val(env, GENERAL_NAME_to_jobject(env, gen)); if (env->ExceptionCheck()) { - JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => threw exception parsing gen name", - x509, type); + JNI_TRACE( + "get_X509_GENERAL_NAME_stack(%p, %d) => threw exception parsing gen " + "name", + x509, type); return nullptr; } @@ -5698,8 +5839,10 @@ static jobjectArray NativeCrypto_get_X509_GENERAL_NAME_stack(JNIEnv* env, jclass } if (count == 0) { - JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) shrunk from %d to 0; returning nullptr", - x509, type, origCount); + JNI_TRACE( + "get_X509_GENERAL_NAME_stack(%p, %d) shrunk from %d to 0; returning " + "nullptr", + x509, type, origCount); joa.reset(nullptr); } else if (origCount != count) { JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) shrunk from %d to %d", x509, type, origCount, @@ -5910,8 +6053,8 @@ static jbyteArray NativeCrypto_get_X509_tbs_cert_without_ext(JNIEnv* env, jclass return nullptr; } - // Remove the extension and re-encode the TBSCertificate. Note |i2d_re_X509_tbs| ignores the - // cached encoding. + // Remove the extension and re-encode the TBSCertificate. Note + // |i2d_re_X509_tbs| ignores the cached encoding. X509_EXTENSION_free(X509_delete_ext(copy.get(), extIndex)); return ASN1ToByteArray(env, copy.get(), i2d_re_X509_tbs); } @@ -5932,10 +6075,11 @@ static jint NativeCrypto_get_X509_ex_flags(JNIEnv* env, jclass, jlong x509Ref, // X509_get_extension_flags sometimes leaves values in the error queue. See // https://crbug.com/boringssl/382. // - // TODO(https://github.com/google/conscrypt/issues/916): This function is used to check - // EXFLAG_CA, but does not check EXFLAG_INVALID. Fold the two JNI calls in getBasicConstraints() - // together and handle errors. (See also NativeCrypto_get_X509_ex_pathlen.) From there, limit - // this JNI call to EXFLAG_CRITICAL. + // TODO(https://github.com/google/conscrypt/issues/916): This function is used + // to check EXFLAG_CA, but does not check EXFLAG_INVALID. Fold the two JNI + // calls in getBasicConstraints() together and handle errors. (See also + // NativeCrypto_get_X509_ex_pathlen.) From there, limit this JNI call to + // EXFLAG_CRITICAL. ERR_clear_error(); return flags; } @@ -6220,8 +6364,8 @@ static jbyteArray get_X509_ALGOR_parameter(JNIEnv* env, const X509_ALGOR* algor) return nullptr; } - // The OpenSSL 1.1.x API lacks a function to get the ASN1_TYPE out of X509_ALGOR directly, so - // recreate it from the returned components. + // The OpenSSL 1.1.x API lacks a function to get the ASN1_TYPE out of + // X509_ALGOR directly, so recreate it from the returned components. bssl::UniquePtr param(ASN1_TYPE_new()); if (!param || !ASN1_TYPE_set1(param.get(), param_type, param_value)) { conscrypt::jniutil::throwOutOfMemory(env, "Unable to serialize parameter"); @@ -6434,8 +6578,9 @@ static void NativeCrypto_X509_REVOKED_print(JNIEnv* env, jclass, jlong bioRef, j BIO_printf(bio, "\nRevocation Date: "); ASN1_TIME_print(bio, X509_REVOKED_get0_revocationDate(revoked)); BIO_printf(bio, "\n"); - // TODO(davidben): Should the flags parameter be |X509V3_EXT_DUMP_UNKNOWN| so we don't error on - // unknown extensions. Alternatively, maybe we can use a simpler toString() implementation. + // TODO(davidben): Should the flags parameter be |X509V3_EXT_DUMP_UNKNOWN| so + // we don't error on unknown extensions. Alternatively, maybe we can use a + // simpler toString() implementation. X509V3_extensions_print(bio, "CRL entry extensions", X509_REVOKED_get0_extensions(revoked), 0, 0); } @@ -7554,9 +7699,10 @@ static jbooleanArray NativeCrypto_get_X509_ex_kusage(JNIEnv* env, jclass, jlong return nullptr; } - // TODO(https://github.com/google/conscrypt/issues/916): Handle errors and remove - // |ERR_clear_error|. Note X509Certificate.getKeyUsage() cannot throw - // CertificateParsingException, so this needs to be checked earlier, e.g. in the constructor. + // TODO(https://github.com/google/conscrypt/issues/916): Handle errors and + // remove |ERR_clear_error|. Note X509Certificate.getKeyUsage() cannot throw + // CertificateParsingException, so this needs to be checked earlier, e.g. in + // the constructor. bssl::UniquePtr bitStr( static_cast(X509_get_ext_d2i(x509, NID_key_usage, nullptr, nullptr))); if (bitStr.get() == nullptr) { @@ -7620,14 +7766,16 @@ static jint NativeCrypto_get_X509_ex_pathlen(JNIEnv* env, jclass, jlong x509Ref, return 0; } - // Use |X509_get_ext_d2i| instead of |X509_get_pathlen| because the latter treats - // |EXFLAG_INVALID| as the error case. |EXFLAG_INVALID| is set if any built-in extension is - // invalid. For now, we preserve Conscrypt's historical behavior in accepting certificates in - // the constructor even if |EXFLAG_INVALID| is set. + // Use |X509_get_ext_d2i| instead of |X509_get_pathlen| because the latter + // treats |EXFLAG_INVALID| as the error case. |EXFLAG_INVALID| is set if any + // built-in extension is invalid. For now, we preserve Conscrypt's historical + // behavior in accepting certificates in the constructor even if + // |EXFLAG_INVALID| is set. // - // TODO(https://github.com/google/conscrypt/issues/916): Handle errors and remove - // |ERR_clear_error|. Note X509Certificate.getBasicConstraints() cannot throw - // CertificateParsingException, so this needs to be checked earlier, e.g. in the constructor. + // TODO(https://github.com/google/conscrypt/issues/916): Handle errors and + // remove |ERR_clear_error|. Note X509Certificate.getBasicConstraints() cannot + // throw CertificateParsingException, so this needs to be checked earlier, + // e.g. in the constructor. bssl::UniquePtr basic_constraints(static_cast( X509_get_ext_d2i(x509, NID_basic_constraints, nullptr, nullptr))); if (basic_constraints == nullptr) { @@ -7643,23 +7791,26 @@ static jint NativeCrypto_get_X509_ex_pathlen(JNIEnv* env, jclass, jlong x509Ref, if (!basic_constraints->ca) { // Path length constraints are only valid for CA certificates. - // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an error condition. + // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an + // error condition. JNI_TRACE("get_X509_ex_path(%p) => -1 (not a CA)", x509); return -1; } if (basic_constraints->pathlen->type == V_ASN1_NEG_INTEGER) { // Path length constraints may not be negative. - // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an error condition. + // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an + // error condition. JNI_TRACE("get_X509_ex_path(%p) => -1 (negative)", x509); return -1; } long pathlen = ASN1_INTEGER_get(basic_constraints->pathlen); if (pathlen == -1 || pathlen > INT_MAX) { - // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an error condition. - // If the integer overflows, the certificate is effectively unconstrained. Reporting no - // constraint is plausible, but Chromium rejects all values above 255. + // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an + // error condition. If the integer overflows, the certificate is effectively + // unconstrained. Reporting no constraint is plausible, but Chromium rejects + // all values above 255. JNI_TRACE("get_X509_ex_path(%p) => -1 (overflow)", x509); return -1; } @@ -7844,13 +7995,14 @@ static void info_callback_LOG(const SSL* s, int where, int ret) { * * @param env * @param type Either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE - * @param fdObject The FileDescriptor, since appData->fileDescriptor should be NULL + * @param fdObject The FileDescriptor, since appData->fileDescriptor should be + * NULL * @param appData The application data structure with mutex info etc. - * @param timeout_millis The timeout value for select call, with the special value - * 0 meaning no timeout at all (wait indefinitely). Note: This is - * the Java semantics of the timeout value, not the usual - * select() semantics. - * @return THROWN_EXCEPTION on close socket, 0 on timeout, -1 on error, and 1 on success + * @param timeout_millis The timeout value for select call, with the special + * value 0 meaning no timeout at all (wait indefinitely). Note: This is the Java + * semantics of the timeout value, not the usual select() semantics. + * @return THROWN_EXCEPTION on close socket, 0 on timeout, -1 on error, and 1 on + * success */ static int sslSelect(JNIEnv* env, int type, jobject fdObject, AppData* appData, int timeout_millis) { @@ -7919,7 +8071,8 @@ static int sslSelect(JNIEnv* env, int type, jobject fdObject, AppData* appData, * * @param env * @param type Either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE - * @param fdObject The FileDescriptor, since appData->fileDescriptor should be nullptr + * @param fdObject The FileDescriptor, since appData->fileDescriptor should be + * nullptr * @param appData The application data structure with mutex info etc. * @param timeout_millis The timeout value for poll call, with the special value * 0 meaning no timeout at all (wait indefinitely). Note: This is @@ -8057,8 +8210,10 @@ static ssl_verify_result_t cert_verify_callback(SSL* ssl, CONSCRYPT_UNUSED uint8 const SSL_CIPHER* cipher = SSL_get_pending_cipher(ssl); const char* authMethod = SSL_CIPHER_get_kx_name(cipher); - JNI_TRACE("ssl=%p cert_verify_callback calling verifyCertificateChain authMethod=%s", ssl, - authMethod); + JNI_TRACE( + "ssl=%p cert_verify_callback calling verifyCertificateChain " + "authMethod=%s", + ssl, authMethod); ScopedLocalRef authMethodString(env, env->NewStringUTF(authMethod)); env->CallVoidMethod(sslHandshakeCallbacks, methodID, array.get(), authMethodString.get()); @@ -8212,8 +8367,29 @@ static enum ssl_select_cert_result_t select_certificate_cb(const SSL_CLIENT_HELL jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_serverCertificateRequested; + const uint16_t* sigalgs = nullptr; + size_t sigalgs_num = SSL_get0_peer_verify_algorithms(ssl, &sigalgs); + + if (sigalgs_num > static_cast(INT_MAX)) { + conscrypt::jniutil::throwRuntimeException(env, "Too many signature algorithms"); + return ssl_select_cert_error; + } + jintArray signatureAlgs = env->NewIntArray(static_cast(sigalgs_num)); + if (signatureAlgs == nullptr) { + return ssl_select_cert_error; + } + { + ScopedIntArrayRW sigAlgsRW(env, signatureAlgs); + if (sigAlgsRW.get() == nullptr) { + return ssl_select_cert_error; + } + for (size_t i = 0; i < sigalgs_num; i++) { + sigAlgsRW[i] = sigalgs[i]; + } + } + JNI_TRACE("ssl=%p select_certificate_cb calling serverCertificateRequested", ssl); - env->CallVoidMethod(sslHandshakeCallbacks, methodID); + env->CallVoidMethod(sslHandshakeCallbacks, methodID, signatureAlgs); if (env->ExceptionCheck()) { JNI_TRACE("ssl=%p select_certificate_cb exception", ssl); @@ -8371,8 +8547,8 @@ static int new_session_callback(SSL* ssl, SSL_SESSION* session) { } JNI_TRACE("ssl=%p new_session_callback completed", ssl); - // Always returning 0 (not taking ownership). The Java code is responsible for incrementing - // the reference count. + // Always returning 0 (not taking ownership). The Java code is responsible for + // incrementing the reference count. return 0; } @@ -8380,8 +8556,8 @@ static SSL_SESSION* server_session_requested_callback(SSL* ssl, const uint8_t* i int* out_copy) { JNI_TRACE("ssl=%p server_session_requested_callback", ssl); - // Always set to out_copy to zero. The Java callback will be responsible for incrementing - // the reference count (and any required synchronization). + // Always set to out_copy to zero. The Java callback will be responsible for + // incrementing the reference count (and any required synchronization). *out_copy = 0; AppData* appData = toAppData(ssl); @@ -8482,7 +8658,8 @@ static jlong NativeCrypto_SSL_CTX_new(JNIEnv* env, jclass) { SSL_OP_ALL // We also disable session tickets for better compatibility b/2682876 | SSL_OP_NO_TICKET - // We also disable compression for better compatibility b/2710492 b/2710497 + // We also disable compression for better compatibility b/2710492 + // b/2710497 | SSL_OP_NO_COMPRESSION // Generate a fresh ECDH keypair for each key exchange. | SSL_OP_SINGLE_ECDH_USE); @@ -8507,10 +8684,9 @@ static jlong NativeCrypto_SSL_CTX_new(JNIEnv* env, jclass) { // Enable False Start. mode |= SSL_MODE_ENABLE_FALSE_START; - // We need to enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER as the memory address may change - // between - // calls to wrap(...). - // See https://github.com/netty/netty-tcnative/issues/100 + // We need to enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER as the memory address + // may change between calls to wrap(...). See + // https://github.com/netty/netty-tcnative/issues/100 mode |= SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER; SSL_CTX_set_mode(sslCtx.get(), mode); @@ -8561,8 +8737,10 @@ static void NativeCrypto_SSL_CTX_set_session_id_context(JNIEnv* env, jclass, jlo ScopedByteArrayRO buf(env, sid_ctx); if (buf.get() == nullptr) { - JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_set_session_id_context => threw exception", - ssl_ctx); + JNI_TRACE( + "ssl_ctx=%p NativeCrypto_SSL_CTX_set_session_id_context => threw " + "exception", + ssl_ctx); return; } @@ -8661,8 +8839,8 @@ static jbyteArray NativeCrypto_SSL_get_tls_channel_id(JNIEnv* env, jclass, jlong return nullptr; } - // Channel ID is 64 bytes long. Unfortunately, OpenSSL doesn't declare this length - // as a constant anywhere. + // Channel ID is 64 bytes long. Unfortunately, OpenSSL doesn't declare this + // length as a constant anywhere. jbyteArray javaBytes = env->NewByteArray(64); ScopedByteArrayRW bytes(env, javaBytes); if (bytes.get() == nullptr) { @@ -8671,9 +8849,10 @@ static jbyteArray NativeCrypto_SSL_get_tls_channel_id(JNIEnv* env, jclass, jlong } unsigned char* tmp = reinterpret_cast(bytes.get()); - // Unfortunately, the SSL_get_tls_channel_id method below always returns 64 (upon success) - // regardless of the number of bytes copied into the output buffer "tmp". Thus, the correctness - // of this code currently relies on the "tmp" buffer being exactly 64 bytes long. + // Unfortunately, the SSL_get_tls_channel_id method below always returns 64 + // (upon success) regardless of the number of bytes copied into the output + // buffer "tmp". Thus, the correctness of this code currently relies on the + // "tmp" buffer being exactly 64 bytes long. size_t ret = SSL_get_tls_channel_id(ssl, tmp, 64); if (ret == 0) { // Channel ID either not set or did not verify @@ -8727,8 +8906,10 @@ static void NativeCrypto_setLocalCertsAndPrivateKey(JNIEnv* env, jclass, jlong s jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_chain_and_key certificates=%p, privateKey=%p", ssl, - encodedCertificatesJava, pkeyRef); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_set_chain_and_key certificates=%p, " + "privateKey=%p", + ssl, encodedCertificatesJava, pkeyRef); if (ssl == nullptr) { return; } @@ -8900,11 +9081,14 @@ static jint NativeCrypto_SSL_set_protocol_versions(JNIEnv* env, jclass, jlong ss int result = 1; if (!min_result || !max_result) { result = 0; - // The only possible error is an invalid version, so we don't need the details. + // The only possible error is an invalid version, so we don't need the + // details. ERR_clear_error(); } - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_protocol_versions => (min: %d, max: %d) == %d", ssl, - min_result, max_result, result); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_set_protocol_versions => (min: %d, max: %d) == " + "%d", + ssl, min_result, max_result, result); return result; } @@ -8952,7 +9136,8 @@ static jbyteArray NativeCrypto_SSL_get_signed_cert_timestamp_list( } /* - * public static native void SSL_set_signed_cert_timestamp_list(long ssl, byte[] response); + * public static native void SSL_set_signed_cert_timestamp_list(long ssl, byte[] + * response); */ static void NativeCrypto_SSL_set_signed_cert_timestamp_list(JNIEnv* env, jclass, jlong ssl_address, CONSCRYPT_UNUSED jobject ssl_holder, @@ -8966,7 +9151,10 @@ static void NativeCrypto_SSL_set_signed_cert_timestamp_list(JNIEnv* env, jclass, ScopedByteArrayRO listBytes(env, list); if (listBytes.get() == nullptr) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_signed_cert_timestamp_list => list == null", ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_set_signed_cert_timestamp_list => list == " + "null", + ssl); return; } @@ -9054,10 +9242,10 @@ static void NativeCrypto_SSL_set_ocsp_response(JNIEnv* env, jclass, jlong ssl_ad } } -// All verify_data values are currently 12 bytes long, but cipher suites are allowed -// to customize the length of their verify_data (with a default of 12 bytes). We accept -// up to 16 bytes so that we can check that the results are actually 12 bytes long in -// tests and update this value if necessary. +// All verify_data values are currently 12 bytes long, but cipher suites are +// allowed to customize the length of their verify_data (with a default of 12 +// bytes). We accept up to 16 bytes so that we can check that the results are +// actually 12 bytes long in tests and update this value if necessary. const size_t MAX_TLS_UNIQUE_LENGTH = 16; static jbyteArray NativeCrypto_SSL_get_tls_unique(JNIEnv* env, jclass, jlong ssl_address, @@ -9103,7 +9291,10 @@ static jbyteArray NativeCrypto_SSL_export_keying_material(JNIEnv* env, jclass, j } ScopedByteArrayRO labelBytes(env, label); if (labelBytes.get() == nullptr) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_export_keying_material label == null => exception", ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_export_keying_material label == null => " + "exception", + ssl); return nullptr; } std::unique_ptr out(new uint8_t[num_bytes]); @@ -9115,8 +9306,10 @@ static jbyteArray NativeCrypto_SSL_export_keying_material(JNIEnv* env, jclass, j } else { ScopedByteArrayRO contextBytes(env, context); if (contextBytes.get() == nullptr) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_export_keying_material context == null => exception", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_export_keying_material context == null => " + "exception", + ssl); return nullptr; } ret = SSL_export_keying_material( @@ -9133,7 +9326,10 @@ static jbyteArray NativeCrypto_SSL_export_keying_material(JNIEnv* env, jclass, j jbyteArray result = env->NewByteArray(static_cast(num_bytes)); if (result == nullptr) { conscrypt::jniutil::throwSSLExceptionStr(env, "Could not create result array"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_export_keying_material => could not create array", ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_export_keying_material => could not create " + "array", + ssl); return nullptr; } const jbyte* src = reinterpret_cast(out.get()); @@ -9250,7 +9446,10 @@ static void NativeCrypto_SSL_set_cipher_lists(JNIEnv* env, jclass, jlong ssl_add SSL_set_cipher_list(ssl, ""); ERR_clear_error(); if (sk_SSL_CIPHER_num(SSL_get_ciphers(ssl)) != 0) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_cipher_lists cipherSuites=empty => error", ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_set_cipher_lists cipherSuites=empty => " + "error", + ssl); conscrypt::jniutil::throwRuntimeException( env, "SSL_set_cipher_list did not update ciphers!"); ERR_clear_error(); @@ -9404,8 +9603,10 @@ static void NativeCrypto_SSL_set_session_creation_enabled(JNIEnv* env, jclass, j jboolean creation_enabled) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_session_creation_enabled creation_enabled=%d", ssl, - creation_enabled); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_set_session_creation_enabled " + "creation_enabled=%d", + ssl, creation_enabled); if (ssl == nullptr) { return; } @@ -9484,8 +9685,8 @@ static jstring NativeCrypto_SSL_get_servername(JNIEnv* env, jclass, jlong ssl_ad } /** - * Selects the ALPN protocol to use. The list of protocols in "primary" is considered the order - * which should take precedence. + * Selects the ALPN protocol to use. The list of protocols in "primary" is + * considered the order which should take precedence. */ static int selectApplicationProtocol(SSL* ssl, unsigned char** out, unsigned char* outLength, const unsigned char* primary, const unsigned int primaryLength, @@ -9693,8 +9894,10 @@ static void NativeCrypto_SSL_do_handshake(JNIEnv* env, jclass, jlong ssl_address } if (shc == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake sslHandshakeCallbacks == null => exception", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_do_handshake sslHandshakeCallbacks == null => " + "exception", + ssl); return; } @@ -9716,8 +9919,8 @@ static void NativeCrypto_SSL_do_handshake(JNIEnv* env, jclass, jlong ssl_address } /* - * Make socket non-blocking, so SSL_connect SSL_read() and SSL_write() don't hang - * forever and we can use select() to find out if the socket is ready. + * Make socket non-blocking, so SSL_connect SSL_read() and SSL_write() don't + * hang forever and we can use select() to find out if the socket is ready. */ if (!conscrypt::netutil::setBlocking(fd.get(), false)) { conscrypt::jniutil::throwSSLExceptionStr(env, "Unable to make socket non blocking"); @@ -9786,15 +9989,19 @@ static void NativeCrypto_SSL_do_handshake(JNIEnv* env, jclass, jlong ssl_address conscrypt::jniutil::throwSSLExceptionWithSslErrors( env, ssl, SSL_ERROR_SYSCALL, "handshake error", conscrypt::jniutil::throwSSLHandshakeExceptionStr); - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake selectResult == -1 => exception", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_do_handshake selectResult == -1 => " + "exception", + ssl); return; } if (selectResult == 0) { conscrypt::jniutil::throwSocketTimeoutException(env, "SSL handshake timed out"); ERR_clear_error(); - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake selectResult == 0 => exception", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_do_handshake selectResult == 0 => " + "exception", + ssl); return; } } else { @@ -10132,8 +10339,8 @@ static jint NativeCrypto_SSL_read(JNIEnv* env, jclass, jlong ssl_address, conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate chunk buffer"); return 0; } - // TODO(flooey): Fix cumulative read timeout? The effective timeout is the multiplied - // by the number of internal calls to sslRead() below. + // TODO(flooey): Fix cumulative read timeout? The effective timeout is the + // multiplied by the number of internal calls to sslRead() below. ret = 0; while (remaining > 0) { jint temp_ret; @@ -10142,8 +10349,8 @@ static jint NativeCrypto_SSL_read(JNIEnv* env, jclass, jlong ssl_address, chunk_size, &sslError, read_timeout_millis); if (temp_ret < 0) { if (ret > 0) { - // We've already read some bytes; attempt to preserve them if this is an - // "expected" error. + // We've already read some bytes; attempt to preserve them if this + // is an "expected" error. if (temp_ret == -1) { // EOF break; @@ -10397,7 +10604,8 @@ static void NativeCrypto_SSL_write(JNIEnv* env, jclass, jlong ssl_address, ret = sslWrite(env, ssl, fdObject, shc, reinterpret_cast(&buf[0]), len, &sslError, write_timeout_millis); } else { - // TODO(flooey): Similar safety concerns and questions here as in SSL_read. + // TODO(flooey): Similar safety concerns and questions here as in + // SSL_read. jint remaining = len; jint buf_size = (remaining >= 65536) ? 65536 : remaining; std::unique_ptr buf(new jbyte[static_cast(buf_size)]); @@ -10735,7 +10943,8 @@ static jlong NativeCrypto_SSL_SESSION_get_timeout(JNIEnv* env, jclass, jlong ssl } /** - * Gets the ID for the SSL session, or null if no session is currently available. + * Gets the ID for the SSL session, or null if no session is currently + * available. */ static jbyteArray NativeCrypto_SSL_session_id(JNIEnv* env, jclass, jlong ssl_address, CONSCRYPT_UNUSED jobject ssl_holder) { @@ -10998,12 +11207,12 @@ static bool ocsp_cert_id_matches_certificate(CBS* cert_id, X509* x509, X509* iss } /** - * Get a SingleResponse whose CertID matches the given certificate and issuer from a - * SEQUENCE OF SingleResponse. + * Get a SingleResponse whose CertID matches the given certificate and issuer + * from a SEQUENCE OF SingleResponse. * - * If found, |out_single_response| is set to the response, and true is returned. Otherwise if an - * error occured or no response matches the certificate, false is returned and |out_single_response| - * is unchanged. + * If found, |out_single_response| is set to the response, and true is returned. + * Otherwise if an error occured or no response matches the certificate, false + * is returned and |out_single_response| is unchanged. */ static bool find_ocsp_single_response(CBS* responses, X509* x509, X509* issuerX509, CBS* out_single_response) { @@ -11037,8 +11246,8 @@ static bool find_ocsp_single_response(CBS* responses, X509* x509, X509* issuerX5 /** * Get the BasicOCSPResponse from an OCSPResponse. - * If parsing succeeds and the response is of type basic, |basic_response| is set to it, and true is - * returned. + * If parsing succeeds and the response is of type basic, |basic_response| is + * set to it, and true is returned. */ static bool get_ocsp_basic_response(CBS* ocsp_response, CBS* basic_response) { CBS tagged_response_bytes, response_bytes, response_type, response; @@ -11068,8 +11277,8 @@ static bool get_ocsp_basic_response(CBS* ocsp_response, CBS* basic_response) { /** * Get the SEQUENCE OF SingleResponse from a BasicOCSPResponse. - * If parsing succeeds, |single_responses| is set to point to the sequence of SingleResponse, and - * true is returned. + * If parsing succeeds, |single_responses| is set to point to the sequence of + * SingleResponse, and true is returned. */ static bool get_ocsp_single_responses(CBS* basic_response, CBS* single_responses) { // Parse the ResponseData out of the BasicOCSPResponse. Ignore the rest. @@ -11092,8 +11301,8 @@ static bool get_ocsp_single_responses(CBS* basic_response, CBS* single_responses /** * Get the SEQUENCE OF Extension from a SingleResponse. - * If parsing succeeds, |extensions| is set to point the the extension sequence and true is - * returned. + * If parsing succeeds, |extensions| is set to point the the extension sequence + * and true is returned. */ static bool get_ocsp_single_response_extensions(CBS* single_response, CBS* extensions) { // Skip the certID, certStatus, thisUpdate and optional nextUpdate fields. @@ -11111,8 +11320,8 @@ static bool get_ocsp_single_response_extensions(CBS* single_response, CBS* exten } /* - public static native byte[] get_ocsp_single_extension(byte[] ocspData, String oid, - long x509Ref, long issuerX509Ref); + public static native byte[] get_ocsp_single_extension(byte[] ocspData, + String oid, long x509Ref, long issuerX509Ref); */ static jbyteArray NativeCrypto_get_ocsp_single_extension( JNIEnv* env, jclass, jbyteArray ocspDataBytes, jstring oid, jlong x509Ref, @@ -11171,8 +11380,9 @@ static jbyteArray NativeCrypto_get_ocsp_single_extension( } static jlong NativeCrypto_getDirectBufferAddress(JNIEnv* env, jclass, jobject buffer) { - // The javadoc for NativeCrypto.getDirectBufferAddress(Buffer buf) defines the behaviour here, - // no throwing if the buffer is null or not a direct ByteBuffer. + // The javadoc for NativeCrypto.getDirectBufferAddress(Buffer buf) defines the + // behaviour here, no throwing if the buffer is null or not a direct + // ByteBuffer. if (!conscrypt::jniutil::isDirectByteBufferInstance(env, buffer)) { return 0; } @@ -11260,8 +11470,10 @@ static jint NativeCrypto_ENGINE_SSL_do_handshake(JNIEnv* env, jclass, jlong ssl_ if (shc == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_do_handshake => sslHandshakeCallbacks == null", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_do_handshake => sslHandshakeCallbacks " + "== null", + ssl); return 0; } @@ -11343,7 +11555,10 @@ static void NativeCrypto_ENGINE_SSL_shutdown(JNIEnv* env, jclass, jlong ssl_addr if (shc == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_shutdown => sslHandshakeCallbacks == null", ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_shutdown => sslHandshakeCallbacks == " + "null", + ssl); return; } @@ -11412,8 +11627,10 @@ static jint NativeCrypto_ENGINE_SSL_read_direct(JNIEnv* env, jclass, jlong ssl_a if (shc == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_read_direct => sslHandshakeCallbacks == null", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_read_direct => sslHandshakeCallbacks " + "== null", + ssl); return -1; } AppData* appData = toAppData(ssl); @@ -11434,7 +11651,8 @@ static jint NativeCrypto_ENGINE_SSL_read_direct(JNIEnv* env, jclass, jlong ssl_a int result = SSL_read(ssl, destPtr, length); appData->clearCallbackState(); if (env->ExceptionCheck()) { - // An exception was thrown by one of the callbacks. Just propagate that exception. + // An exception was thrown by one of the callbacks. Just propagate that + // exception. ERR_clear_error(); JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_read_direct => THROWN_EXCEPTION", ssl); return -1; @@ -11487,8 +11705,10 @@ static jint NativeCrypto_ENGINE_SSL_read_direct(JNIEnv* env, jclass, jlong ssl_a } } - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_read_direct address=%p length=%d shc=%p result=%d", - ssl, destPtr, length, shc, result); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_read_direct address=%p length=%d shc=%p " + "result=%d", + ssl, destPtr, length, shc, result); return result; } @@ -11514,8 +11734,8 @@ static int NativeCrypto_ENGINE_SSL_write_BIO_direct(JNIEnv* env, jclass, jlong s return -1; } if (len < 0 || BIO_ctrl_get_write_guarantee(bio) < static_cast(len)) { - // The network BIO couldn't handle the entire write. Don't write anything, so that we - // only process one packet at a time. + // The network BIO couldn't handle the entire write. Don't write anything, + // so that we only process one packet at a time. return 0; } const char* sourcePtr = reinterpret_cast(address); @@ -11539,7 +11759,8 @@ static int NativeCrypto_ENGINE_SSL_write_BIO_direct(JNIEnv* env, jclass, jlong s int result = BIO_write(bio, reinterpret_cast(sourcePtr), len); appData->clearCallbackState(); JNI_TRACE( - "ssl=%p NativeCrypto_ENGINE_SSL_write_BIO_direct bio=%p sourcePtr=%p len=%d shc=%p => " + "ssl=%p NativeCrypto_ENGINE_SSL_write_BIO_direct bio=%p sourcePtr=%p " + "len=%d shc=%p => " "ret=%d", ssl, bio, sourcePtr, len, shc, result); JNI_TRACE_PACKET_DATA(ssl, 'O', reinterpret_cast(sourcePtr), @@ -11558,8 +11779,10 @@ static int NativeCrypto_ENGINE_SSL_read_BIO_direct(JNIEnv* env, jclass, jlong ss } if (shc == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_read_BIO_direct => sslHandshakeCallbacks == null", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_read_BIO_direct => " + "sslHandshakeCallbacks == null", + ssl); return -1; } BIO* bio = to_BIO(env, bioRef); @@ -11591,7 +11814,8 @@ static int NativeCrypto_ENGINE_SSL_read_BIO_direct(JNIEnv* env, jclass, jlong ss int result = BIO_read(bio, destPtr, outputSize); appData->clearCallbackState(); JNI_TRACE( - "ssl=%p NativeCrypto_ENGINE_SSL_read_BIO_direct bio=%p destPtr=%p outputSize=%d shc=%p " + "ssl=%p NativeCrypto_ENGINE_SSL_read_BIO_direct bio=%p destPtr=%p " + "outputSize=%d shc=%p " "=> ret=%d", ssl, bio, destPtr, outputSize, shc, result); JNI_TRACE_PACKET_DATA(ssl, 'I', destPtr, static_cast(result)); @@ -11608,8 +11832,10 @@ static void NativeCrypto_ENGINE_SSL_force_read(JNIEnv* env, jclass, jlong ssl_ad JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_force_read shc=%p", ssl, shc); if (shc == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_force_read => sslHandshakeCallbacks == null", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_force_read => sslHandshakeCallbacks == " + "null", + ssl); return; } AppData* appData = toAppData(ssl); @@ -11628,7 +11854,8 @@ static void NativeCrypto_ENGINE_SSL_force_read(JNIEnv* env, jclass, jlong ssl_ad int result = SSL_peek(ssl, &c, 1); appData->clearCallbackState(); if (env->ExceptionCheck()) { - // An exception was thrown by one of the callbacks. Just propagate that exception. + // An exception was thrown by one of the callbacks. Just propagate that + // exception. ERR_clear_error(); JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_force_read => THROWN_EXCEPTION", ssl); return; @@ -11693,8 +11920,10 @@ static int NativeCrypto_ENGINE_SSL_write_direct(JNIEnv* env, jclass, jlong ssl_a sourcePtr, len, shc); if (shc == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_write_direct => sslHandshakeCallbacks == null", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_write_direct => sslHandshakeCallbacks " + "== null", + ssl); return -1; } @@ -11716,8 +11945,10 @@ static int NativeCrypto_ENGINE_SSL_write_direct(JNIEnv* env, jclass, jlong ssl_a int result = SSL_write(ssl, sourcePtr, len); appData->clearCallbackState(); - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_write_direct address=%p length=%d shc=%p => ret=%d", - ssl, sourcePtr, len, shc, result); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_write_direct address=%p length=%d shc=%p " + "=> ret=%d", + ssl, sourcePtr, len, shc, result); return result; } @@ -11837,7 +12068,8 @@ static void NativeCrypto_SSL_CTX_set_spake_credential( /* pw_len= */ pw_bytes.size(), /* id_prover= */ reinterpret_cast(id_prover_bytes.get()), /* id_prover_len= */ id_prover_bytes.size(), - /* id_verifier= */ reinterpret_cast(id_verifier_bytes.get()), + /* id_verifier= */ + reinterpret_cast(id_verifier_bytes.get()), /* id_verifier_len= */ id_verifier_bytes.size()); if (ret != 1) { conscrypt::jniutil::throwExceptionFromBoringSSLError(env, @@ -11850,9 +12082,11 @@ static void NativeCrypto_SSL_CTX_set_spake_credential( bssl::UniquePtr creds_client(SSL_CREDENTIAL_new_spake2plusv1_client( /* context= */ reinterpret_cast(context_bytes.get()), /* context_len= */ context_bytes.size(), - /* client_identity= */ reinterpret_cast(id_prover_bytes.get()), + /* client_identity= */ + reinterpret_cast(id_prover_bytes.get()), /* client_identity_len= */ id_prover_bytes.size(), - /* server_identity= */ reinterpret_cast(id_verifier_bytes.get()), + /* server_identity= */ + reinterpret_cast(id_verifier_bytes.get()), /* server_identity_len= */ id_verifier_bytes.size(), /* attempts= */ handshake_limit, /* w0= */ pw_verifier_w0, @@ -11864,9 +12098,11 @@ static void NativeCrypto_SSL_CTX_set_spake_credential( bssl::UniquePtr creds_server(SSL_CREDENTIAL_new_spake2plusv1_server( /* context= */ reinterpret_cast(context_bytes.get()), /* context_len= */ context_bytes.size(), - /* client_identity= */ reinterpret_cast(id_prover_bytes.get()), + /* client_identity= */ + reinterpret_cast(id_prover_bytes.get()), /* client_identity_len= */ id_prover_bytes.size(), - /* server_identity= */ reinterpret_cast(id_verifier_bytes.get()), + /* server_identity= */ + reinterpret_cast(id_verifier_bytes.get()), /* server_identity_len= */ id_verifier_bytes.size(), /* attempts= */ handshake_limit, /* w0= */ pw_verifier_w0, @@ -12108,8 +12344,10 @@ static jbyteArray NativeCrypto_SSL_get0_ech_retry_configs(JNIEnv* env, jclass, j } jbyteArray result = env->NewByteArray(static_cast(retry_configs_len)); if (result == nullptr) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_ech_retry_configs() => creating byte array failed", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_get0_ech_retry_configs() => creating byte " + "array failed", + ssl); return nullptr; } env->SetByteArrayRegion(result, 0, static_cast(retry_configs_len), @@ -12174,8 +12412,10 @@ static jboolean NativeCrypto_SSL_CTX_ech_enable_server(JNIEnv* env, jclass, jlon jbyteArray configJavaBytes) { CHECK_ERROR_QUEUE_ON_RETURN; SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true); - JNI_TRACE("NativeCrypto_SSL_CTX_ech_enable_server(keyJavaBytes=%p, configJavaBytes=%p)", - keyJavaBytes, configJavaBytes); + JNI_TRACE( + "NativeCrypto_SSL_CTX_ech_enable_server(keyJavaBytes=%p, " + "configJavaBytes=%p)", + keyJavaBytes, configJavaBytes); ScopedByteArrayRO keyBytes(env, keyJavaBytes); if (keyBytes.get() == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "Null pointer, key bytes"); @@ -12318,6 +12558,8 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(X25519_keypair, "([B[B)V"), CONSCRYPT_NATIVE_METHOD(ED25519_keypair, "([B[B)V"), CONSCRYPT_NATIVE_METHOD(XWING_public_key_from_seed, "([B)[B"), + CONSCRYPT_NATIVE_METHOD(MLKEM768_public_key_from_seed, "([B)[B"), + CONSCRYPT_NATIVE_METHOD(MLKEM1024_public_key_from_seed, "([B)[B"), 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"), diff --git a/common/src/jni/main/include/conscrypt/NetFd.h b/common/src/jni/main/include/conscrypt/NetFd.h index 522250c5e..2ed7982cf 100644 --- a/common/src/jni/main/include/conscrypt/NetFd.h +++ b/common/src/jni/main/include/conscrypt/NetFd.h @@ -20,7 +20,8 @@ #include /** - * Wraps access to the int inside a java.io.FileDescriptor, taking care of throwing exceptions. + * Wraps access to the int inside a java.io.FileDescriptor, taking care of + * throwing exceptions. */ class NetFd { public: @@ -51,9 +52,9 @@ class NetFd { }; /** - * Used to retry syscalls that can return EINTR. This differs from TEMP_FAILURE_RETRY in that - * it also considers the case where the reason for failure is that another thread called - * Socket.close. + * Used to retry syscalls that can return EINTR. This differs from + * TEMP_FAILURE_RETRY in that it also considers the case where the reason for + * failure is that another thread called Socket.close. */ #define NET_FAILURE_RETRY(fd, exp) \ ({ \ diff --git a/common/src/jni/main/include/conscrypt/app_data.h b/common/src/jni/main/include/conscrypt/app_data.h index cc8d72ffc..ac07ba610 100644 --- a/common/src/jni/main/include/conscrypt/app_data.h +++ b/common/src/jni/main/include/conscrypt/app_data.h @@ -87,13 +87,14 @@ namespace conscrypt { * * (5) the JNIEnv so we can invoke the Java callback * - * (6) a NativeCrypto.SSLHandshakeCallbacks instance for callbacks from native to Java + * (6) a NativeCrypto.SSLHandshakeCallbacks instance for callbacks from native + * to Java * * (7) a java.io.FileDescriptor wrapper to check for socket close * - * We store the ALPN protocols list so we can either send it (from the server) or - * select a protocol (on the client). We eagerly acquire a pointer to the array - * data so the callback doesn't need to acquire resources that it cannot + * We store the ALPN protocols list so we can either send it (from the server) + * or select a protocol (on the client). We eagerly acquire a pointer to the + * array data so the callback doesn't need to acquire resources that it cannot * release. * * Because renegotiation can be requested by the peer at any time, @@ -178,8 +179,10 @@ class AppData { e->GetByteArrayElements(applicationProtocolsJava, nullptr); if (applicationProtocols == nullptr) { clearCallbackState(); - JNI_TRACE("appData=%p setApplicationCallbackState => applicationProtocols == null", - this); + JNI_TRACE( + "appData=%p setApplicationCallbackState => applicationProtocols == " + "null", + this); return false; } applicationProtocolsLength = diff --git a/common/src/jni/main/include/conscrypt/compat.h b/common/src/jni/main/include/conscrypt/compat.h index b19e4d56b..e79621b7b 100644 --- a/common/src/jni/main/include/conscrypt/compat.h +++ b/common/src/jni/main/include/conscrypt/compat.h @@ -86,10 +86,9 @@ inline int asprintf(char** strp, const char* fmt, ...) { } inline int gettimeofday(struct timeval* tp, struct timezone*) { - // Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing - // zero's - // This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC) - // until 00:00:00 January 1, 1970 + // Note: some broken versions only have 8 trailing zero's, the correct epoch + // has 9 trailing zero's This magic number is the number of 100 nanosecond + // intervals since January 1, 1601 (UTC) until 00:00:00 January 1, 1970 static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL); SYSTEMTIME system_time; diff --git a/common/src/jni/main/include/conscrypt/compatibility_close_monitor.h b/common/src/jni/main/include/conscrypt/compatibility_close_monitor.h index 70f6ad3ad..bc7c01328 100644 --- a/common/src/jni/main/include/conscrypt/compatibility_close_monitor.h +++ b/common/src/jni/main/include/conscrypt/compatibility_close_monitor.h @@ -22,14 +22,16 @@ namespace conscrypt { /* - * Where possible, this class hooks into the Android C API for AsynchronousCloseMonitor, - * allowing Java thread wakeup semantics during POSIX system calls. It is only used in sslSelect(). + * Where possible, this class hooks into the Android C API for + * AsynchronousCloseMonitor, allowing Java thread wakeup semantics during POSIX + * system calls. It is only used in sslSelect(). * * When unbundled, if the C API methods are not available, this class will fall * back to looking for the C++ API methods which existed on Android P and below. * - * On non-Android platforms, this class becomes a no-op as all of the function pointers - * to create and destroy AsynchronousCloseMonitor instances will be null. + * On non-Android platforms, this class becomes a no-op as all of the function + * pointers to create and destroy AsynchronousCloseMonitor instances will be + * null. */ class CompatibilityCloseMonitor { public: @@ -74,9 +76,9 @@ class CompatibilityCloseMonitor { // C++ API: Only available on Android P and below. Maintains pointers to // the C++ constructor and destructor methods, which will be null on // non-Android platforms. Calls them directly, passing in a pointer to - // objBuffer, which is large enough to fit an AsynchronousCloseMonitor object on - // Android versions where this class will be using this API. - // This is equivalent to placement new and explicit destruction. + // objBuffer, which is large enough to fit an AsynchronousCloseMonitor object + // on Android versions where this class will be using this API. This is + // equivalent to placement new and explicit destruction. typedef void (*acm_ctor_func)(void*, int); typedef void (*acm_dtor_func)(void*); diff --git a/common/src/jni/main/include/conscrypt/jniutil.h b/common/src/jni/main/include/conscrypt/jniutil.h index 70ae3965e..a7751aeea 100644 --- a/common/src/jni/main/include/conscrypt/jniutil.h +++ b/common/src/jni/main/include/conscrypt/jniutil.h @@ -153,8 +153,8 @@ extern int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor); extern bool isDirectByteBufferInstance(JNIEnv* env, jobject buffer); /** - * Returns true if the VM's JNI GetByteArrayElements method is likely to create a copy when - * invoked on an array of the provided size. + * Returns true if the VM's JNI GetByteArrayElements method is likely to create + * a copy when invoked on an array of the provided size. */ extern bool isGetByteArrayElementsLikelyToReturnACopy(size_t size); @@ -281,7 +281,8 @@ extern int throwSSLHandshakeExceptionStr(JNIEnv* env, const char* message); extern int throwSSLExceptionStr(JNIEnv* env, const char* message); /** - * Throws a javax.net.ssl.SSLProcotolException with the given string as a message. + * Throws a javax.net.ssl.SSLProcotolException with the given string as a + * message. */ extern int throwSSLProtocolExceptionStr(JNIEnv* env, const char* message); @@ -302,9 +303,10 @@ extern int throwSSLExceptionWithSslErrors(JNIEnv* env, SSL* ssl, int sslErrorCod #ifdef CONSCRYPT_CHECK_ERROR_QUEUE /** - * Class that checks that the error queue is empty on destruction. It should only be used - * via the macro CHECK_ERROR_QUEUE_ON_RETURN, which can be placed at the top of a function to - * ensure that the error queue is empty whenever the function exits. + * Class that checks that the error queue is empty on destruction. It should + * only be used via the macro CHECK_ERROR_QUEUE_ON_RETURN, which can be placed + * at the top of a function to ensure that the error queue is empty whenever the + * function exits. */ class ErrorQueueChecker { public: @@ -319,7 +321,8 @@ class ErrorQueueChecker { char result[500]; snprintf(result, sizeof(result), "Error queue should have been empty but was (%s:%d) %s", file, line, message); - // If there's a pending exception, we want to throw the assertion error instead + // If there's a pending exception, we want to throw the assertion error + // instead env->ExceptionClear(); throwAssertionError(env, result); } diff --git a/common/src/jni/main/include/conscrypt/macros.h b/common/src/jni/main/include/conscrypt/macros.h index c7330ea39..f9197cb22 100644 --- a/common/src/jni/main/include/conscrypt/macros.h +++ b/common/src/jni/main/include/conscrypt/macros.h @@ -103,9 +103,10 @@ #endif /** - * Many OpenSSL APIs take ownership of an argument on success but don't free the argument - * on failure. This means we need to tell our scoped pointers when we've transferred ownership, - * without triggering a warning by not using the result of release(). + * Many OpenSSL APIs take ownership of an argument on success but don't free the + * argument on failure. This means we need to tell our scoped pointers when + * we've transferred ownership, without triggering a warning by not using the + * result of release(). */ #define OWNERSHIP_TRANSFERRED(obj) \ do { \ @@ -133,7 +134,8 @@ (len) > static_cast((array).size()) - (offset)) /** - * Check array bounds for arguments when an array length, chunk offset, and chunk length are given. + * Check array bounds for arguments when an array length, chunk offset, and + * chunk length are given. */ #define ARRAY_CHUNK_INVALID(array_len, chunk_offset, chunk_len) \ ((chunk_offset) < 0 || (chunk_offset) > static_cast(array_len) || (chunk_len) < 0 || \ diff --git a/common/src/jni/main/include/conscrypt/scoped_ssl_bio.h b/common/src/jni/main/include/conscrypt/scoped_ssl_bio.h index ecf7abd5c..7ca938c8e 100644 --- a/common/src/jni/main/include/conscrypt/scoped_ssl_bio.h +++ b/common/src/jni/main/include/conscrypt/scoped_ssl_bio.h @@ -22,8 +22,9 @@ namespace conscrypt { /* - * Sets the read and write BIO for an SSL connection and removes it when it goes out of scope. - * We hang on to BIO with a JNI GlobalRef and we want to remove them as soon as possible. + * Sets the read and write BIO for an SSL connection and removes it when it goes + * out of scope. We hang on to BIO with a JNI GlobalRef and we want to remove + * them as soon as possible. */ class ScopedSslBio { public: diff --git a/common/src/jni/main/include/conscrypt/trace.h b/common/src/jni/main/include/conscrypt/trace.h index 738f19c7e..018ba9995 100644 --- a/common/src/jni/main/include/conscrypt/trace.h +++ b/common/src/jni/main/include/conscrypt/trace.h @@ -38,7 +38,8 @@ constexpr std::size_t kWithJniTraceDataChunkSize = 512; * For example, if you were interested in ssl=0x12345678, you would do: * * address=0x12345678 - * awk "match(\$0,/ssl=$address SSL_DATA: (.*)\$/,a){print a[1]}" | text2pcap -T 443,1337 -t + * awk "match(\$0,/ssl=$address SSL_DATA: (.*)\$/,a){print a[1]}" | text2pcap + * -T 443,1337 -t * '%s.' -n -D - $address.pcapng */ constexpr bool kWithJniTracePackets = false; diff --git a/common/src/jni/unbundled/include/nativehelper/scoped_primitive_array.h b/common/src/jni/unbundled/include/nativehelper/scoped_primitive_array.h index 71e3733d2..7fad0e8ac 100644 --- a/common/src/jni/unbundled/include/nativehelper/scoped_primitive_array.h +++ b/common/src/jni/unbundled/include/nativehelper/scoped_primitive_array.h @@ -19,10 +19,11 @@ #include -// ScopedBooleanArrayRO, ScopedByteArrayRO, ScopedCharArrayRO, ScopedDoubleArrayRO, -// ScopedFloatArrayRO, ScopedIntArrayRO, ScopedLongArrayRO, and ScopedShortArrayRO provide -// convenient read-only access to Java arrays from JNI code. This is cheaper than read-write -// access and should be used by default. +// ScopedBooleanArrayRO, ScopedByteArrayRO, ScopedCharArrayRO, +// ScopedDoubleArrayRO, ScopedFloatArrayRO, ScopedIntArrayRO, ScopedLongArrayRO, +// and ScopedShortArrayRO provide convenient read-only access to Java arrays +// from JNI code. This is cheaper than read-write access and should be used by +// default. #define INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(PRIMITIVE_TYPE, NAME) \ class Scoped##NAME##ArrayRO { \ public: \ @@ -77,10 +78,11 @@ INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jshort, Short); #undef INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO -// ScopedBooleanArrayRW, ScopedByteArrayRW, ScopedCharArrayRW, ScopedDoubleArrayRW, -// ScopedFloatArrayRW, ScopedIntArrayRW, ScopedLongArrayRW, and ScopedShortArrayRW provide -// convenient read-write access to Java arrays from JNI code. These are more expensive, -// since they entail a copy back onto the Java heap, and should only be used when necessary. +// ScopedBooleanArrayRW, ScopedByteArrayRW, ScopedCharArrayRW, +// ScopedDoubleArrayRW, ScopedFloatArrayRW, ScopedIntArrayRW, ScopedLongArrayRW, +// and ScopedShortArrayRW provide convenient read-write access to Java arrays +// from JNI code. These are more expensive, since they entail a copy back onto +// the Java heap, and should only be used when necessary. #define INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(PRIMITIVE_TYPE, NAME) \ class Scoped##NAME##ArrayRW { \ public: \ diff --git a/common/src/main/java/org/conscrypt/ActiveSession.java b/common/src/main/java/org/conscrypt/ActiveSession.java index 0997b79b1..7fea51028 100644 --- a/common/src/main/java/org/conscrypt/ActiveSession.java +++ b/common/src/main/java/org/conscrypt/ActiveSession.java @@ -342,4 +342,25 @@ private void checkPeerCertificatesPresent() throws SSLPeerUnverifiedException { throw new SSLPeerUnverifiedException("No peer certificates"); } } + + private String[] peerSupportedSignatureAlgorithms = new String[0]; + + void onPeerSignatureAlgorithmsReceived(String[] algorithms) { + this.peerSupportedSignatureAlgorithms = + algorithms != null ? algorithms.clone() : new String[0]; + } + + @Override + public String[] getPeerSupportedSignatureAlgorithms() { + return peerSupportedSignatureAlgorithms.clone(); + } + + @Override + public String[] getLocalSupportedSignatureAlgorithms() { + return new String[] {// TLS 1.3 & modern TLS 1.2 + "RSASSA-PSS", "Ed25519", "SHA512withRSA", "SHA512withECDSA", + "SHA384withRSA", "SHA384withECDSA", "SHA256withRSA", "SHA256withECDSA", + // Legacy + "SHA224withRSA", "SHA224withECDSA", "SHA1withRSA", "SHA1withECDSA"}; + } } diff --git a/common/src/main/java/org/conscrypt/ConscryptEngine.java b/common/src/main/java/org/conscrypt/ConscryptEngine.java index 8dd220e0d..696b5accd 100644 --- a/common/src/main/java/org/conscrypt/ConscryptEngine.java +++ b/common/src/main/java/org/conscrypt/ConscryptEngine.java @@ -1591,8 +1591,10 @@ public void onSSLStateChange(int type, int val) { } @Override - public void serverCertificateRequested() throws IOException { + public void serverCertificateRequested(int[] signatureAlgs) throws IOException { synchronized (ssl) { + String[] jsseAlgs = SSLUtils.mapSignatureAlgorithms(signatureAlgs); + activeSession.onPeerSignatureAlgorithmsReceived(jsseAlgs); ssl.configureServerCertificate(); } } @@ -1658,6 +1660,8 @@ public void verifyCertificateChain(byte[][] certChain, String authMethod) public void clientCertificateRequested(byte[] keyTypeBytes, int[] signatureAlgs, byte[][] asn1DerEncodedPrincipals) throws CertificateEncodingException, SSLException { + String[] jsseAlgs = SSLUtils.mapSignatureAlgorithms(signatureAlgs); + activeSession.onPeerSignatureAlgorithmsReceived(jsseAlgs); ssl.chooseClientCertificate(keyTypeBytes, signatureAlgs, asn1DerEncodedPrincipals); } diff --git a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java index ba5a525cb..65b296d38 100644 --- a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java +++ b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java @@ -309,6 +309,8 @@ public final void startHandshake() throws IOException { public final void clientCertificateRequested(byte[] keyTypeBytes, int[] signatureAlgs, byte[][] asn1DerEncodedPrincipals) throws CertificateEncodingException, SSLException { + String[] jsseAlgs = SSLUtils.mapSignatureAlgorithms(signatureAlgs); + activeSession.onPeerSignatureAlgorithmsReceived(jsseAlgs); ssl.chooseClientCertificate(keyTypeBytes, signatureAlgs, asn1DerEncodedPrincipals); } @@ -382,8 +384,10 @@ public final long serverSessionRequested(byte[] id) { } @Override - public final void serverCertificateRequested() throws IOException { + public final void serverCertificateRequested(int[] signatureAlgs) throws IOException { synchronized (ssl) { + String[] jsseAlgs = SSLUtils.mapSignatureAlgorithms(signatureAlgs); + activeSession.onPeerSignatureAlgorithmsReceived(jsseAlgs); ssl.configureServerCertificate(); } } diff --git a/common/src/main/java/org/conscrypt/ConscryptSession.java b/common/src/main/java/org/conscrypt/ConscryptSession.java index e25140598..77af0ff88 100644 --- a/common/src/main/java/org/conscrypt/ConscryptSession.java +++ b/common/src/main/java/org/conscrypt/ConscryptSession.java @@ -53,4 +53,8 @@ interface ConscryptSession extends SSLSession { @Override X509Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException; String getApplicationProtocol(); + + public String[] getPeerSupportedSignatureAlgorithms(); + + public String[] getLocalSupportedSignatureAlgorithms(); } diff --git a/common/src/main/java/org/conscrypt/ExternalSession.java b/common/src/main/java/org/conscrypt/ExternalSession.java index 1641a7f14..3e60c5d08 100644 --- a/common/src/main/java/org/conscrypt/ExternalSession.java +++ b/common/src/main/java/org/conscrypt/ExternalSession.java @@ -211,6 +211,16 @@ void removeValue(SSLSession session, String name) { } } + @Override + public String[] getPeerSupportedSignatureAlgorithms() { + return provider.provideSession().getPeerSupportedSignatureAlgorithms(); + } + + @Override + public String[] getLocalSupportedSignatureAlgorithms() { + return provider.provideSession().getLocalSupportedSignatureAlgorithms(); + } + /** * The provider of the current delegate session. */ diff --git a/common/src/main/java/org/conscrypt/HpkeImpl.java b/common/src/main/java/org/conscrypt/HpkeImpl.java index 5c7e19fae..0f97382a7 100644 --- a/common/src/main/java/org/conscrypt/HpkeImpl.java +++ b/common/src/main/java/org/conscrypt/HpkeImpl.java @@ -20,6 +20,8 @@ import static org.conscrypt.HpkeSuite.AEAD_CHACHA20POLY1305; import static org.conscrypt.HpkeSuite.KDF_HKDF_SHA256; import static org.conscrypt.HpkeSuite.KEM_DHKEM_X25519_HKDF_SHA256; +import static org.conscrypt.HpkeSuite.KEM_MLKEM_1024; +import static org.conscrypt.HpkeSuite.KEM_MLKEM_768; import static org.conscrypt.HpkeSuite.KEM_XWING; import java.security.GeneralSecurityException; @@ -267,4 +269,70 @@ public XwingHkdfSha256ChaCha20Poly1305() { super(new HpkeSuite(KEM_XWING, KDF_HKDF_SHA256, AEAD_CHACHA20POLY1305)); } } + + private static class HpkeMlKemImpl extends HpkeImpl { + HpkeMlKemImpl(HpkeSuite hpkeSuite) { + super(hpkeSuite); + } + + @Override + byte[] getRecipientPublicKeyBytes(PublicKey publicKey) throws InvalidKeyException { + if (!(publicKey instanceof OpenSslMlKemPublicKey)) { + throw new InvalidKeyException("Unsupported recipient key class: " + + publicKey.getClass()); + } + return ((OpenSslMlKemPublicKey) publicKey).getRaw(); + } + + @Override + byte[] getPrivateRecipientKeyBytes(PrivateKey recipientKey) throws InvalidKeyException { + if (!(recipientKey instanceof OpenSslMlKemPrivateKey)) { + throw new InvalidKeyException("Unsupported recipient private key class: " + + recipientKey.getClass()); + } + return ((OpenSslMlKemPrivateKey) recipientKey).getSeed(); + } + } + + /** Implementation of MLKEM_768/HKDF_SHA256/AES_128_GCM. */ + public static class MlKem768HkdfSha256Aes128Gcm extends HpkeMlKemImpl { + public MlKem768HkdfSha256Aes128Gcm() { + super(new HpkeSuite(KEM_MLKEM_768, KDF_HKDF_SHA256, AEAD_AES_128_GCM)); + } + } + + /** Implementation of MLKEM_768/HKDF_SHA256/AES_256_GCM. */ + public static class MlKem768HkdfSha256Aes256Gcm extends HpkeMlKemImpl { + public MlKem768HkdfSha256Aes256Gcm() { + super(new HpkeSuite(KEM_MLKEM_768, KDF_HKDF_SHA256, AEAD_AES_256_GCM)); + } + } + + /** Implementation of MLKEM_768/HKDF_SHA256/CHACHA20_POLY1305. */ + public static class MlKem768HkdfSha256ChaCha20Poly1305 extends HpkeMlKemImpl { + public MlKem768HkdfSha256ChaCha20Poly1305() { + super(new HpkeSuite(KEM_MLKEM_768, KDF_HKDF_SHA256, AEAD_CHACHA20POLY1305)); + } + } + + /** Implementation of MLKEM_1024/HKDF_SHA256/AES_128_GCM. */ + public static class MlKem1024HkdfSha256Aes128Gcm extends HpkeMlKemImpl { + public MlKem1024HkdfSha256Aes128Gcm() { + super(new HpkeSuite(KEM_MLKEM_1024, KDF_HKDF_SHA256, AEAD_AES_128_GCM)); + } + } + + /** Implementation of MLKEM_1024/HKDF_SHA256/AES_256_GCM. */ + public static class MlKem1024HkdfSha256Aes256Gcm extends HpkeMlKemImpl { + public MlKem1024HkdfSha256Aes256Gcm() { + super(new HpkeSuite(KEM_MLKEM_1024, KDF_HKDF_SHA256, AEAD_AES_256_GCM)); + } + } + + /** Implementation of MLKEM_1024/HKDF_SHA256/CHACHA20_POLY1305. */ + public static class MlKem1024HkdfSha256ChaCha20Poly1305 extends HpkeMlKemImpl { + public MlKem1024HkdfSha256ChaCha20Poly1305() { + super(new HpkeSuite(KEM_MLKEM_1024, KDF_HKDF_SHA256, AEAD_CHACHA20POLY1305)); + } + } } diff --git a/common/src/main/java/org/conscrypt/HpkeSuite.java b/common/src/main/java/org/conscrypt/HpkeSuite.java index 95bb6742b..bb70716a2 100644 --- a/common/src/main/java/org/conscrypt/HpkeSuite.java +++ b/common/src/main/java/org/conscrypt/HpkeSuite.java @@ -22,12 +22,15 @@ * *
    *
  • KEM
  • + * href="https://www.rfc-editor.org/rfc/rfc9180.html#name-key-encapsulation-mechanism">KEM *
  • KDF
  • + * href="https://www.rfc-editor.org/rfc/rfc9180.html#name-key-derivation-functions-kd">KDF *
  • AEAD
  • + * href="https://www.rfc-editor.org/rfc/rfc9180.html#name-authenticated-encryption-wi">AEAD *
+ * + *

Some of the constants are defined in IANA HPKE. */ public final class HpkeSuite { /** @@ -35,6 +38,12 @@ public final class HpkeSuite { */ public static final int KEM_DHKEM_X25519_HKDF_SHA256 = 0x0020; + /** KEM: 0x0041 ML-KEM-768 */ + public static final int KEM_MLKEM_768 = 0x0041; + + /** KEM: 0x0042 ML-KEM-1024 */ + public static final int KEM_MLKEM_1024 = 0x0042; + /** * KEM: 0x647a X-Wing */ @@ -144,6 +153,10 @@ public AEAD convertAead(int aead) { public enum KEM { DHKEM_X25519_HKDF_SHA256( /* id= */ 0x20, /* nSecret= */ 32, /* nEnc= */ 32, /* nPk= */ 32, /* nSk= */ 32), + MLKEM_768(/* id= */ 0x41, /* nSecret= */ 32, /* nEnc= */ 1088, /* nPk= */ 1184, + /* nSk= */ 64), + MLKEM_1024(/* id= */ 0x42, /* nSecret= */ 32, /* nEnc= */ 1568, /* nPk= */ 1568, + /* nSk= */ 64), XWING(/* id= */ 0x647a, /* nSecret= */ 32, /* nEnc= */ 1120, /* nPk= */ 1216, /* nSk= */ 32); diff --git a/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java b/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java index 431108790..879730422 100644 --- a/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java +++ b/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java @@ -28,15 +28,6 @@ * on Java 7+. */ class Java7ExtendedSSLSession extends ExtendedSSLSession implements ConscryptSession { - // TODO: use BoringSSL API to actually fetch the real data - private static final String[] LOCAL_SUPPORTED_SIGNATURE_ALGORITHMS = new String[] { - "SHA512withRSA", "SHA512withECDSA", "SHA384withRSA", "SHA384withECDSA", - "SHA256withRSA", "SHA256withECDSA", "SHA224withRSA", "SHA224withECDSA", - "SHA1withRSA", "SHA1withECDSA", - }; - // TODO: use BoringSSL API to actually fetch the real data - private static final String[] PEER_SUPPORTED_SIGNATURE_ALGORITHMS = - new String[] {"SHA1withRSA", "SHA1withECDSA"}; protected final ExternalSession delegate; Java7ExtendedSSLSession(ExternalSession delegate) { @@ -44,15 +35,15 @@ class Java7ExtendedSSLSession extends ExtendedSSLSession implements ConscryptSes } /* @Override */ - @SuppressWarnings("MissingOverride") // For Android backward-compatibility. + @SuppressWarnings("MissingOverride") public final String[] getLocalSupportedSignatureAlgorithms() { - return LOCAL_SUPPORTED_SIGNATURE_ALGORITHMS.clone(); + return delegate.getLocalSupportedSignatureAlgorithms(); } /* @Override */ - @SuppressWarnings("MissingOverride") // For Android backward-compatibility. + @SuppressWarnings("MissingOverride") public final String[] getPeerSupportedSignatureAlgorithms() { - return PEER_SUPPORTED_SIGNATURE_ALGORITHMS.clone(); + return delegate.getPeerSupportedSignatureAlgorithms(); } @Override diff --git a/common/src/main/java/org/conscrypt/MlKemAlgorithm.java b/common/src/main/java/org/conscrypt/MlKemAlgorithm.java new file mode 100644 index 000000000..2c06bffbd --- /dev/null +++ b/common/src/main/java/org/conscrypt/MlKemAlgorithm.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +/** ML-KEM algorithm. */ +public enum MlKemAlgorithm { + // Values from https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.203.pdf, table 3. + ML_KEM_768("ML-KEM-768", 1184), + ML_KEM_1024("ML-KEM-1024", 1568); + + private final String name; + private final int publicKeySize; + + private MlKemAlgorithm(String name, int publicKeySize) { + this.name = name; + this.publicKeySize = publicKeySize; + } + + @Override + public String toString() { + return name; + } + + public int publicKeySize() { + return publicKeySize; + } +} diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java index e939f43b7..ba6aaa1ed 100644 --- a/common/src/main/java/org/conscrypt/NativeCrypto.java +++ b/common/src/main/java/org/conscrypt/NativeCrypto.java @@ -260,6 +260,11 @@ static native boolean X25519(byte[] out, byte[] privateKey, byte[] publicKey) static native byte[] XWING_public_key_from_seed(byte[] privateKeySeed); + // --- ML-KEM -------------- + + static native byte[] MLKEM768_public_key_from_seed(byte[] privateKeySeed); + static native byte[] MLKEM1024_public_key_from_seed(byte[] privateKeySeed); + // --- Message digest functions -------------- // These return const references @@ -1470,7 +1475,8 @@ void clientCertificateRequested(byte[] keyTypes, int[] signatureAlgs, * * @throws IOException if there was an error during certificate selection. */ - @SuppressWarnings("unused") void serverCertificateRequested() throws IOException; + @SuppressWarnings("unused") + void serverCertificateRequested(int[] signatureAlgs) throws IOException; /** * Gets the key to be used in client mode for this connection in Pre-Shared Key (PSK) key diff --git a/common/src/main/java/org/conscrypt/OpenSSLProvider.java b/common/src/main/java/org/conscrypt/OpenSSLProvider.java index a583db102..6f6e56810 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLProvider.java +++ b/common/src/main/java/org/conscrypt/OpenSSLProvider.java @@ -225,6 +225,10 @@ public OpenSSLProvider(String providerName) { // We don't support SLH-DSA, because it's not clear which algorithm to use. put("KeyPairGenerator.SLH-DSA-SHA2-128S", PREFIX + "OpenSslSlhDsaKeyPairGenerator"); + put("KeyPairGenerator.ML-KEM", PREFIX + "OpenSslMlKemKeyPairGenerator$MlKem"); + put("KeyPairGenerator.ML-KEM-768", PREFIX + "OpenSslMlKemKeyPairGenerator$MlKem768"); + put("KeyPairGenerator.ML-KEM-1024", PREFIX + "OpenSslMlKemKeyPairGenerator$MlKem1024"); + put("KeyPairGenerator.XWING", PREFIX + "OpenSslXwingKeyPairGenerator"); /* == KeyFactory == */ @@ -256,6 +260,10 @@ public OpenSSLProvider(String providerName) { // We don't support SLH-DSA, because it's not clear which algorithm to use. put("KeyFactory.SLH-DSA-SHA2-128S", PREFIX + "OpenSslSlhDsaKeyFactory"); + put("KeyFactory.ML-KEM", PREFIX + "OpenSslMlKemKeyFactory$MlKem"); + put("KeyFactory.ML-KEM-768", PREFIX + "OpenSslMlKemKeyFactory$MlKem768"); + put("KeyFactory.ML-KEM-1024", PREFIX + "OpenSslMlKemKeyFactory$MlKem1024"); + put("KeyFactory.XWING", PREFIX + "OpenSslXwingKeyFactory"); /* == SecretKeyFactory == */ @@ -580,6 +588,20 @@ public OpenSSLProvider(String providerName) { baseClass + "$X25519_CHACHA20"); put("Alg.Alias.ConscryptHpke.DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_GhpkeCHACHA20POLY1305", "DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/CHACHA20POLY1305"); + + put("ConscryptHpke.MLKEM_768/HKDF_SHA256/AES_128_GCM", + baseClass + "$MlKem768HkdfSha256Aes128Gcm"); + put("ConscryptHpke.MLKEM_768/HKDF_SHA256/AES_256_GCM", + baseClass + "$MlKem768HkdfSha256Aes256Gcm"); + put("ConscryptHpke.MLKEM_768/HKDF_SHA256/CHACHA20POLY1305", + baseClass + "$MlKem768HkdfSha256ChaCha20Poly1305"); + put("ConscryptHpke.MLKEM_1024/HKDF_SHA256/AES_128_GCM", + baseClass + "$MlKem1024HkdfSha256Aes128Gcm"); + put("ConscryptHpke.MLKEM_1024/HKDF_SHA256/AES_256_GCM", + baseClass + "$MlKem1024HkdfSha256Aes256Gcm"); + put("ConscryptHpke.MLKEM_1024/HKDF_SHA256/CHACHA20POLY1305", + baseClass + "$MlKem1024HkdfSha256ChaCha20Poly1305"); + put("ConscryptHpke.XWING/HKDF_SHA256/AES_128_GCM", baseClass + "$XwingHkdfSha256Aes128Gcm"); put("ConscryptHpke.XWING/HKDF_SHA256/AES_256_GCM", baseClass + "$XwingHkdfSha256Aes256Gcm"); put("ConscryptHpke.XWING/HKDF_SHA256/CHACHA20POLY1305", diff --git a/common/src/main/java/org/conscrypt/OpenSSLX25519PrivateKey.java b/common/src/main/java/org/conscrypt/OpenSSLX25519PrivateKey.java index aa85e29bf..a49c2c735 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLX25519PrivateKey.java +++ b/common/src/main/java/org/conscrypt/OpenSSLX25519PrivateKey.java @@ -30,11 +30,16 @@ public class OpenSSLX25519PrivateKey implements OpenSSLX25519Key, PrivateKey { private static final long serialVersionUID = -3136201500221850916L; private static final byte[] PKCS8_PREAMBLE = new byte[] { - 0x30, 0x2e, // Sequence: 46 bytes - 0x02, 0x01, 0x00, // Integer: 0 (version) - 0x30, 0x05, // Sequence: 5 bytes - 0x06, 0x03, 0x2b, 0x65, 0x6e, // OID: 1.3.101.110 (X25519) - 0x04, 0x22, 0x04, 0x20, // Octet string: 32 bytes + 0x30, + 0x2e, // Sequence: 46 bytes + 0x02, 0x01, + 0x00, // Integer: 0 (version) + 0x30, + 0x05, // Sequence: 5 bytes + 0x06, 0x03, 0x2b, 0x65, + 0x6e, // OID: 1.3.101.110 (X25519) + 0x04, 0x22, 0x04, + 0x20, // Octet string: 32 bytes // Key bytes follow directly }; diff --git a/common/src/main/java/org/conscrypt/OpenSSLX25519PublicKey.java b/common/src/main/java/org/conscrypt/OpenSSLX25519PublicKey.java index 7ec4c87a6..0c09efe1b 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLX25519PublicKey.java +++ b/common/src/main/java/org/conscrypt/OpenSSLX25519PublicKey.java @@ -28,10 +28,14 @@ public class OpenSSLX25519PublicKey implements OpenSSLX25519Key, PublicKey { private static final long serialVersionUID = 453861992373478445L; private static final byte[] X509_PREAMBLE = new byte[] { - 0x30, 0x2a, // Sequence: 42 bytes - 0x30, 0x05, // Sequence: 5 bytes - 0x06, 0x03, 0x2b, 0x65, 0x6e, // OID: 1.3.101.110 (X25519) - 0x03, 0x21, 0x00, // Bit string: 256 bits + 0x30, + 0x2a, // Sequence: 42 bytes + 0x30, + 0x05, // Sequence: 5 bytes + 0x06, 0x03, 0x2b, 0x65, + 0x6e, // OID: 1.3.101.110 (X25519) + 0x03, 0x21, + 0x00, // Bit string: 256 bits // Key bytes follow directly }; diff --git a/common/src/main/java/org/conscrypt/OpenSslMlKemKeyFactory.java b/common/src/main/java/org/conscrypt/OpenSslMlKemKeyFactory.java new file mode 100644 index 000000000..c979811a4 --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslMlKemKeyFactory.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactorySpi; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; + +/** An implementation of a {@link KeyFactorySpi} for ML-KEM keys based on BoringSSL. */ +@Internal +public abstract class OpenSslMlKemKeyFactory extends KeyFactorySpi { + // X.509 format preamble for ML-KEM-768 from RFC 9935. + static final byte[] x509PreambleMlKem768 = new byte[] { + (byte) 0x30, (byte) 0x82, (byte) 0x04, (byte) 0xb2, (byte) 0x30, (byte) 0x0b, + (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01, + (byte) 0x65, (byte) 0x03, (byte) 0x04, (byte) 0x04, (byte) 0x02, (byte) 0x03, + (byte) 0x82, (byte) 0x04, (byte) 0xa1, (byte) 0x00}; + + // X.509 format preamble for ML-KEM-1024 from RFC 9935. + static final byte[] x509PreambleMlKem1024 = new byte[] { + (byte) 0x30, (byte) 0x82, (byte) 0x06, (byte) 0x32, (byte) 0x30, (byte) 0x0b, + (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01, + (byte) 0x65, (byte) 0x03, (byte) 0x04, (byte) 0x04, (byte) 0x03, (byte) 0x03, + (byte) 0x82, (byte) 0x06, (byte) 0x21, (byte) 0x00}; + + // PKCS#8 format preamble (seed format) for ML-KEM-768 from RFC 9935. + static final byte[] pkcs8PreambleMlKem768 = new byte[] { + (byte) 0x30, (byte) 0x54, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x30, + (byte) 0x0b, (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, + (byte) 0x01, (byte) 0x65, (byte) 0x03, (byte) 0x04, (byte) 0x04, (byte) 0x02, + (byte) 0x04, (byte) 0x42, (byte) 0x80, (byte) 0x40}; + + // PKCS#8 format preamble (seed format) for ML-KEM-1024 from RFC 9935. + static final byte[] pkcs8PreambleMlKem1024 = new byte[] { + (byte) 0x30, (byte) 0x54, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x30, + (byte) 0x0b, (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, + (byte) 0x01, (byte) 0x65, (byte) 0x03, (byte) 0x04, (byte) 0x04, (byte) 0x03, + (byte) 0x04, (byte) 0x42, (byte) 0x80, (byte) 0x40}; + + private final MlKemAlgorithm defaultAlgorithm; + + private OpenSslMlKemKeyFactory(MlKemAlgorithm defaultAlgorithm) { + this.defaultAlgorithm = defaultAlgorithm; + } + + abstract boolean supportsAlgorithm(MlKemAlgorithm algorithm); + + /** ML-KEM */ + public static class MlKem extends OpenSslMlKemKeyFactory { + public MlKem() { + super(MlKemAlgorithm.ML_KEM_768); + } + + @Override + boolean supportsAlgorithm(MlKemAlgorithm algorithm) { + return algorithm.equals(MlKemAlgorithm.ML_KEM_768) + || algorithm.equals(MlKemAlgorithm.ML_KEM_1024); + } + } + + /** ML-KEM-768 */ + public static class MlKem768 extends OpenSslMlKemKeyFactory { + public MlKem768() { + super(MlKemAlgorithm.ML_KEM_768); + } + + @Override + boolean supportsAlgorithm(MlKemAlgorithm algorithm) { + return algorithm.equals(MlKemAlgorithm.ML_KEM_768); + } + } + + /** ML-KEM-1024 */ + public static class MlKem1024 extends OpenSslMlKemKeyFactory { + public MlKem1024() { + super(MlKemAlgorithm.ML_KEM_1024); + } + + @Override + boolean supportsAlgorithm(MlKemAlgorithm algorithm) { + return algorithm.equals(MlKemAlgorithm.ML_KEM_1024); + } + } + + private OpenSslMlKemPublicKey makePublicKeyFromRaw(byte[] raw, MlKemAlgorithm algorithm) + throws InvalidKeySpecException { + if (!supportsAlgorithm(algorithm)) { + throw new InvalidKeySpecException("Unsupported algorithm: " + algorithm); + } + if (raw.length != algorithm.publicKeySize()) { + throw new InvalidKeySpecException("Invalid raw public key length: " + raw.length + + " != " + algorithm.publicKeySize()); + } + try { + return new OpenSslMlKemPublicKey(raw, algorithm); + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException("Invalid raw public key", e); + } + } + + @Override + protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException { + if (keySpec == null) { + throw new InvalidKeySpecException("keySpec == null"); + } + if (!(keySpec instanceof EncodedKeySpec)) { + throw new InvalidKeySpecException("Currently only EncodedKeySpec is supported; was " + + keySpec.getClass().getName()); + } + EncodedKeySpec encodedKeySpec = (EncodedKeySpec) keySpec; + if (encodedKeySpec.getFormat().equalsIgnoreCase("raw")) { + byte[] raw = encodedKeySpec.getEncoded(); + return makePublicKeyFromRaw(raw, defaultAlgorithm); + } + if (!encodedKeySpec.getFormat().equals("X.509")) { + throw new InvalidKeySpecException("Encoding must be in X.509 format"); + } + byte[] encoded = encodedKeySpec.getEncoded(); + if (ArrayUtils.startsWith(encoded, x509PreambleMlKem768)) { + byte[] raw = Arrays.copyOfRange(encoded, x509PreambleMlKem768.length, encoded.length); + return makePublicKeyFromRaw(raw, MlKemAlgorithm.ML_KEM_768); + } else if (ArrayUtils.startsWith(encoded, x509PreambleMlKem1024)) { + byte[] raw = Arrays.copyOfRange(encoded, x509PreambleMlKem1024.length, encoded.length); + return makePublicKeyFromRaw(raw, MlKemAlgorithm.ML_KEM_1024); + } else { + throw new InvalidKeySpecException( + "Only X.509 format for ML-KEM-768 and ML-KEM-1024 is supported"); + } + } + + private OpenSslMlKemPrivateKey makePrivateKeyFromSeed(byte[] seed, MlKemAlgorithm algorithm) + throws InvalidKeySpecException { + if (!supportsAlgorithm(algorithm)) { + throw new InvalidKeySpecException("Unsupported algorithm: " + algorithm); + } + if (seed.length != OpenSslMlKemPrivateKey.PRIVATE_KEY_SIZE_BYTES) { + throw new InvalidKeySpecException("Invalid raw private key"); + } + try { + return new OpenSslMlKemPrivateKey(seed, algorithm); + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException("Invalid raw private key", e); + } + } + + @Override + protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException { + if (keySpec == null) { + throw new InvalidKeySpecException("keySpec == null"); + } + if (!(keySpec instanceof EncodedKeySpec)) { + throw new InvalidKeySpecException("Currently only EncodedKeySpec is supported; was " + + keySpec.getClass().getName()); + } + EncodedKeySpec encodedKeySpec = (EncodedKeySpec) keySpec; + if (encodedKeySpec.getFormat().equalsIgnoreCase("raw")) { + byte[] raw = encodedKeySpec.getEncoded(); + return makePrivateKeyFromSeed(raw, defaultAlgorithm); + } + if (!encodedKeySpec.getFormat().equals("PKCS#8")) { + throw new InvalidKeySpecException("Encoding must be in PKCS#8 format"); + } + byte[] encoded = encodedKeySpec.getEncoded(); + if (ArrayUtils.startsWith(encoded, pkcs8PreambleMlKem768)) { + byte[] seed = Arrays.copyOfRange(encoded, pkcs8PreambleMlKem768.length, encoded.length); + return makePrivateKeyFromSeed(seed, MlKemAlgorithm.ML_KEM_768); + } else if (ArrayUtils.startsWith(encoded, pkcs8PreambleMlKem1024)) { + byte[] seed = + Arrays.copyOfRange(encoded, pkcs8PreambleMlKem1024.length, encoded.length); + return makePrivateKeyFromSeed(seed, MlKemAlgorithm.ML_KEM_1024); + } else { + throw new InvalidKeySpecException( + "Only PKCS#8 format for ML-KEM-768 and ML-KEM-1024 is supported"); + } + } + + @Override + protected T engineGetKeySpec(Key key, Class keySpec) + throws InvalidKeySpecException { + if (key == null) { + throw new InvalidKeySpecException("key == null"); + } + if (keySpec == null) { + throw new InvalidKeySpecException("keySpec == null"); + } + if (!key.getAlgorithm().equals("ML-KEM")) { + throw new InvalidKeySpecException("Key must be an ML-KEM key"); + } + if (key instanceof OpenSslMlKemPublicKey) { + OpenSslMlKemPublicKey conscryptKey = (OpenSslMlKemPublicKey) key; + if (!supportsAlgorithm(conscryptKey.getMlKemAlgorithm())) { + throw new InvalidKeySpecException("Key algorithm mismatch"); + } + if (X509EncodedKeySpec.class.isAssignableFrom(keySpec)) { + @SuppressWarnings("unchecked") + T result = (T) new X509EncodedKeySpec(key.getEncoded()); + return result; + } else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) { + return KeySpecUtil.makeRawKeySpec(conscryptKey.getRaw(), keySpec); + } + } else if (key instanceof OpenSslMlKemPrivateKey) { + OpenSslMlKemPrivateKey conscryptKey = (OpenSslMlKemPrivateKey) key; + if (!supportsAlgorithm(conscryptKey.getMlKemAlgorithm())) { + throw new InvalidKeySpecException("Key algorithm mismatch"); + } + if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec)) { + @SuppressWarnings("unchecked") + T result = (T) new PKCS8EncodedKeySpec(key.getEncoded()); + return result; + } else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) { + return KeySpecUtil.makeRawKeySpec(conscryptKey.getSeed(), keySpec); + } + } + throw new InvalidKeySpecException("Unsupported key type and key spec combination; key=" + + key.getClass().getName() + + ", keySpec=" + keySpec.getName()); + } + + @Override + protected Key engineTranslateKey(Key key) throws InvalidKeyException { + if ((key instanceof OpenSslMlKemPublicKey)) { + OpenSslMlKemPublicKey conscryptKey = (OpenSslMlKemPublicKey) key; + if (!supportsAlgorithm(conscryptKey.getMlKemAlgorithm())) { + throw new InvalidKeyException("Key algorithm mismatch"); + } + return conscryptKey; + } else if (key instanceof OpenSslMlKemPrivateKey) { + OpenSslMlKemPrivateKey conscryptKey = (OpenSslMlKemPrivateKey) key; + if (!supportsAlgorithm(conscryptKey.getMlKemAlgorithm())) { + throw new InvalidKeyException("Key algorithm mismatch"); + } + return key; + } else if ((key instanceof PrivateKey) && key.getFormat().equals("PKCS#8")) { + byte[] encoded = key.getEncoded(); + try { + return engineGeneratePrivate(new PKCS8EncodedKeySpec(encoded)); + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException(e); + } + } else if ((key instanceof PublicKey) && key.getFormat().equals("X.509")) { + byte[] encoded = key.getEncoded(); + try { + return engineGeneratePublic(new X509EncodedKeySpec(encoded)); + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException(e); + } + } else { + throw new InvalidKeyException("Unable to translate key into ML-KEM key"); + } + } +} diff --git a/common/src/main/java/org/conscrypt/OpenSslMlKemKeyPairGenerator.java b/common/src/main/java/org/conscrypt/OpenSslMlKemKeyPairGenerator.java new file mode 100644 index 000000000..412f2a31f --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslMlKemKeyPairGenerator.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import java.security.InvalidParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; + +/** + * An implementation of {@link KeyPairGenerator} for ML-KEM keys which uses BoringSSL to perform all + * the operations. It supports algorithms "ML-KEM", "ML-KEM-768" and "ML-KEM-1024". "ML-DSA" uses + * ML-DSA-768. + */ +@Internal +public class OpenSslMlKemKeyPairGenerator extends KeyPairGenerator { + private OpenSslMlKemKeyPairGenerator(String algorithm) { + super(algorithm); + } + + /** ML-KEM-768 */ + public static class MlKem768 extends OpenSslMlKemKeyPairGenerator { + public MlKem768() { + super("ML-KEM-768"); + } + + MlKem768(String algorithm) { + super(algorithm); + } + + @Override + public KeyPair generateKeyPair() { + byte[] privateKeyBytes = new byte[64]; + NativeCrypto.RAND_bytes(privateKeyBytes); + byte[] publicKeyBytes = NativeCrypto.MLKEM768_public_key_from_seed(privateKeyBytes); + return new KeyPair( + new OpenSslMlKemPublicKey(publicKeyBytes, MlKemAlgorithm.ML_KEM_768), + new OpenSslMlKemPrivateKey(privateKeyBytes, MlKemAlgorithm.ML_KEM_768)); + } + } + + /** ML-KEM uses ML-KEM-768. */ + public static class MlKem extends MlKem768 { + public MlKem() { + super("ML-KEM"); + } + } + + /** ML-KEM-1024 */ + public static final class MlKem1024 extends OpenSslMlKemKeyPairGenerator { + public MlKem1024() { + super("ML-KEM-1024"); + } + + @Override + public KeyPair generateKeyPair() { + byte[] privateKeyBytes = new byte[OpenSslMlKemPrivateKey.PRIVATE_KEY_SIZE_BYTES]; + NativeCrypto.RAND_bytes(privateKeyBytes); + byte[] publicKeyBytes = NativeCrypto.MLKEM1024_public_key_from_seed(privateKeyBytes); + return new KeyPair( + new OpenSslMlKemPublicKey(publicKeyBytes, MlKemAlgorithm.ML_KEM_1024), + new OpenSslMlKemPrivateKey(privateKeyBytes, MlKemAlgorithm.ML_KEM_1024)); + } + } + + @Override + public void initialize(int bits) throws InvalidParameterException { + if (bits != -1) { + throw new InvalidParameterException("ML-DSA only supports -1 for bits"); + } + } +} diff --git a/common/src/main/java/org/conscrypt/OpenSslMlKemPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSslMlKemPrivateKey.java new file mode 100644 index 000000000..e55dd4568 --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslMlKemPrivateKey.java @@ -0,0 +1,116 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.security.MessageDigest; +import java.security.PrivateKey; +import java.util.Arrays; + +/** An ML-KEM private key. */ +public class OpenSslMlKemPrivateKey implements PrivateKey { + private static final long serialVersionUID = 1L; + + static final int PRIVATE_KEY_SIZE_BYTES = 64; + + private byte[] seed; + private final MlKemAlgorithm algorithm; + + public OpenSslMlKemPrivateKey(byte[] seed, MlKemAlgorithm algorithm) { + if (seed.length != PRIVATE_KEY_SIZE_BYTES) { + throw new IllegalArgumentException("Invalid key size"); + } + this.seed = seed.clone(); + this.algorithm = algorithm; + } + + @Override + public String getAlgorithm() { + return "ML-KEM"; + } + + MlKemAlgorithm getMlKemAlgorithm() { + return algorithm; + } + + @Override + public String getFormat() { + return "PKCS#8"; + } + + private static byte[] getPkcs8Preamble(MlKemAlgorithm algorithm) { + switch (algorithm) { + case ML_KEM_768: + return OpenSslMlKemKeyFactory.pkcs8PreambleMlKem768; + case ML_KEM_1024: + return OpenSslMlKemKeyFactory.pkcs8PreambleMlKem1024; + default: + throw new IllegalArgumentException("Unsupported algorithm: " + algorithm); + } + } + + @Override + public byte[] getEncoded() { + return ArrayUtils.concat(getPkcs8Preamble(algorithm), seed); + } + + byte[] getSeed() { + if (seed == null) { + throw new IllegalStateException("key is destroyed"); + } + return seed.clone(); + } + + @Override + public void destroy() { + if (seed != null) { + Arrays.fill(seed, (byte) 0); + seed = null; + } + } + + @Override + public boolean isDestroyed() { + return seed == null; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof OpenSslMlKemPrivateKey)) { + return false; + } + OpenSslMlKemPrivateKey that = (OpenSslMlKemPrivateKey) o; + return MessageDigest.isEqual(seed, that.seed); + } + + @Override + public int hashCode() { + return Arrays.hashCode(seed) ^ algorithm.hashCode(); + } + + private void readObject(ObjectInputStream in) { + throw new UnsupportedOperationException("serialization not supported"); + } + + private void writeObject(ObjectOutputStream out) { + throw new UnsupportedOperationException("serialization not supported"); + } +} diff --git a/common/src/main/java/org/conscrypt/OpenSslMlKemPublicKey.java b/common/src/main/java/org/conscrypt/OpenSslMlKemPublicKey.java new file mode 100644 index 000000000..373ec5eef --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslMlKemPublicKey.java @@ -0,0 +1,111 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.security.PublicKey; +import java.util.Arrays; + +/** An ML-KEM public key. */ +public class OpenSslMlKemPublicKey implements PublicKey { + private static final long serialVersionUID = 1L; + + private final byte[] raw; + private final MlKemAlgorithm algorithm; + + public OpenSslMlKemPublicKey(byte[] raw, MlKemAlgorithm algorithm) { + if (!algorithm.equals(MlKemAlgorithm.ML_KEM_768) + && !algorithm.equals(MlKemAlgorithm.ML_KEM_1024)) { + throw new IllegalArgumentException("Unsupported algorithm"); + } + if (raw.length != algorithm.publicKeySize()) { + throw new IllegalArgumentException("Invalid raw key of length " + raw.length); + } + this.raw = raw.clone(); + this.algorithm = algorithm; + } + + @Override + public String getAlgorithm() { + return "ML-KEM"; + } + + MlKemAlgorithm getMlKemAlgorithm() { + return algorithm; + } + + @Override + public String getFormat() { + return "X.509"; + } + + private static byte[] getX509Preamble(MlKemAlgorithm algorithm) { + switch (algorithm) { + case ML_KEM_768: + return OpenSslMlKemKeyFactory.x509PreambleMlKem768; + case ML_KEM_1024: + return OpenSslMlKemKeyFactory.x509PreambleMlKem1024; + default: + throw new IllegalArgumentException("Unsupported algorithm: " + algorithm); + } + } + + @Override + public byte[] getEncoded() { + return ArrayUtils.concat(getX509Preamble(algorithm), raw); + } + + byte[] getRaw() { + if (raw == null) { + throw new IllegalStateException("key is destroyed"); + } + return raw.clone(); + } + + @Override + public boolean equals(Object o) { + if (raw == null) { + throw new IllegalStateException("key is destroyed"); + } + + if (this == o) { + return true; + } + if (!(o instanceof OpenSslMlKemPublicKey)) { + return false; + } + OpenSslMlKemPublicKey that = (OpenSslMlKemPublicKey) o; + return Arrays.equals(raw, that.raw); + } + + @Override + public int hashCode() { + if (raw == null) { + throw new IllegalStateException("key is destroyed"); + } + return Arrays.hashCode(raw) ^ algorithm.hashCode(); + } + + private void readObject(ObjectInputStream in) { + throw new UnsupportedOperationException("serialization not supported"); + } + + private void writeObject(ObjectOutputStream out) { + throw new UnsupportedOperationException("serialization not supported"); + } +} diff --git a/common/src/main/java/org/conscrypt/SSLNullSession.java b/common/src/main/java/org/conscrypt/SSLNullSession.java index 9a808cfdb..e6c8fad32 100644 --- a/common/src/main/java/org/conscrypt/SSLNullSession.java +++ b/common/src/main/java/org/conscrypt/SSLNullSession.java @@ -184,4 +184,14 @@ public void removeValue(String name) { throw new UnsupportedOperationException( "All calls to this method should be intercepted by ExternalSession."); } + + @Override + public String[] getPeerSupportedSignatureAlgorithms() { + return new String[0]; + } + + @Override + public String[] getLocalSupportedSignatureAlgorithms() { + return new String[0]; + } } diff --git a/common/src/main/java/org/conscrypt/SSLUtils.java b/common/src/main/java/org/conscrypt/SSLUtils.java index 71852b894..f96c6078a 100644 --- a/common/src/main/java/org/conscrypt/SSLUtils.java +++ b/common/src/main/java/org/conscrypt/SSLUtils.java @@ -47,9 +47,11 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import javax.net.ssl.SSLException; @@ -578,5 +580,66 @@ static String[] concat(String[]... arrays) { return result; } + /** + * Maps BoringSSL/TLS signature scheme identifiers to standard JSSE algorithm names. + * See RFC 8446, Section 4.2.3. + */ + static String[] mapSignatureAlgorithms(int[] algorithms) { + if (algorithms == null || algorithms.length == 0) { + // Fallback for TLS 1.2 peers that don't send the signature_algorithms extension + return new String[] {"SHA256withRSA", "SHA256withECDSA", "SHA384withRSA", + "SHA384withECDSA", "SHA512withRSA", "SHA512withECDSA", + "SHA1withRSA", "SHA1withECDSA"}; + } + List result = new ArrayList<>(); + for (int alg : algorithms) { + switch (alg) { + // TLS 1.3 and modern TLS 1.2 + case 0x0401: + result.add("SHA256withRSA"); + break; + case 0x0501: + result.add("SHA384withRSA"); + break; + case 0x0601: + result.add("SHA512withRSA"); + break; + case 0x0403: + result.add("SHA256withECDSA"); + break; + case 0x0503: + result.add("SHA384withECDSA"); + break; + case 0x0603: + result.add("SHA512withECDSA"); + break; + // RSASSA-PSS schemes + case 0x0804: + case 0x0805: + case 0x0806: + case 0x0809: + case 0x080a: + case 0x080b: + result.add("RSASSA-PSS"); + break; + // EdDSA schemes + case 0x0807: + result.add("Ed25519"); + break; + // Legacy TLS 1.2 + case 0x0201: + result.add("SHA1withRSA"); + break; + case 0x0203: + result.add("SHA1withECDSA"); + break; + default: + // Ignore unknown or unsupported algorithms + break; + } + } + return result.toArray(new String[0]); + } + private SSLUtils() {} } diff --git a/common/src/main/java/org/conscrypt/SessionSnapshot.java b/common/src/main/java/org/conscrypt/SessionSnapshot.java index d9e6465a3..5e92a059d 100644 --- a/common/src/main/java/org/conscrypt/SessionSnapshot.java +++ b/common/src/main/java/org/conscrypt/SessionSnapshot.java @@ -42,6 +42,8 @@ final class SessionSnapshot implements ConscryptSession { private final String peerHost; private final String applicationProtocol; private final int peerPort; + private final String[] peerSupportedSignatureAlgorithms; + private final String[] localSupportedSignatureAlgorithms; SessionSnapshot(ConscryptSession session) { sessionContext = session.getSessionContext(); @@ -56,6 +58,8 @@ final class SessionSnapshot implements ConscryptSession { peerHost = session.getPeerHost(); peerPort = session.getPeerPort(); applicationProtocol = session.getApplicationProtocol(); + peerSupportedSignatureAlgorithms = session.getPeerSupportedSignatureAlgorithms(); + localSupportedSignatureAlgorithms = session.getLocalSupportedSignatureAlgorithms(); } @Override @@ -196,4 +200,16 @@ public int getApplicationBufferSize() { public String getApplicationProtocol() { return applicationProtocol; } + + @Override + public String[] getPeerSupportedSignatureAlgorithms() { + return peerSupportedSignatureAlgorithms != null ? peerSupportedSignatureAlgorithms.clone() + : new String[0]; + } + + @Override + public String[] getLocalSupportedSignatureAlgorithms() { + return localSupportedSignatureAlgorithms != null ? localSupportedSignatureAlgorithms.clone() + : new String[0]; + } } diff --git a/common/src/test/java/org/conscrypt/MlKemTest.java b/common/src/test/java/org/conscrypt/MlKemTest.java new file mode 100644 index 000000000..39049d0f7 --- /dev/null +++ b/common/src/test/java/org/conscrypt/MlKemTest.java @@ -0,0 +1,746 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import static org.conscrypt.HpkeSuite.AEAD_AES_128_GCM; +import static org.conscrypt.HpkeSuite.AEAD_AES_256_GCM; +import static org.conscrypt.HpkeSuite.AEAD_CHACHA20POLY1305; +import static org.conscrypt.HpkeSuite.KDF_HKDF_SHA256; +import static org.conscrypt.HpkeSuite.KEM_MLKEM_1024; +import static org.conscrypt.HpkeSuite.KEM_MLKEM_768; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.List; + +@RunWith(JUnit4.class) +public class MlKemTest { + private final Provider conscryptProvider = TestUtils.getConscryptProvider(); + + @BeforeClass + public static void setUp() { + TestUtils.assumeAllowsUnsignedCrypto(); + } + + public static final class RawKeySpec extends EncodedKeySpec { + public RawKeySpec(byte[] encoded) { + super(encoded); + } + + @Override + public String getFormat() { + return "raw"; + } + } + + public static MlKemAlgorithm toMlKemAlgorithm(String algorithm) { + switch (algorithm) { + case "ML-KEM": + case "ML-KEM-768": + return MlKemAlgorithm.ML_KEM_768; + case "ML-KEM-1024": + return MlKemAlgorithm.ML_KEM_1024; + default: + throw new IllegalArgumentException("Unsupported algorithm: " + algorithm); + } + } + + @Test + public void generateKeyPair_works() throws Exception { + for (String keyGenAlgorithm : new String[] {"ML-KEM", "ML-KEM-768", "ML-KEM-1024"}) { + MlKemAlgorithm expectedAlgorithm = toMlKemAlgorithm(keyGenAlgorithm); + + KeyPairGenerator keyGen = + KeyPairGenerator.getInstance(keyGenAlgorithm, conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + OpenSslMlKemPrivateKey privateKey = (OpenSslMlKemPrivateKey) keyPair.getPrivate(); + OpenSslMlKemPublicKey publicKey = (OpenSslMlKemPublicKey) keyPair.getPublic(); + + assertEquals(expectedAlgorithm, privateKey.getMlKemAlgorithm()); + assertEquals("ML-KEM", privateKey.getAlgorithm()); + byte[] seed = privateKey.getSeed(); + assertEquals(64, seed.length); + + assertEquals(expectedAlgorithm, publicKey.getMlKemAlgorithm()); + assertEquals("ML-KEM", publicKey.getAlgorithm()); + byte[] rawPublicKey = publicKey.getRaw(); + assertEquals(expectedAlgorithm.publicKeySize(), rawPublicKey.length); + } + } + + @Test + public void keyFactory_toAndFromRaw_works() throws Exception { + for (String factoryAlgorithm : new String[] {"ML-KEM", "ML-KEM-768", "ML-KEM-1024"}) { + MlKemAlgorithm algorithm = toMlKemAlgorithm(factoryAlgorithm); + // create random raw keys of the correct size. + int publicKeySize = algorithm.publicKeySize(); + byte[] rawPrivateKey = new byte[64]; + NativeCrypto.RAND_bytes(rawPrivateKey); + byte[] rawPublicKey = new byte[publicKeySize]; + NativeCrypto.RAND_bytes(rawPublicKey); + + KeyFactory keyFactory = KeyFactory.getInstance(factoryAlgorithm, conscryptProvider); + + // generatePrivate works. + OpenSslMlKemPrivateKey privateKey = (OpenSslMlKemPrivateKey) keyFactory.generatePrivate( + new RawKeySpec(rawPrivateKey)); + assertEquals(algorithm, privateKey.getMlKemAlgorithm()); + assertEquals("ML-KEM", privateKey.getAlgorithm()); + assertArrayEquals(rawPrivateKey, privateKey.getSeed()); + + // generatePublic works. + OpenSslMlKemPublicKey publicKey = + (OpenSslMlKemPublicKey) keyFactory.generatePublic(new RawKeySpec(rawPublicKey)); + assertEquals(algorithm, publicKey.getMlKemAlgorithm()); + assertEquals("ML-KEM", publicKey.getAlgorithm()); + assertArrayEquals(rawPublicKey, publicKey.getRaw()); + + // getKeySpec for private key with RawKeySpec works. + EncodedKeySpec privateKeySpec = keyFactory.getKeySpec(privateKey, RawKeySpec.class); + assertEquals("raw", privateKeySpec.getFormat()); + assertArrayEquals(rawPrivateKey, privateKeySpec.getEncoded()); + + // getKeySpec for public key with RawKeySpec works. + EncodedKeySpec publicKeySpec = keyFactory.getKeySpec(publicKey, RawKeySpec.class); + assertEquals("raw", publicKeySpec.getFormat()); + assertArrayEquals(rawPublicKey, publicKeySpec.getEncoded()); + + // generatePrivate and generatePublic for these keySpecs returns the same keys. + PrivateKey privateKey2 = keyFactory.generatePrivate(new RawKeySpec(rawPrivateKey)); + PublicKey publicKey2 = keyFactory.generatePublic(new RawKeySpec(rawPublicKey)); + assertEquals(publicKey, publicKey2); + assertEquals(privateKey, privateKey2); + + // check that generatePrivate and generatePublic reject keys of the wrong size. + RawKeySpec tooSmallPrivateKeySpec = new RawKeySpec(new byte[rawPrivateKey.length - 1]); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(tooSmallPrivateKeySpec)); + RawKeySpec tooLargePrivateKeySpec = new RawKeySpec(new byte[rawPrivateKey.length + 1]); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(tooLargePrivateKeySpec)); + RawKeySpec tooSmallPublicKeySpec = new RawKeySpec(new byte[rawPublicKey.length - 1]); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePublic(tooSmallPublicKeySpec)); + RawKeySpec tooLargePublicKeySpec = new RawKeySpec(new byte[rawPublicKey.length + 1]); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePublic(tooLargePublicKeySpec)); + } + } + + /** Helper class to test KeyFactory.translateKey. */ + private static class TestPublicKey implements PublicKey { + TestPublicKey(byte[] x509Encoded) { + this.x509Encoded = x509Encoded; + } + + private final byte[] x509Encoded; + + @Override + public String getAlgorithm() { + return "ML-KEM"; + } + + @Override + public String getFormat() { + return "X.509"; + } + + @Override + public byte[] getEncoded() { + return x509Encoded; + } + } + + /** Helper class to test KeyFactory.translateKey. */ + private static class TestPrivateKey implements PrivateKey { + TestPrivateKey(byte[] pkcs8Encoded) { + this.pkcs8Encoded = pkcs8Encoded; + } + + private final byte[] pkcs8Encoded; + + @Override + public String getAlgorithm() { + return "ML-KEM"; + } + + @Override + public String getFormat() { + return "PKCS#8"; + } + + @Override + public byte[] getEncoded() { + return pkcs8Encoded; + } + } + + @Test + public void mlKem768KeyPair_x509AndPkcs8_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-768", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + assertEquals("PKCS#8", keyPair.getPrivate().getFormat()); + // 64 bytes for the seed + 22 bytes for the preamble. + assertEquals(86, keyPair.getPrivate().getEncoded().length); + + assertEquals("X.509", keyPair.getPublic().getFormat()); + // 1184 bytes for the raw key + 22 bytes for the preamble. + assertEquals(1206, keyPair.getPublic().getEncoded().length); + + for (String algorithm : new String[] {"ML-KEM-768", "ML-KEM"}) { + KeyFactory keyFactory = KeyFactory.getInstance(algorithm, conscryptProvider); + + PKCS8EncodedKeySpec privateKeySpec = + keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class); + assertEquals("PKCS#8", privateKeySpec.getFormat()); + assertArrayEquals(keyPair.getPrivate().getEncoded(), privateKeySpec.getEncoded()); + + X509EncodedKeySpec publicKeySpec = + keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class); + assertEquals("X.509", publicKeySpec.getFormat()); + assertArrayEquals(keyPair.getPublic().getEncoded(), publicKeySpec.getEncoded()); + + PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + + assertEquals(keyPair.getPrivate(), privateKey); + assertEquals(keyPair.getPublic(), publicKey); + + assertEquals(keyPair.getPrivate(), keyFactory.translateKey(keyPair.getPrivate())); + assertEquals( + keyPair.getPrivate(), + keyFactory.translateKey(new TestPrivateKey(keyPair.getPrivate().getEncoded()))); + assertEquals(keyPair.getPublic(), keyFactory.translateKey(keyPair.getPublic())); + assertEquals( + keyPair.getPublic(), + keyFactory.translateKey(new TestPublicKey(keyPair.getPublic().getEncoded()))); + } + + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-1024", conscryptProvider); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class)); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class)); + assertThrows(InvalidKeySpecException.class, + () + -> keyFactory.generatePrivate( + new RawKeySpec(keyPair.getPrivate().getEncoded()))); + assertThrows( + InvalidKeySpecException.class, + () -> keyFactory.generatePublic(new RawKeySpec(keyPair.getPublic().getEncoded()))); + + assertThrows(InvalidKeyException.class, + () -> keyFactory.translateKey(keyPair.getPrivate())); + assertThrows(InvalidKeyException.class, + () + -> keyFactory.translateKey( + new TestPrivateKey(keyPair.getPrivate().getEncoded()))); + assertThrows(InvalidKeyException.class, () -> keyFactory.translateKey(keyPair.getPublic())); + assertThrows( + InvalidKeyException.class, + () -> keyFactory.translateKey(new TestPublicKey(keyPair.getPublic().getEncoded()))); + } + + @Test + public void mlKem1024KeyPair_x509AndPkcs8_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-1024", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + assertEquals("PKCS#8", keyPair.getPrivate().getFormat()); + // 64 bytes for the seed + 22 bytes for the preamble. + assertEquals(86, keyPair.getPrivate().getEncoded().length); + + assertEquals("X.509", keyPair.getPublic().getFormat()); + // 1568 bytes for the raw key + 22 bytes for the preamble. + assertEquals(1590, keyPair.getPublic().getEncoded().length); + + for (String algorithm : new String[] {"ML-KEM-1024", "ML-KEM"}) { + KeyFactory keyFactory = KeyFactory.getInstance(algorithm, conscryptProvider); + + PKCS8EncodedKeySpec privateKeySpec = + keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class); + assertEquals("PKCS#8", privateKeySpec.getFormat()); + assertArrayEquals(keyPair.getPrivate().getEncoded(), privateKeySpec.getEncoded()); + + X509EncodedKeySpec publicKeySpec = + keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class); + assertEquals("X.509", publicKeySpec.getFormat()); + assertArrayEquals(keyPair.getPublic().getEncoded(), publicKeySpec.getEncoded()); + + PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + + assertEquals(keyPair.getPrivate(), privateKey); + assertEquals(keyPair.getPublic(), publicKey); + + assertEquals(keyPair.getPrivate(), keyFactory.translateKey(keyPair.getPrivate())); + assertEquals( + keyPair.getPrivate(), + keyFactory.translateKey(new TestPrivateKey(keyPair.getPrivate().getEncoded()))); + assertEquals(keyPair.getPublic(), keyFactory.translateKey(keyPair.getPublic())); + assertEquals( + keyPair.getPublic(), + keyFactory.translateKey(new TestPublicKey(keyPair.getPublic().getEncoded()))); + } + + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-768", conscryptProvider); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class)); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class)); + assertThrows(InvalidKeySpecException.class, + () + -> keyFactory.generatePrivate( + new RawKeySpec(keyPair.getPrivate().getEncoded()))); + assertThrows( + InvalidKeySpecException.class, + () -> keyFactory.generatePublic(new RawKeySpec(keyPair.getPublic().getEncoded()))); + + assertThrows(InvalidKeyException.class, + () -> keyFactory.translateKey(keyPair.getPrivate())); + assertThrows(InvalidKeyException.class, + () + -> keyFactory.translateKey( + new TestPrivateKey(keyPair.getPrivate().getEncoded()))); + assertThrows(InvalidKeyException.class, () -> keyFactory.translateKey(keyPair.getPublic())); + assertThrows( + InvalidKeyException.class, + () -> keyFactory.translateKey(new TestPublicKey(keyPair.getPublic().getEncoded()))); + } + + @Test + public void mlKem768_pkcs8TestVector_works() throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-768", conscryptProvider); + + // Example from RFC 9935, C.1.2.1 + String pcks8Base64 = "MFQCAQAwCwYJYIZIAWUDBAQCBEKAQAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ" + + "GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8="; + String rawHex = "000102030405060708090a0b0c0d0e0f10111213141" + + "5161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343" + + "5363738393a3b3c3d3e3f"; + + byte[] seed = TestUtils.decodeHex(rawHex); + byte[] encoded = TestUtils.decodeBase64(pcks8Base64); + + PrivateKey privateKey = keyFactory.generatePrivate(new RawKeySpec(seed)); + assertArrayEquals(encoded, privateKey.getEncoded()); + + EncodedKeySpec encodedKeySpec = + keyFactory.getKeySpec(privateKey, PKCS8EncodedKeySpec.class); + assertEquals("PKCS#8", encodedKeySpec.getFormat()); + assertArrayEquals(encoded, encodedKeySpec.getEncoded()); + + PrivateKey privateKey2 = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded)); + assertArrayEquals(encoded, privateKey2.getEncoded()); + OpenSslMlKemPrivateKey mlKemPrivateKey = (OpenSslMlKemPrivateKey) privateKey2; + assertEquals(MlKemAlgorithm.ML_KEM_768, mlKemPrivateKey.getMlKemAlgorithm()); + assertArrayEquals(seed, mlKemPrivateKey.getSeed()); + } + + @Test + public void mlKem1024_pkcs8TestVector_works() throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-1024", conscryptProvider); + + // Example from RFC 9935, C.1.3.1 + String pcks8Base64 = "MFQCAQAwCwYJYIZIAWUDBAQDBEKAQAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ" + + "GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8="; + String rawHex = "000102030405060708090a0b0c0d0e0f10111213141" + + "5161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343" + + "5363738393a3b3c3d3e3f"; + + byte[] seed = TestUtils.decodeHex(rawHex); + byte[] encoded = TestUtils.decodeBase64(pcks8Base64); + + PrivateKey privateKey = keyFactory.generatePrivate(new RawKeySpec(seed)); + assertArrayEquals(encoded, privateKey.getEncoded()); + + EncodedKeySpec encodedKeySpec = + keyFactory.getKeySpec(privateKey, PKCS8EncodedKeySpec.class); + assertEquals("PKCS#8", encodedKeySpec.getFormat()); + assertArrayEquals(encoded, encodedKeySpec.getEncoded()); + + PrivateKey privateKey2 = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded)); + assertArrayEquals(encoded, privateKey2.getEncoded()); + OpenSslMlKemPrivateKey mlKemPrivateKey = (OpenSslMlKemPrivateKey) privateKey2; + assertEquals(MlKemAlgorithm.ML_KEM_1024, mlKemPrivateKey.getMlKemAlgorithm()); + assertArrayEquals(seed, mlKemPrivateKey.getSeed()); + } + + @Test + public void mlKem768_x509TestVector_works() throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-768", conscryptProvider); + + // Example from RFC 9935, C.2 + String x509Base64 = "MIIEsjALBglghkgBZQMEBAIDggShACmKoQ1CPI3aBp0CvFnmzfA6CWuLPaTKubgM" + + "pKFJB2cszvHsT68jSgvFt+nUc/KzEzs7JqHRdctnp4BZGWmcAvdlMbmcX4kYBwS7" + + "TKRTXFuJcmecZgoHxeUUuHAJyGLrj1FXaV77P8QKne9rgcHMAqJJrk8JStDZvTSF" + + "wcHGgIBSCnyMYyAyzuc4FU5cUXbAfaVgJHdqQw/nbqz2ZaP3uDIQIhW8gvEJOcg1" + + "VwQzao+sHYHkuwSFql18dNa1m75cXpcqDYusQRtVtdVVfNaAoaj3G064a8SMmgUJ" + + "cxpUvZ1ykLJ5Y+Q3Lcmxmc/crAsBrNKKYjlREuTENkjWIsSMgjTQFEDozDdskn8j" + + "pa/JrAR0xmInTkJFJchVLs47P+JlFt6QG8fVFb3olVjmJslcgLkzQvgBAATznmxs" + + "lIccXjRMqzlmyDX5qWpZr9McQChrOLHBp4RwurlHUYk0RTzoZzapGfH1ptUQqG9U" + + "VPw5gMtcdlvSvV97NrFBDWY1yM60fE3aDXaijqyTnHHDAkgEhmxxYmZYRCFjwsIh" + + "F+UKzvzmN4qYVlIwKk7wws4Mxxa3eW4ray43d9+hrD2iWaMbWptTD4y2OKgaYqww" + + "GEmrr5WnMBvaMAaJCb/bfmfbzLs4pVUaJbGjoPaFdIrVdT2IgPABbGJ0hhZjhMVX" + + "H+I2WQA2TQODEeLYdds2ZoaTK17GAkMKNp6Hpu9cM4eGZXglvUwFes65I+sJNeaQ" + + "XmO0ztf4CFenc91ksVDSZhLqmsEgUtsgF78YQ8y0sygbaQ3HKK36hcACgbjjwJKH" + + "M1+Fa0/CiS9povV5Ia2gGRTECYhmLVd2lmKnhjUbm2ZJPat5WU2YbeIQDWW6D/Tq" + + "WLgVONJKRDWiWPrCVASqf0H2WLE4UGXhWNy2ARVzJyD0BFmqrBXkBpU6kKxSmX0c" + + "zQcAYO/GXbnmUzVEZ/rVbscTyG51QMQjrPJmn1L6b0rGiI2HHvPoR8ApqKr7uS4X" + + "skqgebH0GbphdbRCr7EZCdSla3CgM1soc5IYqnyTSOLDwvPrPRWkHmQXwN2Uv+sh" + + "QZsxGnuxOhgLvoMyGKmmsXRHzIXyJYWVh6cwdwSay8/UTQ8CVDjhXRU4Jw1Ybhv4" + + "MZKpRZz2PA6XL4UpdnmDHs8SFQmFHLg0D28Qew+hoO/Rs2qBibwIXE9ct4TlU/Qb" + + "kY+AOXzhlW94W+43fKmqi+aZitowwmt8PYxrVSVMyWIDsgxCruCsTh67QI5JqeP4" + + "edCrB4XrcCVCXRMFoimcAV4SDRY7DhlJTOVyU9AkbRgnRcuBl6t0OLPBu3lyvsWj" + + "BuujVnhVwBRpn+9lrlTHcKDYXBhADPZCrtxmB3e6SxOFAr1aeBL2IfhKSClrmN1D" + + "IrbxWCi4qPDgCoukSlPDqLFDVxsHQKvVZ9rxzenHnCBLbV4lnRdmoxu7y05qBc9F" + + "AhdrMBwcL0Ekd1AVe87IXoCbMKTWDXdHzdD1uZqoyCaYdRd5OqqAgKCxJKhVjfcr" + + "vje3X07btr6CFtbGM/srIoDiURPYaV5DSBw+6zl+sZJQUim2eiAeqJPD4ssy2ovD" + + "QvpN6gV4"; + String rawHex = "298aa10d423c8dda069d02bc59e6cdf03a096b8b3da" + + "4cab9b80ca4a14907672ccef1ec4faf234a0bc5b7e9d473f2b3133b3b26a1d17" + + "5cb67a7805919699c02f76531b99c5f89180704bb4ca4535c5b8972679c660a0" + + "7c5e514b87009c862eb8f5157695efb3fc40a9def6b81c1cc02a249ae4f094ad" + + "0d9bd3485c1c1c68080520a7c8c632032cee738154e5c5176c07da56024776a4" + + "30fe76eacf665a3f7b832102215bc82f10939c8355704336a8fac1d81e4bb048" + + "5aa5d7c74d6b59bbe5c5e972a0d8bac411b55b5d5557cd680a1a8f71b4eb86bc" + + "48c9a0509731a54bd9d7290b27963e4372dc9b199cfdcac0b01acd28a6239511" + + "2e4c43648d622c48c8234d01440e8cc376c927f23a5afc9ac0474c662274e424" + + "525c8552ece3b3fe26516de901bc7d515bde89558e626c95c80b93342f801000" + + "4f39e6c6c94871c5e344cab3966c835f9a96a59afd31c40286b38b1c1a78470b" + + "ab947518934453ce86736a919f1f5a6d510a86f5454fc3980cb5c765bd2bd5f7" + + "b36b1410d6635c8ceb47c4dda0d76a28eac939c71c3024804866c71626658442" + + "163c2c22117e50acefce6378a985652302a4ef0c2ce0cc716b7796e2b6b2e377" + + "7dfa1ac3da259a31b5a9b530f8cb638a81a62ac301849abaf95a7301bda30068" + + "909bfdb7e67dbccbb38a5551a25b1a3a0f685748ad5753d8880f0016c6274861" + + "66384c5571fe2365900364d038311e2d875db366686932b5ec602430a369e87a" + + "6ef5c338786657825bd4c057aceb923eb0935e6905e63b4ced7f80857a773dd6" + + "4b150d26612ea9ac12052db2017bf1843ccb4b3281b690dc728adfa85c00281b" + + "8e3c09287335f856b4fc2892f69a2f57921ada01914c40988662d57769662a78" + + "6351b9b66493dab79594d986de2100d65ba0ff4ea58b81538d24a4435a258fac" + + "25404aa7f41f658b1385065e158dcb60115732720f40459aaac15e406953a90a" + + "c52997d1ccd070060efc65db9e653354467fad56ec713c86e7540c423acf2669" + + "f52fa6f4ac6888d871ef3e847c029a8aafbb92e17b24aa079b1f419ba6175b44" + + "2afb11909d4a56b70a0335b28739218aa7c9348e2c3c2f3eb3d15a41e6417c0d" + + "d94bfeb21419b311a7bb13a180bbe833218a9a6b17447cc85f225859587a7307" + + "7049acbcfd44d0f025438e15d1538270d586e1bf83192a9459cf63c0e972f852" + + "97679831ecf121509851cb8340f6f107b0fa1a0efd1b36a8189bc085c4f5cb78" + + "4e553f41b918f80397ce1956f785bee377ca9aa8be6998ada30c26b7c3d8c6b5" + + "5254cc96203b20c42aee0ac4e1ebb408e49a9e3f879d0ab0785eb7025425d130" + + "5a2299c015e120d163b0e19494ce57253d0246d182745cb8197ab7438b3c1bb7" + + "972bec5a306eba3567855c014699fef65ae54c770a0d85c18400cf642aedc660" + + "777ba4b138502bd5a7812f621f84a48296b98dd4322b6f15828b8a8f0e00a8ba" + + "44a53c3a8b143571b0740abd567daf1cde9c79c204b6d5e259d1766a31bbbcb4" + + "e6a05cf4502176b301c1c2f41247750157bcec85e809b30a4d60d7747cdd0f5b" + + "99aa8c826987517793aaa8080a0b124a8558df72bbe37b75f4edbb6be8216d6c" + + "633fb2b2280e25113d8695e43481c3eeb397eb192505229b67a201ea893c3e2c" + + "b32da8bc342fa4dea0578"; + + byte[] raw = TestUtils.decodeHex(rawHex); + byte[] encoded = TestUtils.decodeBase64(x509Base64); + + PublicKey publicKey = keyFactory.generatePublic(new RawKeySpec(raw)); + assertArrayEquals(encoded, publicKey.getEncoded()); + + EncodedKeySpec encodedKeySpec = keyFactory.getKeySpec(publicKey, X509EncodedKeySpec.class); + assertEquals("X.509", encodedKeySpec.getFormat()); + assertArrayEquals(encoded, encodedKeySpec.getEncoded()); + + PublicKey publicKey2 = keyFactory.generatePublic(new X509EncodedKeySpec(encoded)); + assertArrayEquals(encoded, publicKey2.getEncoded()); + OpenSslMlKemPublicKey mlKemPublicKey = (OpenSslMlKemPublicKey) publicKey2; + assertEquals(MlKemAlgorithm.ML_KEM_768, mlKemPublicKey.getMlKemAlgorithm()); + assertArrayEquals(raw, mlKemPublicKey.getRaw()); + } + + @Test + public void mlKem1024_x509TestVector_works() throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-1024", conscryptProvider); + + // Example from RFC 9935, C.2 + String x509Base64 = "MIIGMjALBglghkgBZQMEBAMDggYhAEuUwpRQERGRgjs1FMmsHqPZglzLhjk6LfsE" + + "ZU+iGS03v60cSXxlAu7lyoCnO/zguvWlSohYWkATl6PSMvQmp6+wgrwhpEMXCQ6q" + + "x1ksLqiKZTxEkeoZOTEzX1LpiaPEzFbZxVNzLVfEcPtBq3WbZdLQREU4L82cTjRK" + + "ESj6nhHgQ1jhku0BSyMjKn7isi4jcX9EER7jNXU5nDdkbamBPsmyEq/pTl3FwjMK" + + "cpTMH0I0ptP7tPFoWriJLASssXzRwXDXsGEbanF2x5TMjGf1X8kjwq0gMQDzZZkY" + + "gsMCQ9d4E4Q7XsfJZAMiY3BgkuzwDHUWvmTkWYykImwGm7XmfkF1zyKGyN1cSIps" + + "WGHzG6oL0CaUcOi1Ud07zTjIbBL5zbF2x33ItsAqcB9HiQLIVT9pTA2CcntMSlws" + + "EEEhKqEnSAi4IRGzd+x1IU6bGXj3YATUE52YYT9LjpjSCve1NAc6UJqVm3p1ZPm0" + + "DKIYv2GCkyCoUCAXlU0yjXrGx2nsKXAHVuewaFs0DV4RgFlQSkmppQoQGY6xCleE" + + "Z460J9e0uruVUpM7BiiXlz4TGOrwoOrDdYSmVAGxcD4EKszYN1MUg/JBytzRwdN4" + + "EZ5pRCnbGZrIkeTFNDdXCFuzrng2ZzUMRFjZdnLoYegLHSZ5UQ6jpvI2DHekaULH" + + "oGpVTSKAgMhLR67xTbF2IMsWwGqzChvkzacIK+n4fpwhHEaRY0mluo6qUgHHKUo8" + + "CIW1O2V0UhCIJexkbJCgRhIyTufQMa/lNDEyy+9ntu+xpewoCbdzU4znez2LBOsL" + + "PCJWAR5McWwZqLoHUr9xSSEXZJ8GFcMpD8KaRv3kvVLbkobWAziCRCWcFaesK2QK" + + "YMwDN2pYQaP7ikc1aPqbGiZyFfNMAWl7Dw5icXXXIQW3cHwpueYUvcM6b2yBipU3" + + "C0J4gte0dnlqnsbrmTJ0zZsjkagrpF4zk9Lprpchyp1sG5iLWCdxP5CmWF3pQzUo" + + "wCsDzhC7X3IBOND7tMMMEma5GOUpJd/hezf5XSK8pU9HWRmshZCYwPDQisWHXvKb" + + "Vv0UHm7xX3AKC2bzlZXFiBdzc8RmmyG8Bx5MOqXwtKMbYljzXaJKw80px/IJJBDF" + + "B4NVsTj7U6a5rm4LnAgkPnuqRcRzduuMfxPUz1Gqc2+jFUDJJB83DaVEv5+cKNml" + + "fi8qfKlaTktGbmQas7zHat8ROdVnpvErUvOmXn7AquJryqjFWDOwTlmZjryaGTD7" + + "ttIjPFPSwfi5UY48Lec6Gd7ms4Clsylxz2ThKf1sH6bnXUojRQHpZt06VAr1yPTz" + + "SmtKJT7ihJJWbV5nxvVYVfywUG+wbBVnRNmgOjGib6lMrRTxV7fzA9B6acdzdo/L" + + "TQecCQWXA6DDqU3kuZ6jovFlg9D5Fwo5UNsHtPC8MIApJ/n3lhtiWYkmNqlQKicF" + + "MDY3eZ3TRNpFHBz3v2eEDOsweauMa4wZJ/ZAU8YSRQxFyeYDvBZmbllrNHHhA7bx" + + "VEdCTRcCIEgRH/vTfhxnD2TxS4p7MrlMGkm0XdL8OM1SidkQrWNgLPXhMELGSsZ5" + + "e4n7VRrQjgWpLSAMzLfnEu8jyTEss1DwKatTfihzR/0wdawQkGp4PxxsB8y4j0Ei" + + "jEvhxkD3kLXDpdXTynkklddLxGFWJljAesYAJ2uSSrW8m+HwSUy3b4L0YKdICXJm" + + "M4HhaZlgYdeZhZ7FTU9cpcQRwB2xWXsWWXdmneE6koo0r7rCWP6oxHZCOclCHcMR" + + "m/W0dpkgaXgyexxTRe90anmDhB8FbiU0EAqyTU6au9CxfGqVvUw8DkD2nhYSrO6y" + + "i5kIbJURbnIEJziTOQv0a4mbNihrDr8ZR7uYhPcyyifagrGbXcDMf4iFcUkQiIsj" + + "EMT5MZ1BCzTmQzuQA+IXa7mVJXRWEG6JUhY7i6WSUwzFqgrrQ605j+npe6pSPXpE" + + "MWd8PTrwcZ5HXbhcqVr1CJvqvrBbL6q0iWumD4HIhHKle0aoKIJqDN+0RvgYkYLS" + + "v16sTsHMXer1mcihPkgjVAbRf/3cg0S2xmmEqGiqkvoCInoIaVDrDIcB7VjcYod2" + + "uYOILhF1"; + String rawHex = "4b94c29450111191823b3514c9ac1ea3d9825ccb863" + + "93a2dfb04654fa2192d37bfad1c497c6502eee5ca80a73bfce0baf5a54a88585" + + "a401397a3d232f426a7afb082bc21a44317090eaac7592c2ea88a653c4491ea1" + + "93931335f52e989a3c4cc56d9c553732d57c470fb41ab759b65d2d04445382fc" + + "d9c4e344a1128fa9e11e04358e192ed014b23232a7ee2b22e23717f44111ee33" + + "575399c37646da9813ec9b212afe94e5dc5c2330a7294cc1f4234a6d3fbb4f16" + + "85ab8892c04acb17cd1c170d7b0611b6a7176c794cc8c67f55fc923c2ad20310" + + "0f365991882c30243d77813843b5ec7c964032263706092ecf00c7516be64e45" + + "98ca4226c069bb5e67e4175cf2286c8dd5c488a6c5861f31baa0bd0269470e8b" + + "551dd3bcd38c86c12f9cdb176c77dc8b6c02a701f478902c8553f694c0d82727" + + "b4c4a5c2c1041212aa1274808b82111b377ec75214e9b1978f76004d4139d986" + + "13f4b8e98d20af7b534073a509a959b7a7564f9b40ca218bf61829320a850201" + + "7954d328d7ac6c769ec29700756e7b0685b340d5e118059504a49a9a50a10198" + + "eb10a5784678eb427d7b4babb9552933b062897973e1318eaf0a0eac37584a65" + + "401b1703e042accd837531483f241cadcd1c1d378119e694429db199ac891e4c" + + "5343757085bb3ae783667350c4458d97672e861e80b1d2679510ea3a6f2360c7" + + "7a46942c7a06a554d228080c84b47aef14db17620cb16c06ab30a1be4cda7082" + + "be9f87e9c211c46916349a5ba8eaa5201c7294a3c0885b53b657452108825ec6" + + "46c90a04612324ee7d031afe5343132cbef67b6efb1a5ec2809b773538ce77b3" + + "d8b04eb0b3c2256011e4c716c19a8ba0752bf71492117649f0615c3290fc29a4" + + "6fde4bd52db9286d603388244259c15a7ac2b640a60cc03376a5841a3fb8a473" + + "568fa9b1a267215f34c01697b0f0e627175d72105b7707c29b9e614bdc33a6f6" + + "c818a95370b427882d7b476796a9ec6eb993274cd9b2391a82ba45e3393d2e9a" + + "e9721ca9d6c1b988b5827713f90a6585de9433528c02b03ce10bb5f720138d0f" + + "bb4c30c1266b918e52925dfe17b37f95d22bca54f475919ac859098c0f0d08ac" + + "5875ef29b56fd141e6ef15f700a0b66f39595c588177373c4669b21bc071e4c3" + + "aa5f0b4a31b6258f35da24ac3cd29c7f2092410c5078355b138fb53a6b9ae6e0" + + "b9c08243e7baa45c47376eb8c7f13d4cf51aa736fa31540c9241f370da544bf9" + + "f9c28d9a57e2f2a7ca95a4e4b466e641ab3bcc76adf1139d567a6f12b52f3a65" + + "e7ec0aae26bcaa8c55833b04e59998ebc9a1930fbb6d2233c53d2c1f8b9518e3" + + "c2de73a19dee6b380a5b32971cf64e129fd6c1fa6e75d4a234501e966dd3a540" + + "af5c8f4f34a6b4a253ee28492566d5e67c6f55855fcb0506fb06c156744d9a03" + + "a31a26fa94cad14f157b7f303d07a69c773768fcb4d079c09059703a0c3a94de" + + "4b99ea3a2f16583d0f9170a3950db07b4f0bc30802927f9f7961b6259892636a" + + "9502a2705303637799dd344da451c1cf7bf67840ceb3079ab8c6b8c1927f6405" + + "3c612450c45c9e603bc16666e596b3471e103b6f15447424d17022048111ffbd" + + "37e1c670f64f14b8a7b32b94c1a49b45dd2fc38cd5289d910ad63602cf5e1304" + + "2c64ac6797b89fb551ad08e05a92d200cccb7e712ef23c9312cb350f029ab537" + + "e287347fd3075ac10906a783f1c6c07ccb88f41228c4be1c640f790b5c3a5d5d" + + "3ca792495d74bc461562658c07ac600276b924ab5bc9be1f0494cb76f82f460a" + + "7480972663381e169996061d799859ec54d4f5ca5c411c01db1597b165977669" + + "de13a928a34afbac258fea8c4764239c9421dc3119bf5b47699206978327b1c5" + + "345ef746a7983841f056e2534100ab24d4e9abbd0b17c6a95bd4c3c0e40f69e1" + + "612aceeb28b99086c95116e7204273893390bf46b899b36286b0ebf1947bb988" + + "4f732ca27da82b19b5dc0cc7f8885714910888b2310c4f9319d410b34e6433b9" + + "003e2176bb995257456106e8952163b8ba592530cc5aa0aeb43ad398fe9e97ba" + + "a523d7a4431677c3d3af0719e475db85ca95af5089beabeb05b2faab4896ba60" + + "f81c88472a57b46a828826a0cdfb446f8189182d2bf5eac4ec1cc5deaf599c8a" + + "13e48235406d17ffddc8344b6c66984a868aa92fa02227a086950eb0c8701ed5" + + "8dc628776b983882e1175"; + + byte[] raw = TestUtils.decodeHex(rawHex); + byte[] encoded = TestUtils.decodeBase64(x509Base64); + + PublicKey publicKey = keyFactory.generatePublic(new RawKeySpec(raw)); + assertArrayEquals(encoded, publicKey.getEncoded()); + + EncodedKeySpec encodedKeySpec = keyFactory.getKeySpec(publicKey, X509EncodedKeySpec.class); + assertEquals("X.509", encodedKeySpec.getFormat()); + assertArrayEquals(encoded, encodedKeySpec.getEncoded()); + + PublicKey publicKey2 = keyFactory.generatePublic(new X509EncodedKeySpec(encoded)); + assertArrayEquals(encoded, publicKey2.getEncoded()); + OpenSslMlKemPublicKey mlKemPublicKey = (OpenSslMlKemPublicKey) publicKey2; + assertEquals(MlKemAlgorithm.ML_KEM_1024, mlKemPublicKey.getMlKemAlgorithm()); + assertArrayEquals(raw, mlKemPublicKey.getRaw()); + } + + @Test + public void sealAndOpen_mlkem768_works() throws Exception { + byte[] info = TestUtils.decodeHex("aa"); + byte[] plaintext = TestUtils.decodeHex("bb"); + byte[] aad = TestUtils.decodeHex("cc"); + + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-768", conscryptProvider); + KeyPair keyPairRecipient = keyGen.generateKeyPair(); + + for (int aead : new int[] {AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_CHACHA20POLY1305}) { + HpkeSuite suite = new HpkeSuite(KEM_MLKEM_768, KDF_HKDF_SHA256, aead); + + HpkeContextSender ctxSender = + HpkeContextSender.getInstance(suite.name(), conscryptProvider); + ctxSender.init(keyPairRecipient.getPublic(), info); + + byte[] encapsulated = ctxSender.getEncapsulated(); + byte[] ciphertext = ctxSender.seal(plaintext, aad); + + HpkeContextRecipient contextRecipient = + HpkeContextRecipient.getInstance(suite.name(), conscryptProvider); + contextRecipient.init(encapsulated, keyPairRecipient.getPrivate(), info); + byte[] output = contextRecipient.open(ciphertext, aad); + + assertArrayEquals(plaintext, output); + } + } + + @Test + public void sealAndOpen_mlkem1024_works() throws Exception { + byte[] info = TestUtils.decodeHex("aa"); + byte[] plaintext = TestUtils.decodeHex("bb"); + byte[] aad = TestUtils.decodeHex("cc"); + + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-1024", conscryptProvider); + KeyPair keyPairRecipient = keyGen.generateKeyPair(); + + for (int aead : new int[] {AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_CHACHA20POLY1305}) { + HpkeSuite suite = new HpkeSuite(KEM_MLKEM_1024, KDF_HKDF_SHA256, aead); + + HpkeContextSender ctxSender = + HpkeContextSender.getInstance(suite.name(), conscryptProvider); + ctxSender.init(keyPairRecipient.getPublic(), info); + + byte[] encapsulated = ctxSender.getEncapsulated(); + byte[] ciphertext = ctxSender.seal(plaintext, aad); + + HpkeContextRecipient contextRecipient = + HpkeContextRecipient.getInstance(suite.name(), conscryptProvider); + contextRecipient.init(encapsulated, keyPairRecipient.getPrivate(), info); + byte[] output = contextRecipient.open(ciphertext, aad); + + assertArrayEquals(plaintext, output); + } + } + + @Test + public void wrongInfoORAad_fails() throws Exception { + byte[] info = TestUtils.decodeHex("aa"); + byte[] plaintext = TestUtils.decodeHex("bb"); + byte[] aad = TestUtils.decodeHex("cc"); + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-768", conscryptProvider); + KeyPair keyPairRecipient = keyGen.generateKeyPair(); + + String hpkeAlgorithm = "MLKEM_768/HKDF_SHA256/AES_128_GCM"; + + HpkeContextSender ctxSender = + HpkeContextSender.getInstance(hpkeAlgorithm, conscryptProvider); + ctxSender.init(keyPairRecipient.getPublic(), info); + + byte[] encapsulated = ctxSender.getEncapsulated(); + byte[] ciphertext = ctxSender.seal(plaintext, aad); + + HpkeContextRecipient contextRecipient = + HpkeContextRecipient.getInstance(hpkeAlgorithm, conscryptProvider); + contextRecipient.init(encapsulated, keyPairRecipient.getPrivate(), info); + + // with correct info and aad, it works. + assertArrayEquals(plaintext, contextRecipient.open(ciphertext, aad)); + + // with correct info and wrong aad, it fails. + assertThrows(GeneralSecurityException.class, + () -> contextRecipient.open(ciphertext, TestUtils.decodeHex("ff"))); + + // with wrong info and correct aad, it fails. + HpkeContextRecipient contextRecipient2 = + HpkeContextRecipient.getInstance(hpkeAlgorithm, conscryptProvider); + contextRecipient2.init(encapsulated, keyPairRecipient.getPrivate(), + TestUtils.decodeHex("ff")); + assertThrows(GeneralSecurityException.class, () -> contextRecipient2.open(ciphertext, aad)); + } + + @Test + public void hpkeContextRecipient_openTestVectors_works() throws Exception { + List vectors = TestUtils.readTestVectors("crypto/mlkem.txt"); + + for (TestVector vector : vectors) { + String keyAlgorithm = vector.getString("key-algorithm"); + String hpkeAlgorithm = vector.getString("hpke-algorithm"); + byte[] info = vector.getBytes("info"); + byte[] pk = vector.getBytes("pk"); + byte[] sk = vector.getBytes("sk"); + byte[] enc = vector.getBytes("enc"); + byte[] ct = vector.getBytes("ct"); + byte[] pt = vector.getBytes("pt"); + byte[] aad = vector.getBytes("aad"); + + KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm, conscryptProvider); + PrivateKey privateKey = keyFactory.generatePrivate(new RawKeySpec(sk)); + PublicKey publicKey = keyFactory.generatePublic(new RawKeySpec(pk)); + + // Open enc/ct pair from test vector. + HpkeContextRecipient ctxRecipient = + HpkeContextRecipient.getInstance(hpkeAlgorithm, conscryptProvider); + + ctxRecipient.init(enc, privateKey, info); + byte[] decrypted = ctxRecipient.open(ct, aad); + + assertArrayEquals(pt, decrypted); + + // Create new enc/ct pair and open it. + HpkeContextSender ctxSender = + HpkeContextSender.getInstance(hpkeAlgorithm, conscryptProvider); + ctxSender.init(publicKey, info); + + byte[] enc2 = ctxSender.getEncapsulated(); + byte[] ct2 = ctxSender.seal(pt, aad); + + HpkeContextRecipient contextRecipient = + HpkeContextRecipient.getInstance(hpkeAlgorithm, conscryptProvider); + contextRecipient.init(enc2, privateKey, info); + byte[] output = contextRecipient.open(ct2, aad); + + assertArrayEquals(pt, output); + } + } + + @Test + public void serialize_throwsUnsupportedOperationException() throws Exception { + for (String algorithm : new String[] {"ML-KEM", "ML-KEM-768", "ML-KEM-1024"}) { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm, conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + + ObjectOutputStream oos = new ObjectOutputStream(new ByteArrayOutputStream(16384)); + PrivateKey privateKey = keyPair.getPrivate(); + assertThrows(UnsupportedOperationException.class, () -> oos.writeObject(privateKey)); + PublicKey publicKey = keyPair.getPublic(); + assertThrows(UnsupportedOperationException.class, () -> oos.writeObject(publicKey)); + } + } +} diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java index 67fc09d45..5ab143c96 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java @@ -50,9 +50,11 @@ import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Enumeration; +import java.util.List; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -83,6 +85,18 @@ private TestKeyStore getTestKeyStore() throws Exception { return testKeyStore; } + // Remove legacy EC_EC type, which Jdk now considers invalid. (may or may not be a bug: + // https://bugs.openjdk.org/browse/JDK-8379191) + private static String[] removeLegacyEcTypes(String[] input) { + List list = new ArrayList<>(); + for (String s : input) { + if (s != null && !s.equals("EC_EC")) { + list.add(s); + } + } + return list.toArray(new String[0]); + } + @Test public void test_KeyManagerFactory_getDefaultAlgorithm() throws Exception { String algorithm = KeyManagerFactory.getDefaultAlgorithm(); @@ -193,7 +207,7 @@ private void test_KeyManagerFactory_getKeyManagers(KeyManagerFactory kmf, boolea private void test_X509KeyManager(X509KeyManager km, boolean empty, String algorithm) throws Exception { - String[] keyTypes = keyTypes(algorithm); + String[] keyTypes = removeLegacyEcTypes(keyTypes(algorithm)); for (String keyType : keyTypes) { String[] aliases = km.getClientAliases(keyType, null); if (empty || keyType == null || keyType.isEmpty()) { @@ -239,7 +253,7 @@ private void test_X509KeyManager(X509KeyManager km, boolean empty, String algori private void test_X509ExtendedKeyManager(X509ExtendedKeyManager km, boolean empty, String algorithm) throws Exception { - String[] keyTypes = keyTypes(algorithm); + String[] keyTypes = removeLegacyEcTypes(keyTypes(algorithm)); String[][] rotatedTypes = rotate(nonEmpty(keyTypes)); for (String[] keyList : rotatedTypes) { String alias = km.chooseEngineClientAlias(keyList, null, null); diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java index 86de4dfbd..114a7ef21 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java @@ -352,7 +352,7 @@ public void test_SSLSession_getProtocol() { assertEquals("NONE", s.invalid.getProtocol()); assertNotNull(s.server.getProtocol()); assertNotNull(s.client.getProtocol()); - assertEquals(s.server.getProtocol(), s.client.getProtocol()); + assertEquals("TLSv1.3", s.client.getProtocol()); assertTrue(StandardNames.SSL_SOCKET_PROTOCOLS.contains(s.server.getProtocol())); s.close(); } diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java index 1fec266de..6fae78e61 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java @@ -20,7 +20,6 @@ import static org.conscrypt.TestUtils.isLinux; import static org.conscrypt.TestUtils.isOsx; import static org.conscrypt.TestUtils.isTlsV1Deprecated; -import static org.conscrypt.TestUtils.isTlsV1Filtered; import static org.conscrypt.TestUtils.isTlsV1Supported; import static org.conscrypt.TestUtils.isWindows; import static org.conscrypt.TestUtils.osName; diff --git a/common/src/test/resources/crypto/mlkem.txt b/common/src/test/resources/crypto/mlkem.txt new file mode 100644 index 000000000..6f80d3772 --- /dev/null +++ b/common/src/test/resources/crypto/mlkem.txt @@ -0,0 +1,54 @@ +name = Test case A.1 for ML-KEM-768 from https://www.ietf.org/archive/id/draft-ietf-hpke-pq-01.html +key-algorithm = ML-KEM-768 +hpke-algorithm = MLKEM_768/HKDF_SHA256/AES_128_GCM +info = 34663634363532303666366532303631323034373732363536333639363136653230353537323665 +sk = 06f7d4f1495a828789f5543cb847369e10751ca5369a473c74e46043080f94f525f2f8cb7d8cfbf3cf8496728611a6567afd446a6ed1d22f6d32f74ef266a97e +pk = 33e49cea9c631f102595637291548f7220782f498bca5073b8039759f5582f45aa59245f41e5516da2a779b325e039651c348f57502602a3b7b8482b4b1576c93aa8b8c5155e5bc5dbc6a08d57103bc970b016ab7ab22320c430291ba1e8085564c4a13686aafefb510ce76e36876f2fdb1b7ba49550c15f5d366abe420e7ff2459cd7af36f5c285981adcf84020b04a66a0bc58172f8a34280fc32497a56308238a9f27ae95f0a593a70ae7594260e60930695f5f803eb2210fe3ac61c8bc20ec2566dafc399cea81a3834619420e5d85476cd573c7a08eaec4c60cf7999acc98724b934e259cda793dfda761cfa4289530b1f75b6a592730b8f5607eba701a150c38ac0e21038bfef496d2cb808f7342317789d1581b6f8565e3018d796013fed26b59fb226b5988633923a72a9acf42960c228f1e25b84f18ba4fd87620679d6c7a9e5f4340dadc3af252afd28231e3d52cb924209e50ba1ca632de811ae1097cd89803884a8c750663baec78c90254f038574fdcb74c527610e21469398a20abcce9f50e1537867aa036bf1452cbf70850e8a5f5464eccc8a2af260a45507e2da24c4868699065c95e3946ace90a655696f654892d8470a535cda5d58c44c8c0cb7bcc7104a847bc4cb1f03b8872143f5491c9e8c8ed46a6a6703c07d42ab8fabc4e25477245754d718ad8c88ce753c9756c2403948c06c1345c570fbd9a5932982f326a2f2ac69ebf5a58bc788fdb72a4d90865d3169d7752579cc49ad5e27479260dbba4800b521d21d22eb633168632b5209c4b81f32026a8111e07b148babf740297af87239bc760b976862c551622416a6fb23825112308867ba70a75ba33179c2373b97492235a27c1f266cbc18f71c0596e47a285bc2e09397a632309ff67739cb132d8c4007d2926fafc5eb56a6cf1960e87b4488c4895aacc6fcbbc4b6dd2b56cc69616653836550bbb9663b678370c3916fb832e17ba753d04787d78b0ddf11f6410bbe847cd6b1abdcee2835d50867a40137723433fdb6e94f902fda88b96d95af872000447c3649a93d3e0013dab5350dbb45c6190089c56cd4c1fce162daba74abfe8ae673105e0ebb94e52b37967b609741eb4608723e9858937b4996269033ca2cb503c4d3ccda0e15137cbc602728cc9bb5cca55b2cbc4579d02bdca430a052bbfc68bb5c8eb7c0139272c545d09f345b49a800314ca63c5097ef24cd793946410a931d5437becb557197affa37071954eee9c093a3486492362595761c83aac2ce46cdaebaa07ab3c3c1363c15bb8c4c869f54c6fdfa78924b954e0b480d3a128a2f64f7527beb2ec92ea6994dddc5eac10bde8a45609784e7948b6acc7c5da8526dd970870f46812bb0b53867668e17183fa6389261fa5da9a3ad8451094cd8ddb28683c1ce853c0e1cb3a4f79a168502242f662172bc21609492fa57a9f077a1889c42cb2aa3bc2583275ce6b171c3e3aa4279483b204cb3a1a98535a2fe7c1b3d322c52dfb9e9ec46f926562ef1c8992a2bc5fc047961898d1eb3d35cb1a018cb440d84de5047e50a4abd891c84f9431e36bb8134642e6fa144c15307a5122f07252b1e65b0e9ab3c2a184826c9a9cd938bdd588138a731c1b51787f166ca6205dadaaffc05c609f747b99fc3b918359e2ac28c8 +enc = 3d857eb5cb43c7ff163af7fb9ec804af496e4983f139bcf54a5b91a32399f63e57d79e8a80489c5355f4d2ce61613cc152da4a30ceef8ba9e36846f54a6dc3cd45f0191ba914e7da0e2b82aef47328972509157706f71a101685522602b9e19cb0574d2ba630a4b1d6cd140c8906d27c8d9573a59cab066acf2aad92a44d2a031b74d64dd1f1dead0612238a470aa089979a7b0e881cb7655b2182a760e4b4a595f82de5dfc4913bf0326bf66791ed7193e2c3b214d679ab1065823be1c90fce2539359338b253d3dd00585c9e4febf87afc2d0cba105e37771cc4b0f5606c09c03e10f3c41f5f06273e2e4d7e85a7a7c0fa7aab5b785af951034f0e4cd06da1b7b64becb1b2e670a6cea15d5e8cdfe97d401ad9c8411805eb4943cba446c88f243650ce185a77e16bf9c8c76b7ab77584462dd68d86cc752f8ca4b4314b6b8cee563b65d47409abfa3c9f71533cd4799d4a0b437525ea0d9f34a76b1aee5903f81079486d2cd759326dc3e385903f64c237847449638dc42b4b3af9d2c9c4d38624a7f766de56f82167caf480dfaadc8565b501711f2500d3cd3ced261f1c97a3268c2d501f76c4b84d5c81fca4158d439e0357e2aed6c706955e57aaeb633c1490225dcf7160b5d8acc4982a9c5a93fe856dddb86da2d4d68fea777b07f4a050a248876c8750f72c4b0e09a53f98c0b6ea0f62d455c25d200eff9d6c41e95ab4d5b402f63bf40720af8d8f5204c2b53c6916e345e38072c7716e6e54261894f55a6c0ded181e9edfdaac22282266e6fbbc476654e3502a1056949c28a1e0f06b5372f9826a5723e86bbfe2e74e72028ca2ab0172f5c77c403ac8a5afb650e19eb8580922eba51f8ec89272b15f207cbe443a25d9a3c3b2c3a547caebd0cb1d262dd5635ba96e0e83ffead58fed63cd3a44d993138367049b706168c649f5d655edffface55ec2ff190baaa437bfd373c2cadcb330ebd6e7ebc416d249a76d4bfd871a63d238b64aaf1b7cb8e9007075a9f48fde7338418d3231c3954ec3cb1922ebd531d54927630b1166c5a5d33886242016adc74730e7a95c315964eb9d10a7d5c3a2dc3dad3bef04f26cc2ce98decf709cfe2343dcfbb27ff588e0abb5725d0c749888aa3369331f389a4b82b427ca8245f4d5555d63067178841c8304cf57ed595acc2e0aaefc82bb9234da182498cf33a5936fd075e02f2df1f31d8bd4292e7fbb32bce4d1e7839958213c7bef1e2032a4ed049defca29166aff2efc90ce527f24352c3c87de024a561cc97000679fc9b86a5bde16c5b1613e4cf19845e71aa4a7ea9fa41137b075573fc22549b51ddf15eeb5f3fbd5365975e4342d35c976f0bc4c13d8208760b8bccffc5fbe3a979a05c5be681e31821180fe564f20aaa064dec3b9ba3ee158b62a42da3905338714778030d09ff5f3862da161b18ce32243e0bee3e56014883d3391a3f2b37fd382f5bda0efa28ebfd64c12b12c3a0341fd4c615e18f1d35c1c18f09835cfeb61e2d0ac29901cb2999507dcd55e5 +ct = 066fbfe3b5a6a046c2a91150ae385a5567b9b71dd9f1b806c0ab581ff2291fe9b1ac414293fb2423bc4c0dedb0dcbd43950d1f039e5f4f56e15fba325d76ee47b53e4e8ee68e23bb7a0b +aad = 436f756e742d30 +pt = 34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739 + +name = Test case for ML-KEM-768 generated with boringSSL. Empty inputs. +key-algorithm = ML-KEM-768 +hpke-algorithm = MLKEM_768/HKDF_SHA256/AES_128_GCM +info = +sk = 7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d8626ed79d451140800e03b59b956f8210e556067407d13dc90fa9e8b872bfb8f +pk = a8e651a1e685f22478a8954f007bc7711b930772c78f092e82878e3e937f367967532913a8d53dfdf4bfb1f8846746596705cf345142b972a3f16325c40c2952a37b25897e5ef35fbaeb73a4acbeb6a0b89942ceb195531cfc0a07993954483e6cbc87c06aa74ff0cac5207e535b260aa98d1198c07da605c4d11020f6c9f7bb68bb3456c73a01b710bc99d17739a51716aa01660c8b628b2f5602ba65f07ea993336e896e83f2c5731bbf03460c5b6c8afecb748ee391e98934a2c57d4d069f50d88b30d6966f38c37bc649b82634ce7722645ccd625063364646d6d699db57b45eb67465e16de4d406a818b9eae1ca916a2594489708a43cea88b02a4c03d09b44815c97101caf5048bbcb247ae2366cdc254ba22129f45b3b0eb399ca91a303402830ec01db7b2ca480cf350409b216094b7b0c3ae33ce10a9124e89651ab901ea253c8415bd7825f02bb229369af972028f22875ea55af16d3bc69f70c2ee8b75f28b47dd391f989ade314729c331fa04c1917b278c3eb602868512821adc825c64577ce1e63b1d9644a612948a3483c7f1b9a258000e30196944a403627609c76c7ea6b5de01764d24379117b9ea29848dc555c454bceae1ba5cc72c74ab96b9c91b910d26b88b25639d4778ae26c7c6151a19c6cd7938454372465e4c5ec29245acb3db5379de3dabfa629a7c04a8353a8530c95acb732bb4bb81932bb2ca7a848cd366801444abe23c83b366a87d6a3cf360924c002bae90af65c48060b3752f2badf1ab2722072554a5059753594e6a702761fc97684c8c4a7540a6b07fbc9de87c974aa8809d928c7f4cbbf8045aea5bc667825fd05a521f1a4bf539210c7113bc37b3e58b0cbfc53c841cbb0371de2e511b989cb7c70c023366d78f9c37ef047f8720be1c759a8d96b93f65a94114ffaf60d9a81795e995c71152a4691a5a602a9e1f3599e37c768c7bc108994c0669f3adc957d46b4b6256968e290d7892ea85464ee7a750f39c5e3152c2dfc56d8b0c924ba8a959a68096547f66423c838982a5794b9e1533771331a9a656c28828beb9126a60e95e8c5d906832c7710705576b1fb9507269ddaf8c95ce9719b2ca8dd112be10bcc9f4a37bd1b1eeeb33ecda76ae9f69a5d4b2923a86957671d619335be1c4c2c77ce87c41f98a8cc466460fa300aaf5b301f0a1d09c88e65da4d8ee64f68c02189bbb3584baff716c85db654048a004333489393a07427cd3e217e6a345f6c2c2b13c27b337271c0b27b2dbaa00d237600b5b594e8cf2dd625ea76cf0ed899122c9796b4b0187004258049a477cd11d68c49b9a0e7b00bce8cac7864cbb375140084744c93062694ca795c4f40e7acc9c5a1884072d8c38dafb501ee4184dd5a819ec24ec1651261f962b17a7215aa4a748c15836c389137678204838d7195a85b4f98a1b574c4cd7909cd1f833effd1485543229d3748d9b5cd6c17b9b3b84aef8bce13e683733659c79542d615782a71cdeee792bab51bdc4bbfe8308e663144ede8491830ad98b4634f64aba8b9c042272653920f380c1a17ca87ced7aac41c82888793181a6f76e197b7b90ef90943bb3844912911d8551e5466c5767ab0bc61a1a3f736162ec098a900b12dd8fabbfb3fe8cb1dc4e8315f2af0d32f0017ae136e19f028 +enc = 9c9b4648259be6f50344250f21ccce64a2a997f4ba061e780e781012d68cda7afdc22b5f1f670800333fbc434c62e0182801747228f3bfbe46eb5aab821154aa912acd2a8e76f832e538e0de647057e0ad164c59f1a016716bfe5dd8a2c96fac418d7bbe902c6bbd6970da7e05c7a2d5c1d388f504587f229cd0ff11d7090b4003e08424048ccad7fd712cb48bffe6bf97e0459df7444836ab37432774a14670f6eb184f9d34c7390b7884370bb65088290217ac540243690fa7d91f1fae35c809ccbbdab475d4907b46f774a6f9a4fe39d5d0cc12288085ef81ded2f44dde78099a5e7758141b0209b08369231ba4181a62537e237a6e67982426d979a8e5c9577265e5337b4cd42a5af59a6d10226ddb7548e0a1998c328c82e23f2ccf526aacd533502042a0d4938d7af73d054db927862b23eadeaa587535b6bb967184062c6767dad2cc2bf12bf9539b750cbe02e42cbf375449b1f73d69ae3c39e7a0b92e80b2ee97c27f0bd5fbd92541a3368c847788ed212ebd5e56b67c4b6ac126d32c3be7d1d2b195b5569260961f472195cebbbb6b531d46b89ad45af7c898daa7165f42cf13db3898567b9ff06f5902105a04f7b92ca0199b73e20b08b321772c85fcc6adc7b604ca8899728de7c0ba581b5d0c6516ca34204872dcb01bff277702ec6b4b0937be03beefc35602445313e57dd867f9a37fd03b16e30acaec7da3d36ea542b2f7c6db0fd5fa2cf292c6c85971fa21ebd635c19ddc95c7c91299cd83f1dfdc1dded29ddbcb3b9742427423502f4904753033aff7483731e93ccb6c1358dc13b42defb10dc59662ff7200fe02ca8fe6f53471b05b4c1389f44ce04e20d9a9cfe8c25acc7178f2977c0195bb2f360a7c74e29122bae32f6b6f6de84dc0263befc4bdd95a4483592388329ee652f6bec22287b5f9f5a100daccda2337bf6ae6c75a91f9a2ba2e67e931b4dddc0ad30d9a33d0bfe09f24c29e83802e540c5c29fd040912a1c806c1aa0bc699eb55e56156f9e617d62f1e4af9ffe5e09d3d16942205f909a49d4d07b27da58ecef5ab5e10e7ed510a6117e7a5c74591a4a6effa10c1a0178e2e9c8fb952f46a967f1261752f9af873db9388e7fdfbaebb5427ead0a88edbc1f01bf3163a503e124a5f1f7638f4ca6053f18201ddc1fb435599917531593d2f42a4c3bf64d414d1d50ac4415dcf100ffbe1216917b7a12529ec50126d2292161bb7c7feb3b2a36a24b07b187f473c9df6c29db4a42da49602a6b67d6e5ee5af1a9f845ebfd064f6ddd651b74abbe166c0ed54429f7c4c8459acb25a4ea802049247db81735a2ba6f01e15b35b43fe29e25d8a0cf2772c461528609dd399228fb020063a895589cb1319d426ea3f925930f1782255156221b7258261830e48c3e0081075ca0efa05c7ef7c192f1185573460540a08e52c886745ace8d57e9fec941c7830acd7e447d799597ec6acac25977aeb9e8ec0fbf12475dad23d20d5b31a4461f8af8cba0d96410b5dc8352ecbf26f78f626d359fd +ct = 0f0007caa9ba3401e631f949b5fc6d3e +aad = +pt = + +name = Test case for ML-KEM-768 generated with boringSSL. Empty aad. +key-algorithm = ML-KEM-768 +hpke-algorithm = MLKEM_768/HKDF_SHA256/AES_128_GCM +info = b254a656608933b934b3f81e8f810214c8135eda92a0614c2b926c4a3075b9f939e6a3c61309f53e +sk = 7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d8626ed79d451140800e03b59b956f8210e556067407d13dc90fa9e8b872bfb8f +pk = a8e651a1e685f22478a8954f007bc7711b930772c78f092e82878e3e937f367967532913a8d53dfdf4bfb1f8846746596705cf345142b972a3f16325c40c2952a37b25897e5ef35fbaeb73a4acbeb6a0b89942ceb195531cfc0a07993954483e6cbc87c06aa74ff0cac5207e535b260aa98d1198c07da605c4d11020f6c9f7bb68bb3456c73a01b710bc99d17739a51716aa01660c8b628b2f5602ba65f07ea993336e896e83f2c5731bbf03460c5b6c8afecb748ee391e98934a2c57d4d069f50d88b30d6966f38c37bc649b82634ce7722645ccd625063364646d6d699db57b45eb67465e16de4d406a818b9eae1ca916a2594489708a43cea88b02a4c03d09b44815c97101caf5048bbcb247ae2366cdc254ba22129f45b3b0eb399ca91a303402830ec01db7b2ca480cf350409b216094b7b0c3ae33ce10a9124e89651ab901ea253c8415bd7825f02bb229369af972028f22875ea55af16d3bc69f70c2ee8b75f28b47dd391f989ade314729c331fa04c1917b278c3eb602868512821adc825c64577ce1e63b1d9644a612948a3483c7f1b9a258000e30196944a403627609c76c7ea6b5de01764d24379117b9ea29848dc555c454bceae1ba5cc72c74ab96b9c91b910d26b88b25639d4778ae26c7c6151a19c6cd7938454372465e4c5ec29245acb3db5379de3dabfa629a7c04a8353a8530c95acb732bb4bb81932bb2ca7a848cd366801444abe23c83b366a87d6a3cf360924c002bae90af65c48060b3752f2badf1ab2722072554a5059753594e6a702761fc97684c8c4a7540a6b07fbc9de87c974aa8809d928c7f4cbbf8045aea5bc667825fd05a521f1a4bf539210c7113bc37b3e58b0cbfc53c841cbb0371de2e511b989cb7c70c023366d78f9c37ef047f8720be1c759a8d96b93f65a94114ffaf60d9a81795e995c71152a4691a5a602a9e1f3599e37c768c7bc108994c0669f3adc957d46b4b6256968e290d7892ea85464ee7a750f39c5e3152c2dfc56d8b0c924ba8a959a68096547f66423c838982a5794b9e1533771331a9a656c28828beb9126a60e95e8c5d906832c7710705576b1fb9507269ddaf8c95ce9719b2ca8dd112be10bcc9f4a37bd1b1eeeb33ecda76ae9f69a5d4b2923a86957671d619335be1c4c2c77ce87c41f98a8cc466460fa300aaf5b301f0a1d09c88e65da4d8ee64f68c02189bbb3584baff716c85db654048a004333489393a07427cd3e217e6a345f6c2c2b13c27b337271c0b27b2dbaa00d237600b5b594e8cf2dd625ea76cf0ed899122c9796b4b0187004258049a477cd11d68c49b9a0e7b00bce8cac7864cbb375140084744c93062694ca795c4f40e7acc9c5a1884072d8c38dafb501ee4184dd5a819ec24ec1651261f962b17a7215aa4a748c15836c389137678204838d7195a85b4f98a1b574c4cd7909cd1f833effd1485543229d3748d9b5cd6c17b9b3b84aef8bce13e683733659c79542d615782a71cdeee792bab51bdc4bbfe8308e663144ede8491830ad98b4634f64aba8b9c042272653920f380c1a17ca87ced7aac41c82888793181a6f76e197b7b90ef90943bb3844912911d8551e5466c5767ab0bc61a1a3f736162ec098a900b12dd8fabbfb3fe8cb1dc4e8315f2af0d32f0017ae136e19f028 +enc = bd48b97bc2c9ef55bcdf65e5c705aad0c190fb3e4271ca78b567a8d3d7070c6e73e4637cd3341ece8858335b3fec417a3671720717d15546eafeb2d3b72e04f87064e2819e90c046085e0704c6589f97d911bf18baf54ca0d07a4ccd954ee62226d760750d9a908142d19109e7dca776be514bb851eb33ce34a533244a5d12300df204b82484f08696588361a0e93f7295d617e7a8d453d78d940a251a440d74b4130801765d05edf5aa70dc8e7d5718bc9d9914e241c928cdc799d4485572ebfa8c8c25a5d055b2317a9aa53f760a995e370c11f8117b213d5b129579ea5d959433bf01feb3bf93f42e047daa1e4f108ae82c66887e65fa8f43995251d55d0ba82df473efaaac43d0a777339f2fa40deb812c7a6ceb1a15817e9408a0a80164fda5d80194cf6ffbbc9fcd21cceac5c091c718486afd22f046c4d62d9e93c3bccee6f3c7629d5f30e4c7509cc1ba70dcc1d12d4609aa1525c5a2ad75135a4fdc044ba72e77282a7ff89e976a0d7ee81def2df82eb96e7057ff9ee94a598376d16b65b918e845be331fea83f391b7eb504e6f1c98a17867dec5829eac0e63672f09b9ccf3b9b489e191bd5cfceb1da0bb4d18ae6e9a15d794f81f36c3148dd288c6a3ebd705a2f601acce37551dda6cc1f8a4a7ea11a2c86254ceca99486d6634251cbda030b9642f68a6f46dcbef3c94c65f772ecb8341703c30068a3c9fcd263a6149d5a6dbeff9b569f7f68d23733f498fbee12d402f980532f3d1ec83b9828ceafb54518447bc2393c973fff668ff54fa7958205e3be4eca40fd2523327568b5245355c828a9f6294ed3ef179b5eb16bc1e43f61ab069d10c89f59cf447d5a61c8498ff84f48a7cd76832c8d694e5b090c176caf81be027215cdfaadf5e05e2c80155ffc6eebb1589f33abaee00ba8bc7f4794561e9a54616bd505896517940ce8adb2d9e2d0d778175a19c987f5791b41af272e89e43c73436fbfb2ead8e8624acf81d81694f21a13cd1e13ea818f46f66ddce1e93e20f4c3b2fa820ebd224937053c4ed1404802634a0b1cd3763970ba6f66e13c6e1d833c9e80e4c041cd1c4947cc9cf0b70fc78b55b2244527bbedc6e618bf96261bec109320ee1a94e84dedd003d37deca5bc6ac6cd3bb8fa6a92fd62b331179f1557632f91cfb5cd327e643aa867dab7801e5e91317191b3ddcec231aa6c1c07c371b5b5a02340cba092605e38642aa190275d8324757d1330a0bfef15841e39c9430cb89ce596e0b715b26aa7c6c4b642f914a9da8ae77f045a112dfd3a0aa9616a817a09c5449b8831ad12bd17af81af7a5808e572b28c75591bcf69ab8077f8e067df75f4365fa9b70de24ed1422ea3229d85d0bfb503bc2bffd89c74055b76758cea078a05bd12c793a1f9c78f5e90f44d89d5bd14a211ae149c91da17b6ac46774cc3c73a807edbfdb3e33002daa2dc2758824435dfc6a0ddfe0bd1db083bf5f5d8e7b18abd1de1a8706f5c0c8a762d0a3d3f1ce02813345937034b973f94b6114651a58aacf +ct = be50e7d9e0aac571eac64b27b296bae1254505ec797a79b772fa60bc8f81bfe367af4ee02107c090f6a60bf8e9f547fd7937ad607ece7d7791817f55411c551f2de6f1a5b6662868d290dc884377a8a225 +aad = +pt = 86526d8f8d975a50785055b1f6120e6e76e1088730919310d486016a1c62b9a797c5f8842c16260f959c1620d43632975a6c3f309b6891398c8c5a4d31481180de + +name = Test case A.2 for ML-KEM-1024, but using HKDF_SHA256. Generated with boringSSL. +key-algorithm = ML-KEM-1024 +hpke-algorithm = MLKEM_1024/HKDF_SHA256/AES_256_GCM +info = 34663634363532303666366532303631323034373732363536333639363136653230353537323665 +sk = 870150f8c622ea6866db299c3348c737f0e8da17c1e7f721029b5e035db5942168522e0bea336dd93031199ab74b3acd684cbd03d6e56f304e5c28e7a9cba3bc +pk = daa944e375a5c36c1f3771be499c297aba61d8762694256ef840460d10bed594641d58446e3795792c51f272383e78aa517112197230c3604993182d046679a870495f1c44a6e2b30da7c251034f127561e02a234d0bbbb4ccc5a15224d0643108c44ad2973085371c80a064eaf524ebd90d5ad6057d45326a8b4ef532a7dfa93ff83783ece423106c8b1ed2627b59ca34d71a3b4014698c9969db604589270b492124a80eba50909c2847a8bc9881b0788de95ad718a9c71a33b7ba98ab8ac31ed744ffca6d6e08a170084c072bafaf7c25d5f56c519a6e3bdb30b0906192e2671da765ef53af8ac0197af2c9ac794a41038599766323718347f8821f3888ce250b84335eb8c43de4d9720f26b92531685af7576f62c6f39249c6645433387a8c617fdeb0650f77580de2af30019eb9959fb39ca2ed5364fc0a54e4809ac6a2424d284113e25179427b6a22936dc5839f3a9cb8bb49a5239ecd09278e432f6559bba0d56f9a9b87af068369c207b040022306a5fcc64d3264338a0789d7a833ad280b9ef2619c75769ed124a19c89a9b00a43c934b0042f24e28d2c353f2718ae9eb556bcd7c408f01e07bc7ad1d563b411133899312e5c6125f3cf39448ba64c8a904414c32a8fc233649a2a69b98ab658094908f89c7089765e8211e5c75d949c6e53c6445d11902b25b452da79db853b78c8a629f1c5ef762f34f2126a2092c85b618c1a1bb506cd60e0c8b39b3c9aec088494c966878db1c3772b0992de4cb3a4958d2c43878f681a53cc207f446c6f95addb7047116bb4d5124b5fc53220401a7b5531fbb49bafa345479c620bfa8529f2a73f830c975b820b465a97453483a746ec573289a8c02b53906c7423dac584495b6c771a60db498ae478a8f9a9833c81666a55a47a01adcd189b0e8c90aaa382a1d2301a873d9370c12ecc6104516e6b2957e3a7806c551d96aabe3b5cc47f918c467732dbe3a138305490c38b33760d4b164bd509595415a3b560082a9b688b386c20f05c0667631555015eba61e87174600c1fd0971b4e776e8452ad50748fd5a783ade73d80a97c8b07a374ca53ba95c89157b5e2bb333518adce58c5ad90734ee20cc4646382f4bb91d9431968895f541a25da9a892c7086cb361e4240ae996afa5445c0cba188d13e4a8863c6918a7e247aa893c055a052bbaa3bd1b2731207006e5403e8742c844605d4aa64963b0af4978fbb7a1e409ca5470395dc24a700a850b189039f87bd2c7858f28a9e2c624ecea50f1f7889f6a99cfcfacb0c5861586c3e97a41d855319d5930e0aba9eb1b1bf013b32f6cb0d1570243eda540538a288d13bad128907d07925153925e9253c117e82ab0c27c49fc68162e465b0e3fb7c1671c0965c71b5836862301a85606122896e8d9a6d848c1818580ddc7955f3446f148338ca14149a205a3c6bb43e30abcf20a69f36a7a5e60efdf69b5922cd6a7744e420311f3b9cb2148ab6ea7e11b522c81ab0209a43f8da598f580fc8536b2838bb8ca0c266905270609304ab78d6c40fa8a7015862b80ebc34a9ea25959a3c3fc4abd56a79616215f2caaacb51033e02ba4b446c0fd47e515ca9ad44c7e3a2739b9b69242a6cc528880491c72c7858ac13804db088a97541ed11208b369bfab3c8e4890ff1c73f2d783004c482458980eeb21f40c73d5d5282a78279d6f71bad650843c83d0323a7e90748b555853a50c091c31a9655b2388226e4524cdcbabb61543eb7caa88396933e1a5986986e41da209f131a9bbc9a44d66ba077229c499386a4418b990e20707c323846c861bcf227b2ff6ca3ee6511b37c190618c1e610be4672507ff7c7f5486930670d99bb6c2c28afaca54b815144aed10f82c27ad8922d5a9c86b15c9da866ad0ff96a2f569b42d52cdec8793d0414cfc35ddcebca8066b25e930b36743db6b05a6ec6641b9403f88a5320842522f388683c72eb60bfe1db314a0b78a0a0cc76a692137c9613bb47c3f54911e67c1ba48a46081fcaf0974783adddc88492943d8b5934a41b46f95120fce5a21e0223dee7b140a924ce711e0e93c434616b58b02c2e30352599380ca04127f40a4134c5862c3bab08a3f265a4679c989cfc1f24c9c553cabb37ab2767f5ab2d7a20e57b0b65fb6e4a6119ba33a736a9568de2ee95c312aad4c14639282831a3461d6e08800b592c8aa9d7ef6028 +enc = a9c43b27ed53d789ac6f74a96a8843ad20379d89a792d887ee6ebb09b271fb22177d0a8c261c6f0ec7b7b1cd22c61a189a82aca4aaab9d789510b69356ef3bd8607742931e7067d9d4f5bdc1d932fc51f15242f223d1f17b036d8acc7014864c002b0f9a3c4c5dc8d5baa8f9752f122d528f2873dd5180ff5c95797952aa89ba072dfc6afe3c866b11409837d2d89d2097e3435682a0444cd5d4ea76a5e1b64986ce51e5f0a40e0bfb1b869a35ac7b4992f2c1aae2af1e4d96b257053cae9f57279ce9c7e472aab074b2aa74ff50cdc455bf9e2b737795f16bbdeb3b46ad61020a79b4f64c4eb727e25eccd9be9cb1a3b593352b6a6314f37834856218dcad1a84af88711d96acea2c2ee7ff3aa9c4fd2b37456fc83c039c1b2918d286382ca9a38a967a27db7eea947ca1c9137e9d53f0a5e2ead709355f4afac7b7ad0bc4b17427dcae0b45e4f8991347f1a715b2910e637a671d9ffd5453ca0eaf79324528cee54c4d0972eb0f21cca55d496a096a53c416a8e44d2647d814355be8b88b4a6a464451410a5ad3f7df1a7af6bf49c1e4523664853e443b446e5c527c779e9423f24f33c6a479a8b3bf679505340db41041c7826b83e678880513f3714fdb351343064ac33746a251c15b9291394dc91ad20ad106979d31aa1a0ae9e176e0a85f4f987d9950b9220a74f83453475c28133f8ebaebf5d6c93ff8a30fefcae937c879be2e1df73f95aa75243ebd7966ada80d2be6828db5328d0aba396dcb6595f60798c177bf6b4e72023c4e84fc8eb4788af12d9f40fb79519263fb7ee99511aecffec5916e55da81bff24a3fa6e8e5f9e43d130383e18402af68eb20fa099d0c3ba69bb5d7211720c49cbb8fb50ab29fcabae1af183c0a045a6c39944d901b80fccb23c18465a10b7a04d9a4c6094ea9eace3a6880b0a73d2d4629f2de056f677174adac30e9e972b2fd442729d777056032dd89227a152c4eae7e95059dcd364de5bee0263abfb9ed2bc91e1f8b595e4a6944c00f1d82fef62b629148486853fc5ae2c9a5b4a29bbc7b56c8eb214391aac7dde7992e70a957441b6efef25b62228dccc32b700b9b82d63cc4bf61c2cee22fb9a30832765428a3f90b092fae9c9fc930a2c7c8aeed444c9c1ffad2ca0c8ef9f7905a7d33a9582158ef860951f8243006b390027d437096bd8617ca49c11a62a82ff7a2b7d9b69fbe0b1269ced4a764ed33e0c9525301c1f98a46dc30cc8f74104e50dc09f1c47beb0880ebc4b7c70786d58f217a794ff0e8f628596b5cc431f0b514464521507aa6f564b59d485f5cdeacdbca1ac96859fc5a3e8d3875ef88c36f2733dfafbd02de8c3e90d300cb62bdbc835f3a25f56f1a0a51a45c06b99aca7abc4b49a9eb90731139df1fdb4e29bfebd72371534f31a56c3c9c30b4921a87e58daed16f61df36f2b8f870118ba47d8d275600e5a50a3bff58bd7c1779bf4ea20fb5e8697eddc9af0d53a703a6f2bf7ce62a7393b97087af108d4c6f4ee938188fc9401a65c1d96a9acfccbb3c766dd192d2f646c67798fe97a39a1a0756ad18ecfefd52ae72d1416b10e28dbeb17004750798593c322fd4ad4136cd4de7fdad1105e06f5c3616dd9e35a57c7f54b2c91fdee4345dc73538cbe9a5f58ccf93574fe3a2984d6da76707c6ab7641b422f7cb60bf2bdaaa119aa9accdb8f60fd78ef6bd60f83d823d994644ad5cf719999b9ca8af6eba716fd70650a34ebd58420c4d218cad6f03ae108aa45a77a04afd95f1e54c356f74794a44bb9a463b11743acc2cd5a11ec5c48cfc15d0942981cf20ac14ef825528c932bb6f1e043da43f0151e481dcdbe74816457bf5168fd3b71a8bbf202b6324fd0e564e8fced1d7d9b2ab1167791736d4689b3767ab1f9d09536a20a774374c142deacaf6d4f4b2e7a2b50ea49c0fbb85207e6fe381e9594ecb27262577ea1d1a9a992ba8d57ae6c37e0783059b255377fe6e1b5d4e29ee63e1de1ecc5d55aa7f8729abe867a8de54360cdecf56885b7ddd282658a5ef84d464d119697fc9fb3a37772e01c11a2d89a5c434eec978ce6512627c008642ab71d4218e0385d5905f7b786a3371c429afa17293abf08a1cbe4424d27689aac3c5ac1ceba9ab6b8187eae97d78978191d2c1928250e18d95629531538e3b2e397d07cfb34a6a489e42b5c1d6897bfa121b9fe920ab8c262c26165b8a56 +ct = fa71be185be45b4734cc6df293a7ce0c66c1d8383dbfb87c8af6306e602b78c4e661fdd15a1747092fe72946e7e764d97e06cb8a6f4d74aa5228f808affa8c15f6e3e348627c7ecfd3a5 +aad = 436f756e742d30 +pt = 34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739 + +name = Test case for ML-KEM-1024 generated with boringSSL. Empty aad. +key-algorithm = ML-KEM-1024 +hpke-algorithm = MLKEM_1024/HKDF_SHA256/AES_256_GCM +info = b9e8c67f3a2d1e0c4b59f7d6a5c3b2e18d9f0a786e5c4d3b2a1f0987e6d5c4b3 +sk = 7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d8626ed79d451140800e03b59b956f8210e556067407d13dc90fa9e8b872bfb8f +pk = 537911957c125148a87f41589cb222d0d19229e2cb55e1a044791e7ca61192a46460c3183d2bcd6de08a5e7651603acc349ca16cba18abb23a3e8c330d7421598a6278ec7ebfabca0ef488b2290554753499c0452e453815309955b8150fa1a1e393386dc12fdb27b38c6745f2944016ec457f39b18d604a07a1abe07bc844050ffa8a06fa154a49d88fac775452d6a7c0e589bfb5c370c2c4b6201dda80c9ab2076ecc08b44522fda3326f033806dd2693f319739f40c4f42b24aca7098fb8ff5f9ac20292d02b56ac746801acccc84863dee32878497b69438bf991776286650482c8d9d9587bc6a55b85c4d7fa74d02656b421c9e23e03a48d4b74425c26e4a20dd9562a4da0793f3a352ccc0f18217d868c7f5002abe768b1fc73f05744e7cc28f10344062c10e08eccced3c1f7d392c01d979dd718d8398374665a16a9870585c39d5589a50e133389c9b9a276c024260d9fc7711c81b6337b57da3c376d0cd74e14c73727b276656b9d8a4eb71896ff589d4b893e7110f3bb948ece291dd86c0b7468a678c746980c12aa6b95e2b0cbe4331bb24a33a270153aa472c47312382ca365c5f35259d025746fc6595fe636c767510a69c1e8a176b7949958f2697399497a2fc7364a12c8198295239c826cb5082086077282ed628651fc04c639b438522a9de309b14b086d6e923c551623bd72a733cb0dabc54a9416a99e72c9fda1cb3fb9ba06b8adb2422d68cadc553c98202a17656478ac044ef3456378abce9991e0141ba79094fa8f77a300805d2d32ffc62bf0ca4554c330c2bb7042db35102f68b1a0062583865381c74dd913af70b26cf0923d0c4cb971692222552a8f4b788b4afd1341a9df415cf203900f5ccf7f65988949a75580d049639853100854b21f4018003502bb1ba95f556a5d67c7eb52410eba288a6d0635ca8a4f6d696d0a020c826938d34943c3808c79cc007768533216bc1b29da6c812eff3340baa8d2e65344f09bd47894f5a3a4118715b3c5020679327f9189f7e10856b238bb9b0ab4ca85abf4b21f5c76bccd71850b22e045928276a0f2e951db0707c6a116dc19113fa762dc5f20bd5d2ab5be71744dc9cbdb51ea757963aac56a90a0d8023bed1f5cae8a64da047279b353a096a835b0b2b023b6aa048989233079aeb467e522fa27a5822921e5c551b4f537536e46f3a6a97e72c3b063104e09a040598940d872f6d871f5ef9b4355073b54769e45454e6a0819599408621ab4413b35507b0df578ce2d511d52058d5749df38b29d6cc58870caf92f69a75161406e71c5ff92451a77522b8b2967a2d58a49a81661aa65ac09b08c9fe45abc3851f99c730c45003aca2bf0f8424a19b7408a537d541c16f5682bfe3a7faea564f1298611a7f5f60922ba19de73b1917f1853273555199a649318b50773345c997460856972acb43fc81ab6321b1c33c2bb5098bd489d696a0f70679c1213873d08bdad42844927216047205633212310ee9a06cb10016c805503c341a36d87e56072eabe23731e34af7e2328f85cdb370ccaf00515b64c9c54bc837578447aacfaed5969aa351e7da4efa7b115c4c51f4a699779850295ca72d781ad41bc680532b89e710e2189eb3c50817ba255c7474c95ca9110cc43b8ba8e682c7fb7b0fdc265c0483a65ca4514ee4b832aac5800c3b08e74f563951c1fbb210353efa1aa866856bc1e034733b0485dab1d020c6bf765ff60b3b801984a90c2fe970bf1de97004a6cf44b4984ab58258b4af71221cd17530a700c32959c9436344b5316f09ccca7029a230d639dcb022d8ba79ba91cd6ab12ae1579c50c7bb10e30301a65cae3101d40c7ba927bb553148d1647024d4a06c8166d0b0b81269b7d5f4b34fb022f69152f514004a7c685368552343bb60360fbb9945edf446d345bdcaa7455c74ba0a551e184620fef97688773d50b6433ca7a7ac5cb6b7f671a15376e5a6747a623fa7bc6630373f5b1b512690a661377870a60a7a189683f9b0cf0466e1f750762631c4ab09f505c42dd28633569472735442851e321616d4009810777b6bd46fa7224461a5cc27405dfbac0d39b002cab33433f2a86eb8ce91c134a6386f860a1994eb4b6875a46d195581d173854b53d2293df3e9a822756cd8f212b325ca29b4f9f8cfbadf2e41869abfbad10738ad04cc752bc20c394746850e0c4847db +enc = 35ab4e29f97d2828aeb7f746f1eaf17fd41a20542cfc6df50009f6558dfb5ca5325e1aeed36a8d51bf4eed0244b655b8a5b1434325424669d01f6133b4ac96b1e9e46dd34fede463f92255bd55c76269858fbbebfc05d69b2873a16f53da8f16d82070ff41a8094b457e0d6695f3b01aa10764fe164a5b88ab822b7d996056ebb29a979cecb9a06965181337f60d7690fefb6fc5e37617c6beee692ac9777bfa0cb792c6ccc2fff66dc986e2df0e94ec88ec3d06a4fdbee5d56a53800b6c286e683b1cb603184754414ee5e459a86d3800b435c6d593c045487cd18c33b1131011d4e390c8417a6b2480a077ddff5b25c48efe4b79d0b0c97ee8546946b52e59a95ea55058a6f265b39c62402831bd9cff4736b7602e799d9501e1e134ae33f63d820a144dad11ed2ef598ae648b425fb95600a7a4a7007bd639a110b3583d65c5e224f699681971eeeccdbf49f2ef22e72c4b14a23034ea16c4e0ca61fc1a0a1d25081110a5dcebd5d421d90b64ba00ac0015bc1b3ec8d59402b351a7099fc6e6f1f1484bb614d3ad7c02af2dbac41615d0c14de681f65a229228782f9f692a2d9fd4ed04bceaa429dcc0bc4610c275487d74fa8dd08d1ac0f2242bb0387ac980088f6187834717cfc856d32a99d7e68ff318d5c562202ef766274a76c1e6ed5c42ae7fa9140907102a11ca4a1ecf35e133b41788e7362a8a3eb6bff94523ff1315b076d5e1a81a593cac13235ebd8d95942026bdda9e0d7cd8ba186344a7d8c407fe05522ff0026d47fd759dc09f843cf424ffcce3a18423f0c0b17a816dbaeb950cfcfdc9bc150b51bdf019022c95cd03940f3b6611e76487962f64e18c1026cdaa74d24f390942ec77fc9cfb45260153a68fa22ed7d283306539b66aa0dc03abeeb79ac99d9bc84a7c822b57c41b7eeadd0037380724b1780f24b265e09988fa40e6f59d6dfb3903e4c55b6f0e0c204fe6b7cfc0d172f58614cf1f76ada5b4e1fa27606c182970fd032a8d81917fe5a11efad9dd41cfc805b3211c2c2eb59f65fe0c7af68a393a12c78de044a9c2678afff346f7fc6c69427bbe1f9068fef9478c788912ac87340297aa1d685e06a7c86ac141127cc1f6dc7c20da5b7f61289b4e881390e4ec28d5ef64da41c2701cc74a24e9212f17388102224cefe260ece85faceb3d1f5e67a2af99fbd10eb951d038cf455901f8996dc1bc091f41d8543d4440684872742ea50ebb21cf4ac21d5148e1588f9173943010ab0d00cd2a04c72c68a897768cf6ef195c3f7650462a7b3bc5b9edeba690dcc3ba8e818cb5e5c2b8a7c57905f07c711e587e33bedba755cff2f2c41177a0ea984d377aea7b0148c8def3c515924351b30eadf795610d386e10e37001594fa66be43a1d39a83a4737b31d04fb4310fd62fa56fbaa4a2c6dbb7029be979887bb2635f2863bb92aa6f145b44170fe476c8969c7de535deaf7f302673144c90c24dbc6df6bb81b72e24a091aa120f75cbb659caaae465b66ea64db727cead53d65f167a3cab1e97e4c7f3e9332500ae9d1745a725289f1327c8cfd6d9380d45eb7bf03e37825fa8e438b462e8db54351528f9550b763a003a46335f79f3ada75f916b028a364a16c527d2ea9a21fd587e8a34339d66156d6a0055a15a51d1206a824d2ae94e95382e888150ada59d606ac4b17f855563c737f50ec449a7d7c1a7cf2801a81578a5d0c6afbecf25cde3b333bc1d25af54c67a5b212933388140810a1dad0895add3452c0ad0e4cd83b055c5ded7c8f463a28154948acc99e6695dc523a8255c1ffb29b86d3330ad153776bb340dad43c5ea59a69009d3dff057e019cddcd0ac055ba22ba04cb241f3b082c05695a5b07602194d714e84bad39e37017eacd7ca7026c307c67a4f63d08a0b0f5155a6b9a79564a10cd23cecf28659a04a7732aebfb9edaa486bcdf6fbd1a115477f221027a21f9e3a1db0ce995fd1b92759cfa3778d202a43cbc767a2cd4bf7cf7461e74de3e2aeaeb1a30884563c3f44928085427f6205b43d2a93cb7927bd0eca44783d1c65cad1d968e434daf6b851ab2b8d9f1516afb9ea65dac8715a1e83eee0be7c31a82d33d581a910baae404c06e954cf3281d19c3665756cf262f6d028832a6bf16cfc860f7a7538a19e1558e2fdaa56bd26ecba4c7342f28250f498cbc1075b181c762dd3afbdaa42f3267f736faecdc +ct = 81f49b1a4d2eaff96b635a505f1ae8b5210ca44fba8f1c0f2de96a38b35e64305d58c0be8f3cff0aa51f769139b6308b9ec4da6844709cda17d47cfa7deef634b8fa3215f2c38b1d3057cd3c +aad = +pt = c8a4153f9b7e2d06c5478f1a3e6d9c0b25748f3e9a6c1b4d8e7f5a2b0d9e8c1f7a4b6d3e9f2a7c5b8e1d4f0a3c7b8e2d5f1a4c9b8d2e6f3a7c1b5d9e diff --git a/openjdk/src/main/java/org/conscrypt/Platform.java b/openjdk/src/main/java/org/conscrypt/Platform.java index 58a88d848..5bccb6988 100644 --- a/openjdk/src/main/java/org/conscrypt/Platform.java +++ b/openjdk/src/main/java/org/conscrypt/Platform.java @@ -42,7 +42,6 @@ import org.conscrypt.metrics.NoopStatsLog; import org.conscrypt.metrics.Source; import org.conscrypt.metrics.StatsLog; -import org.conscrypt.metrics.StatsLogImpl; import java.io.File; import java.io.FileDescriptor; @@ -776,16 +775,28 @@ static boolean serverNamePermitted(SSLParametersImpl parameters, String serverNa return true; } - private static boolean isAndroid() { - boolean android; + /** {@return the Android SDK int if this is an Android platform, or 0 if it is not} */ + private static int androidSdkInt() { + boolean isAndroid = false; try { Class.forName("android.app.Application", false, getSystemClassLoader()); - android = true; + isAndroid = true; } catch (Throwable ignored) { // Failed to load the class uniquely available in Android. - android = false; + isAndroid = false; } - return android; + if (!isAndroid) { + return 0; + } + int sdkInt = 1; + try { + Class versionClass = + Class.forName("android.os.Build$VERSION", false, getSystemClassLoader()); + sdkInt = versionClass.getField("SDK_INT").getInt(null); + } catch (ReflectiveOperationException | UnsatisfiedLinkError ignored) { + // Ignored + } + return sdkInt; } static int javaVersion() { @@ -795,8 +806,9 @@ static int javaVersion() { private static int javaVersion0() { final int majorVersion; - if (isAndroid()) { - majorVersion = 6; + int androidSdkInt = androidSdkInt(); + if (androidSdkInt != 0) { + majorVersion = androidSdkInt >= 24 ? 8 : 7; } else { majorVersion = majorVersionFromJavaSpecificationVersion(); } diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java b/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java index 22c292d8c..16a1bbe47 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java @@ -87,6 +87,7 @@ MlDsaTest.class, NativeCryptoArgTest.class, NativeCryptoTest.class, + NativeSslTest.class, NativeRefTest.class, NativeSslSessionTest.class, OpenSSLKeyTest.class, diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java index 350f4b33d..b4e8708b3 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java @@ -103,6 +103,7 @@ HpkeTestVectorsTest.class, KeySpecUtilTest.class, MlDsaTest.class, + MlKemTest.class, NativeCryptoArgTest.class, NativeCryptoTest.class, NativeSslTest.class, diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java index d7ca7b3ac..ed1e039fc 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java @@ -570,18 +570,6 @@ public long beforeHandshake(long c) throws SSLException { assertTrue(serverCallback.serverCertificateRequestedInvoked); } - /** Convenient debug print for ECH Config Lists */ - private void printEchConfigList(String msg, byte[] buf) { - int blen = buf.length; - System.out.print(msg + " (" + blen + "):\n "); - for (int i = 0; i < blen; i++) { - if ((i != 0) && (i % 16 == 0)) - System.out.print("\n "); - System.out.print(String.format("%02x:", Byte.toUnsignedInt(buf[i]))); - } - System.out.print("\n"); - } - @Test public void test_SSL_do_handshake_ech_client_server() throws Exception { final ServerSocket listener = newServerSocket(); @@ -1203,10 +1191,12 @@ public long serverSessionRequested(byte[] id) { } private boolean serverCertificateRequestedInvoked; + private int[] serverSignatureAlgs; @Override - public void serverCertificateRequested() { + public void serverCertificateRequested(int[] signatureAlgs) { serverCertificateRequestedInvoked = true; + this.serverSignatureAlgs = signatureAlgs; } @Override @@ -1439,6 +1429,8 @@ public void test_SSL_do_handshake_normal() throws Exception { assertTrue(serverCallback.handshakeCompletedCalled); assertFalse(clientCallback.serverCertificateRequestedInvoked); assertTrue(serverCallback.serverCertificateRequestedInvoked); + assertNotNull(serverCallback.serverSignatureAlgs); + // g3-add: assertTrue(serverCallback.serverSignatureAlgs.length > 0); } @Test @@ -3954,6 +3946,129 @@ public void test_ecdsaSignVerify_works() throws Exception { () -> NativeCrypto.ECDSA_verify(data, invalidDataLen, signature, publicKey)); } + @Test + public void mlKem768PublicKeyFromSeed_returnsPublicKeyIfPrivateKeyIsValid() throws Exception { + // test vector from https://www.ietf.org/archive/id/draft-ietf-hpke-pq-01.html, Section A.1 + byte[] privateKey = + decodeHex("06f7d4f1495a828789f5543cb847369e10751ca5369a473c74e46043080f94f5" + + "25f2f8cb7d8cfbf3cf8496728611a6567afd446a6ed1d22f6d32f74ef266a97e"); + byte[] expectedPublicKey = + decodeHex("33e49cea9c631f102595637291548f7220782f498bca5073b8039759f5582f45" + + "aa59245f41e5516da2a779b325e039651c348f57502602a3b7b8482b4b1576c9" + + "3aa8b8c5155e5bc5dbc6a08d57103bc970b016ab7ab22320c430291ba1e80855" + + "64c4a13686aafefb510ce76e36876f2fdb1b7ba49550c15f5d366abe420e7ff2" + + "459cd7af36f5c285981adcf84020b04a66a0bc58172f8a34280fc32497a56308" + + "238a9f27ae95f0a593a70ae7594260e60930695f5f803eb2210fe3ac61c8bc20" + + "ec2566dafc399cea81a3834619420e5d85476cd573c7a08eaec4c60cf7999acc" + + "98724b934e259cda793dfda761cfa4289530b1f75b6a592730b8f5607eba701a" + + "150c38ac0e21038bfef496d2cb808f7342317789d1581b6f8565e3018d796013" + + "fed26b59fb226b5988633923a72a9acf42960c228f1e25b84f18ba4fd8762067" + + "9d6c7a9e5f4340dadc3af252afd28231e3d52cb924209e50ba1ca632de811ae1" + + "097cd89803884a8c750663baec78c90254f038574fdcb74c527610e21469398a" + + "20abcce9f50e1537867aa036bf1452cbf70850e8a5f5464eccc8a2af260a4550" + + "7e2da24c4868699065c95e3946ace90a655696f654892d8470a535cda5d58c44" + + "c8c0cb7bcc7104a847bc4cb1f03b8872143f5491c9e8c8ed46a6a6703c07d42a" + + "b8fabc4e25477245754d718ad8c88ce753c9756c2403948c06c1345c570fbd9a" + + "5932982f326a2f2ac69ebf5a58bc788fdb72a4d90865d3169d7752579cc49ad5" + + "e27479260dbba4800b521d21d22eb633168632b5209c4b81f32026a8111e07b1" + + "48babf740297af87239bc760b976862c551622416a6fb23825112308867ba70a" + + "75ba33179c2373b97492235a27c1f266cbc18f71c0596e47a285bc2e09397a63" + + "2309ff67739cb132d8c4007d2926fafc5eb56a6cf1960e87b4488c4895aacc6f" + + "cbbc4b6dd2b56cc69616653836550bbb9663b678370c3916fb832e17ba753d04" + + "787d78b0ddf11f6410bbe847cd6b1abdcee2835d50867a40137723433fdb6e94" + + "f902fda88b96d95af872000447c3649a93d3e0013dab5350dbb45c6190089c56" + + "cd4c1fce162daba74abfe8ae673105e0ebb94e52b37967b609741eb4608723e9" + + "858937b4996269033ca2cb503c4d3ccda0e15137cbc602728cc9bb5cca55b2cb" + + "c4579d02bdca430a052bbfc68bb5c8eb7c0139272c545d09f345b49a800314ca" + + "63c5097ef24cd793946410a931d5437becb557197affa37071954eee9c093a34" + + "86492362595761c83aac2ce46cdaebaa07ab3c3c1363c15bb8c4c869f54c6fdf" + + "a78924b954e0b480d3a128a2f64f7527beb2ec92ea6994dddc5eac10bde8a456" + + "09784e7948b6acc7c5da8526dd970870f46812bb0b53867668e17183fa638926" + + "1fa5da9a3ad8451094cd8ddb28683c1ce853c0e1cb3a4f79a168502242f66217" + + "2bc21609492fa57a9f077a1889c42cb2aa3bc2583275ce6b171c3e3aa4279483" + + "b204cb3a1a98535a2fe7c1b3d322c52dfb9e9ec46f926562ef1c8992a2bc5fc0" + + "47961898d1eb3d35cb1a018cb440d84de5047e50a4abd891c84f9431e36bb813" + + "4642e6fa144c15307a5122f07252b1e65b0e9ab3c2a184826c9a9cd938bdd588" + + "138a731c1b51787f166ca6205dadaaffc05c609f747b99fc3b918359e2ac28c8"); + + byte[] publicKey = NativeCrypto.MLKEM768_public_key_from_seed(privateKey); + assertArrayEquals(expectedPublicKey, publicKey); + + byte[] privateKeyTooShort = Arrays.copyOf(privateKey, privateKey.length - 1); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLKEM768_public_key_from_seed(privateKeyTooShort)); + byte[] privateKeyTooLong = Arrays.copyOf(privateKey, privateKey.length + 1); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLKEM768_public_key_from_seed(privateKeyTooLong)); + } + + @Test + public void mlKem1024PublicKeyFromSeed_returnsPublicKeyIfPrivateKeyIsValid() throws Exception { + // test vector from https://www.ietf.org/archive/id/draft-ietf-hpke-pq-01.html, Section A.2 + byte[] privateKey = + decodeHex("870150f8c622ea6866db299c3348c737f0e8da17c1e7f721029b5e035db59421" + + "68522e0bea336dd93031199ab74b3acd684cbd03d6e56f304e5c28e7a9cba3bc"); + byte[] expectedPublicKey = + decodeHex("daa944e375a5c36c1f3771be499c297aba61d8762694256ef840460d10bed594" + + "641d58446e3795792c51f272383e78aa517112197230c3604993182d046679a8" + + "70495f1c44a6e2b30da7c251034f127561e02a234d0bbbb4ccc5a15224d06431" + + "08c44ad2973085371c80a064eaf524ebd90d5ad6057d45326a8b4ef532a7dfa9" + + "3ff83783ece423106c8b1ed2627b59ca34d71a3b4014698c9969db604589270b" + + "492124a80eba50909c2847a8bc9881b0788de95ad718a9c71a33b7ba98ab8ac3" + + "1ed744ffca6d6e08a170084c072bafaf7c25d5f56c519a6e3bdb30b0906192e2" + + "671da765ef53af8ac0197af2c9ac794a41038599766323718347f8821f3888ce" + + "250b84335eb8c43de4d9720f26b92531685af7576f62c6f39249c6645433387a" + + "8c617fdeb0650f77580de2af30019eb9959fb39ca2ed5364fc0a54e4809ac6a2" + + "424d284113e25179427b6a22936dc5839f3a9cb8bb49a5239ecd09278e432f65" + + "59bba0d56f9a9b87af068369c207b040022306a5fcc64d3264338a0789d7a833" + + "ad280b9ef2619c75769ed124a19c89a9b00a43c934b0042f24e28d2c353f2718" + + "ae9eb556bcd7c408f01e07bc7ad1d563b411133899312e5c6125f3cf39448ba6" + + "4c8a904414c32a8fc233649a2a69b98ab658094908f89c7089765e8211e5c75d" + + "949c6e53c6445d11902b25b452da79db853b78c8a629f1c5ef762f34f2126a20" + + "92c85b618c1a1bb506cd60e0c8b39b3c9aec088494c966878db1c3772b0992de" + + "4cb3a4958d2c43878f681a53cc207f446c6f95addb7047116bb4d5124b5fc532" + + "20401a7b5531fbb49bafa345479c620bfa8529f2a73f830c975b820b465a9745" + + "3483a746ec573289a8c02b53906c7423dac584495b6c771a60db498ae478a8f9" + + "a9833c81666a55a47a01adcd189b0e8c90aaa382a1d2301a873d9370c12ecc61" + + "04516e6b2957e3a7806c551d96aabe3b5cc47f918c467732dbe3a138305490c3" + + "8b33760d4b164bd509595415a3b560082a9b688b386c20f05c0667631555015e" + + "ba61e87174600c1fd0971b4e776e8452ad50748fd5a783ade73d80a97c8b07a3" + + "74ca53ba95c89157b5e2bb333518adce58c5ad90734ee20cc4646382f4bb91d9" + + "431968895f541a25da9a892c7086cb361e4240ae996afa5445c0cba188d13e4a" + + "8863c6918a7e247aa893c055a052bbaa3bd1b2731207006e5403e8742c844605" + + "d4aa64963b0af4978fbb7a1e409ca5470395dc24a700a850b189039f87bd2c78" + + "58f28a9e2c624ecea50f1f7889f6a99cfcfacb0c5861586c3e97a41d855319d5" + + "930e0aba9eb1b1bf013b32f6cb0d1570243eda540538a288d13bad128907d079" + + "25153925e9253c117e82ab0c27c49fc68162e465b0e3fb7c1671c0965c71b583" + + "6862301a85606122896e8d9a6d848c1818580ddc7955f3446f148338ca14149a" + + "205a3c6bb43e30abcf20a69f36a7a5e60efdf69b5922cd6a7744e420311f3b9c" + + "b2148ab6ea7e11b522c81ab0209a43f8da598f580fc8536b2838bb8ca0c26690" + + "5270609304ab78d6c40fa8a7015862b80ebc34a9ea25959a3c3fc4abd56a7961" + + "6215f2caaacb51033e02ba4b446c0fd47e515ca9ad44c7e3a2739b9b69242a6c" + + "c528880491c72c7858ac13804db088a97541ed11208b369bfab3c8e4890ff1c7" + + "3f2d783004c482458980eeb21f40c73d5d5282a78279d6f71bad650843c83d03" + + "23a7e90748b555853a50c091c31a9655b2388226e4524cdcbabb61543eb7caa8" + + "8396933e1a5986986e41da209f131a9bbc9a44d66ba077229c499386a4418b99" + + "0e20707c323846c861bcf227b2ff6ca3ee6511b37c190618c1e610be4672507f" + + "f7c7f5486930670d99bb6c2c28afaca54b815144aed10f82c27ad8922d5a9c86" + + "b15c9da866ad0ff96a2f569b42d52cdec8793d0414cfc35ddcebca8066b25e93" + + "0b36743db6b05a6ec6641b9403f88a5320842522f388683c72eb60bfe1db314a" + + "0b78a0a0cc76a692137c9613bb47c3f54911e67c1ba48a46081fcaf0974783ad" + + "ddc88492943d8b5934a41b46f95120fce5a21e0223dee7b140a924ce711e0e93" + + "c434616b58b02c2e30352599380ca04127f40a4134c5862c3bab08a3f265a467" + + "9c989cfc1f24c9c553cabb37ab2767f5ab2d7a20e57b0b65fb6e4a6119ba33a7" + + "36a9568de2ee95c312aad4c14639282831a3461d6e08800b592c8aa9d7ef6028"); + byte[] publicKey = NativeCrypto.MLKEM1024_public_key_from_seed(privateKey); + assertArrayEquals(expectedPublicKey, publicKey); + + byte[] privateKeyTooShort = Arrays.copyOf(privateKey, privateKey.length - 1); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLKEM1024_public_key_from_seed(privateKeyTooShort)); + byte[] privateKeyTooLong = Arrays.copyOf(privateKey, privateKey.length + 1); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLKEM1024_public_key_from_seed(privateKeyTooLong)); + } + @Test public void xwingPublicKeyFromSeed_returnsPublicKeyIfPrivateKeyIsValid() throws Exception { // test vector from @@ -3983,6 +4098,8 @@ public void xwingPublicKeyFromSeed_returnsPublicKeyIfPrivateKeyIsValid() throws private static final int DHKEM_X25519_HKDF_SHA256 = 0x0020; private static final int DHKEM_X448_HKDF_SHA256 = 0x0021; private static final int XWING = 0x647a; + private static final int MLKEM768 = 0x0041; + private static final int MLKEM1024 = 0x0042; // KDF IDs private static final int HKDF_SHA256 = 0x0001; private static final int HKDF_SHA384 = 0x0002; @@ -4032,7 +4149,7 @@ public void hpkeWithXwing_publicKeyFromSeedSealOpen_success() throws Exception { byte[] aad = decodeHex("cc"); Object[] result = NativeCrypto.EVP_HPKE_CTX_setup_base_mode_sender( - XWING, HKDF_SHA256, HKDF_SHA256, publicKey, info); + XWING, HKDF_SHA256, AES_128_GCM, publicKey, info); NativeRef.EVP_HPKE_CTX ctxSender = (NativeRef.EVP_HPKE_CTX) result[0]; byte[] encapsulated = (byte[]) result[1]; assertEquals(1120, encapsulated.length); @@ -4041,8 +4158,61 @@ public void hpkeWithXwing_publicKeyFromSeedSealOpen_success() throws Exception { NativeRef.EVP_HPKE_CTX ctxRecipient = (NativeRef.EVP_HPKE_CTX) NativeCrypto.EVP_HPKE_CTX_setup_base_mode_recipient( - /* kem= */ 0x647a, /*kdf=*/0x0001, /* aead= */ 0x0001, privateKey, - encapsulated, info); + XWING, HKDF_SHA256, AES_128_GCM, privateKey, encapsulated, info); + byte[] output = NativeCrypto.EVP_HPKE_CTX_open(ctxRecipient, ciphertext, aad); + + assertArrayEquals(plaintext, output); + } + + @Test + public void hpkeWithMLKEM768_publicKeyFromSeedSealOpen_success() throws Exception { + byte[] privateKey = decodeHex( + "06f7d4f1495a828789f5543cb847369e10751ca5369a473c74e46043080f94f525f2f8cb7d8cfbf3" + + "cf8496728611a6567afd446a6ed1d22f6d32f74ef266a97e"); + byte[] publicKey = NativeCrypto.MLKEM768_public_key_from_seed(privateKey); + + byte[] info = decodeHex("aa"); + byte[] plaintext = decodeHex("bb"); + byte[] aad = decodeHex("cc"); + + Object[] result = NativeCrypto.EVP_HPKE_CTX_setup_base_mode_sender( + MLKEM768, HKDF_SHA256, AES_128_GCM, publicKey, info); + NativeRef.EVP_HPKE_CTX ctxSender = (NativeRef.EVP_HPKE_CTX) result[0]; + byte[] encapsulated = (byte[]) result[1]; + assertEquals(1088, encapsulated.length); + + byte[] ciphertext = NativeCrypto.EVP_HPKE_CTX_seal(ctxSender, plaintext, aad); + + NativeRef.EVP_HPKE_CTX ctxRecipient = + (NativeRef.EVP_HPKE_CTX) NativeCrypto.EVP_HPKE_CTX_setup_base_mode_recipient( + MLKEM768, HKDF_SHA256, AES_128_GCM, privateKey, encapsulated, info); + byte[] output = NativeCrypto.EVP_HPKE_CTX_open(ctxRecipient, ciphertext, aad); + + assertArrayEquals(plaintext, output); + } + + @Test + public void hpkeWithMLKEM1024_publicKeyFromSeedSealOpen_success() throws Exception { + byte[] privateKey = + decodeHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"); + byte[] publicKey = NativeCrypto.MLKEM1024_public_key_from_seed(privateKey); + + byte[] info = decodeHex("aa"); + byte[] plaintext = decodeHex("bb"); + byte[] aad = decodeHex("cc"); + + Object[] result = NativeCrypto.EVP_HPKE_CTX_setup_base_mode_sender( + MLKEM1024, HKDF_SHA256, AES_128_GCM, publicKey, info); + NativeRef.EVP_HPKE_CTX ctxSender = (NativeRef.EVP_HPKE_CTX) result[0]; + byte[] encapsulated = (byte[]) result[1]; + assertEquals(1568, encapsulated.length); + + byte[] ciphertext = NativeCrypto.EVP_HPKE_CTX_seal(ctxSender, plaintext, aad); + + NativeRef.EVP_HPKE_CTX ctxRecipient = + (NativeRef.EVP_HPKE_CTX) NativeCrypto.EVP_HPKE_CTX_setup_base_mode_recipient( + MLKEM1024, HKDF_SHA256, AES_128_GCM, privateKey, encapsulated, info); byte[] output = NativeCrypto.EVP_HPKE_CTX_open(ctxRecipient, ciphertext, aad); assertArrayEquals(plaintext, output); diff --git a/testing/src/main/java/org/conscrypt/java/security/TestKeyStore.java b/testing/src/main/java/org/conscrypt/java/security/TestKeyStore.java index f3acc3ff0..3f0df8d16 100644 --- a/testing/src/main/java/org/conscrypt/java/security/TestKeyStore.java +++ b/testing/src/main/java/org/conscrypt/java/security/TestKeyStore.java @@ -98,7 +98,7 @@ public final class TestKeyStore { private static final int EC_KEY_SIZE_BITS = 256; /** Size of RSA keys to generate for testing. */ - private static final int RSA_KEY_SIZE_BITS = 1024; + private static final int RSA_KEY_SIZE_BITS = 2048; // Generated with: openssl dhparam -C 1024 private static final BigInteger DH_PARAMS_P = new BigInteger(