From c8f9310d8376c36dceaf7f7ef34a0bff79007146 Mon Sep 17 00:00:00 2001 From: Miguel Aranda Date: Wed, 23 Sep 2026 11:04:49 +0000 Subject: [PATCH] Project import generated by Copybara. PiperOrigin-RevId: 986649101 --- .github/workflows/ci.yml | 13 +- CAPABILITIES.md | 6 +- .../main/java/com/android/libcore/Flags.java | 4 - android/build.gradle | 6 + .../src/main/java/org/conscrypt/Platform.java | 51 +- .../jni/main/cpp/conscrypt/native_crypto.cc | 384 +++++--- .../main/java/org/conscrypt/EchOptions.java | 10 + common/src/main/java/org/conscrypt/Hkdf.java | 6 +- .../main/java/org/conscrypt/IvParameters.java | 3 + .../java/org/conscrypt/KeyGeneratorImpl.java | 13 + .../main/java/org/conscrypt/NativeCrypto.java | 56 +- .../main/java/org/conscrypt/NativeSsl.java | 4 + .../conscrypt/OpenSSLAeadCipherChaCha20.java | 16 + .../java/org/conscrypt/OpenSSLEvpCipher.java | 18 +- .../org/conscrypt/OpenSSLEvpCipherAES.java | 41 +- .../org/conscrypt/OpenSSLEvpCipherARC4.java | 4 +- .../org/conscrypt/OpenSSLEvpCipherDESEDE.java | 17 +- .../java/org/conscrypt/OpenSSLProvider.java | 5 + .../org/conscrypt/OpenSSLX509Certificate.java | 12 +- .../conscrypt/OpenSslSignatureHashSlhDsa.java | 161 ++-- .../java/org/conscrypt/SSLParametersImpl.java | 2 +- .../java/org/conscrypt/TrustManagerImpl.java | 14 +- .../TlsEncryptedClientHelloHandshake.java | 99 +- .../org/conscrypt/ChaCha20Poly1305Test.java | 324 +++++++ .../test/java/org/conscrypt/SlhDsaTest.java | 6 +- .../org/conscrypt/TrustManagerImplTest.java | 57 ++ .../security/AlgorithmParametersTestAES.java | 4 +- .../AlgorithmParametersTestDESede.java | 4 +- .../security/AlgorithmParametersTestEC.java | 4 +- .../security/AlgorithmParametersTestGCM.java | 4 +- .../security/AlgorithmParametersTestOAEP.java | 4 +- .../java/security/KeyFactoryTestEC.java | 4 +- .../java/security/KeyFactoryTestRSA.java | 4 +- .../java/security/KeyPairGeneratorTest.java | 4 +- .../java/security/MessageDigestTest.java | 4 +- .../java/security/SignatureTest.java | 18 +- .../security/cert/CertificateFactoryTest.java | 4 +- .../java/security/cert/X509CRLTest.java | 4 +- .../security/cert/X509CertificateTest.java | 17 +- .../javax/crypto/AeadCipherTest.java | 5 +- .../conscrypt/javax/crypto/CipherTest.java | 21 +- .../javax/crypto/ECDHKeyAgreementTest.java | 4 +- .../javax/crypto/KeyGeneratorTest.java | 5 +- .../javax/crypto/XDHKeyAgreementTest.java | 4 +- .../SSLEngineVersionCompatibilityTest.java | 134 +-- .../ssl/SslEngineAndSocketResumptionTest.java | 4 +- .../resources/crypto/xchacha20-poly1305.txt | 419 +++++++++ .../java/org/conscrypt/EchConfigList.java | 83 ++ .../conscrypt/EchConfigMismatchException.java | 97 ++ .../conscrypt/InvalidEchDataException.java | 29 + .../src/main/java/org/conscrypt/Platform.java | 26 +- .../org/conscrypt/ConscryptAndroidSuite.java | 1 + .../ConscryptAndroidWithoutTlsSuite.java | 1 + .../org/conscrypt/ConscryptOpenJdkSuite.java | 1 + .../java/org/conscrypt/EchConfigListTest.java | 86 ++ .../EchConfigMismatchExceptionTest.java | 84 ++ .../java/org/conscrypt/NativeCryptoTest.java | 134 ++- .../org/conscrypt/OpenSSLX509CRLTest.java | 2 + .../conscrypt/OpenSSLX509CertificateTest.java | 2 +- .../test/java/org/conscrypt/PlatformTest.java | 60 ++ .../ConscryptNetworkSecurityPolicy.java | 3 +- .../src/main/java/org/conscrypt/Platform.java | 36 +- .../test/java/org/conscrypt/SpakeTest.java | 2 +- .../org/conscrypt/TlsDeprecationTest.java | 2 +- scripts/export_to_ag.py | 879 +++++++++++++++--- .../javax/net/ssl/TestSSLContext.java | 5 +- 66 files changed, 2971 insertions(+), 569 deletions(-) create mode 100644 common/src/test/java/org/conscrypt/ChaCha20Poly1305Test.java create mode 100644 common/src/test/resources/crypto/xchacha20-poly1305.txt create mode 100644 openjdk/src/main/java/org/conscrypt/EchConfigList.java create mode 100644 openjdk/src/main/java/org/conscrypt/EchConfigMismatchException.java create mode 100644 openjdk/src/main/java/org/conscrypt/InvalidEchDataException.java create mode 100644 openjdk/src/test/java/org/conscrypt/EchConfigListTest.java create mode 100644 openjdk/src/test/java/org/conscrypt/EchConfigMismatchExceptionTest.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51cdb8083..612bb96be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -249,13 +249,12 @@ jobs: mkdir -p "$ANDROID_HOME" unzip -q android-tools.zip -d "$ANDROID_HOME" yes | "$SDKMANAGER" --sdk_root="$ANDROID_HOME" --licenses || true - "$SDKMANAGER" --sdk_root="$ANDROID_HOME" tools - "$SDKMANAGER" --sdk_root="$ANDROID_HOME" platform-tools - "$SDKMANAGER" --sdk_root="$ANDROID_HOME" 'build-tools;30.0.3' - "$SDKMANAGER" --sdk_root="$ANDROID_HOME" 'platforms;android-26' - "$SDKMANAGER" --sdk_root="$ANDROID_HOME" 'extras;android;m2repository' - "$SDKMANAGER" --sdk_root="$ANDROID_HOME" 'ndk;27.3.13750724' - "$SDKMANAGER" --sdk_root="$ANDROID_HOME" 'cmake;3.22.1' + "$SDKMANAGER" --sdk_root="$ANDROID_HOME" \ + platform-tools \ + 'build-tools;30.0.3' \ + 'platforms;android-26' \ + 'ndk;27.3.13750724' \ + 'cmake;3.22.1' - name: Build with Gradle shell: bash diff --git a/CAPABILITIES.md b/CAPABILITIES.md index 471f38582..81968eac5 100644 --- a/CAPABILITIES.md +++ b/CAPABILITIES.md @@ -134,11 +134,13 @@ Key-restricted versions of the AES ciphers. The RC4 stream cipher. * `ChaCha20/NONE/NoPadding` -* `ChaCha20/Poly1305/NoPadding` +* `ChaCha20/Poly1305/NoPadding` (alias: `ChaCha20-Poly1305`) +* `XChaCha20/Poly1305/NoPadding` (alias: `XChaCha20-Poly1305`) ChaCha with 20 rounds, 96-bit nonce, and 32-bit counter as described in [RFC 7539](https://tools.ietf.org/html/rfc7539), either with or without a Poly1305 AEAD authenticator. +`XChaCha20/Poly1305/NoPadding` provides an extended 192-bit nonce for safer random generation (see [draft-irtf-cfrg-xchacha](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha)). * `DESEDE/CBC/NoPadding` * `DESEDE/CBC/PKCS5Padding` @@ -165,6 +167,7 @@ should use `RSA/ECB/OAEPPadding` and initialize it with an * `AES` * `ChaCha20` +* `XChaCha20` * `DESEDE` * `EC` * `GCM` @@ -204,6 +207,7 @@ Conscrypt's EC AlgorithmParameters implementation only supports named curves. * `AES` * `ARC4` * `ChaCha20` +* `XChaCha20` * `DESEDE` * `HmacMD5` * `HmacSHA1` diff --git a/android-stub/src/main/java/com/android/libcore/Flags.java b/android-stub/src/main/java/com/android/libcore/Flags.java index 24855eae1..4d0245e2a 100644 --- a/android-stub/src/main/java/com/android/libcore/Flags.java +++ b/android-stub/src/main/java/com/android/libcore/Flags.java @@ -20,10 +20,6 @@ public final class Flags { private Flags() {} - public static boolean networkSecurityPolicyReasonCtEnabledApi() { - throw new RuntimeException("Stub!"); - } - public static boolean networkSecurityPolicyEchApi() { throw new RuntimeException("Stub!"); } diff --git a/android/build.gradle b/android/build.gradle index e5cf34de3..e27b507c9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -90,6 +90,12 @@ android { "${rootDir}/common/src/test/java", "${rootDir}/openjdk/src/test/java", ] + excludes = [ + 'org/conscrypt/ConscryptOpenJdkSuite.java', + 'org/conscrypt/EchConfigListTest.java', + 'org/conscrypt/EchConfigMismatchExceptionTest.java', + 'org/conscrypt/PlatformTest.java', + ] } resources { srcDirs = [ diff --git a/android/src/main/java/org/conscrypt/Platform.java b/android/src/main/java/org/conscrypt/Platform.java index bcb8152d5..248f82576 100644 --- a/android/src/main/java/org/conscrypt/Platform.java +++ b/android/src/main/java/org/conscrypt/Platform.java @@ -85,7 +85,6 @@ final public class Platform { private static Method m_getCurveName; static { - NativeCrypto.setTlsV1DeprecationStatus(DEPRECATED_TLS_V1, ENABLED_TLS_V1); try { m_getCurveName = ECParameterSpec.class.getDeclaredMethod("getCurveName"); m_getCurveName.setAccessible(true); @@ -886,4 +885,54 @@ public static boolean isPakeSupported() { public static boolean isSdkGreater(int sdk) { return Build.VERSION.SDK_INT > sdk; } + + private static class X509NativeAllocationRegistryHolder { + private static final Object REGISTRY; + private static final Method REGISTER_METHOD; + + static { + Object registry = null; + Method registerMethod = null; + try { + Class narClass = Class.forName("libcore.util.NativeAllocationRegistry"); + Method createMethod = + narClass.getMethod("createMalloced", ClassLoader.class, long.class); + ClassLoader classLoader = OpenSSLX509Certificate.class.getClassLoader(); + if (classLoader == null) { + classLoader = ClassLoader.getSystemClassLoader(); + } + registry = createMethod.invoke(null, + classLoader, + NativeCrypto.get_X509_free_func()); + registerMethod = + narClass.getMethod("registerNativeAllocation", Object.class, long.class); + } catch (ReflectiveOperationException ignored) { + registry = null; + registerMethod = null; + } catch (Throwable t) { + registry = null; + registerMethod = null; + Log.w(TAG, "Could not initialize NativeAllocationRegistry", t); + } + REGISTRY = registry; + REGISTER_METHOD = registerMethod; + } + } + + public static boolean registerX509CertificateAllocation( + OpenSSLX509Certificate cert, long nativePtr) { + if (X509NativeAllocationRegistryHolder.REGISTRY != null + && X509NativeAllocationRegistryHolder.REGISTER_METHOD != null) { + try { + X509NativeAllocationRegistryHolder.REGISTER_METHOD.invoke( + X509NativeAllocationRegistryHolder.REGISTRY, cert, nativePtr); + return true; + } catch (ReflectiveOperationException ignored) { + // Do nothing. + } catch (Throwable t) { + Log.w(TAG, "Could not register native allocation", t); + } + } + return false; + } } diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index 8d80a5768..e1960940a 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -178,7 +178,6 @@ static bool arrayToBignum(JNIEnv* env, jbyteArray source, BIGNUM** dest) { ScopedByteArrayRO sourceBytes(env, source); if (sourceBytes.get() == nullptr) { JNI_TRACE("arrayToBignum(%p, %p) => null", source, dest); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate source bytes"); return false; } const unsigned char* tmp = reinterpret_cast(sourceBytes.get()); @@ -389,7 +388,6 @@ bssl::UniquePtr ByteArrayToCryptoBuffer(JNIEnv* env, const jbyteA ScopedByteArrayRO arrayRo(env, array); if (arrayRo.get() == nullptr) { JNI_TRACE("failed to get bytes"); - conscrypt::jniutil::throwOutOfMemory(env, "failed to get bytes from array"); return nullptr; } @@ -855,7 +853,6 @@ static jbyteArray NativeCrypto_wrap_EC_private_key_pkcs8(JNIEnv* env, jclass, ScopedByteArrayRO bytes(env, rawKeyBytes); if (bytes.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Failed to get rawKeyBytes"); return nullptr; } @@ -916,7 +913,6 @@ static jbyteArray NativeCrypto_wrap_RSA_private_key_pkcs8(JNIEnv* env, jclass, ScopedByteArrayRO bytes(env, rawKeyBytes); if (bytes.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Failed to get rawKeyBytes"); return nullptr; } @@ -978,7 +974,6 @@ static jbyteArray NativeCrypto_wrap_RSA_public_key_x509(JNIEnv* env, jclass, ScopedByteArrayRO bytes(env, rawKeyBytes); if (bytes.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Failed to get rawKeyBytes"); return nullptr; } @@ -1056,7 +1051,6 @@ static jbyteArray NativeCrypto_wrap_EC_public_key_x509(JNIEnv* env, jclass, jbyt ScopedByteArrayRO bytes(env, rawKeyBytes); if (bytes.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Failed to get rawKeyBytes"); return nullptr; } @@ -1124,7 +1118,6 @@ static jbyteArray NativeCrypto_unwrap_RSA_private_key_pkcs8(JNIEnv* env, jclass, ScopedByteArrayRO bytes(env, rawKeyBytes); if (bytes.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Failed to get rawKeyBytes"); return nullptr; } @@ -3327,9 +3320,13 @@ static jbyteArray NativeCrypto_MLDSA44_public_key_from_seed(JNIEnv* env, jclass, jbyteArray privateKeySeed) { CHECK_ERROR_QUEUE_ON_RETURN; + if (privateKeySeed == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "privateKeySeed == null"); + JNI_TRACE("NativeCrypto_MLDSA44_public_key_from_seed => privateKeySeed == null"); + return nullptr; + } ScopedByteArrayRO seedArray(env, privateKeySeed); if (seedArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_MLDSA44_public_key_from_seed => privateKeySeed == null"); return nullptr; } @@ -3375,10 +3372,13 @@ static jbyteArray NativeCrypto_MLDSA44_public_key_from_seed(JNIEnv* env, jclass, static jbyteArray NativeCrypto_MLDSA65_public_key_from_seed(JNIEnv* env, jclass, jbyteArray privateKeySeed) { CHECK_ERROR_QUEUE_ON_RETURN; - + if (privateKeySeed == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "privateKeySeed == null"); + JNI_TRACE("NativeCrypto_MLDSA65_public_key_from_seed => privateKeySeed == null"); + return nullptr; + } ScopedByteArrayRO seedArray(env, privateKeySeed); if (seedArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_MLDSA65_public_key_from_seed => privateKeySeed == null"); return nullptr; } @@ -3425,10 +3425,13 @@ static jbyteArray NativeCrypto_MLDSA65_public_key_from_seed(JNIEnv* env, jclass, static jbyteArray NativeCrypto_MLDSA87_public_key_from_seed(JNIEnv* env, jclass, jbyteArray privateKeySeed) { CHECK_ERROR_QUEUE_ON_RETURN; - + if (privateKeySeed == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "privateKeySeed == null"); + JNI_TRACE("NativeCrypto_MLDSA87_public_key_from_seed => privateKeySeed == null"); + return nullptr; + } ScopedByteArrayRO seedArray(env, privateKeySeed); if (seedArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_MLDSA87_public_key_from_seed => privateKeySeed == null"); return nullptr; } @@ -3841,9 +3844,14 @@ static jbyteArray NativeCrypto_XWING_public_key_from_seed(JNIEnv* env, jclass, jbyteArray privateKeySeed) { CHECK_ERROR_QUEUE_ON_RETURN; + if (privateKeySeed == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "privateKeySeed is null"); + JNI_TRACE("NativeCrypto_XWING_public_key_from_seed => privateKeySeed == null"); + return nullptr; + } + ScopedByteArrayRO seedArray(env, privateKeySeed); if (seedArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_XWING_public_key_from_seed => privateKeySeed == null"); return nullptr; } @@ -3885,9 +3893,13 @@ static jbyteArray NativeCrypto_MLKEM768_public_key_from_seed(JNIEnv* env, jclass jbyteArray privateKeySeed) { CHECK_ERROR_QUEUE_ON_RETURN; + if (privateKeySeed == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "privateKeySeed is null"); + JNI_TRACE("MLKEM768_public_key_from_seed => privateKeySeed == null"); + return nullptr; + } ScopedByteArrayRO seedArray(env, privateKeySeed); if (seedArray.get() == nullptr) { - JNI_TRACE("MLKEM768_public_key_from_seed => privateKeySeed == null"); return nullptr; } @@ -3936,9 +3948,13 @@ static jbyteArray NativeCrypto_MLKEM1024_public_key_from_seed(JNIEnv* env, jclas jbyteArray privateKeySeed) { CHECK_ERROR_QUEUE_ON_RETURN; + if (privateKeySeed == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "privateKeySeed is null"); + JNI_TRACE("MLKEM1024_public_key_from_seed => privateKeySeed == null"); + return nullptr; + } ScopedByteArrayRO seedArray(env, privateKeySeed); if (seedArray.get() == nullptr) { - JNI_TRACE("MLKEM1024_public_key_from_seed => privateKeySeed == null"); return nullptr; } @@ -4851,57 +4867,115 @@ static void NativeCrypto_EVP_PKEY_CTX_set1_signature_context_string(JNIEnv* env, contextJava); } -static jlong NativeCrypto_EVP_get_cipherbyname(JNIEnv* env, jclass, jstring algorithm) { +static jlong NativeCrypto_EVP_rc4(JNIEnv* env, jclass) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_get_cipherbyname(%p)", algorithm); + const EVP_CIPHER* cipher = EVP_rc4(); + JNI_TRACE("EVP_rc4 => cipher=%p", cipher); + return reinterpret_cast(cipher); +} - if (algorithm == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "algorithm == null"); - JNI_TRACE("EVP_get_cipherbyname(%p) => algorithm == null", algorithm); - return -1; - } +static jlong NativeCrypto_EVP_des_cbc(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* cipher = EVP_des_cbc(); + JNI_TRACE("EVP_des_cbc => cipher=%p", cipher); + return reinterpret_cast(cipher); +} - ScopedUtfChars scoped_alg(env, algorithm); - const char* alg = scoped_alg.c_str(); - const EVP_CIPHER* cipher; +static jlong NativeCrypto_EVP_des_ede_cbc(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* cipher = EVP_des_ede_cbc(); + JNI_TRACE("EVP_des_ede_cbc => cipher=%p", cipher); + return reinterpret_cast(cipher); +} - if (strcasecmp(alg, "rc4") == 0) { - cipher = EVP_rc4(); - } else if (strcasecmp(alg, "des-cbc") == 0) { - cipher = EVP_des_cbc(); - } else if (strcasecmp(alg, "des-ede-cbc") == 0) { - cipher = EVP_des_ede_cbc(); - } else if (strcasecmp(alg, "des-ede3-cbc") == 0) { - cipher = EVP_des_ede3_cbc(); - } else if (strcasecmp(alg, "aes-128-ecb") == 0) { - cipher = EVP_aes_128_ecb(); - } else if (strcasecmp(alg, "aes-128-cbc") == 0) { - cipher = EVP_aes_128_cbc(); - } else if (strcasecmp(alg, "aes-128-ctr") == 0) { - cipher = EVP_aes_128_ctr(); - } else if (strcasecmp(alg, "aes-128-gcm") == 0) { - cipher = EVP_aes_128_gcm(); - } else if (strcasecmp(alg, "aes-192-ecb") == 0) { - cipher = EVP_aes_192_ecb(); - } else if (strcasecmp(alg, "aes-192-cbc") == 0) { - cipher = EVP_aes_192_cbc(); - } else if (strcasecmp(alg, "aes-192-ctr") == 0) { - cipher = EVP_aes_192_ctr(); - } else if (strcasecmp(alg, "aes-192-gcm") == 0) { - cipher = EVP_aes_192_gcm(); - } else if (strcasecmp(alg, "aes-256-ecb") == 0) { - cipher = EVP_aes_256_ecb(); - } else if (strcasecmp(alg, "aes-256-cbc") == 0) { - cipher = EVP_aes_256_cbc(); - } else if (strcasecmp(alg, "aes-256-ctr") == 0) { - cipher = EVP_aes_256_ctr(); - } else if (strcasecmp(alg, "aes-256-gcm") == 0) { - cipher = EVP_aes_256_gcm(); - } else { - JNI_TRACE("NativeCrypto_EVP_get_cipherbyname(%s) => error", alg); - return 0; - } +static jlong NativeCrypto_EVP_des_ede3_cbc(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* cipher = EVP_des_ede3_cbc(); + JNI_TRACE("EVP_des_ede3_cbc => cipher=%p", cipher); + return reinterpret_cast(cipher); +} + +static jlong NativeCrypto_EVP_aes_128_ecb(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* cipher = EVP_aes_128_ecb(); + JNI_TRACE("EVP_aes_128_ecb => cipher=%p", cipher); + return reinterpret_cast(cipher); +} + +static jlong NativeCrypto_EVP_aes_128_cbc(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* cipher = EVP_aes_128_cbc(); + JNI_TRACE("EVP_aes_128_cbc => cipher=%p", cipher); + return reinterpret_cast(cipher); +} + +static jlong NativeCrypto_EVP_aes_128_ctr(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* cipher = EVP_aes_128_ctr(); + JNI_TRACE("EVP_aes_128_ctr => cipher=%p", cipher); + return reinterpret_cast(cipher); +} + +static jlong NativeCrypto_EVP_aes_128_gcm(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* cipher = EVP_aes_128_gcm(); + JNI_TRACE("EVP_aes_128_gcm => cipher=%p", cipher); + return reinterpret_cast(cipher); +} + +static jlong NativeCrypto_EVP_aes_192_ecb(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* cipher = EVP_aes_192_ecb(); + JNI_TRACE("EVP_aes_192_ecb => cipher=%p", cipher); + return reinterpret_cast(cipher); +} + +static jlong NativeCrypto_EVP_aes_192_cbc(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* cipher = EVP_aes_192_cbc(); + JNI_TRACE("EVP_aes_192_cbc => cipher=%p", cipher); + return reinterpret_cast(cipher); +} + +static jlong NativeCrypto_EVP_aes_192_ctr(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* cipher = EVP_aes_192_ctr(); + JNI_TRACE("EVP_aes_192_ctr => cipher=%p", cipher); + return reinterpret_cast(cipher); +} + +static jlong NativeCrypto_EVP_aes_192_gcm(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* cipher = EVP_aes_192_gcm(); + JNI_TRACE("EVP_aes_192_gcm => cipher=%p", cipher); + return reinterpret_cast(cipher); +} +static jlong NativeCrypto_EVP_aes_256_ecb(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* cipher = EVP_aes_256_ecb(); + JNI_TRACE("EVP_aes_256_ecb => cipher=%p", cipher); + return reinterpret_cast(cipher); +} + +static jlong NativeCrypto_EVP_aes_256_cbc(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* cipher = EVP_aes_256_cbc(); + JNI_TRACE("EVP_aes_256_cbc => cipher=%p", cipher); + return reinterpret_cast(cipher); +} + +static jlong NativeCrypto_EVP_aes_256_ctr(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* cipher = EVP_aes_256_ctr(); + JNI_TRACE("EVP_aes_256_ctr => cipher=%p", cipher); + return reinterpret_cast(cipher); +} + +static jlong NativeCrypto_EVP_aes_256_gcm(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* cipher = EVP_aes_256_gcm(); + JNI_TRACE("EVP_aes_256_gcm => cipher=%p", cipher); return reinterpret_cast(cipher); } @@ -5192,6 +5266,13 @@ static jlong NativeCrypto_EVP_aead_chacha20_poly1305(JNIEnv* env, jclass) { return reinterpret_cast(ctx); } +static jlong NativeCrypto_EVP_aead_xchacha20_poly1305(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_AEAD* ctx = EVP_aead_xchacha20_poly1305(); + JNI_TRACE("EVP_aead_xchacha20_poly1305 => ctx=%p", ctx); + return reinterpret_cast(ctx); +} + static jlong NativeCrypto_EVP_aead_aes_128_gcm_siv(JNIEnv* env, jclass) { CHECK_ERROR_QUEUE_ON_RETURN; const EVP_AEAD* ctx = EVP_aead_aes_128_gcm_siv(); @@ -5704,6 +5785,9 @@ static jobject NativeCrypto_EVP_HPKE_CTX_setup_base_mode_recipient( } ScopedByteArrayRO privateKey(env, privateKeyArray); + if (privateKey.get() == nullptr) { + return nullptr; + } bssl::ScopedEVP_HPKE_KEY key; @@ -5723,13 +5807,16 @@ static jobject NativeCrypto_EVP_HPKE_CTX_setup_base_mode_recipient( optionalInfo.emplace(env, infoArray); info = reinterpret_cast(optionalInfo->get()); if (info == nullptr) { - return {}; + return nullptr; } infoLen = optionalInfo->size(); } bssl::UniquePtr ctx(EVP_HPKE_CTX_new()); ScopedByteArrayRO enc(env, encArray); + if (enc.get() == nullptr) { + return nullptr; + } if (!EVP_HPKE_CTX_setup_recipient( /* ctx= */ ctx.get(), /* key= */ key.get(), @@ -5746,7 +5833,11 @@ static jobject NativeCrypto_EVP_HPKE_CTX_setup_base_mode_recipient( ScopedLocalRef ctxObject( env, env->NewObject(conscrypt::jniutil::nativeRefHpkeCtxClass, conscrypt::jniutil::nativeRefHpkeCtxClass_constructor, - reinterpret_cast(ctx.release()))); + reinterpret_cast(ctx.get()))); + if (ctxObject.get() == nullptr) { + return nullptr; + } + ctx.release(); return ctxObject.release(); } @@ -5789,6 +5880,9 @@ static jobjectArray NativeCrypto_EVP_HPKE_CTX_setup_base_mode_sender(JNIEnv* env } ScopedByteArrayRO peer_public_key(env, publicKeyArray); + if (peer_public_key.get() == nullptr) { + return {}; + } size_t encapsulatedSharedSecretLen; uint8_t encapsulatedSharedSecret[EVP_HPKE_MAX_ENC_LENGTH]; @@ -5826,12 +5920,17 @@ static jobjectArray NativeCrypto_EVP_HPKE_CTX_setup_base_mode_sender(JNIEnv* env ScopedLocalRef result( env, env->NewObjectArray(2, conscrypt::jniutil::objectClass, nullptr)); - + if (result.get() == nullptr) { + return {}; + } ScopedLocalRef ctxObject( env, env->NewObject(conscrypt::jniutil::nativeRefHpkeCtxClass, conscrypt::jniutil::nativeRefHpkeCtxClass_constructor, - reinterpret_cast(ctx.release()))); - + reinterpret_cast(ctx.get()))); + if (ctxObject.get() == nullptr) { + return {}; + } + ctx.release(); env->SetObjectArrayElement(result.get(), 0, ctxObject.release()); env->SetObjectArrayElement(result.get(), 1, encArray.release()); @@ -5859,7 +5958,7 @@ static jobjectArray NativeCrypto_EVP_HPKE_CTX_setup_base_mode_sender_with_seed_f } if (publicKeyArray == nullptr || seedArray == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "publicKeyArray or seedArray == null"); - return {}; + return nullptr; } std::optional optionalInfo; @@ -5869,14 +5968,20 @@ static jobjectArray NativeCrypto_EVP_HPKE_CTX_setup_base_mode_sender_with_seed_f optionalInfo.emplace(env, infoArray); info = reinterpret_cast(optionalInfo->get()); if (info == nullptr) { - return {}; + return nullptr; } infoLen = optionalInfo->size(); } ScopedByteArrayRO peer_public_key(env, publicKeyArray); + if (peer_public_key.get() == nullptr) { + return nullptr; + } ScopedByteArrayRO seed(env, seedArray); + if (seed.get() == nullptr) { + return nullptr; + } size_t encapsulatedSharedSecretLen; uint8_t encapsulatedSharedSecret[EVP_HPKE_MAX_ENC_LENGTH]; @@ -5900,28 +6005,35 @@ static jobjectArray NativeCrypto_EVP_HPKE_CTX_setup_base_mode_sender_with_seed_f /* seed_len= */ seed.size())) { conscrypt::jniutil::throwExceptionFromBoringSSLError( env, "EVP_HPKE_CTX_setup_sender_with_seed_for_testing"); - return {}; + return nullptr; } ScopedLocalRef encArray( env, env->NewByteArray(static_cast(encapsulatedSharedSecretLen))); if (encArray.get() == nullptr) { - return {}; + return nullptr; } ScopedByteArrayRW encBytes(env, encArray.get()); if (encBytes.get() == nullptr) { - return {}; + return nullptr; } memcpy(encBytes.get(), reinterpret_cast(encapsulatedSharedSecret), encapsulatedSharedSecretLen); ScopedLocalRef result( env, env->NewObjectArray(2, conscrypt::jniutil::objectClass, nullptr)); + if (result.get() == nullptr) { + return nullptr; + } ScopedLocalRef ctxObject( env, env->NewObject(conscrypt::jniutil::nativeRefHpkeCtxClass, conscrypt::jniutil::nativeRefHpkeCtxClass_constructor, - reinterpret_cast(ctx.release()))); + reinterpret_cast(ctx.get()))); + if (ctxObject.get() == nullptr) { + return nullptr; + } + ctx.release(); env->SetObjectArrayElement(result.get(), 0, ctxObject.release()); env->SetObjectArrayElement(result.get(), 1, encArray.release()); @@ -5965,7 +6077,6 @@ static void NativeCrypto_CMAC_Init(JNIEnv* env, jclass, jobject cmacCtxRef, jbyt } ScopedByteArrayRO keyBytes(env, keyArray); if (keyBytes.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Failed to allocate memory for keyBytes"); return; } @@ -6034,7 +6145,6 @@ static void NativeCrypto_CMAC_Update(JNIEnv* env, jclass, jobject cmacCtxRef, jb } ScopedByteArrayRO inBytes(env, inArray); if (inBytes.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Failed to allocate memory for inBytes"); return; } @@ -6260,6 +6370,10 @@ static void NativeCrypto_RAND_bytes(JNIEnv* env, jclass, jbyteArray output) { CHECK_ERROR_QUEUE_ON_RETURN; JNI_TRACE("NativeCrypto_RAND_bytes(%p)", output); + if (output == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "output == null"); + return; + } ScopedByteArrayRW outputBytes(env, output); if (outputBytes.get() == nullptr) { return; @@ -6445,9 +6559,11 @@ static jobject GENERAL_NAME_to_jobject(JNIEnv* env, GENERAL_NAME* gen) { case GEN_RID: return ASN1_OBJECT_to_OID_string(env, gen->d.registeredID); case GEN_OTHERNAME: + return ASN1ToByteArray(env, gen, i2d_GENERAL_NAME); case GEN_X400: + case GEN_EDIPARTY: default: - return ASN1ToByteArray(env, gen, i2d_GENERAL_NAME); + return nullptr; } return nullptr; @@ -6480,12 +6596,18 @@ static jobjectArray NativeCrypto_get_X509_GENERAL_NAME_stack(JNIEnv* env, jclass JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => unknown type", x509, type); return nullptr; } - // TODO(https://github.com/google/conscrypt/issues/916): Handle errors, remove - // |ERR_clear_error|, and throw CertificateParsingException. if (gn_stack == nullptr) { - JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => null (no extension or error)", x509, - type); - ERR_clear_error(); + // X509_get_ext_d2i returns nullptr both when the extension is not present (no error in + // queue) and when the extension DER encoding is malformed (error pushed to queue). Throw on + // parse failure. + if (ERR_peek_error() != 0) { + JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => error parsing extension", x509, type); + conscrypt::jniutil::throwException(env, + "java/security/cert/CertificateParsingException", + "Error parsing Alternative Name extension"); + return nullptr; + } + JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => null (no extension)", x509, type); return nullptr; } @@ -6495,15 +6617,10 @@ static jobjectArray NativeCrypto_get_X509_GENERAL_NAME_stack(JNIEnv* env, jclass return nullptr; } - /* - * Keep track of how many originally so we can ignore any invalid - * values later. - */ - const int origCount = count; - ScopedLocalRef joa( env, env->NewObjectArray(count, conscrypt::jniutil::objectArrayClass, nullptr)); - for (int i = 0, j = 0; i < origCount; i++, j++) { + int result_index = 0; + for (int i = 0; i < count; i++) { 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()) { @@ -6514,13 +6631,7 @@ static jobjectArray NativeCrypto_get_X509_GENERAL_NAME_stack(JNIEnv* env, jclass return nullptr; } - /* - * If it's nullptr, we'll have to skip this, reduce the number of total - * entries, and fix up the array later. - */ if (val.get() == nullptr) { - j--; - count--; continue; } @@ -6534,32 +6645,27 @@ static jobjectArray NativeCrypto_get_X509_GENERAL_NAME_stack(JNIEnv* env, jclass env->SetObjectArrayElement(item.get(), 0, parsedType.get()); env->SetObjectArrayElement(item.get(), 1, val.get()); - env->SetObjectArrayElement(joa.get(), j, item.get()); + env->SetObjectArrayElement(joa.get(), result_index++, item.get()); } - if (count == 0) { - 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, - count); - - ScopedLocalRef joa_copy( - env, env->NewObjectArray(count, conscrypt::jniutil::objectArrayClass, nullptr)); - - for (int i = 0; i < count; i++) { - ScopedLocalRef item(env, env->GetObjectArrayElement(joa.get(), i)); - env->SetObjectArrayElement(joa_copy.get(), i, item.get()); - } + if (result_index == 0) { + return nullptr; + } - joa.reset(joa_copy.release()); + if (result_index == count) { + JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => %d entries", x509, type, count); + return joa.release(); } - JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => %d entries", x509, type, count); - return joa.release(); + ScopedLocalRef result( + env, env->NewObjectArray(result_index, conscrypt::jniutil::objectArrayClass, nullptr)); + for (int i = 0; i < result_index; i++) { + ScopedLocalRef item(env, env->GetObjectArrayElement(joa.get(), i)); + env->SetObjectArrayElement(result.get(), i, item.get()); + } + + JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => %d entries", x509, type, result_index); + return result.release(); } static jlong NativeCrypto_X509_get_notBefore(JNIEnv* env, jclass, jlong x509Ref, @@ -7455,7 +7561,6 @@ static jlong NativeCrypto_asn1_read_init(JNIEnv* env, jclass, jbyteArray data) { ScopedByteArrayRO bytes(env, data); if (bytes.get() == nullptr) { - conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); return 0; } @@ -7523,12 +7628,10 @@ static jbyteArray NativeCrypto_asn1_read_octetstring(JNIEnv* env, jclass, jlong } ScopedLocalRef out(env, env->NewByteArray(static_cast(CBS_len(str.get())))); if (out.get() == nullptr) { - conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); return 0; } ScopedByteArrayRW outBytes(env, out.get()); if (outBytes.get() == nullptr) { - conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); return 0; } memcpy(outBytes.get(), CBS_data(str.get()), CBS_len(str.get())); @@ -7861,12 +7964,10 @@ static jbyteArray NativeCrypto_asn1_write_finish(JNIEnv* env, jclass, jlong cbbR bssl::UniquePtr data_storage(data); ScopedLocalRef out(env, env->NewByteArray(static_cast(data_len))); if (out.get() == nullptr) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); return 0; } ScopedByteArrayRW outBytes(env, out.get()); if (outBytes.get() == nullptr) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); return 0; } memcpy(outBytes.get(), data, data_len); @@ -8277,6 +8378,10 @@ static jbyteArray NativeCrypto_ASN1_seq_pack_X509(JNIEnv* env, jclass, jlongArra return CBBToByteArray(env, result.get()); } +static jlong NativeCrypto_get_X509_free_func(JNIEnv*, jclass) { + return static_cast(reinterpret_cast(X509_free)); +} + static void NativeCrypto_X509_free(JNIEnv* env, jclass, jlong x509Ref, CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; @@ -11863,6 +11968,9 @@ static void NativeCrypto_SSL_CTX_set_spake_credential( CHECK_ERROR_QUEUE_ON_RETURN; SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true); + if (ssl_ctx == nullptr) { + return; + } JNI_TRACE("SSL_CTX_set_spake_credential(%p, %p, %p, %p, %d, %d, %p)", context, pw_array, id_prover_array, id_verifier_array, is_client, handshake_limit, ssl_ctx); @@ -11876,29 +11984,24 @@ static void NativeCrypto_SSL_CTX_set_spake_credential( ScopedByteArrayRO context_bytes(env, context); if (context_bytes.get() == nullptr) { JNI_TRACE("ctx=%p SSL_CTX_set_spake_credential => threw exception", ssl_ctx); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for context"); return; } ScopedByteArrayRO pw_bytes(env, pw_array); if (pw_bytes.get() == nullptr) { JNI_TRACE("ctx=%p SSL_CTX_set_spake_credential => threw exception", ssl_ctx); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for pw_array"); return; } ScopedByteArrayRO id_prover_bytes(env, id_prover_array); if (id_prover_bytes.get() == nullptr) { JNI_TRACE("ctx=%p SSL_CTX_set_spake_credential => threw exception", ssl_ctx); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for id_prover_array"); return; } ScopedByteArrayRO id_verifier_bytes(env, id_verifier_array); if (id_verifier_bytes.get() == nullptr) { JNI_TRACE("ctx=%p SSL_CTX_set_spake_credential => threw exception", ssl_ctx); - conscrypt::jniutil::throwOutOfMemory(env, - "Unable to allocate buffer for id_verifier_array"); return; } @@ -12125,6 +12228,19 @@ static void NativeCrypto_SSL_set_enable_ech_grease(JNIEnv* env, jclass, jlong ss JNI_TRACE("ssl=%p NativeCrypto_SSL_set_enable_ech_grease(%d) => success", ssl, enable); } +static void NativeCrypto_SSL_set_reject_unusable_ech_config(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jboolean enable) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_reject_unusable_ech_config(%d)", ssl, enable); + if (ssl == nullptr) { + return; + } + SSL_set_reject_unusable_ech_config(ssl, enable ? 1 : 0); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_reject_unusable_ech_config(%d) => success", ssl, enable); +} + static jboolean NativeCrypto_SSL_set1_ech_config_list(JNIEnv* env, jclass, jlong ssl_address, CONSCRYPT_UNUSED jobject ssl_holder, jbyteArray configJavaBytes) { @@ -12461,7 +12577,22 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(EVP_PKEY_CTX_set_rsa_oaep_md, "(JJ)V"), CONSCRYPT_NATIVE_METHOD(EVP_PKEY_CTX_set_rsa_oaep_label, "(J[B)V"), CONSCRYPT_NATIVE_METHOD(EVP_PKEY_CTX_set1_signature_context_string, "(J[B)V"), - CONSCRYPT_NATIVE_METHOD(EVP_get_cipherbyname, "(Ljava/lang/String;)J"), + CONSCRYPT_NATIVE_METHOD(EVP_rc4, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_des_cbc, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_des_ede_cbc, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_des_ede3_cbc, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_aes_128_ecb, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_aes_128_cbc, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_aes_128_ctr, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_aes_128_gcm, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_aes_192_ecb, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_aes_192_cbc, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_aes_192_ctr, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_aes_192_gcm, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_aes_256_ecb, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_aes_256_cbc, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_aes_256_ctr, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_aes_256_gcm, "()J"), CONSCRYPT_NATIVE_METHOD(EVP_CipherInit_ex, "(" REF_EVP_CIPHER_CTX "J[B[BZ)V"), CONSCRYPT_NATIVE_METHOD(EVP_CipherUpdate, "(" REF_EVP_CIPHER_CTX "[BI[BII)I"), CONSCRYPT_NATIVE_METHOD(EVP_CipherFinal_ex, "(" REF_EVP_CIPHER_CTX "[BI)I"), @@ -12476,6 +12607,7 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(EVP_aead_aes_128_gcm, "()J"), CONSCRYPT_NATIVE_METHOD(EVP_aead_aes_256_gcm, "()J"), CONSCRYPT_NATIVE_METHOD(EVP_aead_chacha20_poly1305, "()J"), + CONSCRYPT_NATIVE_METHOD(EVP_aead_xchacha20_poly1305, "()J"), CONSCRYPT_NATIVE_METHOD(EVP_aead_aes_128_gcm_siv, "()J"), CONSCRYPT_NATIVE_METHOD(EVP_aead_aes_256_gcm_siv, "()J"), CONSCRYPT_NATIVE_METHOD(EVP_AEAD_max_overhead, "(J)I"), @@ -12517,6 +12649,7 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(i2d_PKCS7, "([J)[B"), CONSCRYPT_NATIVE_METHOD(ASN1_seq_unpack_X509_bio, "(J)[J"), CONSCRYPT_NATIVE_METHOD(ASN1_seq_pack_X509, "([J)[B"), + CONSCRYPT_NATIVE_METHOD(get_X509_free_func, "()J"), CONSCRYPT_NATIVE_METHOD(X509_free, "(J" REF_X509 ")V"), CONSCRYPT_NATIVE_METHOD(X509_cmp, "(J" REF_X509 "J" REF_X509 ")I"), CONSCRYPT_NATIVE_METHOD(X509_print_ex, "(JJ" REF_X509 "JJ)V"), @@ -12688,6 +12821,7 @@ static JNINativeMethod sNativeCryptoMethods[] = { // FOR ECH TESTING CONSCRYPT_NATIVE_METHOD(SSL_set_enable_ech_grease, "(J" REF_SSL "Z)V"), + CONSCRYPT_NATIVE_METHOD(SSL_set_reject_unusable_ech_config, "(J" REF_SSL "Z)V"), CONSCRYPT_NATIVE_METHOD(SSL_set1_ech_config_list, "(J" REF_SSL "[B)Z"), CONSCRYPT_NATIVE_METHOD(SSL_get0_ech_name_override, "(J" REF_SSL ")Ljava/lang/String;"), CONSCRYPT_NATIVE_METHOD(SSL_get0_ech_retry_configs, "(J" REF_SSL ")[B"), diff --git a/common/src/main/java/org/conscrypt/EchOptions.java b/common/src/main/java/org/conscrypt/EchOptions.java index 6daca14f8..d1d72bca2 100644 --- a/common/src/main/java/org/conscrypt/EchOptions.java +++ b/common/src/main/java/org/conscrypt/EchOptions.java @@ -20,10 +20,16 @@ public class EchOptions { private final byte[] configList; private final boolean enableGrease; + private final boolean failClosed; EchOptions(byte[] configList, boolean enableGrease) { + this(configList, enableGrease, false); + } + + EchOptions(byte[] configList, boolean enableGrease, boolean failClosed) { this.configList = configList; this.enableGrease = enableGrease; + this.failClosed = failClosed; } public byte[] getConfigList() { @@ -33,4 +39,8 @@ public byte[] getConfigList() { public boolean isGreaseEnabled() { return enableGrease; } + + public boolean isFailClosed() { + return failClosed; + } } diff --git a/common/src/main/java/org/conscrypt/Hkdf.java b/common/src/main/java/org/conscrypt/Hkdf.java index 54118b0e0..bbe13bcbf 100644 --- a/common/src/main/java/org/conscrypt/Hkdf.java +++ b/common/src/main/java/org/conscrypt/Hkdf.java @@ -45,9 +45,9 @@ public final class Hkdf { */ public Hkdf(String hmacName) throws NoSuchAlgorithmException { Objects.requireNonNull(hmacName); - if (!hmacName.equals("HmacSHA1") && - !hmacName.equals("HmacSHA224") && !hmacName.equals("HmacSHA256") && - !hmacName.equals("HmacSHA384") && !hmacName.equals("HmacSHA512")) { + if (!hmacName.equals("HmacSHA1") && !hmacName.equals("HmacSHA224") + && !hmacName.equals("HmacSHA256") && !hmacName.equals("HmacSHA384") + && !hmacName.equals("HmacSHA512")) { throw new NoSuchAlgorithmException("HMAC algorithm not supported: " + hmacName); } this.hmacName = hmacName; diff --git a/common/src/main/java/org/conscrypt/IvParameters.java b/common/src/main/java/org/conscrypt/IvParameters.java index 36dff4c54..2398ddcd7 100644 --- a/common/src/main/java/org/conscrypt/IvParameters.java +++ b/common/src/main/java/org/conscrypt/IvParameters.java @@ -117,4 +117,7 @@ public DESEDE() {} public static class ChaCha20 extends IvParameters { public ChaCha20() {} } + public static class XChaCha20 extends IvParameters { + public XChaCha20() {} + } } diff --git a/common/src/main/java/org/conscrypt/KeyGeneratorImpl.java b/common/src/main/java/org/conscrypt/KeyGeneratorImpl.java index 0701a5211..602074520 100644 --- a/common/src/main/java/org/conscrypt/KeyGeneratorImpl.java +++ b/common/src/main/java/org/conscrypt/KeyGeneratorImpl.java @@ -178,6 +178,19 @@ protected void checkKeySize(int keySize) { } } + public static final class XChaCha20 extends KeyGeneratorImpl { + public XChaCha20() { + super("XChaCha20", 256); + } + + @Override + protected void checkKeySize(int keySize) { + if (keySize != 256) { + throw new InvalidParameterException("Key size must be 256 bits"); + } + } + } + public static final class ARC4 extends KeyGeneratorImpl { public ARC4() { super("ARC4", 128); diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java index 01d60b812..4b1893383 100644 --- a/common/src/main/java/org/conscrypt/NativeCrypto.java +++ b/common/src/main/java/org/conscrypt/NativeCrypto.java @@ -20,10 +20,8 @@ // android-add: import dalvik.annotation.optimization.FastNative; import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException; -import java.io.FileDescriptor; import java.io.IOException; import java.io.OutputStream; -import java.net.SocketTimeoutException; import java.nio.Buffer; import java.nio.ByteBuffer; import java.security.InvalidAlgorithmParameterException; @@ -473,7 +471,52 @@ static native void EVP_PKEY_CTX_set_rsa_oaep_label(long ctx, byte[] label) // These return const references // android-add: @FastNative - static native long EVP_get_cipherbyname(String string); + static native long EVP_rc4(); + + // android-add: @FastNative + static native long EVP_des_cbc(); + + // android-add: @FastNative + static native long EVP_des_ede_cbc(); + + // android-add: @FastNative + static native long EVP_des_ede3_cbc(); + + // android-add: @FastNative + static native long EVP_aes_128_ecb(); + + // android-add: @FastNative + static native long EVP_aes_128_cbc(); + + // android-add: @FastNative + static native long EVP_aes_128_ctr(); + + // android-add: @FastNative + static native long EVP_aes_128_gcm(); + + // android-add: @FastNative + static native long EVP_aes_192_ecb(); + + // android-add: @FastNative + static native long EVP_aes_192_cbc(); + + // android-add: @FastNative + static native long EVP_aes_192_ctr(); + + // android-add: @FastNative + static native long EVP_aes_192_gcm(); + + // android-add: @FastNative + static native long EVP_aes_256_ecb(); + + // android-add: @FastNative + static native long EVP_aes_256_cbc(); + + // android-add: @FastNative + static native long EVP_aes_256_ctr(); + + // android-add: @FastNative + static native long EVP_aes_256_gcm(); // android-add: @FastNative static native void EVP_CipherInit_ex(NativeRef.EVP_CIPHER_CTX ctx, long evpCipher, byte[] key, @@ -523,6 +566,9 @@ static native void EVP_CIPHER_CTX_set_padding(NativeRef.EVP_CIPHER_CTX ctx, // android-add: @FastNative static native long EVP_aead_chacha20_poly1305(); + // android-add: @FastNative + static native long EVP_aead_xchacha20_poly1305(); + // android-add: @FastNative static native long EVP_aead_aes_128_gcm_siv(); @@ -716,6 +762,8 @@ private static int X509_NAME_hash(X500Principal principal, String algorithm) { // android-add: @FastNative static native long[] ASN1_seq_unpack_X509_bio(long bioRef) throws ParsingException; + static native long get_X509_free_func(); + static native void X509_free(long x509ctx, OpenSSLX509Certificate holder); // android-add: @FastNative @@ -1962,6 +2010,8 @@ static native byte[] Scrypt_generate_key(byte[] password, byte[] salt, int n, in static native void SSL_set_enable_ech_grease(long ssl, NativeSsl ssl_holder, boolean enable); + static native void SSL_set_reject_unusable_ech_config(long ssl, NativeSsl ssl_holder, boolean enable); + static native boolean SSL_set1_ech_config_list(long ssl, NativeSsl ssl_holder, byte[] echConfig) throws SSLException; diff --git a/common/src/main/java/org/conscrypt/NativeSsl.java b/common/src/main/java/org/conscrypt/NativeSsl.java index 29e14c799..530a94368 100644 --- a/common/src/main/java/org/conscrypt/NativeSsl.java +++ b/common/src/main/java/org/conscrypt/NativeSsl.java @@ -544,6 +544,10 @@ private void enableEchBasedOnPolicy(String hostname) throws SSLException { if (opts.isGreaseEnabled()) { NativeCrypto.SSL_set_enable_ech_grease(ssl, this, /* enable= */ true); } + + if (opts.isFailClosed()) { + NativeCrypto.SSL_set_reject_unusable_ech_config(ssl, this, /* enable= */ true); + } } TlsEncryptedClientHelloHandshake.Builder getEchHandshakeMetricsBuilder() { diff --git a/common/src/main/java/org/conscrypt/OpenSSLAeadCipherChaCha20.java b/common/src/main/java/org/conscrypt/OpenSSLAeadCipherChaCha20.java index aab1c1e7d..da162b9bc 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLAeadCipherChaCha20.java +++ b/common/src/main/java/org/conscrypt/OpenSSLAeadCipherChaCha20.java @@ -70,4 +70,20 @@ int getOutputSizeForFinal(int inputLen) { return Math.max(0, bufCount + inputLen - 16); } } + + public static class XChaCha20 extends OpenSSLAeadCipherChaCha20 { + @Override + String getBaseCipherName() { + return "XChaCha20"; + } + + @Override + long getEVP_AEAD(int keyLength) throws InvalidKeyException { + if (keyLength == 32) { + return NativeCrypto.EVP_aead_xchacha20_poly1305(); + } else { + throw new RuntimeException("Unexpected key length: " + keyLength); + } + } + } } diff --git a/common/src/main/java/org/conscrypt/OpenSSLEvpCipher.java b/common/src/main/java/org/conscrypt/OpenSSLEvpCipher.java index 5873627c0..c0a6db091 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLEvpCipher.java +++ b/common/src/main/java/org/conscrypt/OpenSSLEvpCipher.java @@ -63,8 +63,7 @@ void engineInitInternal(byte[] encodedKey, AlgorithmParameterSpec params, Secure iv = null; } - final long cipherType = - NativeCrypto.EVP_get_cipherbyname(getCipherName(encodedKey.length, mode)); + final long cipherType = getCipherType(encodedKey.length, mode); if (cipherType == 0) { throw new InvalidAlgorithmParameterException("Cannot find name for key length = " + (encodedKey.length * 8) @@ -172,7 +171,8 @@ protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) int bytesWritten; if (inputLen > 0) { try { - bytesWritten = updateInternal(input, inputOffset, inputLen, output, 0, maximumLen); + bytesWritten = + updateInternal(input, inputOffset, inputLen, output, 0, maximumLen); } catch (ShortBufferException e) { /* This should not happen since we sized our own buffer. */ throw new RuntimeException("our calculated buffer was too small", e); @@ -206,8 +206,8 @@ protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) @Override protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, - int outputOffset) throws ShortBufferException, IllegalBlockSizeException, - BadPaddingException { + int outputOffset) + throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { // Assume we will need to reset unless we hit a ShortBufferException boolean resetNeeded = true; try { @@ -220,7 +220,7 @@ protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] final int bytesWritten; if (inputLen > 0) { bytesWritten = updateInternal(input, inputOffset, inputLen, output, outputOffset, - maximumLen); + maximumLen); outputOffset += bytesWritten; maximumLen -= bytesWritten; } else { @@ -269,10 +269,10 @@ int getOutputSizeForUpdate(int inputLen) { } /** - * Returns the OpenSSL cipher name for the particular {@code keySize} - * and cipher {@code mode}. + * Returns the OpenSSL cipher for the particular {@code keySize} + * and cipher {@code mode}, or 0 if the combination is not supported. */ - abstract String getCipherName(int keySize, Mode mode); + abstract long getCipherType(int keySize, Mode mode); /** * Reset this Cipher instance state to process a new chunk of data. diff --git a/common/src/main/java/org/conscrypt/OpenSSLEvpCipherAES.java b/common/src/main/java/org/conscrypt/OpenSSLEvpCipherAES.java index ccde84143..1c0a45abd 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLEvpCipherAES.java +++ b/common/src/main/java/org/conscrypt/OpenSSLEvpCipherAES.java @@ -18,7 +18,6 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.Locale; import javax.crypto.NoSuchPaddingException; @@ -59,8 +58,44 @@ String getBaseCipherName() { } @Override - String getCipherName(int keyLength, Mode mode) { - return "aes-" + (keyLength * 8) + "-" + mode.toString().toLowerCase(Locale.US); + long getCipherType(int keyLength, Mode mode) { + switch (mode) { + case CBC: + switch (keyLength) { + case 16: + return NativeCrypto.EVP_aes_128_cbc(); + case 24: + return NativeCrypto.EVP_aes_192_cbc(); + case 32: + return NativeCrypto.EVP_aes_256_cbc(); + default: + return 0; + } + case CTR: + switch (keyLength) { + case 16: + return NativeCrypto.EVP_aes_128_ctr(); + case 24: + return NativeCrypto.EVP_aes_192_ctr(); + case 32: + return NativeCrypto.EVP_aes_256_ctr(); + default: + return 0; + } + case ECB: + switch (keyLength) { + case 16: + return NativeCrypto.EVP_aes_128_ecb(); + case 24: + return NativeCrypto.EVP_aes_192_ecb(); + case 32: + return NativeCrypto.EVP_aes_256_ecb(); + default: + return 0; + } + default: + return 0; + } } @Override diff --git a/common/src/main/java/org/conscrypt/OpenSSLEvpCipherARC4.java b/common/src/main/java/org/conscrypt/OpenSSLEvpCipherARC4.java index befa6983a..4adb66216 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLEvpCipherARC4.java +++ b/common/src/main/java/org/conscrypt/OpenSSLEvpCipherARC4.java @@ -34,8 +34,8 @@ String getBaseCipherName() { } @Override - String getCipherName(int keySize, Mode mode) { - return "rc4"; + long getCipherType(int keySize, Mode mode) { + return NativeCrypto.EVP_rc4(); } @Override diff --git a/common/src/main/java/org/conscrypt/OpenSSLEvpCipherDESEDE.java b/common/src/main/java/org/conscrypt/OpenSSLEvpCipherDESEDE.java index 0a4ff63c3..95bd77f39 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLEvpCipherDESEDE.java +++ b/common/src/main/java/org/conscrypt/OpenSSLEvpCipherDESEDE.java @@ -18,7 +18,6 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.Locale; import javax.crypto.NoSuchPaddingException; @@ -54,15 +53,15 @@ String getBaseCipherName() { } @Override - String getCipherName(int keySize, Mode mode) { - final String baseCipherName; - if (keySize == 16) { - baseCipherName = "des-ede"; - } else { - baseCipherName = "des-ede3"; + long getCipherType(int keySize, Mode mode) { + if (mode == Mode.CBC) { + if (keySize == 16) { + return NativeCrypto.EVP_des_ede_cbc(); + } else if (keySize == 24) { + return NativeCrypto.EVP_des_ede3_cbc(); + } } - - return baseCipherName + "-" + mode.toString().toLowerCase(Locale.US); + return 0; } @Override diff --git a/common/src/main/java/org/conscrypt/OpenSSLProvider.java b/common/src/main/java/org/conscrypt/OpenSSLProvider.java index 1c3d94916..d55636a92 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLProvider.java +++ b/common/src/main/java/org/conscrypt/OpenSSLProvider.java @@ -140,6 +140,7 @@ public OpenSSLProvider(String providerName) { put("Alg.Alias.AlgorithmParameters.2.16.840.1.101.3.4.1.42", "AES"); put("AlgorithmParameters.ChaCha20", PREFIX + "IvParameters$ChaCha20"); + put("AlgorithmParameters.XChaCha20", PREFIX + "IvParameters$XChaCha20"); put("AlgorithmParameters.DESEDE", PREFIX + "IvParameters$DESEDE"); put("Alg.Alias.AlgorithmParameters.TDEA", "DESEDE"); @@ -187,6 +188,7 @@ public OpenSSLProvider(String providerName) { put("KeyGenerator.AES", PREFIX + "KeyGeneratorImpl$AES"); put("KeyGenerator.ChaCha20", PREFIX + "KeyGeneratorImpl$ChaCha20"); + put("KeyGenerator.XChaCha20", PREFIX + "KeyGeneratorImpl$XChaCha20"); put("KeyGenerator.DESEDE", PREFIX + "KeyGeneratorImpl$DESEDE"); put("Alg.Alias.KeyGenerator.TDEA", "DESEDE"); @@ -694,6 +696,9 @@ public OpenSSLProvider(String providerName) { putSymmetricCipherImplClass("ChaCha20", "OpenSSLCipherChaCha20"); putSymmetricCipherImplClass("ChaCha20/Poly1305/NoPadding", "OpenSSLAeadCipherChaCha20"); put("Alg.Alias.Cipher.ChaCha20-Poly1305", "ChaCha20/Poly1305/NoPadding"); + putSymmetricCipherImplClass("XChaCha20/Poly1305/NoPadding", + "OpenSSLAeadCipherChaCha20$XChaCha20"); + put("Alg.Alias.Cipher.XChaCha20-Poly1305", "XChaCha20/Poly1305/NoPadding"); /* === Mac === */ diff --git a/common/src/main/java/org/conscrypt/OpenSSLX509Certificate.java b/common/src/main/java/org/conscrypt/OpenSSLX509Certificate.java index dd17bdef9..2d6d638b9 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLX509Certificate.java +++ b/common/src/main/java/org/conscrypt/OpenSSLX509Certificate.java @@ -62,6 +62,7 @@ public final class OpenSSLX509Certificate extends X509Certificate { private static final long serialVersionUID = 1992239142393372128L; private transient volatile long mContext; + private transient boolean mNativeAllocationRegistered; private transient Integer mHashCode; private final Date notBefore; @@ -73,6 +74,7 @@ public final class OpenSSLX509Certificate extends X509Certificate { // parse them here because this is the only time we're allowed to throw ParsingException notBefore = toDate(NativeCrypto.X509_get_notBefore(mContext, this)); notAfter = toDate(NativeCrypto.X509_get_notAfter(mContext, this)); + mNativeAllocationRegistered = Platform.registerX509CertificateAllocation(this, ctx); } private static Date toDate(long asn1time) throws ParsingException { @@ -572,10 +574,12 @@ public byte[] getTBSCertificateWithoutExtension(String oid) { @SuppressWarnings("Finalize") protected void finalize() throws Throwable { try { - long toFree = mContext; - if (toFree != 0) { - mContext = 0; - NativeCrypto.X509_free(toFree, this); + if (!mNativeAllocationRegistered) { + long toFree = mContext; + if (toFree != 0) { + mContext = 0; + NativeCrypto.X509_free(toFree, this); + } } } finally { super.finalize(); diff --git a/common/src/main/java/org/conscrypt/OpenSslSignatureHashSlhDsa.java b/common/src/main/java/org/conscrypt/OpenSslSignatureHashSlhDsa.java index b1ecb5a9a..a050a90a2 100644 --- a/common/src/main/java/org/conscrypt/OpenSslSignatureHashSlhDsa.java +++ b/common/src/main/java/org/conscrypt/OpenSslSignatureHashSlhDsa.java @@ -28,106 +28,105 @@ */ @Internal public class OpenSslSignatureHashSlhDsa extends SignatureSpi { - private final int hashNid; - private OpenSSLMessageDigestJDK messageDigest; + private final int hashNid; + private OpenSSLMessageDigestJDK messageDigest; - /** The current OpenSSL key we're operating on. */ - private OpenSslSlhDsaPrivateKey privateKey; + /** The current OpenSSL key we're operating on. */ + private OpenSslSlhDsaPrivateKey privateKey; - private OpenSslSlhDsaPublicKey publicKey; + private OpenSslSlhDsaPublicKey publicKey; - protected OpenSslSignatureHashSlhDsa(int hashNid) { - this.hashNid = hashNid; - } + protected OpenSslSignatureHashSlhDsa(int hashNid) { + this.hashNid = hashNid; + } - /** SHA-384 prehash SLH-DSA signature implementation. */ - public static final class Sha384 extends OpenSslSignatureHashSlhDsa { - public Sha384() { - super(NativeConstants.NID_sha384); + /** SHA-384 prehash SLH-DSA signature implementation. */ + public static final class Sha384 extends OpenSslSignatureHashSlhDsa { + public Sha384() { + super(NativeConstants.NID_sha384); + } } - } - private void resetDigest() { - try { - if (hashNid == NativeConstants.NID_sha384) { - messageDigest = new OpenSSLMessageDigestJDK.SHA384(); - } else { - throw new IllegalStateException("Unsupported hash NID: " + hashNid); - } - } catch (NoSuchAlgorithmException e) { - throw new AssertionError("Failed to create message digest", e); + private void resetDigest() { + try { + if (hashNid == NativeConstants.NID_sha384) { + messageDigest = new OpenSSLMessageDigestJDK.SHA384(); + } else { + throw new IllegalStateException("Unsupported hash NID: " + hashNid); + } + } catch (NoSuchAlgorithmException e) { + throw new AssertionError("Failed to create message digest", e); + } } - } - @Override - protected void engineUpdate(byte input) throws SignatureException { - if (messageDigest == null) { - throw new SignatureException("Not initialized"); + @Override + protected void engineUpdate(byte input) throws SignatureException { + if (messageDigest == null) { + throw new SignatureException("Not initialized"); + } + messageDigest.engineUpdate(input); } - messageDigest.engineUpdate(input); - } - @Override - protected void engineUpdate(byte[] input, int offset, int len) throws SignatureException { - if (messageDigest == null) { - throw new SignatureException("Not initialized"); + @Override + protected void engineUpdate(byte[] input, int offset, int len) throws SignatureException { + if (messageDigest == null) { + throw new SignatureException("Not initialized"); + } + messageDigest.engineUpdate(input, offset, len); } - messageDigest.engineUpdate(input, offset, len); - } - @Override - // Deprecated in Java 9, but still required by SignatureSpi. - @SuppressWarnings("deprecation") - protected Object engineGetParameter(String param) { - return null; - } + @Override + // Deprecated in Java 9, but still required by SignatureSpi. + @SuppressWarnings("deprecation") + protected Object engineGetParameter(String param) { + return null; + } - @Override - @SuppressWarnings("PatternMatchingInstanceof") - protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { - if (!(privateKey instanceof OpenSslSlhDsaPrivateKey)) { - throw new InvalidKeyException("Must be OpenSslSlhDsaPrivateKey"); + @Override + @SuppressWarnings("PatternMatchingInstanceof") + protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { + if (!(privateKey instanceof OpenSslSlhDsaPrivateKey)) { + throw new InvalidKeyException("Must be OpenSslSlhDsaPrivateKey"); + } + this.privateKey = (OpenSslSlhDsaPrivateKey) privateKey; + this.publicKey = null; + resetDigest(); } - this.privateKey = (OpenSslSlhDsaPrivateKey) privateKey; - this.publicKey = null; - resetDigest(); - } - @Override - @SuppressWarnings("PatternMatchingInstanceof") - protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { - if (!(publicKey instanceof OpenSslSlhDsaPublicKey)) { - throw new InvalidKeyException("Must be OpenSslSlhDsaPublicKey"); + @Override + @SuppressWarnings("PatternMatchingInstanceof") + protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { + if (!(publicKey instanceof OpenSslSlhDsaPublicKey)) { + throw new InvalidKeyException("Must be OpenSslSlhDsaPublicKey"); + } + this.publicKey = (OpenSslSlhDsaPublicKey) publicKey; + this.privateKey = null; + resetDigest(); } - this.publicKey = (OpenSslSlhDsaPublicKey) publicKey; - this.privateKey = null; - resetDigest(); - } - @Override - // Deprecated in Java 9, but still required by SignatureSpi. - @SuppressWarnings("deprecation") - protected void engineSetParameter(String param, Object value) {} + @Override + // Deprecated in Java 9, but still required by SignatureSpi. + @SuppressWarnings("deprecation") + protected void engineSetParameter(String param, Object value) {} - @Override - protected byte[] engineSign() throws SignatureException { - if (privateKey == null || messageDigest == null) { - throw new SignatureException("Not initialized for signing"); + @Override + protected byte[] engineSign() throws SignatureException { + if (privateKey == null || messageDigest == null) { + throw new SignatureException("Not initialized for signing"); + } + byte[] digest = messageDigest.engineDigest(); + return NativeCrypto.SLHDSA_SHA2_128S_prehash_sign(digest, digest.length, hashNid, + privateKey.getRaw()); } - byte[] digest = messageDigest.engineDigest(); - return NativeCrypto.SLHDSA_SHA2_128S_prehash_sign( - digest, digest.length, hashNid, privateKey.getRaw()); - } - @Override - protected boolean engineVerify(byte[] sigBytes) throws SignatureException { - if (publicKey == null || messageDigest == null) { - throw new SignatureException("Not initialized for verification"); + @Override + protected boolean engineVerify(byte[] sigBytes) throws SignatureException { + if (publicKey == null || messageDigest == null) { + throw new SignatureException("Not initialized for verification"); + } + byte[] digest = messageDigest.engineDigest(); + int result = NativeCrypto.SLHDSA_SHA2_128S_prehash_verify(digest, digest.length, sigBytes, + hashNid, publicKey.getRaw()); + return result == 1; } - byte[] digest = messageDigest.engineDigest(); - int result = - NativeCrypto.SLHDSA_SHA2_128S_prehash_verify( - digest, digest.length, sigBytes, hashNid, publicKey.getRaw()); - return result == 1; - } } diff --git a/common/src/main/java/org/conscrypt/SSLParametersImpl.java b/common/src/main/java/org/conscrypt/SSLParametersImpl.java index 40b4236c3..18467f7c6 100644 --- a/common/src/main/java/org/conscrypt/SSLParametersImpl.java +++ b/common/src/main/java/org/conscrypt/SSLParametersImpl.java @@ -890,7 +890,7 @@ EchOptions getEchOptions(String hostname) throws SSLException { if (echConfigList == null) { throw new SSLException("No ECH config provided when required"); } - return new EchOptions(echConfigList, /* enableGrease= */ false); + return new EchOptions(echConfigList, /* enableGrease= */ false, /* failClosed= */ true); default: return null; } diff --git a/common/src/main/java/org/conscrypt/TrustManagerImpl.java b/common/src/main/java/org/conscrypt/TrustManagerImpl.java index 557c3bef1..ff2d55ac4 100644 --- a/common/src/main/java/org/conscrypt/TrustManagerImpl.java +++ b/common/src/main/java/org/conscrypt/TrustManagerImpl.java @@ -874,18 +874,18 @@ public Set getSupportedExtensions() { @Override public void check(Certificate c, Collection unresolvedCritExts) throws CertPathValidatorException { - // We only want to validate the EKU on the leaf certificate. - if (c != leaf) { + if (!(c instanceof X509Certificate)) { return; } + X509Certificate x509Cert = (X509Certificate) c; List ekuOids; try { - ekuOids = leaf.getExtendedKeyUsage(); + ekuOids = x509Cert.getExtendedKeyUsage(); } catch (CertificateParsingException e) { // A malformed EKU is bad news, consider it fatal. throw new CertPathValidatorException(e); } - // We are here to check EKU, but there is none. + // If there is no EKU on this cert, it is unrestricted for EKU purposes. if (ekuOids == null) { return; } @@ -923,9 +923,11 @@ public void check(Certificate c, Collection unresolvedCritExts) } if (goodExtendedKeyUsage) { // Mark extendedKeyUsage as resolved if present. - unresolvedCritExts.remove(EKU_OID); + if (unresolvedCritExts != null) { + unresolvedCritExts.remove(EKU_OID); + } } else { - throw new CertPathValidatorException("End-entity certificate does not have a valid " + throw new CertPathValidatorException("Certificate does not have a valid " + "extendedKeyUsage."); } } diff --git a/common/src/main/java/org/conscrypt/metrics/TlsEncryptedClientHelloHandshake.java b/common/src/main/java/org/conscrypt/metrics/TlsEncryptedClientHelloHandshake.java index 27a4e2993..e41eb97a1 100644 --- a/common/src/main/java/org/conscrypt/metrics/TlsEncryptedClientHelloHandshake.java +++ b/common/src/main/java/org/conscrypt/metrics/TlsEncryptedClientHelloHandshake.java @@ -27,17 +27,16 @@ @Internal public class TlsEncryptedClientHelloHandshake { enum Result { - UNKNOWN(ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_UNKNOWN), - SUCCESS(ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_SUCCESS), + UNKNOWN( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_UNKNOWN), + SUCCESS( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_SUCCESS), SUCCESS_WITH_GREASE( - ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_SUCCESS_GREASE), - FAILURE(ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_FAILURE), - SKIPPED(ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_SKIPPED); + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_SUCCESS_GREASE), + FAILURE( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_FAILURE), + SKIPPED( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_SKIPPED); private final int metricsValue; @@ -51,19 +50,16 @@ private Result(int metricsValue) { } enum UsageReason { - UNKNOWN(ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_UNKNOWN), - DEFAULT(ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_DEFAULT), + UNKNOWN( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_UNKNOWN), + DEFAULT( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_DEFAULT), SDK_TARGET( - ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_SDK_TARGET), + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_SDK_TARGET), NSC_APP_OPT_IN( - ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_NSC_APP_OPT_IN), + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_NSC_APP_OPT_IN), NSC_DOMAIN_OPT_IN( - ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_NSC_DOMAIN_OPT_IN); + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_NSC_DOMAIN_OPT_IN); private final int metricsValue; @@ -77,26 +73,20 @@ private UsageReason(int metricsValue) { } enum SkipReason { - UNKNOWN(ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_UNKNOWN), + UNKNOWN( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_UNKNOWN), SDK_TARGET( - ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_SDK_TARGET), + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_SDK_TARGET), NSC_APP_OPT_OUT( - ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_NSC_APP_OPT_OUT), + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_NSC_APP_OPT_OUT), NSC_DOMAIN_OPT_OUT( - ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_NSC_DOMAIN_OPT_OUT), + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_NSC_DOMAIN_OPT_OUT), NO_CONFIG( - ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_NO_CONFIG), + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_NO_CONFIG), SERVER_SNI_MISMATCH( - ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_SERVER_SNI_MISMATCH), + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_SERVER_SNI_MISMATCH), UNSUPPORTED_TLS_VERSION( - ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_UNSUPPORTED_TLS_VERSION); + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_UNSUPPORTED_TLS_VERSION); private final int metricsValue; @@ -110,20 +100,16 @@ private SkipReason(int metricsValue) { } public enum FailureReason { - UNKNOWN(ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_UNKNOWN), + UNKNOWN( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_UNKNOWN), SERVER_REJECTION( - ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_SERVER_REJECTION), + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_SERVER_REJECTION), INVALID_CONFIG( - ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_INVALID_CONFIG), + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_INVALID_CONFIG), INCONSISTENT_NEGOTIATION( - ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_INCONSISTENT_NEGOTIATION), + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_INCONSISTENT_NEGOTIATION), NO_RETRY_CONFIG( - ConscryptStatsLog - .TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_NO_RETRY_CONFIGS); + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_NO_RETRY_CONFIGS); private final int metricsValue; @@ -143,8 +129,7 @@ private FailureReason(int metricsValue) { private final int handshakeDurationMillis; private TlsEncryptedClientHelloHandshake(Result result, UsageReason usageReason, - SkipReason skipReason, FailureReason failureReason, - int handshakeDurationMillis) { + SkipReason skipReason, FailureReason failureReason, int handshakeDurationMillis) { this.result = result; this.usageReason = usageReason; this.skipReason = skipReason; @@ -153,6 +138,7 @@ private TlsEncryptedClientHelloHandshake(Result result, UsageReason usageReason, } public static final class Builder { + private Result result = Result.UNKNOWN; private EchOptions opts; private FailureReason failureReason = FailureReason.UNKNOWN; private NetworkSecurityPolicy policy; @@ -161,7 +147,8 @@ public static final class Builder { private int handshakeDurationMillis; private boolean handshakeSuccess; - public Builder() {} + public Builder() { + } public Builder setEchOptions(EchOptions opts) { this.opts = opts; @@ -209,8 +196,8 @@ private Result calculateResult() { } if (calculateSkipReason() != SkipReason.UNKNOWN) { - return (opts != null && opts.isGreaseEnabled()) ? Result.SUCCESS_WITH_GREASE - : Result.SKIPPED; + return (opts != null && opts.isGreaseEnabled()) + ? Result.SUCCESS_WITH_GREASE : Result.SKIPPED; } return Result.SUCCESS; @@ -224,10 +211,10 @@ private UsageReason calculateUsageReason() { return UsageReason.UNKNOWN; } - if (policy.getDomainEncryptionMode("") == DomainEncryptionMode.OPPORTUNISTIC - || policy.getDomainEncryptionMode(hostname) == DomainEncryptionMode.OPPORTUNISTIC - || policy.getDomainEncryptionMode("") == DomainEncryptionMode.ENABLED - || policy.getDomainEncryptionMode(hostname) == DomainEncryptionMode.ENABLED) { + if (policy.getDomainEncryptionMode("") == DomainEncryptionMode.OPPORTUNISTIC || + policy.getDomainEncryptionMode(hostname) == DomainEncryptionMode.OPPORTUNISTIC || + policy.getDomainEncryptionMode("") == DomainEncryptionMode.ENABLED || + policy.getDomainEncryptionMode(hostname) == DomainEncryptionMode.ENABLED) { // ECH mode was default opportunistic for 26Q2, and default enabled for 26Q4 onwards return UsageReason.DEFAULT; } @@ -258,9 +245,9 @@ private SkipReason calculateSkipReason() { } public TlsEncryptedClientHelloHandshake build() { - return new TlsEncryptedClientHelloHandshake(calculateResult(), calculateUsageReason(), - calculateSkipReason(), failureReason, - handshakeDurationMillis); + return new TlsEncryptedClientHelloHandshake( + calculateResult(), calculateUsageReason(), calculateSkipReason(), + failureReason, handshakeDurationMillis); } } diff --git a/common/src/test/java/org/conscrypt/ChaCha20Poly1305Test.java b/common/src/test/java/org/conscrypt/ChaCha20Poly1305Test.java new file mode 100644 index 000000000..26c4d147d --- /dev/null +++ b/common/src/test/java/org/conscrypt/ChaCha20Poly1305Test.java @@ -0,0 +1,324 @@ +/* + * 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; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertArrayEquals; +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.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Provider; +import java.util.List; + +import javax.crypto.AEADBadTagException; +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +@RunWith(JUnit4.class) +public class ChaCha20Poly1305Test { + private final Provider conscryptProvider = TestUtils.getConscryptProvider(); + + @BeforeClass + public static void setUp() { + TestUtils.assumeAllowsUnsignedCrypto(); + } + + @Test + public void chaCha20Poly1305_encryptAndDecrypt_succeeds() throws Exception { + Cipher c = Cipher.getInstance("ChaCha20/Poly1305/NoPadding", conscryptProvider); + SecretKeySpec key = new SecretKeySpec(new byte[32], "ChaCha20"); + IvParameterSpec iv = new IvParameterSpec(new byte[12]); + + c.init(Cipher.ENCRYPT_MODE, key, iv); + byte[] plaintext = "Hello World".getBytes(TestUtils.UTF_8); + byte[] ciphertext = c.doFinal(plaintext); + + c.init(Cipher.DECRYPT_MODE, key, iv); + byte[] decrypted = c.doFinal(ciphertext); + + assertArrayEquals(plaintext, decrypted); + } + + @Test + public void chaCha20Poly1305_decryptWithDifferentKey_throwsException() throws Exception { + Cipher c = Cipher.getInstance("ChaCha20/Poly1305/NoPadding", conscryptProvider); + SecretKeySpec key = new SecretKeySpec(new byte[32], "ChaCha20"); + IvParameterSpec iv = new IvParameterSpec(new byte[12]); + + c.init(Cipher.ENCRYPT_MODE, key, iv); + byte[] plaintext = "Hello World".getBytes(TestUtils.UTF_8); + byte[] ciphertext = c.doFinal(plaintext); + + byte[] differentKeyBytes = new byte[32]; + differentKeyBytes[0] = 1; // Change one byte + SecretKeySpec differentKey = new SecretKeySpec(differentKeyBytes, "ChaCha20"); + + c.init(Cipher.DECRYPT_MODE, differentKey, iv); + assertThrows(AEADBadTagException.class, () -> c.doFinal(ciphertext)); + } + + @Test + public void chaCha20Poly1305_decryptWithModifiedCiphertext_throwsException() throws Exception { + Cipher c = Cipher.getInstance("ChaCha20/Poly1305/NoPadding", conscryptProvider); + SecretKeySpec key = new SecretKeySpec(new byte[32], "ChaCha20"); + IvParameterSpec iv = new IvParameterSpec(new byte[12]); + + c.init(Cipher.ENCRYPT_MODE, key, iv); + byte[] plaintext = "Hello World".getBytes(TestUtils.UTF_8); + byte[] ciphertext = c.doFinal(plaintext); + + ciphertext[0] ^= 1; // Flip a bit + + c.init(Cipher.DECRYPT_MODE, key, iv); + assertThrows(AEADBadTagException.class, () -> c.doFinal(ciphertext)); + } + + @Test + public void xChaCha20Poly1305_encryptAndDecrypt_succeeds() throws Exception { + Cipher c = Cipher.getInstance("XChaCha20/Poly1305/NoPadding", conscryptProvider); + SecretKeySpec key = new SecretKeySpec(new byte[32], "XChaCha20"); + IvParameterSpec iv = new IvParameterSpec(new byte[24]); + + c.init(Cipher.ENCRYPT_MODE, key, iv); + byte[] plaintext = "Hello World".getBytes(TestUtils.UTF_8); + byte[] ciphertext = c.doFinal(plaintext); + + c.init(Cipher.DECRYPT_MODE, key, iv); + byte[] decrypted = c.doFinal(ciphertext); + + assertArrayEquals(plaintext, decrypted); + } + + @Test + public void xChaCha20Poly1305_decryptWithDifferentKey_throwsException() throws Exception { + Cipher c = Cipher.getInstance("XChaCha20/Poly1305/NoPadding", conscryptProvider); + SecretKeySpec key = new SecretKeySpec(new byte[32], "XChaCha20"); + IvParameterSpec iv = new IvParameterSpec(new byte[24]); + + c.init(Cipher.ENCRYPT_MODE, key, iv); + byte[] plaintext = "Hello World".getBytes(TestUtils.UTF_8); + byte[] ciphertext = c.doFinal(plaintext); + + byte[] differentKeyBytes = new byte[32]; + differentKeyBytes[0] = 1; // Change one byte + SecretKeySpec differentKey = new SecretKeySpec(differentKeyBytes, "XChaCha20"); + + c.init(Cipher.DECRYPT_MODE, differentKey, iv); + assertThrows(AEADBadTagException.class, () -> c.doFinal(ciphertext)); + } + + @Test + public void xChaCha20Poly1305_decryptWithModifiedCiphertext_throwsException() throws Exception { + Cipher c = Cipher.getInstance("XChaCha20/Poly1305/NoPadding", conscryptProvider); + SecretKeySpec key = new SecretKeySpec(new byte[32], "XChaCha20"); + IvParameterSpec iv = new IvParameterSpec(new byte[24]); + + c.init(Cipher.ENCRYPT_MODE, key, iv); + byte[] plaintext = "Hello World".getBytes(TestUtils.UTF_8); + byte[] ciphertext = c.doFinal(plaintext); + + ciphertext[0] ^= 1; // Flip a bit + + c.init(Cipher.DECRYPT_MODE, key, iv); + assertThrows(AEADBadTagException.class, () -> c.doFinal(ciphertext)); + } + + @Test + public void xChaCha20Poly1305_alias_encryptAndDecrypt_succeeds() throws Exception { + Cipher c = Cipher.getInstance("XChaCha20-Poly1305", conscryptProvider); + SecretKeySpec key = new SecretKeySpec(new byte[32], "XChaCha20"); + IvParameterSpec iv = new IvParameterSpec(new byte[24]); + + c.init(Cipher.ENCRYPT_MODE, key, iv); + byte[] plaintext = "Hello World".getBytes(TestUtils.UTF_8); + byte[] ciphertext = c.doFinal(plaintext); + + c.init(Cipher.DECRYPT_MODE, key, iv); + byte[] decrypted = c.doFinal(ciphertext); + + assertArrayEquals(plaintext, decrypted); + } + + @Test + public void chaCha20Poly1305_shortIv_throwsException() throws Exception { + Cipher c = Cipher.getInstance("ChaCha20/Poly1305/NoPadding", conscryptProvider); + SecretKeySpec key = new SecretKeySpec(new byte[32], "ChaCha20"); + // ChaCha20/Poly1305/NoPadding expects 12 bytes IV + IvParameterSpec shortIv = new IvParameterSpec(new byte[11]); + + assertThrows(InvalidAlgorithmParameterException.class, + () -> c.init(Cipher.ENCRYPT_MODE, key, shortIv)); + } + + @Test + public void xChaCha20Poly1305_shortIv_throwsException() throws Exception { + Cipher c = Cipher.getInstance("XChaCha20/Poly1305/NoPadding", conscryptProvider); + SecretKeySpec key = new SecretKeySpec(new byte[32], "XChaCha20"); + // XChaCha20/Poly1305/NoPadding expects 24 bytes IV + IvParameterSpec shortIv = new IvParameterSpec(new byte[23]); + + assertThrows(InvalidAlgorithmParameterException.class, + () -> c.init(Cipher.ENCRYPT_MODE, key, shortIv)); + } + + @Test + public void chaCha20Poly1305_longIv_throwsException() throws Exception { + Cipher c = Cipher.getInstance("ChaCha20/Poly1305/NoPadding", conscryptProvider); + SecretKeySpec key = new SecretKeySpec(new byte[32], "ChaCha20"); + IvParameterSpec longIv = new IvParameterSpec(new byte[13]); + + assertThrows(InvalidAlgorithmParameterException.class, + () -> c.init(Cipher.ENCRYPT_MODE, key, longIv)); + } + + @Test + public void xChaCha20Poly1305_longIv_throwsException() throws Exception { + Cipher c = Cipher.getInstance("XChaCha20/Poly1305/NoPadding", conscryptProvider); + SecretKeySpec key = new SecretKeySpec(new byte[32], "XChaCha20"); + IvParameterSpec longIv = new IvParameterSpec(new byte[25]); + + assertThrows(InvalidAlgorithmParameterException.class, + () -> c.init(Cipher.ENCRYPT_MODE, key, longIv)); + } + + @Test + public void chaCha20Poly1305_invalidKeySize_throwsException() throws Exception { + Cipher c = Cipher.getInstance("ChaCha20/Poly1305/NoPadding", conscryptProvider); + // Expects 32 bytes key + SecretKeySpec shortKey = new SecretKeySpec(new byte[16], "ChaCha20"); + IvParameterSpec iv = new IvParameterSpec(new byte[12]); + + assertThrows(InvalidKeyException.class, () -> c.init(Cipher.ENCRYPT_MODE, shortKey, iv)); + } + + @Test + public void xChaCha20Poly1305_invalidKeySize_throwsException() throws Exception { + Cipher c = Cipher.getInstance("XChaCha20/Poly1305/NoPadding", conscryptProvider); + // Expects 32 bytes key + SecretKeySpec shortKey = new SecretKeySpec(new byte[16], "XChaCha20"); + IvParameterSpec iv = new IvParameterSpec(new byte[24]); + + assertThrows(InvalidKeyException.class, () -> c.init(Cipher.ENCRYPT_MODE, shortKey, iv)); + } + + @Test + public void xChaCha20Poly1305_chaCha20Key_encryptAndDecrypt_succeeds() throws Exception { + Cipher c = Cipher.getInstance("XChaCha20/Poly1305/NoPadding", conscryptProvider); + + byte[] keyBytes = new byte[32]; + SecretKeySpec chaCha20Key = new SecretKeySpec(keyBytes, "ChaCha20"); + SecretKeySpec xChaCha20Key = new SecretKeySpec(keyBytes, "XChaCha20"); + IvParameterSpec iv = new IvParameterSpec(new byte[24]); + + c.init(Cipher.ENCRYPT_MODE, chaCha20Key, iv); + byte[] plaintext = "Hello World".getBytes(TestUtils.UTF_8); + byte[] ciphertext = c.doFinal(plaintext); + + // Only the length of a key is checked, not its algorithm name. + c.init(Cipher.DECRYPT_MODE, xChaCha20Key, iv); + byte[] decrypted = c.doFinal(ciphertext); + + assertArrayEquals(plaintext, decrypted); + } + + @Test + public void chaCha20Poly1305_xChaCha20Key_encryptAndDecrypt_succeeds() throws Exception { + Cipher c = Cipher.getInstance("ChaCha20/Poly1305/NoPadding", conscryptProvider); + + byte[] keyBytes = new byte[32]; + SecretKeySpec xChaCha20Key = new SecretKeySpec(keyBytes, "XChaCha20"); + SecretKeySpec chaCha20Key = new SecretKeySpec(keyBytes, "ChaCha20"); + IvParameterSpec iv = new IvParameterSpec(new byte[12]); + + c.init(Cipher.ENCRYPT_MODE, xChaCha20Key, iv); + byte[] plaintext = "Hello World".getBytes(TestUtils.UTF_8); + byte[] ciphertext = c.doFinal(plaintext); + + // Only the length of a key is checked, not its algorithm name. + c.init(Cipher.DECRYPT_MODE, chaCha20Key, iv); + byte[] decrypted = c.doFinal(ciphertext); + + assertArrayEquals(plaintext, decrypted); + } + + @Test + public void chaCha20_keyGenerator_generatesCorrectKey() throws Exception { + KeyGenerator kg = KeyGenerator.getInstance("ChaCha20", conscryptProvider); + SecretKey key = kg.generateKey(); + assertThat(key.getAlgorithm()).isEqualTo("ChaCha20"); + assertThat(key.getEncoded()).hasLength(32); // 256 bits + } + + @Test + public void xChaCha20_keyGenerator_generatesCorrectKey() throws Exception { + KeyGenerator kg = KeyGenerator.getInstance("XChaCha20", conscryptProvider); + SecretKey key = kg.generateKey(); + assertThat(key.getAlgorithm()).isEqualTo("XChaCha20"); + assertThat(key.getEncoded()).hasLength(32); // 256 bits + } + + @Test + public void xChaCha20Poly1305_testVectors_encryptAndDecrypt_succeeds() throws Exception { + List vectors = TestUtils.readTestVectors("crypto/xchacha20-poly1305.txt"); + + for (TestVector vector : vectors) { + String name = vector.getString("name"); + byte[] keyBytes = vector.getBytes("key"); + byte[] ivBytes = vector.getBytes("iv"); + byte[] plaintext = vector.getBytes("plaintext"); + byte[] ciphertext = vector.getBytes("ciphertext"); + byte[] tag = vector.getBytes("tag"); + byte[] aad = vector.getBytes("aad"); + + SecretKeySpec key = new SecretKeySpec(keyBytes, "XChaCha20"); + IvParameterSpec iv = new IvParameterSpec(ivBytes); + + Cipher c = Cipher.getInstance("XChaCha20/Poly1305/NoPadding", conscryptProvider); + + // Test encryption + c.init(Cipher.ENCRYPT_MODE, key, iv); + if (aad.length > 0) { + c.updateAAD(aad); + } + byte[] encrypted = c.doFinal(plaintext); + + byte[] expectedOutput = new byte[ciphertext.length + tag.length]; + System.arraycopy(ciphertext, 0, expectedOutput, 0, ciphertext.length); + System.arraycopy(tag, 0, expectedOutput, ciphertext.length, tag.length); + + assertArrayEquals("Encryption failed for " + name, expectedOutput, encrypted); + + // Test decryption + c.init(Cipher.DECRYPT_MODE, key, iv); + if (aad.length > 0) { + c.updateAAD(aad); + } + byte[] decrypted = c.doFinal(encrypted); + assertArrayEquals("Decryption failed for " + name, plaintext, decrypted); + } + } +} diff --git a/common/src/test/java/org/conscrypt/SlhDsaTest.java b/common/src/test/java/org/conscrypt/SlhDsaTest.java index f3641e65e..807a16ecd 100644 --- a/common/src/test/java/org/conscrypt/SlhDsaTest.java +++ b/common/src/test/java/org/conscrypt/SlhDsaTest.java @@ -130,15 +130,13 @@ public void prehashSignAndVerify_works() throws Exception { PublicKey publicKey = keyPair.getPublic(); byte[] msg = new byte[123]; - Signature ss = - Signature.getInstance("SLH-DSA-SHA2-128S-WITH-SHA384", conscryptProvider); + Signature ss = Signature.getInstance("SLH-DSA-SHA2-128S-WITH-SHA384", conscryptProvider); ss.initSign(privateKey); ss.update(msg); byte[] sig = ss.sign(); assertEquals(7856, sig.length); - Signature sv = - Signature.getInstance("SLH-DSA-SHA2-128S-WITH-SHA384", conscryptProvider); + Signature sv = Signature.getInstance("SLH-DSA-SHA2-128S-WITH-SHA384", conscryptProvider); sv.initVerify(publicKey); sv.update(msg); boolean verified = sv.verify(sig); diff --git a/common/src/test/java/org/conscrypt/TrustManagerImplTest.java b/common/src/test/java/org/conscrypt/TrustManagerImplTest.java index bb633c054..1771b4319 100644 --- a/common/src/test/java/org/conscrypt/TrustManagerImplTest.java +++ b/common/src/test/java/org/conscrypt/TrustManagerImplTest.java @@ -230,6 +230,63 @@ public void testSetNetworkSecurityPolicy() throws Exception { assertEquals(nsp, tm.getNetworkSecurityPolicy()); } + @Test + public void testIntermediateExtendedKeyUsage() throws Exception { + TestUtils.assumeExtendedTrustManagerAvailable(); + + TestKeyStore rootCaStore = new TestKeyStore.Builder() + .aliasPrefix("root") + .subject("CN=Test Root Certificate Authority") + .ca(true) + .build(); + KeyStore.PrivateKeyEntry rootEntry = rootCaStore.getPrivateKey("RSA", "RSA"); + X509Certificate rootCert = (X509Certificate) rootEntry.getCertificate(); + + TestKeyStore clientOnlyIntermediateStore = new TestKeyStore.Builder() + .aliasPrefix("intermediate") + .subject("CN=Test Intermediate Certificate Authority") + .ca(true) + .signer(rootEntry) + .rootCa(rootCert) + .addExtendedKeyUsage(org.bouncycastle.asn1.x509.KeyPurposeId.id_kp_clientAuth, false) + .build(); + KeyStore.PrivateKeyEntry intermediateEntry = clientOnlyIntermediateStore.getPrivateKey("RSA", "RSA"); + X509Certificate intermediateCert = (X509Certificate) intermediateEntry.getCertificate(); + + TestKeyStore serverStore = new TestKeyStore.Builder() + .aliasPrefix("server") + .subject("CN=Test Server") + .signer(intermediateEntry) + .rootCa(rootCert) + .addExtendedKeyUsage(org.bouncycastle.asn1.x509.KeyPurposeId.id_kp_serverAuth, false) + .build(); + KeyStore.PrivateKeyEntry serverEntry = serverStore.getPrivateKey("RSA", "RSA"); + X509Certificate serverCert = (X509Certificate) serverEntry.getCertificate(); + + X509Certificate[] serverChain = new X509Certificate[] {serverCert, intermediateCert}; + TrustManagerImpl tm = (TrustManagerImpl) trustManager(rootCert); + + try { + tm.checkServerTrusted(serverChain, "RSA"); + fail("Expected CertificateException when intermediate CA lacks serverAuth EKU"); + } catch (CertificateException expected) { + // Expected + } + + TestKeyStore clientStore = new TestKeyStore.Builder() + .aliasPrefix("client") + .subject("CN=Test Client") + .signer(intermediateEntry) + .rootCa(rootCert) + .addExtendedKeyUsage(org.bouncycastle.asn1.x509.KeyPurposeId.id_kp_clientAuth, false) + .build(); + KeyStore.PrivateKeyEntry clientEntry = clientStore.getPrivateKey("RSA", "RSA"); + X509Certificate clientCert = (X509Certificate) clientEntry.getCertificate(); + + X509Certificate[] clientChain = new X509Certificate[] {clientCert, intermediateCert}; + tm.checkClientTrusted(clientChain, "RSA"); + } + private X509TrustManager trustManager(X509Certificate ca) throws Exception { KeyStore keyStore = TestKeyStore.createKeyStore(); keyStore.setCertificateEntry("alias", ca); diff --git a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestAES.java b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestAES.java index 192bd2bba..3c3497b2f 100644 --- a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestAES.java +++ b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestAES.java @@ -21,9 +21,9 @@ // android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; import org.conscrypt.TestUtils; -import org.junit.ClassRule; +// android-add: import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.TestRule; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestDESede.java b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestDESede.java index 858755fea..28f3af6d0 100644 --- a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestDESede.java +++ b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestDESede.java @@ -21,9 +21,9 @@ // android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; import org.conscrypt.TestUtils; -import org.junit.ClassRule; +// android-add: import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.TestRule; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestEC.java b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestEC.java index 53a60f47b..c0aa015fc 100644 --- a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestEC.java +++ b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestEC.java @@ -21,9 +21,9 @@ // android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; import org.conscrypt.TestUtils; -import org.junit.ClassRule; +// android-add: import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.TestRule; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestGCM.java b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestGCM.java index 8ec33a416..5efbae96b 100644 --- a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestGCM.java +++ b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestGCM.java @@ -23,9 +23,9 @@ // android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; import org.conscrypt.TestUtils; -import org.junit.ClassRule; +// android-add: import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.TestRule; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestOAEP.java b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestOAEP.java index 56b755e45..931164e61 100644 --- a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestOAEP.java +++ b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestOAEP.java @@ -21,9 +21,9 @@ // android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; import org.conscrypt.TestUtils; -import org.junit.ClassRule; +// android-add: import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.TestRule; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestEC.java b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestEC.java index 87192b6a6..dfc819c7b 100644 --- a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestEC.java +++ b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestEC.java @@ -17,9 +17,9 @@ // android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; -import org.junit.ClassRule; +// android-add: import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.TestRule; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java index a2e23bc53..82fe22cae 100644 --- a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java +++ b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java @@ -22,9 +22,9 @@ // android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; -import org.junit.ClassRule; +// android-add: import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.TestRule; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java index af4a2c71a..61aa0bb61 100644 --- a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java +++ b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java @@ -26,10 +26,10 @@ // android-add: import libcore.test.reasons.NonCtsReasons; // android-add: import libcore.test.reasons.NonMtsReasons; -// android-add: import org.junit.ClassRule; import org.conscrypt.TestUtils; -// android-add: import org.junit.rules.TestRule; +// android-add: import org.junit.ClassRule; import org.junit.Test; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/common/src/test/java/org/conscrypt/java/security/MessageDigestTest.java b/common/src/test/java/org/conscrypt/java/security/MessageDigestTest.java index 0d79008d4..8801c70ac 100644 --- a/common/src/test/java/org/conscrypt/java/security/MessageDigestTest.java +++ b/common/src/test/java/org/conscrypt/java/security/MessageDigestTest.java @@ -21,9 +21,9 @@ // android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; import org.conscrypt.TestUtils; -import org.junit.ClassRule; +// android-add: import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.TestRule; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/common/src/test/java/org/conscrypt/java/security/SignatureTest.java b/common/src/test/java/org/conscrypt/java/security/SignatureTest.java index 7acdc81fd..11c977ef8 100644 --- a/common/src/test/java/org/conscrypt/java/security/SignatureTest.java +++ b/common/src/test/java/org/conscrypt/java/security/SignatureTest.java @@ -29,6 +29,15 @@ // android-add: import libcore.test.reasons.NonCtsReasons; // android-add: import libcore.test.reasons.NonMtsReasons; +import org.conscrypt.TestUtils; +import org.conscrypt.testing.BrokenProvider; +import org.conscrypt.testing.OpaqueProvider; +// android-add: import org.junit.ClassRule; +import org.junit.Test; +// android-add: import org.junit.rules.TestRule; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.charset.Charset; @@ -64,14 +73,7 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; -import org.conscrypt.TestUtils; -import org.conscrypt.testing.BrokenProvider; -import org.conscrypt.testing.OpaqueProvider; -// android-add: import org.junit.ClassRule; -import org.junit.Test; -// android-add: import org.junit.rules.TestRule; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; + import tests.util.ServiceTester; @RunWith(JUnit4.class) diff --git a/common/src/test/java/org/conscrypt/java/security/cert/CertificateFactoryTest.java b/common/src/test/java/org/conscrypt/java/security/cert/CertificateFactoryTest.java index a44e8c27f..d778645db 100644 --- a/common/src/test/java/org/conscrypt/java/security/cert/CertificateFactoryTest.java +++ b/common/src/test/java/org/conscrypt/java/security/cert/CertificateFactoryTest.java @@ -34,9 +34,9 @@ import org.conscrypt.Conscrypt; import org.conscrypt.TestUtils; import org.conscrypt.java.security.StandardNames; -import org.junit.ClassRule; +// android-add: import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.TestRule; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/common/src/test/java/org/conscrypt/java/security/cert/X509CRLTest.java b/common/src/test/java/org/conscrypt/java/security/cert/X509CRLTest.java index 5f8dcc57a..dd7548cf6 100644 --- a/common/src/test/java/org/conscrypt/java/security/cert/X509CRLTest.java +++ b/common/src/test/java/org/conscrypt/java/security/cert/X509CRLTest.java @@ -26,9 +26,9 @@ // android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; import org.conscrypt.TestUtils; -import org.junit.ClassRule; +// android-add: import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.TestRule; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/common/src/test/java/org/conscrypt/java/security/cert/X509CertificateTest.java b/common/src/test/java/org/conscrypt/java/security/cert/X509CertificateTest.java index 6085094e4..cbe49837d 100644 --- a/common/src/test/java/org/conscrypt/java/security/cert/X509CertificateTest.java +++ b/common/src/test/java/org/conscrypt/java/security/cert/X509CertificateTest.java @@ -26,7 +26,9 @@ // android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; import org.conscrypt.TestUtils; +// android-add: import org.junit.ClassRule; import org.junit.Test; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -911,15 +913,12 @@ public void testEcdsaCert() { @Test public void testRsaPssCert() throws Exception { - ServiceTester.test("CertificateFactory") - .withAlgorithm("X509") - .run( - (p, algorithm) -> { - X509Certificate c = certificateFromPEM(p, CERT_WITH_RSA_PSS); - assertEquals("1.2.840.113549.1.1.10", c.getSigAlgOID()); - assertEquals("RSASSA-PSS", c.getSigAlgName()); - c.verify(c.getPublicKey()); - }); + ServiceTester.test("CertificateFactory").withAlgorithm("X509").run((p, algorithm) -> { + X509Certificate c = certificateFromPEM(p, CERT_WITH_RSA_PSS); + assertEquals("1.2.840.113549.1.1.10", c.getSigAlgOID()); + assertEquals("RSASSA-PSS", c.getSigAlgName()); + c.verify(c.getPublicKey()); + }); } @Test diff --git a/common/src/test/java/org/conscrypt/javax/crypto/AeadCipherTest.java b/common/src/test/java/org/conscrypt/javax/crypto/AeadCipherTest.java index 380273f22..eadbec7f8 100644 --- a/common/src/test/java/org/conscrypt/javax/crypto/AeadCipherTest.java +++ b/common/src/test/java/org/conscrypt/javax/crypto/AeadCipherTest.java @@ -57,7 +57,10 @@ public static Iterable ciphers() { new GCMParameterSpec(128, new byte[12])), new CipherParam("ChaCha20/Poly1305/NoPadding", new SecretKeySpec(new byte[32], "ChaCha20"), - new IvParameterSpec(new byte[12]))); + new IvParameterSpec(new byte[12])), + new CipherParam("XChaCha20/Poly1305/NoPadding", + new SecretKeySpec(new byte[32], "XChaCha20"), + new IvParameterSpec(new byte[24]))); } private final CipherParam param; diff --git a/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java b/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java index 874f886cf..9a7df9139 100644 --- a/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java +++ b/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java @@ -37,9 +37,9 @@ import org.conscrypt.java.security.TestKeyStore; import org.junit.Assume; import org.junit.BeforeClass; -import org.junit.ClassRule; +// android-add: import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.TestRule; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -219,6 +219,9 @@ private static String getBaseAlgorithm(String algorithm) { if (algorithm.startsWith("CHACHA20/")) { return "CHACHA20"; } + if (algorithm.startsWith("XCHACHA20/")) { + return "XCHACHA20"; + } if (algorithm.startsWith("DESEDE/")) { return "DESEDE"; } @@ -300,7 +303,8 @@ private static boolean isPBE(String algorithm) { private static boolean isAEAD(String algorithm) { return "GCM".equals(algorithm) || algorithm.contains("/GCM/") || algorithm.contains("/GCM-SIV/") - || algorithm.equals("CHACHA20/POLY1305/NOPADDING"); + || algorithm.equals("CHACHA20/POLY1305/NOPADDING") + || algorithm.equals("XCHACHA20/POLY1305/NOPADDING"); } private static boolean isStreamMode(String algorithm) { @@ -431,6 +435,7 @@ private synchronized static Key getDecryptKey(String algorithm) { setExpectedBlockSize("ARC4", 0); setExpectedBlockSize("CHACHA20", 0); setExpectedBlockSize("CHACHA20/POLY1305/NOPADDING", 0); + setExpectedBlockSize("XCHACHA20/POLY1305/NOPADDING", 0); setExpectedBlockSize("PBEWITHSHAAND40BITRC4", 0); setExpectedBlockSize("PBEWITHSHAAND128BITRC4", 0); @@ -678,6 +683,7 @@ private static int getExpectedBlockSize(String algorithm, int mode, String provi setExpectedOutputSize("ARCFOUR", 0); setExpectedOutputSize("CHACHA20", 0); setExpectedOutputSize("CHACHA20/POLY1305/NOPADDING", 0); + setExpectedOutputSize("XCHACHA20/POLY1305/NOPADDING", 0); setExpectedOutputSize("PBEWITHSHAAND40BITRC4", 0); setExpectedOutputSize("PBEWITHSHAAND128BITRC4", 0); @@ -981,6 +987,11 @@ private static AlgorithmParameterSpec getEncryptAlgorithmParameterSpec(String al new SecureRandom().nextBytes(iv); return new IvParameterSpec(iv); } + if (algorithm.equals("XCHACHA20/POLY1305/NOPADDING")) { + final byte[] iv = new byte[24]; + new SecureRandom().nextBytes(iv); + return new IvParameterSpec(iv); + } return null; } @@ -1072,7 +1083,9 @@ public void test_getInstance() throws Exception { if (!seenBaseCipherNames.contains(baseCipherName) && !(baseCipherName.equals("AES_128") || baseCipherName.equals("AES_192") - || baseCipherName.equals("AES_256"))) { + || baseCipherName.equals("AES_256") + // There is no bare XChaCha20, only XChaCha20/Poly1305/NoPadding. + || baseCipherName.equals("XCHACHA20"))) { seenCiphersWithModeAndPadding.add(baseCipherName); } if (!Conscrypt.isConscrypt(provider)) { diff --git a/common/src/test/java/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java b/common/src/test/java/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java index 5cce097e4..bd077d72c 100644 --- a/common/src/test/java/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java +++ b/common/src/test/java/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java @@ -30,9 +30,9 @@ import org.conscrypt.Conscrypt; import org.conscrypt.TestUtils; import org.junit.BeforeClass; -import org.junit.ClassRule; +// android-add: import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.TestRule; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/common/src/test/java/org/conscrypt/javax/crypto/KeyGeneratorTest.java b/common/src/test/java/org/conscrypt/javax/crypto/KeyGeneratorTest.java index f3675c8d4..297e8a3c6 100644 --- a/common/src/test/java/org/conscrypt/javax/crypto/KeyGeneratorTest.java +++ b/common/src/test/java/org/conscrypt/javax/crypto/KeyGeneratorTest.java @@ -24,9 +24,9 @@ import org.conscrypt.TestUtils; import org.conscrypt.java.security.StandardNames; import org.junit.BeforeClass; -import org.junit.ClassRule; +// android-add: import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.TestRule; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -114,6 +114,7 @@ private static List getKeySizes(String algorithm) throws Exception { putKeySize("Blowfish", 32 + 8); putKeySize("Blowfish", 448); putKeySize("ChaCha20", 256); + putKeySize("XChaCha20", 256); putKeySize("DES", 56); putKeySize("DESede", 112); putKeySize("DESede", 168); diff --git a/common/src/test/java/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java b/common/src/test/java/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java index 299253790..300b01dce 100644 --- a/common/src/test/java/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java +++ b/common/src/test/java/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java @@ -63,7 +63,7 @@ public void keyAgreement_xdh_works() throws Exception { // SunEC in OpenJDK 11 has a bug where the format specified in RFC 8410 // Section 7. if (p.getName().equals("SunEC") - && System.getProperty("java.specification.version").equals("11")) { + && System.getProperty("java.specification.version").equals("11")) { continue; } KeyFactory kf = KeyFactory.getInstance("XDH", p); @@ -89,7 +89,7 @@ public void keyAgreement_x25519_works() throws Exception { // SunEC in OpenJDK 11 has a bug where the format specified in RFC 8410 // Section 7. if (p.getName().equals("SunEC") - && System.getProperty("java.specification.version").equals("11")) { + && System.getProperty("java.specification.version").equals("11")) { continue; } KeyFactory kf = KeyFactory.getInstance("X25519", p); diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java index 202234197..acf74af85 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java @@ -267,19 +267,19 @@ public void test_SSLEngine_clientAuth() throws Exception { // TODO Fix KnownFailure "init - invalid private key" try (TestSSLContext clientAuthContext = - new TestSSLContext.Builder() - .client(TestKeyStore.getClientCertificate()) - .server(TestKeyStore.getServer()) - .clientProtocol(clientVersion) - .serverProtocol(serverVersion) - .build(); + new TestSSLContext.Builder() + .client(TestKeyStore.getClientCertificate()) + .server(TestKeyStore.getServer()) + .clientProtocol(clientVersion) + .serverProtocol(serverVersion) + .build(); TestSSLEnginePair p = - TestSSLEnginePair.create(clientAuthContext, new TestSSLEnginePair.Hooks() { - @Override - void beforeBeginHandshake(SSLEngine client, SSLEngine server) { - server.setWantClientAuth(true); - } - })) { + TestSSLEnginePair.create(clientAuthContext, new TestSSLEnginePair.Hooks() { + @Override + void beforeBeginHandshake(SSLEngine client, SSLEngine server) { + server.setWantClientAuth(true); + } + })) { assertConnected(p); assertNotNull(p.client.getSession().getLocalCertificates()); TestKeyStore.assertChainLength(p.client.getSession().getLocalCertificates()); @@ -296,20 +296,19 @@ void beforeBeginHandshake(SSLEngine client, SSLEngine server) { */ @Test public void test_SSLEngine_clientAuthWantedNoClientCert() throws Exception { - try (TestSSLContext clientAuthContext = - new TestSSLContext.Builder() - .client(TestKeyStore.getClient()) - .server(TestKeyStore.getServer()) - .clientProtocol(clientVersion) - .serverProtocol(serverVersion) - .build(); + try (TestSSLContext clientAuthContext = new TestSSLContext.Builder() + .client(TestKeyStore.getClient()) + .server(TestKeyStore.getServer()) + .clientProtocol(clientVersion) + .serverProtocol(serverVersion) + .build(); TestSSLEnginePair p = - TestSSLEnginePair.create(clientAuthContext, new TestSSLEnginePair.Hooks() { - @Override - void beforeBeginHandshake(SSLEngine client, SSLEngine server) { - server.setWantClientAuth(true); - } - })) { + TestSSLEnginePair.create(clientAuthContext, new TestSSLEnginePair.Hooks() { + @Override + void beforeBeginHandshake(SSLEngine client, SSLEngine server) { + server.setWantClientAuth(true); + } + })) { assertConnected(p); } } @@ -322,20 +321,19 @@ void beforeBeginHandshake(SSLEngine client, SSLEngine server) { */ @Test public void test_SSLEngine_clientAuthNeededNoClientCert() throws Exception { - try (TestSSLContext clientAuthContext = - new TestSSLContext.Builder() - .client(TestKeyStore.getClient()) - .server(TestKeyStore.getServer()) - .clientProtocol(clientVersion) - .serverProtocol(serverVersion) - .build()) { + try (TestSSLContext clientAuthContext = new TestSSLContext.Builder() + .client(TestKeyStore.getClient()) + .server(TestKeyStore.getServer()) + .clientProtocol(clientVersion) + .serverProtocol(serverVersion) + .build()) { try (TestSSLEnginePair p = - TestSSLEnginePair.create(clientAuthContext, new TestSSLEnginePair.Hooks() { - @Override - void beforeBeginHandshake(SSLEngine client, SSLEngine server) { - server.setNeedClientAuth(true); - } - })) { + TestSSLEnginePair.create(clientAuthContext, new TestSSLEnginePair.Hooks() { + @Override + void beforeBeginHandshake(SSLEngine client, SSLEngine server) { + server.setNeedClientAuth(true); + } + })) { fail(); } catch (SSLException expected) { // Ignored. @@ -679,11 +677,10 @@ public X509Certificate[] getAcceptedIssuers() { } ThrowingTrustManager trustManager = new ThrowingTrustManager(); try (TestSSLContext c = TestSSLContext.newBuilder() - .clientProtocol(clientVersion) - .serverProtocol(serverVersion) - .clientTrustManager(trustManager) - .build()) { - + .clientProtocol(clientVersion) + .serverProtocol(serverVersion) + .clientTrustManager(trustManager) + .build()) { // The following code is taken from TestSSLEnginePair.connect() SSLSession session = c.clientContext.createSSLEngine().getSession(); @@ -703,16 +700,18 @@ public X509Certificate[] getAcceptedIssuers() { try { while (true) { - boolean clientDone = client.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING; - boolean serverDone = server.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING; + boolean clientDone = + client.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING; + boolean serverDone = + server.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING; if (clientDone && serverDone) { break; } boolean progress = TestSSLEnginePair.handshakeStep( client, clientToServer, serverToClient, scratch, new boolean[1]); - progress |= TestSSLEnginePair.handshakeStep(server, serverToClient, clientToServer, - scratch, new boolean[1]); + progress |= TestSSLEnginePair.handshakeStep( + server, serverToClient, clientToServer, scratch, new boolean[1]); assertFalse(trustManager.threw); if (!progress) { break; @@ -761,32 +760,33 @@ public void sniHandlerIsCalledAfterHandshakeAndBeforeServerCert() throws Excepti final AtomicReference serverHost = new AtomicReference<>(); final AtomicBoolean serverAliasCalled = new AtomicBoolean(false); - try (TestSSLContext c = - TestSSLContext.newBuilder() - .clientProtocol(clientVersion) - .serverProtocol(serverVersion) - .server(addServerCertListener(new Runnable() { - @Override - public void run() { - assertEquals("cert is loaded after sni", host, serverHost.get()); - serverAliasCalled.set(true); - } - })) - .build(); + try (TestSSLContext c = TestSSLContext.newBuilder() + .clientProtocol(clientVersion) + .serverProtocol(serverVersion) + .server(addServerCertListener(new Runnable() { + @Override + public void run() { + assertEquals("cert is loaded after sni", host, + serverHost.get()); + serverAliasCalled.set(true); + } + })) + .build(); TestSSLEnginePair pair = TestSSLEnginePair.create(c, new TestSSLEnginePair.Hooks() { @Override void beforeBeginHandshake(SSLEngine client, SSLEngine server) { Conscrypt.setHostname(client, host); SSLParameters sslParameters = server.getSSLParameters(); - sslParameters.setSNIMatchers(Collections.singleton(new SNIMatcher(0) { - @Override - public boolean matches(SNIServerName sniServerName) { - String host = ((SNIHostName) sniServerName).getAsciiName(); - serverHost.set(host); - return true; - } - })); + sslParameters.setSNIMatchers( + Collections.singleton(new SNIMatcher(0) { + @Override + public boolean matches(SNIServerName sniServerName) { + String host = ((SNIHostName) sniServerName).getAsciiName(); + serverHost.set(host); + return true; + } + })); server.setSSLParameters(sslParameters); } })) { diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SslEngineAndSocketResumptionTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SslEngineAndSocketResumptionTest.java index 22218a1af..e2ca67e2a 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/SslEngineAndSocketResumptionTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SslEngineAndSocketResumptionTest.java @@ -126,7 +126,7 @@ void beforeBeginHandshake(SSLEngine client, SSLEngine server) { // 2. Second handshake via SSLSocket using the same clientContext (same host and port) SSLSocket clientSocket = (SSLSocket) context.clientContext.getSocketFactory().createSocket( - context.host.getHostName(), context.port); + context.host.getHostName(), context.port); SSLSocket serverSocket = (SSLSocket) context.serverSocket.accept(); try { @@ -151,7 +151,7 @@ public void test_resumption_socketThenEngine() throws Exception { // 1. First handshake via SSLSocket SSLSocket clientSocket = (SSLSocket) context.clientContext.getSocketFactory().createSocket( - context.host.getHostName(), context.port); + context.host.getHostName(), context.port); SSLSocket serverSocket = (SSLSocket) context.serverSocket.accept(); connectSockets(clientSocket, serverSocket); diff --git a/common/src/test/resources/crypto/xchacha20-poly1305.txt b/common/src/test/resources/crypto/xchacha20-poly1305.txt new file mode 100644 index 000000000..5437abf96 --- /dev/null +++ b/common/src/test/resources/crypto/xchacha20-poly1305.txt @@ -0,0 +1,419 @@ +# Source: boringssl/src/crypto/cipher/test/xchacha20_poly1305_tests.txt +# Converted to key-value format + +name = Test case 1 +key = 1f4774fbe6324700d62dd6a104e7b3ca7160cfd958413f2afdb96695475f007e +iv = 029174e5102710975a8a4a936075eb3e0f470d436884d250 +plaintext = +aad = +ciphertext = +tag = f55cf0949af356f977479f1f187d7291 + +name = Test case 2 +key = eb27969c7abf9aff79348e1e77f1fcba7508ceb29a7471961b017aef9ceaf1c2 +iv = 990009311eab3459c1bee84b5b860bb5bdf93c7bec8767e2 +plaintext = e7ec3d4b9f +aad = +ciphertext = 66bd484861 +tag = 07e31b4dd0f51f0819a0641c86380f32 + +name = Test case 3 +key = 4b6d89dbd7d019c0e1683d4c2a497305c778e2089ddb0f383f2c7fa2a5a52153 +iv = 97525eb02a8d347fcf38c81b1be5c3ba59406241cf251ba6 +plaintext = 074db54ef9fbc680b41a +aad = +ciphertext = 1221898afd6f516f770f +tag = 75e7182e7d715f5a32ee6733fd324539 + +name = Test case 4 +key = 766997b1dc6c3c73b1f50e8c28c0fcb90f206258e685aff320f2d4884506c8f4 +iv = 30e7a9454892ef304776b6dc3d2c2f767ed97041b331c173 +plaintext = b8250c93ac6cf28902137b4522cc67 +aad = +ciphertext = e2a13eeff8831a35d9336cb3b5c5d9 +tag = 62fdf67735cad0172f9b88603b5f3c13 + +name = Test case 5 +key = 6585031b5649fcabd9d4971d4ac5646fc7dca22f991dfa7dac39647001004e20 +iv = 705ee25d03fec430e24c9c6ccaa633f5b86dd43682778278 +plaintext = 9a4ca0633886a742e0241f132e8f90794c34dfd4 +aad = +ciphertext = 0a8e6fd4cd1640be77c4c87dde4ae6222c887ed7 +tag = edc4fbc91dfa07021e74ae0d9d1c98dc + +name = Test case 6 +key = dfc6f7c86a10a319ebcb6362997e585f55b67f3434f47dc4039c2d67973e3077 +iv = 6097f30fd75229d928454c7d59a2d2c58bfddcb14c16438e +plaintext = 74c946a7f0733377e852a23087506a28dccef86e101a4359c0 +aad = +ciphertext = 6e8ea0bb4c2f1323841d8e236816c61c3295866b75cefb5c25 +tag = f16c0e9487ca7de5e7cb2a1b8bb370fc + +name = Test case 7 +key = 59b8d488773767c4804d918709cfec6c69a193371145bb94f183899851aaadac +iv = ad5bdf8f190ca2d2cc02a75bb62aa22274cb3c98fe2d25f2 +plaintext = 066b9ed10f16d3dc132b409aae02d8cac209dd9b4fb789c4d34725ab2a1f +aad = +ciphertext = 2bbd4542489006df66ad1462a932524642b139ddcbf86b6b480e9e6d976c +tag = ca4835419ba029bc57010a8cc8bca80c + +name = Test case 8 +key = 8c0cb4633cf8dc6b4b9552d1035f85517cb1ba4c36bcbc43338a8c6c7d15ce20 +iv = 8418b9655a0376fadefa3cdf8805815c4f7b56f467a74a95 +plaintext = 50c205a9c5d4088ba8e59a96fcd837f5170669854547678288199f1078ff2a81f0b19a +aad = +ciphertext = 8b55a12df1a85dd3fb19c34ab047a85849d15a30225bb5360bad1f0a8f5f2bd49f5898 +tag = bce13201df6e4a7e6d896262e45d969d + +name = Test case 9 +key = b45386a75a5772e34bd193e1946f69ebfb90c37ae4581d39c9669d75e4584f50 +iv = 9fb763d0926585b5f726af9b8e3babdb331e9aa97f8d99ed +plaintext = 64df0e341145d9e4a0d090153591a74893bc36cb9dae1e9570d8fee62e907cf004f9d8a360343483 +aad = +ciphertext = 3146d8a5c898edd832ec9d126e93b3a433ec97dc47dce0e1985bda88c88c6aeca46fc7d9a68e30ab +tag = 44fdb0d69abd8068442cb2ea6df8b2f2 + +name = Test case 10 +key = f2efbd358dd353639a162be39a957d27c0175d5ab72aeba4a266aeda434e4a58 +iv = 65a6f7ebe48de78beb183b518589a0afacf71b40a949fa59 +plaintext = f7473947996e6682a3b9c720f03cfaf26bbcdaf76c83342d2ad922435e227a5d1eacbd9bd6ea1727ec19fb0e42 +aad = +ciphertext = 778a0fb701b9d671ccfaf1454e8928158ede9bb4395119356a8133036840c1bcbb8fe5e19922fbbcf8b18596e7 +tag = 9d195a89fdd29ca271405d3330f996f9 + +name = Test case 11 +key = 9dd674fb4a30a7bb85fc78050479ab0e2c3cc9f9f5b8689a7a67413aca304b21 +iv = ad9e8fe15940694725f232e88f79cda7c82fe1b8aae58ba4 +plaintext = 7272bb6609cbd1399a0b89f6ea255165f99330aeb170ac88fccdd8e226df0952407e35718fb5edc9e987faabb271cc69f7e7 +aad = +ciphertext = 846901650cb38974463a18c367676e1579ebdaf3e96b57224e842f5d5f678f3270b9a15f01241795662befb3db0768800e25 +tag = 900004db3613acbeb33d65d74dd437d7 + +name = Test case 12 +key = 280cbe7380a0d8bb4d8dd4476012f2eeb388a37b8b71067969abb99f6a888007 +iv = 2e1854617c67002599e6b077a812c326deb22fe29d093cbb +plaintext = d0901ec3d31ece2832685ff577f383bdff26c31341ea254acee7c5929a5df74fea2aa964524dc680b2f55fbd4fea900e956c304cc4ac3c +aad = +ciphertext = 546370726cc63068d3520d67f4f57f65d03b9ecec21c2a8c7b1133089ad28b07025a7181bddeb4a49f514fac1a44f64ee3af33d778fb98 +tag = 39084e33e42a1b05f58da65ba487d138 + +name = Test case 13 +key = 887564f75afa78f595cdadcea7340d20f5c5a2df169d0ad14b15fe32ce337004 +iv = 54c11df13d1f444da80b0964caeb59474b17b23a650a33f5 +plaintext = f0f008eece79ecb24b715dff8a3456dfe253924b99f98f2f1b18564cced50925fca860d1c2d4785bdf4a964c76c3079efa6b37c4ba2cacc534fb590c +aad = +ciphertext = 32bb077268568d569b39e8ccdeeeb447ef424eaa2ffab565209a19b16a25952f897e5405bb0d67d8c9005d1c0b32687164d17fa4d0f412b80414c025 +tag = 0bac7c0f8dce12917fbd4ed1738ac0cc + +name = Test case 14 +key = 21c6aa88eb1a320d251f71a4b312ca75347040990d869a1dd2a1982c30fda2c7 +iv = 7dead2f1a3d9d45a9124a40efe8994300976991a4417ef4d +plaintext = +aad = e1bf7de4 +ciphertext = +tag = 341e9d0687006f981bced2f985f953e6 + +name = Test case 15 +key = 0c97b9a65ffcd80b8f7c20c3904d0d6dd8809a7f97d7f46d39a12c198a85da5d +iv = 1f2c1dbc5f52fc9c8f9ca7695515d01d15904b86f703fba3 +plaintext = ecaf65b66d +aad = bd8a6f18 +ciphertext = 8d1b2b0e38 +tag = 27a7c7ac8bda627085414f0f31206a07 + +name = Test case 16 +key = 4ab5e3595f39c4379a924e5f8ebcf3279075c08d18daff01d9ddfa40e03faf12 +iv = 94e6ddc294f5f1531924ec018823343ebcc220a88ea5ee33 +plaintext = c91b73abe5316c3effc6 +aad = c576f6ea +ciphertext = abe960fbc64b339c53b1 +tag = 7ebae48a2ff10117069324f04619ad6f + +name = Test case 17 +key = a1e6146c71c2ea22300e9063455f621e15bd5bf1a3762e17f845e1aba5dd5a9c +iv = 82ddb6929abff8a9ad03dfb86c0bb3e7c092d45ebfa60a1b +plaintext = f011f32ccc2955158c117f53cf7b12 +aad = 5d14bc05 +ciphertext = 44592321c665f51e9ffea052df1fea +tag = d556798b97f9b647729801419424affc + +name = Test case 18 +key = 7a1af30362c27fd55b8c24b7fca324d350decee1d1f8fae56b66253a9dd127dd +iv = 61201d6247992002e24e1a893180d4f0c19a3ae4cc74bf0c +plaintext = 5c7150b6a4daa362e62f82f676fdc4c4b558df64 +aad = 00c49210 +ciphertext = 27d9e2730b6809c08efbd4b0d24639c7b67486f3 +tag = 5889fdee25379960038778e36b2cedb2 + +name = Test case 19 +key = 0b3fd9073e545ac44a7967263ead139c9547f7a54f06228fd3c8609fa2620784 +iv = 6450e1097d6f9ea76eb42e8e65972d501041c3a58baf8770 +plaintext = d679ae442b0351e5bff9906b099d45aab4f6aea5306a7a794f +aad = 318d292b +ciphertext = a3f9ee45316d7b0f948a26145ee4fd0552bc6dc25e577e777a +tag = 0068a401a194b8417ec0e198baa81830 + +name = Test case 20 +key = 047c7d378fe80c02ee48df6f679a859253aed534fdcdd87023eb3d2f93fcafe3 +iv = ed240b0ff6f8ac585b3ea1ab2dab8080fc2f6401b010c5d0 +plaintext = 7288afb4e0fa5c58602090a75c10d84b5f5f1c0e03498519afe457251aa7 +aad = e4310302 +ciphertext = 87906b14ca3e32ab01523b31ae0bb74590ce9e1df0811e743a2c7a93415a +tag = 3a0abeab93792b1ffe768d316da74741 + +name = Test case 21 +key = 1ad4e42acc5dfd07eb0a2456e9103cd0e150a36c667eb2f2b73c0d1ac1089ce3 +iv = 48efb52387284c5d38b4940c75f0c39a3f81f60bfebb48cb +plaintext = da7edb5b3193b4484f09efa85fcf85600968ecdc537d3829a469c866ee67b0df677866 +aad = 446be8e3 +ciphertext = b76457ca99e95b6539b12f1d6bdac55a6d5c6469b1ff274459363ec05241f7e6e5d3ce +tag = 06880ee508ce929da5a81f8b9de0031c + +name = Test case 22 +key = 702a554c1b703d4dd69ad51234293ab787a01e15bdb3ce88bf89e18c01a67164 +iv = ea535d9c371241b9850b8b4a596b63db79eea60bd2cd9fbb +plaintext = a97156e9b39d05c00b811552d22088d7ee090a117a7f08adac574820d592021f16207720d49fb5fd +aad = ba5790e3 +ciphertext = 8d0b2b04479c33287096f0c6276a73f6c037edc1a2b28f8d3b2b8e6d4c5f9dc5113309dd3ecb15e6 +tag = 3cf303305e12924d29c223976699fb73 + +name = Test case 23 +key = 1bb7303fefa4d8d344bb9a215901b2314324bf1f3aeb9df5d1c1532c3a55ebf1 +iv = a304551e5f0dc98995ddfee6215a9995023a3696debfd302 +plaintext = 6cf6819ce3e7ed9d4f85f4a5699701dbcaf3161adc210c0b7825ddfd83d6d7c685db62f68b3801ccc8a786066d +aad = 901c5feb +ciphertext = bc5ef09c111f76e54f897e6fce4aee1d25b6ed934f641ed5262d0c5eed45f610a6aea3b58b7771e34256d43a16 +tag = b83f73f7995ba1b243dbf48ddfeb8e3a + +name = Test case 24 +key = 24b294f6cbac10d87158d1c6aca83b337d596132afac7633f69a3b3e58823f11 +iv = 805772ff619cc6fcc5ec0e9965435d6f74a2290c055ec754 +plaintext = 65e8581286868caabcec1a9814db00b805edc660b94ee3babc6ce19a3ca868bd322105484d59b4ce02ced4071bc16642a1f2 +aad = 7ae1c561 +ciphertext = fe1d463b1466e8e411f0b0700f90760472ee5141f3e5afef43fd729f1623dca75cd4d00576765b335f8b2b77b00527599cb3 +tag = 111d8540fd5ec04b9ba16ed810133026 + +name = Test case 25 +key = 38e63e8b6402ac3f6d1641a1e3b74d2074be0fe41129975a3ff62b74ca52af05 +iv = 228d671b036710cbdaa72e9bf1d9ed6982b0bb3428a69fd6 +plaintext = 20a8d18878924d09aac32853c10e73dbd741134b7050ae6999839f2dbc727cb0052b5497c4bbd2a89e716278f15c81b871953614a49693 +aad = e9e6ac73 +ciphertext = 80e0fe8eb26e5df229c6d939c944d440a37aa3cabf76eab5b9a420095513021ea4241ab367f6f44a20817b14631549ae6c96aa963970e1 +tag = 1e80fbafcc7168e0494fce4cd76d692c + +name = Test case 26 +key = 4325dd8406fdb8431a81f1b5db3603995256de36121019724cca2190c87a6e83 +iv = dcbf3077b36d5d678d668fd2d0c99284c780b55c4658ea75 +plaintext = 4f599ad04f79be9add10fdc649b8be53e1062ea5e9c2bed22265dc6fb30d5ab4fd4425b38ff14d8e68013405bec1eff8c9ef3069902e492aac73dcd9 +aad = 6fa0d757 +ciphertext = 7decbdc7043495c59ecc64e720436bb0708b586a46f8745f74391477f5a2520905dfcebc3765a330999013d309dfaa997bf70bab6a0b8f4f2a2a3cdf +tag = 051ec4ecce208d9be0cd17f434e13be3 + +name = Test case 27 +key = 2d3d9ed4bc9eb9668733bafbb73e88be2cd17021c3a23be69b981d9f0df71df1 +iv = 84cae69639240c82b58895997511f145e474ebe1b008f391 +plaintext = +aad = 64db597c26a4c3da +ciphertext = +tag = 2a22c4a962d46a719014ab7b0ffaf6d3 + +name = Test case 28 +key = 09ec4e79a02db53b19b54dd2d3592afc92c74ef57d1e0f51f3726a6631b1b73f +iv = 2907ced16e0777fedb1e2de30df11b3fd712af41dd714a4b +plaintext = b6e50cd4ea +aad = b5488e9b7f339b7b +ciphertext = 0163e75330 +tag = e29401c6d756adcc516580ae656852aa + +name = Test case 29 +key = 9d5ac25a417b8a57b85332979e8a7cbad23617bb27772bbccc2acb0acae7b755 +iv = ff152421688dd6af7fef87817b508493a32d97a06fbda4f3 +plaintext = 92f4b9bc809be77e6a0d +aad = 892b793f7a6e0727 +ciphertext = bcc594f59de8ee8c22c6 +tag = 1a8275816c0d32a1b6cfd41fa3889558 + +name = Test case 30 +key = eccf80c5f744d2ecc932f95ade0d9fe9327e19795023db1846d68d04720a2401 +iv = abc050fad8876589633b222d6a0f2e0bf709f73610aa23ee +plaintext = 45a380e438405314510c166bac6840 +aad = c32c9a1ce6852046 +ciphertext = 9fa452dc9ca04c16ff7bde9925e246 +tag = 3d5e826162fa78de3fc043af26044a08 + +name = Test case 31 +key = b1912d6bc3cff47f0c3beccff85d7cd915b70ab88d0d3a8a59e994e1b0da8ac8 +iv = d8756090a42eea14ff25be890e66bfe4949fad498776ea20 +plaintext = e2f85df2ebcfa6045bd521abfe8af37fc88a0be1 +aad = 4576bb59b78032c8 +ciphertext = 5eb6324aa48e0a4f72f5cb0a4917faf93af4209c +tag = 774f8077f039588495045fee07950e14 + +name = Test case 32 +key = 85162b111c9f3163f57c2cbc311a1e9aeed9dd6136b5784bc9c0b5052f8bffbd +iv = 23cdb8b546bb8a5a746b24446f0ab4199f0543d915ff51f1 +plaintext = dc81000077d5743beef09ac91663885d984212bbccf3dbe6f3 +aad = 3084f3e9c4d0a15f +ciphertext = 692d17ae0b524ec6edc0cf49b69ac90c99bed44691f7ae63b7 +tag = efe72ff84b3bccb4d83a27ddc574bc21 + +name = Test case 33 +key = b05ca358d8ca79f51283d83e2673bfb741c379ba271a773b8dd9c6a108e758d3 +iv = 9a53ad79f535c6e9da011463063c896f2ec7645e6e3548fc +plaintext = 44e793742c774020e7349c996418042dc0dc30ee2bfd2654008c8929a436 +aad = 71ab5948c5e0f4c6 +ciphertext = c5eddb7aeaa175b5f3dab68cf746f2acaf56fc62b29804629e25e2d63879 +tag = bec3b7a8b8dad22ff3d14d26273294d2 + +name = Test case 34 +key = abb5136a01354c765a96e832df58bec3b088bd19dc4d6bd6674f2f02007ebdaa +iv = 71267ac9f4fe5caa1d52cd85948a170a778f0141d54dbffe +plaintext = afb526fe41c4e2a767ce77c4145b9d054268f5f3b279237dec97f8bc46f9d158868b86 +aad = 047baa2b04748b62 +ciphertext = 0032d4c1e65da2266539464c5d3c2b1618454a6af0e7f1e3cfc87845c75f2f4ae8b03f +tag = b526a95a33f17ab61f2cdfc1e2dd486a + +name = Test case 35 +key = bb826ed38008a0d7fb34c0c1a1a1149d2cad16b691d5129cc83f5eff2b3e5748 +iv = 4e02fe0915d81e9d5a62e5b3551b9db882e3873c0aaa230d +plaintext = 20270d291a8d9791b0f5e35a64387bb4237bad61169841d7e1667c994ad49869c7d5580ffa752a2d +aad = db852a275081e29b +ciphertext = d740012efb7e1bb986ce2c535134a45f658b92163c109bdecf1ce5b836879fe9e006a56be1fac8d7 +tag = 21e931042e7df80695262198a06286c9 + +name = Test case 36 +key = 938d2c59f6f3e2e7316726537932372e05e8c1b5577aae0ee870bf712ff001ab +iv = fb4d71cf7eb2f70df9759a64c76a36b75203f88bf64f4edb +plaintext = 8910415d674a93c54c8f5e4aa88e59648d9a0a5039a66837d58ab14f0665a5f6d9af9b839f9033d0fe8bc58f19 +aad = a3fca278a63bf944 +ciphertext = 1905c6987a702980b7f87f1ed2d3ae073abe1401b23434f3db43b5c37c979c2068ce9a92afedcdc218003848ea +tag = 1bd712f64777381f68be5ccc73f364a3 + +name = Test case 37 +key = dd0521842f498d23236692a22db0eb2f0f14fef57577e5fb194503e206b0973d +iv = 519e0eee8f86c75c7a364e0905a5d10d82073e11b91083a5 +plaintext = 61ff13acb99c5a7fd1921ec787c8de23c1a712ff002b08cecc644a78c47341eab78e7680380c93c7d53d5e56ef050d6ff192 +aad = bb5c4e5ae8f7e461 +ciphertext = 9bfdb0fd195fa5d37da3416b3b1e8f67bd2a456eb0317c02aabf9aac9d833a19bda299e6388e7b7119be235761477a34d49e +tag = 0f0c03b8423583cb8305a74f622fa1f9 + +name = Test case 38 +key = 189bd84be3fb02723539b29cf76d41507c8b85b7217777ee1fb8f84a24aa7fee +iv = ef1bf39f22ba2edf86853505c24fafdf62c1a067963c63ba +plaintext = d5f96e240b5dd77b9fb2bf11c154fcbff312a791c3eb0717684e4fd84bf943e788050b47e76c427f42f3e5344b2636091603ba3b1d7a91 +aad = 93368a8e0900c7b6 +ciphertext = c55a8b7f587bee4f97514582c5115582abffd6312914d76c2568be6836f62ba098789ed897c9a7508a5dc214bf8c218664f29941ccdfd6 +tag = 78f87352dcb1143038c95dc6e7352cfd + +name = Test case 39 +key = 23a2dbfcd02d265805169fa86e6927c7d49c9a24d2707884e18955e32dafc542 +iv = 305c7851f46f23ea8d832d5ed09d266714fd14f82ba0f69c +plaintext = 224de94a938d49cad46144e657e548bd86690a1b57b81558095eace59df1c552600dea389aaa609304fbc1eadf2241f2118c8bdf04522e1898efe1d4 +aad = 0075b20502bd29b2 +ciphertext = 8e10c59369bbb0d72958100b05788498f59588795e075b8bce21d92d320206348b04010ced9b8cd3d651e825488915ce4a6e4f1af2f4d2f77b955376 +tag = c39f0595ae8112dea6ef96df1c12458b + +name = Test case 40 +key = 264e3c3f47bdf795cdde57d9a30be5a4da8b18463c0e3e05df28b7bf4e56410b +iv = 3ee09b6e205c261bf48ac53a9ba0afa460a5d5c0f2d80be8 +plaintext = +aad = 8eeec09d8972cb8ab0069554 +ciphertext = +tag = 245a034d84edab9fa6f0decb6b984766 + +name = Test case 41 +key = d8ba98a272b5f91797b04b114311c3b92b7f2e3bb72edb7f78ed311b9f8ea2ad +iv = 481de9a06eee76a501e3c2b9d7423d90596193ad9d8a6564 +plaintext = 9ee1a3134d +aad = 928653701f6d6c8429b08c0d +ciphertext = 459a07898f +tag = 9188ec8d8e3bd91dcfda48fcc76773f7 + +name = Test case 42 +key = ac9afd627a745df682bb003517056f07876eb94d2f8c610c61b6ac0d34ec4ec0 +iv = eaae7b8704530db1e8c3dcc968a00604a333c7c27ba51b16 +plaintext = f7c3f6ee2e9c03394dc8 +aad = 796620b367d5f041821baf69 +ciphertext = d4a69005790cc91d8d34 +tag = e4c83def113afcf83a1ea8cb204a0eae + +name = Test case 43 +key = ea1a07c1fd60a5421f1fb6c43b4318090e290c97aa3bfa037e6fc5ee00fd47d4 +iv = 37327805cce92b38a669affbca1de92e068727fcf6fbb09a +plaintext = 7002ca765b91913ee719e7521ef5ac +aad = 64e7c48fc3041eac0734737f +ciphertext = 9d8857a8c52a9ab3bf44b024b191b6 +tag = d072c31714a7d0fe1596fd443a96e715 + +name = Test case 44 +key = b3beb34fe0229fc8f49b354e941025bde6a788f25017a60e8a49591ed5d7e7da +iv = dd0e9fec76de1f6efb022b12164f7e9248b8e8c01d14ac02 +plaintext = acf360d7529a42be1f132f74745a940da9e823f2 +aad = 1489ca8d852f0a8547dbe8bc +ciphertext = 2e8718372d6e8167213cf112dc41c80377244f5a +tag = e4f31e8f84b9356999dc60989009e698 + +name = Test case 45 +key = 9357cecd10bab8d2e42ed88c0386204827c3b76e9e51150d09fd4e3b4e0e1e6f +iv = 81f2106a5379e0ed861cf76b3cf95afb17515478b5cbcae9 +plaintext = ee51a0f25d091288b5e2b91ad11d491329e48b35a18a3a8685 +aad = b80cb677f4b409cd1537363b +ciphertext = f681f19fa8de1fdea3538001a46f30fa6333b76d6439337e68 +tag = afad5e6d282d9df6d8119c32237b3e60 + +name = Test case 46 +key = 9f868600fbf81e40398b7dfb201fcae35d34bba10908860b0b2bf8b942b4e8fa +iv = 2ddcc13c97185614095d437900b8c0a9170e0a4a50e46ba5 +plaintext = 133fa3ac176fee6df67472752e41c6834f13300c0064ff5b190f903b7ac7 +aad = 0d61321fbee8bb1f3f5cb454 +ciphertext = b93abb311ec0bf018dc300c7d511b42ade72780373186e231820b44f22f0 +tag = f8bd2f649a337783ff911e37966037bd + +name = Test case 47 +key = 05affcdfce0a28539924370db8d80a78b835254778ec41acbff52bfab092fa33 +iv = 3edaeb185f7273b1a7cccba54f84c5f7d6583433b49d3694 +plaintext = 7657581faad266cc1037962a380c8aa5306f88000427d0a05397696b503790ad2643c6 +aad = d7c213e9e6f4a40f3e5b662c +ciphertext = 5eb19080aadc89f2329da4f5c41dc60568651c424c1b05d827f2bfb8dbff42c5a08224 +tag = 2da20087b5674f0b967d1baa664bbd82 + +name = Test case 48 +key = 645ed60ec74ddfe1f02694792db4436c262d20405d8645cd9755d64876219799 +iv = d83665b44c1fdf567299f2b8501e9c0e7ae2dda0bb8f2c82 +plaintext = ceee69d32ad4667a00909964d9611bf34fd98be41ad7f0feaaaff8169060d64cf310c13bcb9394cf +aad = 57379f8f44191ec9cf3b1a07 +ciphertext = 4496a0666f0f895ebce224b448a04502f2ae7b354d868b7c54295bf051162e82c530c767d1ffd2cc +tag = 1ffc56da4fb961ffdfabe66d82ec8f29 + +name = Test case 49 +key = 06624c9a75bb7dbe224a3f23791281f53c40b407a14161a3f82f34924623dc02 +iv = e647b8b4739bf542a81d72d695e1cd6ba348fa593987ac47 +plaintext = 2658763f8d70e8c3303582d66ba3d736ce9d407e9507f6c6627e382d0144da157d73d0aee10ef034083cdd9013 +aad = 75536443a6c2189a57d553bb +ciphertext = 305cab5c2f9a6edccac307d6965febe3c86f2a1e31ac8c74e88924a10c2a29106bce980c803b7886985bba8ec5 +tag = 8c12bb58c84175b9f601b704d0f8a25c + +name = Test case 50 +key = 63aeb46083100bbcc430f4f09bcc34410df9cfd5883d629e4af8645ffabb89c2 +iv = b09830874dc549195a5d6da93b9dcc12aa1ec8af201c96bd +plaintext = 1b3c9050e0a062f5a5cff7bec8706864cf8648142ec5cb1f9867ace384e9b2bba33aab8dc83e83b2d2fac70cd5189f2b5ab5 +aad = 7dcc05b0940198bd5c68cdf1 +ciphertext = d8b22e5d381de08a50b163c00dbbca6c07d61c80199cebd52234c7bd4f7ed0a90d47ef05617cdb8e3f782875ae629c0f0ad6 +tag = 194077f0e6d415bf7307d171e8484a9c + +name = Test case 51 +key = 4826c1bf8b48088fece4008922173c500ff45790f945b1027f36110da4fecc92 +iv = 3a78fc7397944d762303b0a75974ac92a60e250bf112600a +plaintext = d26e3a2b92120ff8056bb992660cc8a2364792589c16a518b8d232b8184aed05ba8d4fd0b2ad2b928cd873e11905a21ffece5f1e63c974 +aad = 904d2cd3e50f7bfb9352f142 +ciphertext = 21f4cf679662fad36f57945fc0c0753c3791261eb58d643278dfe1f14bfb585c5a01370ba96f18dc3f6b6945a2c6997330b24f12f5219a +tag = 95397c54428f9d069c511b5c82e0151c + +name = Test case 52 +key = ec526c03d8a08e8a63751112428a76399c399e8b83d98c9247c73164805ac8fe +iv = 2cc1a6ae89c2a091415fa2964b44a0e5da629d40d77b77f1 +plaintext = 567377f5b6df5442e70bc9a31bc450bd4febfcf89d7ca611353c7e612d8b7e36e859f6365ec7e5e99e9e0e882532666dd7203d06f6e25439ed871237 +aad = 35575b56716868b66cd21e24 +ciphertext = 6b738274fe974438f1f5fca8ef1ee7df664f1e72bc54ccd3fb58c4a3df67ef9a73261df41ffe9c52aeafc8be4f6524baf9efb1558d4a57defec7bee3 +tag = 92599d4b14a795e8c375ec2a8960b4dc + diff --git a/openjdk/src/main/java/org/conscrypt/EchConfigList.java b/openjdk/src/main/java/org/conscrypt/EchConfigList.java new file mode 100644 index 000000000..f4bca6448 --- /dev/null +++ b/openjdk/src/main/java/org/conscrypt/EchConfigList.java @@ -0,0 +1,83 @@ +/* + * 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; + +import java.nio.ByteBuffer; + +/** + * Data used to configure ECH (Encrypted Client Hello) in a TLS handshake. + * + *

This object can only be constructed by feeding in the raw bytes of the EchConfigList from + * a HTTPS DNS record (see https://datatracker.ietf.org/doc/html/rfc460), and may contain multiple + * EchConfigs. + * + *

The general structure starts with the length of the EchConfigList (2 bytes), then each + * entry in the list contains the following: + *

    + *
  • Version: 2 bytes + *
  • Length of the individual EchConfig: 2 bytes + *
  • Contents: unspecified number of bytes + *
+ * + *

See https://datatracker.ietf.org/doc/draft-ietf-tls-esni for details of the exact structure. + */ +public class EchConfigList { + private final byte[] rawData; + + private EchConfigList(byte[] rawData) { + // Make a copy of the raw data to prevent the caller from being able to modify it directly. + this.rawData = rawData.clone(); + } + + /** + * Factory method to construct a new {@code EchConfigList} from a byte array. + * + *

The raw bytes from a HTTPS DNS record should be fed directly into this method. + * + * @throws NullPointerException if {@code byteArr} is null. + * @throws InvalidEchDataException if the ECH data is empty, does not contain a length, or has + * a length mismatch. + */ + public static EchConfigList fromBytes(byte[] byteArr) throws InvalidEchDataException { + if (byteArr == null) { + throw new NullPointerException("ECH config list should not be null"); + } + + if (byteArr.length == 0) { + throw new InvalidEchDataException("Empty ECH config list"); + } + + if (byteArr.length < 2) { + throw new InvalidEchDataException("ECH config list does not contain a length"); + } + + int echConfigListLength = Short.toUnsignedInt(ByteBuffer.wrap(byteArr).getShort()); + // Subtract the 2 bytes corresponding to the overall EchConfigList length + if (echConfigListLength != byteArr.length - 2) { + throw new InvalidEchDataException("ECH config list length does not match"); + } + + return new EchConfigList(byteArr); + } + + /** Returns the raw byte representation of an EchConfigList. */ + public byte[] toBytes() { + // Defensive copy, so that the caller can't modify the underlying raw data. + return rawData.clone(); + } +} + diff --git a/openjdk/src/main/java/org/conscrypt/EchConfigMismatchException.java b/openjdk/src/main/java/org/conscrypt/EchConfigMismatchException.java new file mode 100644 index 000000000..e9a7b874c --- /dev/null +++ b/openjdk/src/main/java/org/conscrypt/EchConfigMismatchException.java @@ -0,0 +1,97 @@ +/* + * 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; + +import javax.net.ssl.SSLException; + +/** + * Exception thrown when the provided ECH (Encrypted Client Hello) config does not match the server. + * + *

Before accessing the retry configuration, clients must call {@link #getPublicHostname()} + * and verify that the hostname matches the connection hostname (using their preferred {@link + * javax.net.ssl.HostnameVerifier}). If the returned hostname is {@code null}, any provided retry + * configuration must be ignored. + * + *

Clients can then attempt to establish a new connection, using the provided retry {@link + * EchConfigList}, if available. A retry {@link EchConfigList} may not be available if the server + * has not provided any. + */ +public class EchConfigMismatchException extends SSLException { + private final String publicName; + private final EchConfigList echRetryConfigList; + + /** + * Returns the {@link EchConfigList} provided by the server for retrying the connection, or + * {@code null} if no retry configuration was set by the server. + * + * Prior to reading this value, the client must verify that the certificate is valid for + * the name returned by {@link #getPublicHostname()}. + */ + public EchConfigList getRetryConfigList() { + if (publicName == null) { + return null; + } + return echRetryConfigList; + } + + /** + * Returns the hostname that should be used for verification. + * + * This method must be called before interpreting the retry config list, returned by {@link + * #getRetryConfigList()}. + * + * For more details see section 6.1.7 "Authenticating for the Public Name" in RFC TLS Encrypted + * Client Hello (draft-ietf-tls-esni-25). + */ + public String getPublicHostname() { + return publicName; + } + + /** + * Returns {@code true} if a retry {@link EchConfigList} is available, false otherwise. + */ + public boolean hasRetryConfigList() { + return echRetryConfigList != null; + } + + /** + * Constructs a new {@code EchConfigMismatchException}. + * + * @param message the detail message. + */ + public EchConfigMismatchException(String message) { + super(message); + this.publicName = null; + this.echRetryConfigList = null; + } + + /** + * Constructs a new {@code EchConfigMismatchException}. + * + * @param message the detail message. + * @param publicName the hostname that must be used for verification, or {@code null} if the + * server did not provide a valid public name. + * @param echRetryConfigList the {@link EchConfigList} provided by the server for retrying the + * connection, or {@code null} if no retry configuration was set by the server. + */ + public EchConfigMismatchException(String message, String publicName, + EchConfigList echRetryConfigList) { + super(message); + this.publicName = publicName; + this.echRetryConfigList = echRetryConfigList; + } +} diff --git a/openjdk/src/main/java/org/conscrypt/InvalidEchDataException.java b/openjdk/src/main/java/org/conscrypt/InvalidEchDataException.java new file mode 100644 index 000000000..4a3224589 --- /dev/null +++ b/openjdk/src/main/java/org/conscrypt/InvalidEchDataException.java @@ -0,0 +1,29 @@ +/* + * 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; + +import javax.net.ssl.SSLException; + +/** + * Exception thrown when the ECH (Encrypted Client Hello) data is empty or invalid. + */ +public class InvalidEchDataException extends SSLException { + public InvalidEchDataException(String message) { + super(message); + } +} + diff --git a/openjdk/src/main/java/org/conscrypt/Platform.java b/openjdk/src/main/java/org/conscrypt/Platform.java index 1701876cd..337d13df1 100644 --- a/openjdk/src/main/java/org/conscrypt/Platform.java +++ b/openjdk/src/main/java/org/conscrypt/Platform.java @@ -104,7 +104,6 @@ final public class Platform { private static boolean FILTERED_TLS_V1 = false; static { - NativeCrypto.setTlsV1DeprecationStatus(DEPRECATED_TLS_V1, ENABLED_TLS_V1); Method getCurveNameMethod = null; try { getCurveNameMethod = ECParameterSpec.class.getDeclaredMethod("getCurveName"); @@ -581,12 +580,23 @@ static boolean supportsX509ExtendedTrustManager() { } static SSLException wrapInvalidEchDataException(SSLException e) { - return e; + SSLException exception = new InvalidEchDataException(e.getMessage()); + exception.initCause(e); + return exception; } static SSLException wrapEchRejectedException(EchRejectedException e, String hostname, byte[] retryConfigs) { - return e; + EchConfigList configs; + try { + configs = (retryConfigs != null) ? EchConfigList.fromBytes(retryConfigs) : null; + } catch (InvalidEchDataException ignored) { + configs = null; + } + SSLException exception = new EchConfigMismatchException( + "The ECH configuration has been rejected by the server", hostname, configs); + exception.initCause(e); + return exception; } static boolean supportsConscryptCertStore() { @@ -719,8 +729,8 @@ private static int majorVersion(final String javaSpecVersion) { if (version[0] == 1) { if (version[1] < 6) { - throw new IllegalArgumentException( - "Unsupported Java specification version: " + javaSpecVersion); + throw new IllegalArgumentException("Unsupported Java specification version: " + + javaSpecVersion); } return version[1]; } else { @@ -787,4 +797,10 @@ public static boolean isPakeSupported() { public static boolean isSdkGreater(int sdk) { return false; } + + public static boolean registerX509CertificateAllocation( + OpenSSLX509Certificate cert, long nativePtr) { + // On OpenJDK, finalize() is used as fallback. + return false; + } } diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java b/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java index 01308252b..0c93741b7 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java @@ -85,6 +85,7 @@ ApplicationProtocolSelectorAdapterTest.class, ArrayUtilsTest.class, CertPinManagerTest.class, + ChaCha20Poly1305Test.class, ChainStrengthAnalyzerTest.class, DuckTypedHpkeSpiTest.class, EdDsaTest.class, diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptAndroidWithoutTlsSuite.java b/openjdk/src/test/java/org/conscrypt/ConscryptAndroidWithoutTlsSuite.java index 8186eb275..ffb52b96d 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptAndroidWithoutTlsSuite.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptAndroidWithoutTlsSuite.java @@ -77,6 +77,7 @@ ApplicationProtocolSelectorAdapterTest.class, ArrayUtilsTest.class, CertPinManagerTest.class, + ChaCha20Poly1305Test.class, ChainStrengthAnalyzerTest.class, DuckTypedHpkeSpiTest.class, EdDsaTest.class, diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java index 5047c33f5..db7a6eb2d 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java @@ -87,6 +87,7 @@ ApplicationProtocolSelectorAdapterTest.class, ArrayUtilsTest.class, CertPinManagerTest.class, + ChaCha20Poly1305Test.class, ChainStrengthAnalyzerTest.class, ClientSessionContextTest.class, ConscryptSocketTest.class, diff --git a/openjdk/src/test/java/org/conscrypt/EchConfigListTest.java b/openjdk/src/test/java/org/conscrypt/EchConfigListTest.java new file mode 100644 index 000000000..69a3fa3ba --- /dev/null +++ b/openjdk/src/test/java/org/conscrypt/EchConfigListTest.java @@ -0,0 +1,86 @@ +/* + * 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; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class EchConfigListTest { + // Actual EchConfigList in the cloudflare-ech.com HTTPS DNS record + // Explicit byte casting required to prevent lossy conversion errors + private static final byte[] VALID_ECH_CONFIG_LIST = new byte[] { + 0x00, 0x45, (byte) 0xfe, 0x0d, 0x00, 0x41, + (byte) 0xf7, 0x00, 0x20, 0x00, 0x20, (byte) 0xfd, + 0x4b, (byte) 0x91, 0x2a, (byte) 0xf0, (byte) 0xdc, (byte) 0xba, + 0x52, (byte) 0xb5, (byte) 0x98, (byte) 0x8b, (byte) 0xea, (byte) 0xb2, + 0x50, 0x7b, (byte) 0xfc, 0x4f, (byte) 0x24, (byte) 0xea, + (byte) 0xdb, (byte) 0xf9, 0x54, 0x3a, (byte) 0xa3, 0x71, + 0x34, (byte) 0xdd, (byte) 0xff, 0x40, (byte) 0xcc, (byte) 0xa8, + 0x68, 0x00, 0x04, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x12, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x66, 0x6c, 0x61, 0x72, + 0x65, 0x2d, 0x65, 0x63, 0x68, 0x2e, + 0x63, 0x6f, 0x6d, 0x00, 0x00}; + + @Test + public void testFromBytes_whenNull_throwsNullPointerException() { + assertThrows(NullPointerException.class, + () -> EchConfigList.fromBytes(/* byteArr= */ null)); + } + + @Test + public void testFromBytes_whenEmpty_throwsInvalidEchDataException() { + assertThrows("Empty ECH config list", InvalidEchDataException.class, + () -> EchConfigList.fromBytes(new byte[] {})); + } + + @Test + public void testFromBytes_whenTooShort_throwsInvalidEchDataException() { + assertThrows("ECH config list does not contain a length", InvalidEchDataException.class, + () -> EchConfigList.fromBytes(new byte[] {0x00})); + } + + @Test + public void testFromBytes_whenMismatchedLength_throwsInvalidEchDataException() { + byte[] byteArr = new byte[] {0x00, 0x02, 0x05, 0x06, 0x7}; + + assertThrows("ECH config list length does not match", InvalidEchDataException.class, + () -> EchConfigList.fromBytes(byteArr)); + } + + @Test + public void testFromBytes_whenValidEchConfigList_createsObject() throws Exception { + EchConfigList echConfigList = EchConfigList.fromBytes(VALID_ECH_CONFIG_LIST); + + assertArrayEquals(VALID_ECH_CONFIG_LIST, echConfigList.toBytes()); + } + + @Test + public void testToBytes_defensiveCopy() throws Exception { + EchConfigList echConfigList = EchConfigList.fromBytes(VALID_ECH_CONFIG_LIST); + byte[] bytes = echConfigList.toBytes(); + bytes[0] = (byte) 0xFF; + + assertArrayEquals(VALID_ECH_CONFIG_LIST, echConfigList.toBytes()); + } +} + diff --git a/openjdk/src/test/java/org/conscrypt/EchConfigMismatchExceptionTest.java b/openjdk/src/test/java/org/conscrypt/EchConfigMismatchExceptionTest.java new file mode 100644 index 000000000..4b1fbc98f --- /dev/null +++ b/openjdk/src/test/java/org/conscrypt/EchConfigMismatchExceptionTest.java @@ -0,0 +1,84 @@ +/* + * 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; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class EchConfigMismatchExceptionTest { + private static final byte[] VALID_RETRY_CONFIGS = + new byte[] {0x00, 0x04, 0x01, 0x02, 0x03, 0x04}; + + @Test + public void testConstructor_messageOnly() { + EchConfigMismatchException e = new EchConfigMismatchException("ECH rejected"); + + assertEquals("ECH rejected", e.getMessage()); + assertNull(e.getPublicHostname()); + assertNull(e.getRetryConfigList()); + assertFalse(e.hasRetryConfigList()); + } + + @Test + public void testConstructor_withPublicNameAndRetryConfigs() throws Exception { + EchConfigList configList = EchConfigList.fromBytes(VALID_RETRY_CONFIGS); + EchConfigMismatchException e = new EchConfigMismatchException( + "The ECH configuration has been rejected by the server", + "public.example.com", + configList); + + assertEquals("The ECH configuration has been rejected by the server", e.getMessage()); + assertEquals("public.example.com", e.getPublicHostname()); + assertTrue(e.hasRetryConfigList()); + assertNotNull(e.getRetryConfigList()); + assertArrayEquals(VALID_RETRY_CONFIGS, e.getRetryConfigList().toBytes()); + } + + @Test + public void testGetRetryConfigList_nullPublicName_returnsNull() throws Exception { + EchConfigList configList = EchConfigList.fromBytes(VALID_RETRY_CONFIGS); + EchConfigMismatchException e = new EchConfigMismatchException( + "The ECH configuration has been rejected by the server", + null, + configList); + + assertNull(e.getPublicHostname()); + assertTrue(e.hasRetryConfigList()); + assertNull(e.getRetryConfigList()); + } + + @Test + public void testConstructor_nullRetryConfigs() { + EchConfigMismatchException e = new EchConfigMismatchException( + "The ECH configuration has been rejected by the server", + "public.example.com", + null); + + assertEquals("public.example.com", e.getPublicHostname()); + assertFalse(e.hasRetryConfigList()); + assertNull(e.getRetryConfigList()); + } +} diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java index b8e039d31..8be0a8a9f 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java @@ -1003,6 +1003,18 @@ public void test_SSL_set_enable_ech_grease() throws Exception { NativeCrypto.SSL_CTX_free(c, null); } + @Test + public void test_SSL_set_reject_unusable_ech_config() throws Exception { + long c = NativeCrypto.SSL_CTX_new(); + long s = NativeCrypto.SSL_new(c, null); + + NativeCrypto.SSL_set_reject_unusable_ech_config(s, null, true); + NativeCrypto.SSL_set_reject_unusable_ech_config(s, null, false); + + NativeCrypto.SSL_free(s, null); + NativeCrypto.SSL_CTX_free(c, null); + } + @Test public void test_SSL_set1_ech_valid_config_list() throws Exception { long c = NativeCrypto.SSL_CTX_new(); @@ -3949,7 +3961,7 @@ public void test_ECDH_compute_key_null_key_Failure() throws Exception { @Test public void EVP_CipherInit_ex_withNullCtxShouldThrow() throws Exception { - final long evpCipher = NativeCrypto.EVP_get_cipherbyname("aes-128-ecb"); + final long evpCipher = NativeCrypto.EVP_aes_128_ecb(); assertThrows(NullPointerException.class, () -> NativeCrypto.EVP_CipherInit_ex(null, evpCipher, null, null, true)); } @@ -3958,7 +3970,7 @@ public void EVP_CipherInit_ex_withNullCtxShouldThrow() throws Exception { public void test_EVP_CipherInit_ex_Null_Failure() throws Exception { final NativeRef.EVP_CIPHER_CTX ctx = new NativeRef.EVP_CIPHER_CTX(NativeCrypto.EVP_CIPHER_CTX_new()); - final long evpCipher = NativeCrypto.EVP_get_cipherbyname("aes-128-ecb"); + final long evpCipher = NativeCrypto.EVP_aes_128_ecb(); /* Initialize encrypting. */ NativeCrypto.EVP_CipherInit_ex(ctx, evpCipher, null, null, true); @@ -3973,19 +3985,131 @@ public void test_EVP_CipherInit_ex_Null_Failure() throws Exception { public void test_EVP_CipherInit_ex_Success() throws Exception { final NativeRef.EVP_CIPHER_CTX ctx = new NativeRef.EVP_CIPHER_CTX(NativeCrypto.EVP_CIPHER_CTX_new()); - final long evpCipher = NativeCrypto.EVP_get_cipherbyname("aes-128-ecb"); + final long evpCipher = NativeCrypto.EVP_aes_128_ecb(); NativeCrypto.EVP_CipherInit_ex(ctx, evpCipher, AES_128_KEY, null, true); } @Test public void test_EVP_CIPHER_iv_length() throws Exception { - long aes128ecb = NativeCrypto.EVP_get_cipherbyname("aes-128-ecb"); + long aes128ecb = NativeCrypto.EVP_aes_128_ecb(); assertEquals(0, NativeCrypto.EVP_CIPHER_iv_length(aes128ecb)); - long aes128cbc = NativeCrypto.EVP_get_cipherbyname("aes-128-cbc"); + long aes128cbc = NativeCrypto.EVP_aes_128_cbc(); assertEquals(16, NativeCrypto.EVP_CIPHER_iv_length(aes128cbc)); } + @Test + public void test_EVP_rc4() throws Exception { + long cipher = NativeCrypto.EVP_rc4(); + assertNotEquals(NULL, cipher); + assertEquals(0, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + + @Test + public void test_EVP_des_cbc() throws Exception { + long cipher = NativeCrypto.EVP_des_cbc(); + assertNotEquals(NULL, cipher); + assertEquals(8, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + + @Test + public void test_EVP_des_ede_cbc() throws Exception { + long cipher = NativeCrypto.EVP_des_ede_cbc(); + assertNotEquals(NULL, cipher); + assertEquals(8, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + + @Test + public void test_EVP_des_ede3_cbc() throws Exception { + long cipher = NativeCrypto.EVP_des_ede3_cbc(); + assertNotEquals(NULL, cipher); + assertEquals(8, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + + @Test + public void test_EVP_aes_128_ecb() throws Exception { + long cipher = NativeCrypto.EVP_aes_128_ecb(); + assertNotEquals(NULL, cipher); + assertEquals(0, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + + @Test + public void test_EVP_aes_128_cbc() throws Exception { + long cipher = NativeCrypto.EVP_aes_128_cbc(); + assertNotEquals(NULL, cipher); + assertEquals(16, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + + @Test + public void test_EVP_aes_128_ctr() throws Exception { + long cipher = NativeCrypto.EVP_aes_128_ctr(); + assertNotEquals(NULL, cipher); + assertEquals(16, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + + @Test + public void test_EVP_aes_128_gcm() throws Exception { + long cipher = NativeCrypto.EVP_aes_128_gcm(); + assertNotEquals(NULL, cipher); + assertEquals(12, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + + @Test + public void test_EVP_aes_192_ecb() throws Exception { + long cipher = NativeCrypto.EVP_aes_192_ecb(); + assertNotEquals(NULL, cipher); + assertEquals(0, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + + @Test + public void test_EVP_aes_192_cbc() throws Exception { + long cipher = NativeCrypto.EVP_aes_192_cbc(); + assertNotEquals(NULL, cipher); + assertEquals(16, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + + @Test + public void test_EVP_aes_192_ctr() throws Exception { + long cipher = NativeCrypto.EVP_aes_192_ctr(); + assertNotEquals(NULL, cipher); + assertEquals(16, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + + @Test + public void test_EVP_aes_192_gcm() throws Exception { + long cipher = NativeCrypto.EVP_aes_192_gcm(); + assertNotEquals(NULL, cipher); + assertEquals(12, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + + @Test + public void test_EVP_aes_256_ecb() throws Exception { + long cipher = NativeCrypto.EVP_aes_256_ecb(); + assertNotEquals(NULL, cipher); + assertEquals(0, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + + @Test + public void test_EVP_aes_256_cbc() throws Exception { + long cipher = NativeCrypto.EVP_aes_256_cbc(); + assertNotEquals(NULL, cipher); + assertEquals(16, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + + @Test + public void test_EVP_aes_256_ctr() throws Exception { + long cipher = NativeCrypto.EVP_aes_256_ctr(); + assertNotEquals(NULL, cipher); + assertEquals(16, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + + @Test + public void test_EVP_aes_256_gcm() throws Exception { + long cipher = NativeCrypto.EVP_aes_256_gcm(); + assertNotEquals(NULL, cipher); + assertEquals(12, NativeCrypto.EVP_CIPHER_iv_length(cipher)); + } + @Test public void test_OpenSSLKey_toJava() throws Exception { OpenSSLKey key1; diff --git a/openjdk/src/test/java/org/conscrypt/OpenSSLX509CRLTest.java b/openjdk/src/test/java/org/conscrypt/OpenSSLX509CRLTest.java index 94d7a0f5b..39316596a 100644 --- a/openjdk/src/test/java/org/conscrypt/OpenSSLX509CRLTest.java +++ b/openjdk/src/test/java/org/conscrypt/OpenSSLX509CRLTest.java @@ -31,6 +31,7 @@ import static java.nio.charset.StandardCharsets.US_ASCII; import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -310,6 +311,7 @@ public void verify_withOpenSSLKey() throws Exception { } @Test + @Ignore public void verify_withNonOpenSSLKey() throws Exception { OpenSSLX509CRL crl = loadTestCrl("crl.pem"); OpenSSLX509Certificate caCert = loadTestCertificate("ca-cert.pem"); diff --git a/openjdk/src/test/java/org/conscrypt/OpenSSLX509CertificateTest.java b/openjdk/src/test/java/org/conscrypt/OpenSSLX509CertificateTest.java index 3c78c434f..9cdf0b457 100644 --- a/openjdk/src/test/java/org/conscrypt/OpenSSLX509CertificateTest.java +++ b/openjdk/src/test/java/org/conscrypt/OpenSSLX509CertificateTest.java @@ -158,7 +158,7 @@ public void toString_printsCertificate() throws Exception { + " 20:31:54:1A:F2:5C:05:FF:D8:65:8B:68:43:79:4F:5E:90:36:F7:B4\n" + " X509v3 Authority Key Identifier:\n" + " " - + "keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55\n" + + "keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55\n" + " DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen\n" + " serial:0\n" + "\n" diff --git a/openjdk/src/test/java/org/conscrypt/PlatformTest.java b/openjdk/src/test/java/org/conscrypt/PlatformTest.java index 9c3699a93..4fc0ea8a5 100644 --- a/openjdk/src/test/java/org/conscrypt/PlatformTest.java +++ b/openjdk/src/test/java/org/conscrypt/PlatformTest.java @@ -21,6 +21,9 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import org.conscrypt.testing.FailingSniMatcher; import org.conscrypt.testing.RestrictedAlgorithmConstraints; @@ -39,6 +42,7 @@ import javax.net.ssl.SNIHostName; import javax.net.ssl.SNIMatcher; import javax.net.ssl.SNIServerName; +import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; /** @@ -214,4 +218,60 @@ private static void setApplicationProtocols(SSLParameters params, String[] proto } } } + + @Test + public void test_wrapInvalidEchDataException() { + SSLException original = new SSLException("Invalid ECH data"); + SSLException wrapped = Platform.wrapInvalidEchDataException(original); + + assertTrue(wrapped instanceof InvalidEchDataException); + assertEquals("Invalid ECH data", wrapped.getMessage()); + assertEquals(original, wrapped.getCause()); + } + + @Test + public void test_wrapEchRejectedException() { + byte[] retryConfigs = new byte[] {0x00, 0x02, 0x01, 0x02}; + EchRejectedException cause = new EchRejectedException("Rejected"); + SSLException wrapped = + Platform.wrapEchRejectedException(cause, "public.example.com", retryConfigs); + + assertTrue(wrapped instanceof EchConfigMismatchException); + EchConfigMismatchException mismatchException = (EchConfigMismatchException) wrapped; + assertEquals("The ECH configuration has been rejected by the server", + mismatchException.getMessage()); + assertEquals("public.example.com", mismatchException.getPublicHostname()); + assertTrue(mismatchException.hasRetryConfigList()); + assertNotNull(mismatchException.getRetryConfigList()); + assertArrayEquals(retryConfigs, mismatchException.getRetryConfigList().toBytes()); + assertEquals(cause, mismatchException.getCause()); + } + + @Test + public void test_wrapEchRejectedException_nullRetryConfigs() { + EchRejectedException cause = new EchRejectedException("Rejected"); + SSLException wrapped = Platform.wrapEchRejectedException(cause, "public.example.com", null); + + assertTrue(wrapped instanceof EchConfigMismatchException); + EchConfigMismatchException mismatchException = (EchConfigMismatchException) wrapped; + assertEquals("public.example.com", mismatchException.getPublicHostname()); + assertFalse(mismatchException.hasRetryConfigList()); + assertNull(mismatchException.getRetryConfigList()); + assertEquals(cause, mismatchException.getCause()); + } + + @Test + public void test_wrapEchRejectedException_invalidRetryConfigs() { + byte[] invalidRetryConfigs = new byte[] {0x00}; + EchRejectedException cause = new EchRejectedException("Rejected"); + SSLException wrapped = + Platform.wrapEchRejectedException(cause, "public.example.com", invalidRetryConfigs); + + assertTrue(wrapped instanceof EchConfigMismatchException); + EchConfigMismatchException mismatchException = (EchConfigMismatchException) wrapped; + assertEquals("public.example.com", mismatchException.getPublicHostname()); + assertFalse(mismatchException.hasRetryConfigList()); + assertNull(mismatchException.getRetryConfigList()); + assertEquals(cause, mismatchException.getCause()); + } } diff --git a/platform/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java b/platform/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java index 728ac4b93..625d21e6c 100644 --- a/platform/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java +++ b/platform/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java @@ -47,8 +47,7 @@ public boolean isCertificateTransparencyVerificationRequired(String hostname) { @Override public CertificateTransparencyVerificationReason getCertificateTransparencyVerificationReason( String hostname) { - if (Platform.isSdkGreater(33) - && com.android.libcore.Flags.networkSecurityPolicyReasonCtEnabledApi()) { + if (Platform.isSdkGreater(36)) { CertificateTransparencyVerificationReason reason = plaformCtReasonToConscryptReason( policy.getCertificateTransparencyVerificationReason(hostname)); if (reason != CertificateTransparencyVerificationReason.UNKNOWN) { diff --git a/platform/src/main/java/org/conscrypt/Platform.java b/platform/src/main/java/org/conscrypt/Platform.java index 964129076..ee9fec5f6 100644 --- a/platform/src/main/java/org/conscrypt/Platform.java +++ b/platform/src/main/java/org/conscrypt/Platform.java @@ -28,6 +28,8 @@ import dalvik.system.VMRuntime; import dalvik.system.ZygoteHooks; +import libcore.util.NativeAllocationRegistry; + import org.conscrypt.NativeCrypto; import org.conscrypt.ct.CertificateTransparency; import org.conscrypt.ct.LogStore; @@ -99,7 +101,6 @@ private static class NoPreloadHolder { static { canProbeZygote = isSdkGreater(32); canCallZygoteMethod = isSdkGreater(36); - NativeCrypto.setTlsV1DeprecationStatus(DEPRECATED_TLS_V1, ENABLED_TLS_V1); } /** @@ -698,4 +699,37 @@ public static boolean isSdkGreater(int sdk) { throw new RuntimeException(e); } } + + private static class X509NativeAllocationRegistryHolder { + private static final NativeAllocationRegistry REGISTRY; + + static { + NativeAllocationRegistry registry = null; + try { + ClassLoader classLoader = OpenSSLX509Certificate.class.getClassLoader(); + if (classLoader == null) { + classLoader = ClassLoader.getSystemClassLoader(); + } + registry = NativeAllocationRegistry.createMalloced( + classLoader, NativeCrypto.get_X509_free_func()); + } catch (Throwable ignored) { + registry = null; + } + REGISTRY = registry; + } + } + + public static boolean registerX509CertificateAllocation( + OpenSSLX509Certificate cert, long nativePtr) { + if (X509NativeAllocationRegistryHolder.REGISTRY != null) { + try { + X509NativeAllocationRegistryHolder.REGISTRY.registerNativeAllocation( + cert, nativePtr); + return true; + } catch (IllegalArgumentException ignored) { + // Do nothing. + } + } + return false; + } } diff --git a/platform/src/test/java/org/conscrypt/SpakeTest.java b/platform/src/test/java/org/conscrypt/SpakeTest.java index 3bd170ee7..b072b4248 100644 --- a/platform/src/test/java/org/conscrypt/SpakeTest.java +++ b/platform/src/test/java/org/conscrypt/SpakeTest.java @@ -30,7 +30,7 @@ import org.conscrypt.Spake2PlusKeyManager; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestRule; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/platform/src/test/java/org/conscrypt/TlsDeprecationTest.java b/platform/src/test/java/org/conscrypt/TlsDeprecationTest.java index 0ebb329ce..1c8da1b1c 100644 --- a/platform/src/test/java/org/conscrypt/TlsDeprecationTest.java +++ b/platform/src/test/java/org/conscrypt/TlsDeprecationTest.java @@ -29,7 +29,7 @@ import org.conscrypt.javax.net.ssl.TestSSLContext; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TestRule; +// android-add: import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; diff --git a/scripts/export_to_ag.py b/scripts/export_to_ag.py index 53d0e74d3..ff0eae95f 100755 --- a/scripts/export_to_ag.py +++ b/scripts/export_to_ag.py @@ -34,18 +34,168 @@ from __future__ import annotations import argparse +import base64 import getpass +import hashlib +import io import os import pathlib import re import shutil import subprocess import sys +import tarfile import tempfile -from typing import Any, Dict, List, Optional, Tuple +from typing import Dict, List, Optional, Tuple +import urllib.request +import zipfile DEFAULT_COPYBARA_BIN = "/google/data/ro/teams/copybara/copybara" GERRIT_SSO_URL = "sso://googleplex-android/platform/external/conscrypt" +GERRIT_HTTPS_GOB_URL = ( + "https://googleplex-android.googlesource.com/platform/external/conscrypt" +) +GERRIT_HTTPS_URL = ( + "https://android.googlesource.com/platform/external/conscrypt" +) +GERRIT_PUSH_URLS = ( + "https://googleplex-android.googlesource.com/a/platform/external/conscrypt", + "https://googleplex-android-review.googlesource.com/a/platform/external/conscrypt", + "https://android-review.googlesource.com/a/platform/external/conscrypt", + GERRIT_SSO_URL, +) +X20_CURRYSRC_RO = pathlib.Path( + "/google/data/ro/users/mi/miguelaranda/currysrc.jar" +) + +# Relative paths where currysrc.jar is located inside an Android checkout. +CURRYSRC_HOST_OUT_JARS = ( + pathlib.Path("out/host/linux-x86/framework/currysrc.jar"), + pathlib.Path("out/soong/host/linux-x86/framework/currysrc.jar"), + pathlib.Path( + "out/soong/.intermediates/external/icu/tools/srcgen/currysrc/currysrc/linux_glibc_common/combined/currysrc.jar" + ), +) + + +def get_writable_bin_dir() -> pathlib.Path: + """Returns a writable directory for wrapper scripts (/tmpfs/bin or tempdir).""" + for cand in [ + pathlib.Path("/tmpfs/bin"), + pathlib.Path(tempfile.gettempdir()) + / f"conscrypt_bin_{os.environ.get('USER', 'user')}", + ]: + try: + cand.mkdir(parents=True, exist_ok=True) + if os.access(cand, os.W_OK): + return cand + except OSError: + continue + return pathlib.Path(tempfile.mkdtemp(prefix="conscrypt_bin_")) + + +def setup_kokoro_git_env() -> None: + """Configures git wrapper and credential helpers for Kokoro environments.""" + real_git = shutil.which("git") or "/usr/bin/git" + wrapper_dir = get_writable_bin_dir() + wrapper_path = wrapper_dir / "git" + + # Create a git wrapper that strips --object-format=* and --ref-format=* flags + # unsupported by Git < 2.36 (e.g. Git 2.25 on Kokoro ubuntu2004). + if real_git != str(wrapper_path): + wrapper_path.write_text( + "#!/bin/bash\n" + "args=()\n" + 'for arg in "$@"; do\n' + ' case "$arg" in\n' + " --object-format=*|--ref-format=*)\n" + " ;;\n" + " *)\n" + ' args+=("$arg")\n' + " ;;\n" + " esac\n" + "done\n" + f'exec "{real_git}" "${{args[@]}}"\n' + ) + wrapper_path.chmod(0o755) + current_path = os.environ.get("PATH", "") + if str(wrapper_dir) not in current_path.split(":"): + os.environ["PATH"] = f"{wrapper_dir}:{current_path}" + + # Ensure global git identity is configured for Copybara and Git commits + res_name = subprocess.run( + [real_git, "config", "--get", "user.name"], + capture_output=True, + text=True, + check=False, + ) + if res_name.returncode != 0 or not res_name.stdout.strip(): + subprocess.run( + [real_git, "config", "--global", "user.name", "Conscrypt Team"], + capture_output=True, + check=False, + ) + res_email = subprocess.run( + [real_git, "config", "--get", "user.email"], + capture_output=True, + text=True, + check=False, + ) + if res_email.returncode != 0 or not res_email.stdout.strip(): + subprocess.run( + [real_git, "config", "--global", "user.email", "no-reply@google.com"], + capture_output=True, + check=False, + ) + + # If git-remote-sso is missing (e.g. in Kokoro GCP Docker container), + # set up git-cookie-authdaemon and HTTPS URL rewrites. + if not shutil.which("git-remote-sso"): + artifacts_dir = pathlib.Path( + os.environ.get("KOKORO_ARTIFACTS_DIR", "/tmpfs/src") + ) + gcompute_dir = artifacts_dir / "gcompute-tools" + if not gcompute_dir.is_dir(): + subprocess.run( + [ + real_git, + "clone", + "--depth", + "1", + "https://gerrit.googlesource.com/gcompute-tools", + str(gcompute_dir), + ], + capture_output=True, + check=False, + ) + auth_daemon = gcompute_dir / "git-cookie-authdaemon" + if auth_daemon.is_file(): + subprocess.run( + [sys.executable, str(auth_daemon)], capture_output=True, check=False + ) + + subprocess.run( + [ + real_git, + "config", + "--global", + "url.https://googleplex-android.googlesource.com/.insteadOf", + "sso://googleplex-android/", + ], + capture_output=True, + check=False, + ) + subprocess.run( + [ + real_git, + "config", + "--global", + "url.https://googleplex-android.googlesource.com/.insteadOf", + "rpc://googleplex-android/", + ], + capture_output=True, + check=False, + ) def run_cmd( @@ -53,7 +203,7 @@ def run_cmd( cwd: Optional[pathlib.Path] = None, env: Optional[Dict[str, str]] = None, check: bool = True, -) -> subprocess.CompletedProcess: +) -> subprocess.CompletedProcess[str]: """Helper to run a subprocess command with logging.""" print(f"==> Running: {' '.join(cmd)}" + (f" (in {cwd})" if cwd else "")) try: @@ -96,8 +246,8 @@ def get_copybara_bin(custom_path: Optional[str]) -> str: if mpm_jar.is_file(): jdk_java = pathlib.Path(artifacts_dir) / "mpm/java/jdk/bin/java" java_bin = str(jdk_java) if jdk_java.is_file() else "java" - wrapper_path = pathlib.Path("/tmpfs/bin/copybara") - wrapper_path.parent.mkdir(parents=True, exist_ok=True) + wrapper_dir = get_writable_bin_dir() + wrapper_path = wrapper_dir / "copybara" runfiles_path = mpm_jar.parent / "google3" wrapper_path.write_text( "#!/bin/bash\n" @@ -118,7 +268,7 @@ def get_current_user() -> str: """Extracts username using getpass or path fallback.""" try: return getpass.getuser() - except Exception: + except (KeyError, OSError): pass parts = pathlib.Path(__file__).resolve().parts @@ -130,12 +280,82 @@ def get_current_user() -> str: return "miguelaranda" +def find_google3_parent(start_path: pathlib.Path) -> pathlib.Path: + """Finds the directory containing google3 (client root or artifacts dir).""" + if "KOKORO_PIPER_DIR" in os.environ: + p = pathlib.Path(os.environ["KOKORO_PIPER_DIR"]) + if (p / "google3").is_dir(): + return p + if "KOKORO_ARTIFACTS_DIR" in os.environ: + p = pathlib.Path(os.environ["KOKORO_ARTIFACTS_DIR"]) / "piper" + if (p / "google3").is_dir(): + return p + + curr = start_path.resolve() + while curr != curr.parent: + if curr.name == "google3": + return curr.parent + curr = curr.parent + return start_path.parents[4] + + +def find_candidate_android_trees() -> List[pathlib.Path]: + """Discovers potential Android source checkouts in the user's environment.""" + candidates: List[pathlib.Path] = [] + seen: set[pathlib.Path] = set() + + def add_candidate(path: pathlib.Path) -> None: + resolved = path.resolve() + if resolved not in seen and resolved.is_dir(): + seen.add(resolved) + candidates.append(resolved) + + # 1. Check explicit environment variable + env_top = os.environ.get("ANDROID_BUILD_TOP") + if env_top: + add_candidate(pathlib.Path(env_top)) + + # 2. Check current working directory and its parents + curr = pathlib.Path.cwd() + while curr != curr.parent: + if (curr / "build" / "envsetup.sh").is_file() or (curr / ".repo").is_dir(): + add_candidate(curr) + break + curr = curr.parent + + # 3. Discover checkouts in user's home directories + home_bases: List[pathlib.Path] = [pathlib.Path.home()] + user = get_current_user() + corp_home = pathlib.Path(f"/usr/local/google/home/{user}") + if corp_home != pathlib.Path.home() and corp_home.is_dir(): + home_bases.append(corp_home) + + for base in home_bases: + try: + for child in base.iterdir(): + if not child.is_dir() or child.is_symlink(): + continue + # An Android tree typically has build/envsetup.sh, .repo, or + # external/conscrypt. + if ( + (child / "build" / "envsetup.sh").is_file() + or (child / ".repo").is_dir() + or (child / "external" / "conscrypt").is_dir() + or (child / "tools" / "currysrc").is_dir() + ): + add_candidate(child) + except (OSError, PermissionError): + continue + + return candidates + + def resolve_android_and_build_top( explicit_dir: Optional[str], ) -> Tuple[ pathlib.Path, Optional[pathlib.Path], - Optional[tempfile.TemporaryDirectory], + Optional[tempfile.TemporaryDirectory[str]], ]: """Resolves Android conscrypt directory and ANDROID_BUILD_TOP.""" temp_dir_obj = None @@ -158,22 +378,7 @@ def resolve_android_and_build_top( curr = curr.parent return p, build_top, None - # Check local home directory checkouts first - user = get_current_user() - home_candidates = [ - pathlib.Path(f"/usr/local/google/home/{user}/main/external/conscrypt"), - pathlib.Path(f"/usr/local/google/home/{user}/external/conscrypt"), - pathlib.Path.home() / "main" / "external" / "conscrypt", - ] - for cand in home_candidates: - if (cand / ".git").is_dir(): - bt = ( - cand.parent.parent - if (cand.parent.parent / "tools" / "currysrc").is_dir() - else None - ) - return cand.resolve(), bt.resolve() if bt else None, None - + # Check explicit environment variables for env_var in ["CONSCYPT_ANDROID_DIR", "ANDROID_BUILD_TOP"]: val = os.environ.get(env_var) if val: @@ -186,10 +391,63 @@ def resolve_android_and_build_top( bt = pathlib.Path(os.environ.get("ANDROID_BUILD_TOP", val)) return p.resolve(), bt.resolve(), None + # Check Kokoro Git-on-Borg SCM directory if present + kokoro_git_conscrypt = ( + pathlib.Path(os.environ.get("KOKORO_ARTIFACTS_DIR", "/tmpfs/src")) + / "git" + / "conscrypt" + ) + if (kokoro_git_conscrypt / ".git").is_dir(): + return kokoro_git_conscrypt.resolve(), None, None + + # Search discovered candidate Android trees in the user's workspace/home + for tree in find_candidate_android_trees(): + conscrypt_dir = tree / "external" / "conscrypt" + if (conscrypt_dir / ".git").is_dir(): + return conscrypt_dir.resolve(), tree.resolve(), None + print("\nCreating temporary clone of Android Gerrit repo...") temp_dir_obj = tempfile.TemporaryDirectory(prefix="conscrypt_export_") temp_path = pathlib.Path(temp_dir_obj.name) - run_cmd(["git", "clone", "--depth", "1", GERRIT_SSO_URL, str(temp_path)]) + clone_urls = [ + GERRIT_SSO_URL, + GERRIT_HTTPS_GOB_URL, + GERRIT_HTTPS_URL, + ] + cloned = False + for url in clone_urls: + res = subprocess.run( + [ + "git", + "clone", + "--branch", + "master", + url, + str(temp_path), + ], + capture_output=True, + text=True, + check=False, + ) + if res.returncode != 0: + res = subprocess.run( + [ + "git", + "clone", + "--no-single-branch", + url, + str(temp_path), + ], + capture_output=True, + text=True, + check=False, + ) + if res.returncode == 0: + cloned = True + break + print(f"Clone from {url} failed: {res.stderr.strip()}") + if not cloned: + sys.exit("ERROR: Could not clone Android conscrypt repository.") return temp_path, None, temp_dir_obj @@ -230,21 +488,59 @@ def step_copybara_export( copybara_bin: str, copybara_config: pathlib.Path, android_dir: pathlib.Path, + google3_parent: pathlib.Path, cl: Optional[str], dry_run: bool, + use_folder_origin: bool, extra_copybara_args: List[str], + is_kokoro: bool = False, ) -> Optional[str]: - """Runs Copybara export_to_ag workflow and extracts the active Gerrit review URL or CL number.""" - print("\n--- Step 2: Running Copybara export_to_ag ---") + """Runs Copybara export_to_ag or export_to_ag_folder workflow.""" + workflow_name = "export_to_ag_folder" if use_folder_origin else "export_to_ag" + print(f"\n--- Step 2: Running Copybara {workflow_name} ---") cmd = [ copybara_bin, str(copybara_config), - "export_to_ag", + workflow_name, ] - if cl: - cmd.append(cl) + temp_origin_obj = None + if use_folder_origin: + if is_kokoro: + cmd.append(str(google3_parent)) + else: + temp_origin_obj = tempfile.TemporaryDirectory( + prefix="conscrypt_g3_origin_" + ) + staged_main_src = ( + pathlib.Path(temp_origin_obj.name) + / "google3" + / "third_party" + / "java" + / "conscrypt" + / "main_src" + ) + staged_main_src.parent.mkdir(parents=True, exist_ok=True) + src_main_src = ( + google3_parent + / "google3" + / "third_party" + / "java" + / "conscrypt" + / "main_src" + ) + shutil.copytree(src_main_src, staged_main_src, symlinks=True) + cmd.append(temp_origin_obj.name) + if cl: + cmd.append( + "--force-message=Conscrypt: Automated export from google3 (CL" + f" {cl})\n\nPiperOrigin-RevId: {cl}" + ) + cmd.append("--force-author=Conscrypt Team ") + else: + if cl: + cmd.append(cl) cmd.extend(["--force", "--init-history", "--ignore-noop", "--verbose"]) @@ -257,12 +553,61 @@ def step_copybara_export( if dry_run: cmd.append("--dry-run") - print(f"--- Resetting {android_dir} master branch to goog/master ---") - run_cmd(["git", "fetch", "goog", "master"], cwd=android_dir, check=False) - run_cmd(["git", "checkout", "master"], cwd=android_dir, check=False) + # Determine remote name: goog or origin + remote_name = "origin" + for cand in ["goog", "origin"]: + if ( + subprocess.run( + ["git", "remote", "get-url", cand], + cwd=android_dir, + capture_output=True, + check=False, + ).returncode + == 0 + ): + remote_name = cand + break + + remote_ref = f"{remote_name}/master" + print(f"--- Resetting {android_dir} master branch to {remote_ref} ---") + fetch_res = subprocess.run( + [ + "git", + "fetch", + remote_name, + f"+refs/heads/master:refs/remotes/{remote_name}/master", + ], + cwd=android_dir, + capture_output=True, + text=True, + check=False, + ) + if fetch_res.returncode != 0: + subprocess.run( + [ + "git", + "fetch", + remote_name, + f"+refs/heads/main:refs/remotes/{remote_name}/master", + ], + cwd=android_dir, + capture_output=True, + text=True, + check=False, + ) + run_cmd( + ["git", "checkout", "-B", "master", f"refs/remotes/{remote_name}/master"], + cwd=android_dir, + check=False, + ) run_cmd( - ["git", "reset", "--hard", "goog/master"], cwd=android_dir, check=False + ["git", "reset", "--hard", f"refs/remotes/{remote_name}/master"], + cwd=android_dir, + check=False, ) + if (android_dir / ".git" / "shallow").is_file(): + print("--- Unshallowing destination repository for Copybara ---") + run_cmd(["git", "fetch", "--unshallow"], cwd=android_dir, check=False) run_cmd( ["git", "config", "receive.denyCurrentBranch", "ignore"], cwd=android_dir, @@ -271,7 +616,11 @@ def step_copybara_export( cmd.extend(extra_copybara_args) print(f"==> Running: {' '.join(cmd)}") - proc = subprocess.run(cmd, text=True, capture_output=True, check=False) + try: + proc = subprocess.run(cmd, text=True, capture_output=True, check=False) + finally: + if temp_origin_obj: + temp_origin_obj.cleanup() if proc.stdout: print(proc.stdout) if proc.stderr: @@ -282,6 +631,7 @@ def step_copybara_export( "Copybara reported NOOP (return code 4): changes are already exported" " to destination." ) + return "NOOP" elif proc.returncode != 0 and not dry_run: sys.exit( f"ERROR: Copybara export failed with return code {proc.returncode}." @@ -324,52 +674,255 @@ def find_java_binary(build_top: Optional[pathlib.Path]) -> str: if candidate.is_file(): return str(candidate) + java_home = os.environ.get("JAVA_HOME") + if java_home: + jh_java = pathlib.Path(java_home) / "bin" / "java" + if jh_java.is_file(): + return str(jh_java) + + artifacts_dir = os.environ.get("KOKORO_ARTIFACTS_DIR", "/tmpfs/src") + kokoro_java = ( + pathlib.Path(artifacts_dir) / "mpm" / "java" / "jdk" / "bin" / "java" + ) + if kokoro_java.is_file(): + return str(kokoro_java) + return shutil.which("java") or "java" -def find_currysrc_jar( +def find_javac_binary(build_top: Optional[pathlib.Path]) -> str: + """Finds an appropriate Java compiler binary (javac).""" + if build_top: + jdk21 = ( + build_top + / "prebuilts" + / "jdk" + / "jdk21" + / "linux-x86" + / "bin" + / "javac" + ) + if jdk21.is_file(): + return str(jdk21) + + for candidate in (build_top / "prebuilts" / "jdk").glob("**/bin/javac"): + if candidate.is_file(): + return str(candidate) + + java_home = os.environ.get("JAVA_HOME") + if java_home: + jh_javac = pathlib.Path(java_home) / "bin" / "javac" + if jh_javac.is_file(): + return str(jh_javac) + + artifacts_dir = os.environ.get("KOKORO_ARTIFACTS_DIR", "/tmpfs/src") + kokoro_javac = ( + pathlib.Path(artifacts_dir) / "mpm" / "java" / "jdk" / "bin" / "javac" + ) + if kokoro_javac.is_file(): + return str(kokoro_javac) + + return shutil.which("javac") or "javac" + + +def build_currysrc_jar( build_top: Optional[pathlib.Path], ) -> Optional[pathlib.Path]: - """Finds a prebuilt currysrc.jar in the build tree if available.""" - if not build_top: + """Builds a self-contained currysrc.jar on the fly from Android Gitiles.""" + cached_jar = get_writable_bin_dir() / "currysrc_built.jar" + if cached_jar.is_file() and cached_jar.stat().st_size > 1_000_000: + return cached_jar + + print("Building currysrc.jar from Android Gitiles sources...") + javac_bin = find_javac_binary(build_top) + hosts = [ + "https://android.googlesource.com", + "https://googleplex-android.googlesource.com", + ] + + try: + with tempfile.TemporaryDirectory(prefix="currysrc_build_") as tmpdir: + tmp = pathlib.Path(tmpdir) + curry_dir = tmp / "currysrc" + curry_dir.mkdir() + + # 1. Download currysrc archive (use revision with module-api-file support) + archive_data = None + archive_refs = [ + "25da81f7065b464ca0a2400c21be38a3373f88da", + "refs/heads/main", + ] + for host in hosts: + for ref in archive_refs: + url = f"{host}/platform/external/icu/+archive/{ref}/tools/srcgen/currysrc.tar.gz" + try: + archive_data = urllib.request.urlopen(url, timeout=30).read() + if archive_data: + break + except Exception: # pylint: disable=broad-except + continue + if archive_data: + break + if not archive_data: + print("Failed to download currysrc.tar.gz from Gitiles.") + return None + + with tarfile.open(fileobj=io.BytesIO(archive_data), mode="r:gz") as tf: + if hasattr(tarfile, "data_filter"): + tf.extractall(curry_dir, filter="data") + else: + tf.extractall(curry_dir) + + # Ensure Java 11 source compatibility across all JDK versions + for jf in (curry_dir / "src" / "main" / "java").glob("**/*.java"): + txt = jf.read_text() + modified = False + if "instanceof Placeholder placeholder" in txt: + txt = txt.replace( + "if (value instanceof Placeholder placeholder) {", + "if (value instanceof Placeholder) { Placeholder placeholder =" + " (Placeholder) value;", + ) + modified = True + if ".getFirst()" in txt: + txt = txt.replace(".getFirst()", ".get(0)") + modified = True + if modified: + jf.write_text(txt) + + # 2. Download Maven dependencies (jopt-simple, gson, guava) + deps = [ + ( + "jopt-simple.jar", + "platform/prebuilts/tools/+/refs/heads/main/common/m2/repository/net/sf/jopt-simple/jopt-simple/4.9/jopt-simple-4.9.jar?format=TEXT", + ), + ( + "gson.jar", + "platform/prebuilts/tools/+/refs/heads/main/common/m2/repository/com/google/code/gson/gson/2.9.1/gson-2.9.1.jar?format=TEXT", + ), + ( + "guava.jar", + "platform/prebuilts/tools/+/refs/heads/main/common/m2/repository/com/google/guava/guava/32.1.1-jre/guava-32.1.1-jre.jar?format=TEXT", + ), + ] + libs_dir = curry_dir / "libs" + libs_dir.mkdir(exist_ok=True) + for name, rel_url in deps: + dep_bytes = None + for host in hosts: + try: + b64_data = urllib.request.urlopen( + f"{host}/{rel_url}", timeout=30 + ).read() + dep_bytes = base64.b64decode(b64_data) + if dep_bytes: + break + except Exception: # pylint: disable=broad-except + continue + if not dep_bytes: + print(f"Failed to download dependency {name} from Gitiles.") + return None + (libs_dir / name).write_bytes(dep_bytes) + + # 3. Compile currysrc Java sources + classes_dir = tmp / "classes" + classes_dir.mkdir() + jars = [p for p in libs_dir.glob("*.jar") if ".source_" not in p.name] + cp = ":".join(str(p) for p in jars) + java_files = [ + str(p) + for p in (curry_dir / "src" / "main" / "java").glob("**/*.java") + ] + res = subprocess.run( + [javac_bin, "-cp", cp, "-d", str(classes_dir)] + java_files, + capture_output=True, + text=True, + check=False, + ) + if res.returncode != 0: + print(f"javac compilation of currysrc failed:\n{res.stderr}") + return None + + # 4. Package into a single self-contained fat jar + seen: set[str] = set() + with zipfile.ZipFile(cached_jar, "w", zipfile.ZIP_DEFLATED) as zout: + for p in classes_dir.rglob("*"): + if p.is_file(): + rel = p.relative_to(classes_dir).as_posix() + seen.add(rel) + zout.write(p, rel) + for j in jars: + with zipfile.ZipFile(j, "r") as zin: + for info in zin.infolist(): + if info.is_dir() or info.filename in seen: + continue + if info.filename.startswith( + "META-INF/" + ) and info.filename.endswith((".SF", ".DSA", ".RSA")): + continue + seen.add(info.filename) + zout.writestr(info, zin.read(info.filename)) + + print( + f"Successfully built {cached_jar} ({cached_jar.stat().st_size} bytes)" + ) + return cached_jar + except Exception as e: # pylint: disable=broad-except + print(f"Error building currysrc.jar: {e}") return None - paths = [ - build_top / "out" / "host" / "linux-x86" / "framework" / "currysrc.jar", - build_top - / "out" - / "soong" - / "host" - / "linux-x86" - / "framework" - / "currysrc.jar", - build_top - / "out" - / "soong" - / ".intermediates" - / "external" - / "icu" - / "tools" - / "srcgen" - / "currysrc" - / "currysrc" - / "linux_glibc_common" - / "combined" - / "currysrc.jar", - ] - for p in paths: - if p.is_file(): - return p - for p in (build_top / "out").glob("**/currysrc.jar"): - if p.is_file(): - return p +def find_currysrc_jar( + build_top: Optional[pathlib.Path], +) -> Optional[pathlib.Path]: + """Finds a prebuilt currysrc.jar in the build tree or search locations.""" + # 1. Check explicit environment variable override + env_jar = os.environ.get("CURRYSRC_JAR") + if env_jar and pathlib.Path(env_jar).is_file(): + return pathlib.Path(env_jar) + + # 2. Check Kokoro gfile directory + gfile_dir = os.environ.get("KOKORO_GFILE_DIR") + if gfile_dir: + gfile_jar = pathlib.Path(gfile_dir) / "currysrc.jar" + if gfile_jar.is_file(): + return gfile_jar + + # 3. Check x20 shared location + if X20_CURRYSRC_RO.is_file(): + return X20_CURRYSRC_RO + + # 4. Check specified build_top and all discovered Android trees + candidate_trees: List[pathlib.Path] = [] + if build_top: + candidate_trees.append(build_top) + for tree in find_candidate_android_trees(): + if tree not in candidate_trees: + candidate_trees.append(tree) + + found_jar = None + for tree in candidate_trees: + for rel_path in CURRYSRC_HOST_OUT_JARS: + jar = tree / rel_path + if jar.is_file(): + found_jar = jar + break + if found_jar: + break + + if found_jar: + return found_jar + + # 5. Build on the fly from Android Gitiles if not found locally + built_jar = build_currysrc_jar(build_top) + if built_jar and built_jar.is_file(): + return built_jar return None def run_direct_repackage( - android_dir: pathlib.Path, build_top: pathlib.Path + android_dir: pathlib.Path, build_top: Optional[pathlib.Path] ) -> bool: """Runs the currysrc Java repackaging transformation safely into temporary staging directories before copying.""" currysrc_jar = find_currysrc_jar(build_top) @@ -497,10 +1050,28 @@ def run_generate_android_src( def step_repackage_android( - android_dir: pathlib.Path, build_top: Optional[pathlib.Path] + android_dir: pathlib.Path, + build_top: Optional[pathlib.Path], + is_kokoro: bool = False, ) -> None: - """Runs currysrc repackaging via generate_android_src.sh.""" + """Runs currysrc repackaging via generate_android_src.sh or direct repackage.""" print("\n--- Step 3: Running Android repackaging ---") + diff_proc = subprocess.run( + ["git", "diff", "--name-only", "HEAD~1", "HEAD"], + cwd=android_dir, + capture_output=True, + text=True, + check=False, + ) + changed_files = [ + f.strip() for f in (diff_proc.stdout or "").splitlines() if f.strip() + ] + repackage_prefixes = ("common/", "openjdk/", "platform/", "testing/") + needs_repackage = any( + f.startswith(repackage_prefixes) and f.endswith(".java") + for f in changed_files + ) + if build_top: success = run_generate_android_src(android_dir, build_top) if success: @@ -509,10 +1080,22 @@ def step_repackage_android( "[Notice] generate_android_src.sh failed; falling back to direct" " repackaging..." ) - success = run_direct_repackage(android_dir, build_top) - if success: - return + success = run_direct_repackage(android_dir, build_top) + if success: + return + + if not needs_repackage: + print( + "[Notice] No repackageable Java files modified in this change; skipping" + " currysrc repackaging." + ) + return + if is_kokoro: + sys.exit( + "ERROR: Repackageable Java source files were modified, but currysrc.jar" + " was not found or repackaging failed." + ) print("[Notice] Could not run currysrc repackaging.") @@ -520,6 +1103,7 @@ def step_format_and_commit_android( android_dir: pathlib.Path, build_top: Optional[pathlib.Path], skip_format: bool, + cl: Optional[str] = None, ) -> None: """Stages repackaged files, formats with git-clang-format, and amends the Copybara commit.""" print( @@ -531,9 +1115,16 @@ def step_format_and_commit_android( if not skip_format: git_clang_format = None clang_format = None + candidate_trees: List[pathlib.Path] = [] if build_top: + candidate_trees.append(build_top) + for tree in find_candidate_android_trees(): + if tree not in candidate_trees: + candidate_trees.append(tree) + + for tree in candidate_trees: cand_gcf = ( - build_top + tree / "prebuilts" / "clang" / "host" @@ -543,7 +1134,7 @@ def step_format_and_commit_android( / "git-clang-format" ) cand_cf = ( - build_top + tree / "prebuilts" / "clang" / "host" @@ -552,10 +1143,12 @@ def step_format_and_commit_android( / "bin" / "clang-format" ) - if cand_gcf.is_file(): + if cand_gcf.is_file() and not git_clang_format: git_clang_format = str(cand_gcf) - if cand_cf.is_file(): + if cand_cf.is_file() and not clang_format: clang_format = str(cand_cf) + if git_clang_format and clang_format: + break if not git_clang_format: git_clang_format = shutil.which("git-clang-format") @@ -571,9 +1164,33 @@ def step_format_and_commit_android( print("Warning: git-clang-format not found. Skipping Android formatting.") print("Amending Copybara commit with repackaged and formatted changes...") + # Ensure git user identity is configured in repository (needed in Kokoro VMs) + subprocess.run( + ["git", "config", "user.name", "Conscrypt Team"], + cwd=android_dir, + check=False, + ) + subprocess.run( + ["git", "config", "user.email", "no-reply@google.com"], + cwd=android_dir, + check=False, + ) + msg = subprocess.check_output( ["git", "log", "-1", "--format=%B"], cwd=android_dir, text=True ).strip() + + # Ensure Gerrit Change-Id footer is present + if "Change-Id:" not in msg: + if cl: + seed = f"conscrypt-export-{cl}" + else: + seed = subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=android_dir, text=True + ).strip() + change_id = "I" + hashlib.sha1(seed.encode("utf-8")).hexdigest() + msg = f"{msg}\n\nChange-Id: {change_id}\n" + run_cmd( ["git", "commit", "--amend", "--allow-empty", "-m", msg], cwd=android_dir, @@ -585,17 +1202,45 @@ def step_upload_gerrit(android_dir: pathlib.Path, upload: bool) -> None: """Uploads to Gerrit directly.""" if upload: print("\n--- Step 6: Uploading complete change to Gerrit ---") - run_cmd( - [ - "git", - "push", - "-o", - "nokeycheck", - GERRIT_SSO_URL, - "HEAD:refs/for/master", - ], - cwd=android_dir, - ) + if shutil.which("git-remote-sso"): + push_urls = [GERRIT_SSO_URL] + else: + push_urls = list(GERRIT_PUSH_URLS) + + uploaded = False + last_err = "" + for url in push_urls: + print(f"==> Attempting push to {url}...") + res = subprocess.run( + [ + "git", + "push", + "-o", + "nokeycheck", + url, + "HEAD:refs/for/master", + ], + cwd=android_dir, + capture_output=True, + text=True, + check=False, + ) + if res.stdout: + print(res.stdout) + if res.stderr: + print(res.stderr, file=sys.stderr) + if res.returncode == 0: + uploaded = True + break + last_err = res.stderr.strip() + + if not uploaded: + sys.exit( + "ERROR: Failed to upload change to Android Gerrit across all" + f" candidate URLs.\nLast error: {last_err}\nIf running in Kokoro," + " ensure the job's service account or git cookies have push access" + " to googleplex-android/platform/external/conscrypt." + ) print( "\nSuccessfully uploaded complete (source + repackaged + formatted)" " change to Android Gerrit!" @@ -634,7 +1279,10 @@ def main() -> None: "cl_pos", nargs="?", default=None, - help="Optional positional CL number or revision to export (e.g. 123456789).", + help=( + "Optional positional CL number or revision to export (e.g." + " 123456789)." + ), ) parser.add_argument( "--cl", @@ -691,6 +1339,15 @@ def main() -> None: " to Android Gerrit." ), ) + parser.add_argument( + "--folder_origin", + action="store_true", + default=None, + help=( + "Use folder.origin() workflow (export_to_ag_folder) instead of" + " piper.origin(). Automatically enabled in Kokoro CI mode." + ), + ) parser.add_argument( "--dry_run", action="store_true", @@ -699,6 +1356,8 @@ def main() -> None: args, extra_copybara_args = parser.parse_known_args() + setup_kokoro_git_env() + script_path = pathlib.Path(__file__).resolve() main_src_dir = ( script_path.parent.parent @@ -721,42 +1380,27 @@ def main() -> None: is_kokoro = bool( os.environ.get("KOKORO_JOB_NAME") or os.environ.get("KOKORO_BUILD_NUMBER") ) + use_folder_origin = ( + args.folder_origin if args.folder_origin is not None else is_kokoro + ) + google3_parent = find_google3_parent(script_path) print("=======================================================") print(" Conscrypt google3 -> Android Gerrit Automated Exporter") print("=======================================================") print(f"Google3 source dir : {main_src_dir}") + print(f"Google3 parent dir : {google3_parent}") print(f"Copybara config : {copybara_config}") print( "CL / Revision :" f" {target_cl if target_cl else '(Latest HEAD / default)'}" ) print(f"Kokoro CI mode : {is_kokoro}") + print(f"Folder origin mode : {use_folder_origin}") print(f"Upload to Gerrit : {args.upload}") print(f"Dry run mode : {args.dry_run}") print("=======================================================\n") - if is_kokoro: - print("\n--- Running in Kokoro CI mode: Direct Copybara export ---") - copybara_cmd = [ - copybara_bin, - str(copybara_config), - "export_to_ag", - "--force", - "--init-history", - "--ignore-noop", - "--verbose", - ] - if target_cl: - copybara_cmd.append(target_cl) - if args.dry_run: - copybara_cmd.append("--dry-run") - if extra_copybara_args: - copybara_cmd.extend(extra_copybara_args) - run_cmd(copybara_cmd) - print("\n[Kokoro] Direct Copybara export completed successfully.") - return - android_dir, build_top, temp_dir_holder = resolve_android_and_build_top( args.android_dir ) @@ -770,20 +1414,30 @@ def main() -> None: force=args.force, ) - # Step 1: Format google3 - if not args.skip_format_g3 and not args.dry_run: + # Step 1: Format google3 (skipped in Kokoro as CL is already submitted) + if not args.skip_format_g3 and not is_kokoro and not args.dry_run: step_format_google3(main_src_dir) # Step 2: Copybara export - _ = step_copybara_export( + export_res = step_copybara_export( copybara_bin=copybara_bin, copybara_config=copybara_config, android_dir=android_dir, + google3_parent=google3_parent, cl=target_cl, dry_run=args.dry_run, + use_folder_origin=use_folder_origin, extra_copybara_args=extra_copybara_args, + is_kokoro=is_kokoro, ) + if export_res == "NOOP": + print( + "\n[NOOP] Changes are already exported to destination. Exiting" + " cleanly." + ) + return + if args.dry_run: print("\n[Dry Run] Copybara dry-run completed successfully.") return @@ -793,13 +1447,14 @@ def main() -> None: # Step 4: Repackage Android safely if not args.skip_repackage: - step_repackage_android(android_dir, build_top) + step_repackage_android(android_dir, build_top, is_kokoro=is_kokoro) # Step 5: Format Android with git-clang-format and Amend Copybara commit step_format_and_commit_android( android_dir=android_dir, build_top=build_top, skip_format=args.skip_format_ag, + cl=target_cl, ) # Step 6: Upload to Gerrit diff --git a/testing/src/main/java/org/conscrypt/javax/net/ssl/TestSSLContext.java b/testing/src/main/java/org/conscrypt/javax/net/ssl/TestSSLContext.java index 20cfae792..4a3652bf9 100644 --- a/testing/src/main/java/org/conscrypt/javax/net/ssl/TestSSLContext.java +++ b/testing/src/main/java/org/conscrypt/javax/net/ssl/TestSSLContext.java @@ -200,9 +200,8 @@ public void close() { } catch (SocketException e) { // Sockets on older Android runtimes (e.g. Android 8.0 / API 26) may throw // "socket already closed" exceptions when closing. - if (TestUtils.isAndroid() - && !TestUtils.isAndroidSdkGreater(27) - && e.getMessage().contains("socket already closed")) { + if (TestUtils.isAndroid() && !TestUtils.isAndroidSdkGreater(27) + && e.getMessage().contains("socket already closed")) { return; } throw new RuntimeException(e);