From 5f882cadde5a496b5d308dc03eba60d4cb7dda90 Mon Sep 17 00:00:00 2001 From: Miguel Aranda Date: Tue, 17 Mar 2026 12:39:26 +0000 Subject: [PATCH] Project import generated by Copybara. PiperOrigin-RevId: 884967932 --- README.md | 80 -- .../ConscryptNetworkSecurityPolicy.java | 45 ++ .../src/main/java/org/conscrypt/Platform.java | 60 +- .../conscrypt/compatibility_close_monitor.cc | 3 +- common/src/jni/main/cpp/conscrypt/jniload.cc | 8 +- common/src/jni/main/cpp/conscrypt/jniutil.cc | 19 +- .../jni/main/cpp/conscrypt/native_crypto.cc | 678 +++++++++++----- common/src/jni/main/include/conscrypt/NetFd.h | 9 +- .../src/jni/main/include/conscrypt/app_data.h | 15 +- .../src/jni/main/include/conscrypt/compat.h | 7 +- .../conscrypt/compatibility_close_monitor.h | 16 +- .../src/jni/main/include/conscrypt/jniutil.h | 25 +- .../src/jni/main/include/conscrypt/macros.h | 10 +- .../main/include/conscrypt/scoped_ssl_bio.h | 5 +- common/src/jni/main/include/conscrypt/trace.h | 3 +- .../nativehelper/scoped_primitive_array.h | 18 +- .../java/org/conscrypt/ActiveSession.java | 21 + .../java/org/conscrypt/CertBlocklist.java | 3 + .../org/conscrypt/CertBlocklistEntry.java | 37 + .../java/org/conscrypt/ConscryptEngine.java | 6 +- .../ConscryptFileDescriptorSocket.java | 6 +- .../java/org/conscrypt/ConscryptSession.java | 4 + .../conscrypt/ConscryptX509TrustManager.java | 63 ++ .../org/conscrypt/DomainEncryptionMode.java | 19 + .../main/java/org/conscrypt/EchOptions.java | 36 + .../java/org/conscrypt/ExternalSession.java | 10 + .../src/main/java/org/conscrypt/HpkeImpl.java | 68 ++ .../main/java/org/conscrypt/HpkeSuite.java | 19 +- .../conscrypt/Java7ExtendedSSLSession.java | 17 +- .../java/org/conscrypt/MlKemAlgorithm.java | 41 + .../main/java/org/conscrypt/NativeCrypto.java | 636 ++++++++++----- .../main/java/org/conscrypt/NativeSsl.java | 22 + .../org/conscrypt/NetworkSecurityPolicy.java | 34 + .../java/org/conscrypt/OpenSSLProvider.java | 22 + .../conscrypt/OpenSSLX25519PrivateKey.java | 15 +- .../org/conscrypt/OpenSSLX25519PublicKey.java | 12 +- .../org/conscrypt/OpenSslMlKemKeyFactory.java | 273 +++++++ .../OpenSslMlKemKeyPairGenerator.java | 85 ++ .../org/conscrypt/OpenSslMlKemPrivateKey.java | 116 +++ .../org/conscrypt/OpenSslMlKemPublicKey.java | 111 +++ .../java/org/conscrypt/SSLNullSession.java | 10 + .../java/org/conscrypt/SSLParametersImpl.java | 81 +- .../src/main/java/org/conscrypt/SSLUtils.java | 63 ++ .../java/org/conscrypt/SessionSnapshot.java | 16 + .../java/org/conscrypt/TrustManagerImpl.java | 65 +- .../org/conscrypt/ct/CertificateEntry.java | 13 + .../conscrypt/ct/CertificateTransparency.java | 19 +- .../main/java/org/conscrypt/ct/LogInfo.java | 56 +- .../org/conscrypt/ct/PolicyCompliance.java | 9 +- .../ct/SignedCertificateTimestamp.java | 13 +- ...ificateTransparencyVerificationReason.java | 7 +- .../conscrypt/metrics/ConscryptStatsLog.java | 32 +- .../org/conscrypt/metrics/NoopStatsLog.java | 3 + .../java/org/conscrypt/metrics/StatsLog.java | 3 + .../org/conscrypt/metrics/StatsLogImpl.java | 123 ++- .../test/java/org/conscrypt/MlKemTest.java | 747 ++++++++++++++++++ .../org/conscrypt/NativeCryptoArgTest.java | 6 - .../org/conscrypt/ct/SerializationTest.java | 9 + .../java/org/conscrypt/ct/VerifierTest.java | 14 +- .../javax/net/ssl/KeyManagerFactoryTest.java | 18 +- .../javax/net/ssl/SSLSessionTest.java | 2 +- .../SSLSocketVersionCompatibilityTest.java | 1 - common/src/test/resources/crypto/mlkem.txt | 54 ++ .../ConscryptNetworkSecurityPolicy.java | 45 ++ .../src/main/java/org/conscrypt/Platform.java | 58 +- .../org/conscrypt/ConscryptAndroidSuite.java | 1 + .../org/conscrypt/ConscryptOpenJdkSuite.java | 1 + .../java/org/conscrypt/NativeCryptoTest.java | 202 ++++- platform/src/main/ct_log_store.fbs | 38 + .../ConscryptNetworkSecurityPolicy.java | 102 +++ platform/src/main/java/org/conscrypt/Hex.java | 41 +- .../src/main/java/org/conscrypt/Platform.java | 80 +- .../java/org/conscrypt/ct/LogStoreImpl.java | 182 +++-- .../java/org/conscrypt/ct/PolicyImpl.java | 30 +- .../java/org/conscrypt/CertBlocklistTest.java | 43 + .../org/conscrypt/ct/LogStoreImplTest.java | 259 ++++-- .../java/org/conscrypt/ct/PolicyImplTest.java | 91 ++- .../conscrypt/java/security/TestKeyStore.java | 2 +- 78 files changed, 4283 insertions(+), 933 deletions(-) create mode 100644 android/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java create mode 100644 common/src/main/java/org/conscrypt/CertBlocklistEntry.java create mode 100644 common/src/main/java/org/conscrypt/ConscryptX509TrustManager.java create mode 100644 common/src/main/java/org/conscrypt/DomainEncryptionMode.java create mode 100644 common/src/main/java/org/conscrypt/EchOptions.java create mode 100644 common/src/main/java/org/conscrypt/MlKemAlgorithm.java create mode 100644 common/src/main/java/org/conscrypt/NetworkSecurityPolicy.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslMlKemKeyFactory.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslMlKemKeyPairGenerator.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslMlKemPrivateKey.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslMlKemPublicKey.java create mode 100644 common/src/test/java/org/conscrypt/MlKemTest.java create mode 100644 common/src/test/resources/crypto/mlkem.txt create mode 100644 openjdk/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java create mode 100644 platform/src/main/ct_log_store.fbs create mode 100644 platform/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java diff --git a/README.md b/README.md index 44f163372..3a7a82479 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,3 @@ -# Evolving Conscrypt's Open Source Approach - -Hello Conscrypt Developers, - -We're refining our **open source strategy for Conscrypt** to ensure its long-term health and sustainability. To optimize our development efforts and focus resources, we are making some changes to how Conscrypt is developed and how we handle contributions. The primary development of Conscrypt will now be done internally at Google. While we value community input, we will no longer be able to accept external contributions in the form of pull requests on the GitHub repository. This change allows us to better allocate resources to core development and ensure the project's long-term sustainability. To ensure transparency and continued access for the community, we will: - -* **Continue Mirroring to GitHub:** All internal changes will be regularly mirrored to the public GitHub repository. Note that mirroring to GitHub might be paused for a short period of time during the transition to internal development. -* **Maintain Bug Reporting Channels:** Please keep reporting bugs through GitHub Issues. For Android-specific bugs, the Android Issue Tracker is the place to go: [Report Bugs](https://source.android.com/docs/setup/contribute/report-bugs). - -### What’s staying the same - -The platform version of Conscrypt receives regular updates, including the latest features and security patches, through the Google Play system updates program (Project Mainline). This means that even devices running older Android versions can benefit from the most recent Conscrypt improvements without requiring a full OS update. - -### What’s changing - -As part of this shift, we will no longer be able to accept external pull requests on GitHub. - -### What do you need to do - -* Immediately, nothing. -* For Android developers, we recommend leveraging the Conscrypt version built into the Android platform, which is also the default provider. - -We appreciate the Conscrypt community and look forward to continuing to offer a secure and efficient security provider. - -## Guidance for Android App Developers: - -Most Android devices include Conscrypt as a core part of the platform's security providers. The Java Cryptography Architecture (JCA) framework allows for multiple security providers, and the system selects one when you request a cryptographic algorithm implementation (like Cipher, SSLContext, MessageDigest, etc.). - -### Using the Platform Version (Recommended): - -To use the platform-provided Conscrypt, you generally don't need to do anything specific. When requesting an algorithm, omit the provider name. The Android system will automatically select the highest-priority provider that offers the requested algorithm, which is typically the built-in Conscrypt. - -*Example (Java):* - -```java -import javax.net.ssl.SSLContext; -import java.security.Security; -import java.security.Provider; - -try { - // Get an SSLContext instance using the default highest-priority provider - SSLContext sslContext = SSLContext.getInstance("TLS"); - // Initialize and use sslContext - - // Example: Listing providers to see what's available - // Provider[] providers = Security.getProviders(); - // for (Provider provider : providers) { - // System.out.println("Provider: " + provider.getName()); - // } -} catch (NoSuchAlgorithmException e) { - // Handle exception - e.printStackTrace(); -} -``` - -*Example (Kotlin):* - -```kotlin -import javax.net.ssl.SSLContext -import java.security.Security -import java.security.Provider - -try { - // Get an SSLContext instance using the default highest-priority provider - val sslContext = SSLContext.getInstance("TLS") - // Initialize and use sslContext - - // Example: Listing providers to see what's available - // val providers = Security.getProviders() - // providers.forEach { provider -> - // println("Provider: ${provider.name}") - // } -} catch (e: NoSuchAlgorithmException) { - // Handle exception - e.printStackTrace() -} -``` - -By *not* specifying a provider name in getInstance() calls, you rely on the Android system's default provider order, ensuring you use the up-to-date and maintained version of Conscrypt that is part of the Android platform. - Conscrypt - A Java Security Provider ======================================== diff --git a/android/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java b/android/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java new file mode 100644 index 000000000..4f5f0e6ad --- /dev/null +++ b/android/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import org.conscrypt.metrics.CertificateTransparencyVerificationReason; + +/** + * A default NetworkSecurityPolicy for unbundled Android. + */ +@Internal +public class ConscryptNetworkSecurityPolicy implements NetworkSecurityPolicy { + public static ConscryptNetworkSecurityPolicy getDefault() { + return new ConscryptNetworkSecurityPolicy(); + } + + @Override + public boolean isCertificateTransparencyVerificationRequired(String hostname) { + return false; + } + + @Override + public CertificateTransparencyVerificationReason getCertificateTransparencyVerificationReason( + String hostname) { + return CertificateTransparencyVerificationReason.UNKNOWN; + } + + @Override + public DomainEncryptionMode getDomainEncryptionMode(String hostname) { + return DomainEncryptionMode.UNKNOWN; + } +} diff --git a/android/src/main/java/org/conscrypt/Platform.java b/android/src/main/java/org/conscrypt/Platform.java index b3433f12c..905835ada 100644 --- a/android/src/main/java/org/conscrypt/Platform.java +++ b/android/src/main/java/org/conscrypt/Platform.java @@ -59,11 +59,13 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.function.Supplier; import javax.net.ssl.SNIHostName; import javax.net.ssl.SNIMatcher; import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; @@ -859,59 +861,8 @@ static boolean supportsX509ExtendedTrustManager() { return Build.VERSION.SDK_INT > 23; } - /** - * Check if SCT verification is required for a given hostname. - * - * SCT Verification is enabled using {@code Security} properties. - * The "conscrypt.ct.enable" property must be true, as well as a per domain property. - * The reverse notation of the domain name, prefixed with "conscrypt.ct.enforce." - * is used as the property name. - * Basic globbing is also supported. - * - * For example, for the domain foo.bar.com, the following properties will be - * looked up, in order of precedence. - * - conscrypt.ct.enforce.com.bar.foo - * - conscrypt.ct.enforce.com.bar.* - * - conscrypt.ct.enforce.com.* - * - conscrypt.ct.enforce.* - */ - public static boolean isCTVerificationRequired(String hostname) { - if (hostname == null) { - return false; - } - // TODO: Use the platform version on platforms that support it - - String property = Security.getProperty("conscrypt.ct.enable"); - if (property == null || !Boolean.parseBoolean(property)) { - return false; - } - - List parts = Arrays.asList(hostname.split("\\.")); - Collections.reverse(parts); - - boolean enable = false; - String propertyName = "conscrypt.ct.enforce"; - // The loop keeps going on even once we've found a match - // This allows for finer grained settings on subdomains - for (String part : parts) { - property = Security.getProperty(propertyName + ".*"); - if (property != null) { - enable = Boolean.parseBoolean(property); - } - - propertyName = propertyName + "." + part; - } - - property = Security.getProperty(propertyName); - if (property != null) { - enable = Boolean.parseBoolean(property); - } - return enable; - } - - public static CertificateTransparencyVerificationReason reasonCTVerificationRequired( - String hostname) { - return CertificateTransparencyVerificationReason.UNKNOWN; + static SSLException wrapInvalidEchDataException(SSLException e) { + return e; } static boolean supportsConscryptCertStore() { @@ -940,7 +891,8 @@ static CertBlocklist newDefaultBlocklist() { return null; } - static CertificateTransparency newDefaultCertificateTransparency() { + static CertificateTransparency newDefaultCertificateTransparency( + Supplier policySupplier) { return null; } diff --git a/common/src/jni/main/cpp/conscrypt/compatibility_close_monitor.cc b/common/src/jni/main/cpp/conscrypt/compatibility_close_monitor.cc index a4d9a9e82..ca0d37770 100644 --- a/common/src/jni/main/cpp/conscrypt/compatibility_close_monitor.cc +++ b/common/src/jni/main/cpp/conscrypt/compatibility_close_monitor.cc @@ -43,7 +43,8 @@ void CompatibilityCloseMonitor::init() { return; } #ifdef CONSCRYPT_UNBUNDLED - // Only attempt to initialise the legacy C++ API if the C API symbols were not found. + // Only attempt to initialise the legacy C++ API if the C API symbols were not + // found. lib = dlopen("libjavacore.so", RTLD_NOW); if (lib != nullptr) { if (asyncCloseMonitorCreate == nullptr) { diff --git a/common/src/jni/main/cpp/conscrypt/jniload.cc b/common/src/jni/main/cpp/conscrypt/jniload.cc index 49a3bd24d..b54aa93b3 100644 --- a/common/src/jni/main/cpp/conscrypt/jniload.cc +++ b/common/src/jni/main/cpp/conscrypt/jniload.cc @@ -41,15 +41,17 @@ jint libconscrypt_JNI_OnLoad(JavaVM* vm, void*) { // Register all of the native JNI methods. NativeCrypto::registerNativeMethods(env); - // Perform static initialization of the close monitor (if required on this platform). + // Perform static initialization of the close monitor (if required on this + // platform). CompatibilityCloseMonitor::init(); return CONSCRYPT_JNI_VERSION; } #ifdef STATIC_LIB -// A version of OnLoad called when the Conscrypt library has been statically linked to the JVM (For -// Java >= 1.8). The manner in which the library is statically linked is implementation specific. +// A version of OnLoad called when the Conscrypt library has been statically +// linked to the JVM (For Java >= 1.8). The manner in which the library is +// statically linked is implementation specific. // // See http://openjdk.java.net/jeps/178 CONSCRYPT_PUBLIC jint JNI_OnLoad_conscrypt(JavaVM* vm, void* reserved) { diff --git a/common/src/jni/main/cpp/conscrypt/jniutil.cc b/common/src/jni/main/cpp/conscrypt/jniutil.cc index ae24377e5..a57c6d810 100644 --- a/common/src/jni/main/cpp/conscrypt/jniutil.cc +++ b/common/src/jni/main/cpp/conscrypt/jniutil.cc @@ -93,7 +93,10 @@ void init(JavaVM* vm, JNIEnv* env) { openSslInputStreamClass = getGlobalRefToClass( env, TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLBIOInputStream"); sslHandshakeCallbacksClass = getGlobalRefToClass( - env, TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/NativeCrypto$SSLHandshakeCallbacks"); + env, + TO_STRING( + JNI_JARJAR_PREFIX) "org/conscrypt/" + "NativeCrypto$SSLHandshakeCallbacks"); nativeRef_address = getFieldRef(env, nativeRefClass, "address", "J"); #if defined(ANDROID) && !defined(CONSCRYPT_OPENJDK) @@ -119,7 +122,7 @@ void init(JavaVM* vm, JNIEnv* env) { sslHandshakeCallbacks_clientCertificateRequested = getMethodRef( env, sslHandshakeCallbacksClass, "clientCertificateRequested", "([B[I[[B)V"); sslHandshakeCallbacks_serverCertificateRequested = - getMethodRef(env, sslHandshakeCallbacksClass, "serverCertificateRequested", "()V"); + getMethodRef(env, sslHandshakeCallbacksClass, "serverCertificateRequested", "([I)V"); sslHandshakeCallbacks_clientPSKKeyRequested = getMethodRef( env, sslHandshakeCallbacksClass, "clientPSKKeyRequested", "(Ljava/lang/String;[B[B)I"); sslHandshakeCallbacks_serverPSKKeyRequested = @@ -178,8 +181,8 @@ int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor) { } extern bool isDirectByteBufferInstance(JNIEnv* env, jobject buffer) { - // Some versions of ART do not check the buffer validity when handling GetDirectBufferAddress() - // and GetDirectBufferCapacity(). + // Some versions of ART do not check the buffer validity when handling + // GetDirectBufferAddress() and GetDirectBufferCapacity(). if (buffer == nullptr) { return false; } @@ -191,7 +194,8 @@ extern bool isDirectByteBufferInstance(JNIEnv* env, jobject buffer) { bool isGetByteArrayElementsLikelyToReturnACopy(size_t size) { #if defined(ANDROID) && !defined(CONSCRYPT_OPENJDK) - // ART's GetByteArrayElements creates copies only for arrays smaller than 12 kB. + // ART's GetByteArrayElements creates copies only for arrays smaller than 12 + // kB. return size <= 12 * 1024; #else (void)size; @@ -447,8 +451,9 @@ void throwExceptionFromBoringSSLError(JNIEnv* env, CONSCRYPT_UNUSED const char* return; } - // If there's an error from BoringSSL it may have been caused by an exception in Java code, so - // ensure there isn't a pending exception before we throw a new one. + // If there's an error from BoringSSL it may have been caused by an exception + // in Java code, so ensure there isn't a pending exception before we throw a + // new one. if (!env->ExceptionCheck()) { char message[256]; ERR_error_string_n(error, message, sizeof(message)); diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index eaea7717f..628ba29c8 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +59,8 @@ #include #include +#include "jni.h" + using conscrypt::AppData; using conscrypt::BioInputStream; using conscrypt::BioOutputStream; @@ -67,8 +70,8 @@ using conscrypt::NativeCrypto; using conscrypt::SslError; /** - * Helper function that grabs the casts an ssl pointer and then checks for nullness. - * If this function returns nullptr and throwIfNull is + * Helper function that grabs the casts an ssl pointer and then checks for + * nullness. If this function returns nullptr and throwIfNull is * passed as true, then this function will call * throwSSLExceptionStr before returning, so in this case of * nullptr, a caller of this function should simply return and allow JNI @@ -248,7 +251,8 @@ static jbyteArray bignumToArray(JNIEnv* env, const BIGNUM* source, const char* s return nullptr; } - // Set the sign and convert to two's complement if necessary for the Java code. + // Set the sign and convert to two's complement if necessary for the Java + // code. if (BN_is_negative(source)) { bool carry = true; for (ssize_t i = static_cast(numBytes - 1); i >= 0; i--) { @@ -825,14 +829,17 @@ void init_engine_globals() { #define THROWN_EXCEPTION (-4) /** - * private static native int EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q); + * private static native int EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, + * byte[] p, byte[] q); */ static jlong NativeCrypto_EVP_PKEY_new_RSA(JNIEnv* env, jclass, jbyteArray n, jbyteArray e, jbyteArray d, jbyteArray p, jbyteArray q, jbyteArray dmp1, jbyteArray dmq1, jbyteArray iqmp) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_PKEY_new_RSA(n=%p, e=%p, d=%p, p=%p, q=%p, dmp1=%p, dmq1=%p, iqmp=%p)", n, e, d, - p, q, dmp1, dmq1, iqmp); + JNI_TRACE( + "EVP_PKEY_new_RSA(n=%p, e=%p, d=%p, p=%p, q=%p, dmp1=%p, dmq1=%p, " + "iqmp=%p)", + n, e, d, p, q, dmp1, dmq1, iqmp); if (e == nullptr && d == nullptr) { conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", @@ -892,10 +899,11 @@ static jlong NativeCrypto_EVP_PKEY_new_RSA(JNIEnv* env, jclass, jbyteArray n, jb // Determine what kind of key this is. // - // TODO(davidben): The caller already knows what kind of key they expect. Ideally we would have - // separate APIs for the caller. However, we currently tolerate, say, an RSAPrivateCrtKeySpec - // where most fields are null and silently make a public key out of it. This is probably a - // mistake, but would need to be a breaking change. + // TODO(davidben): The caller already knows what kind of key they expect. + // Ideally we would have separate APIs for the caller. However, we currently + // tolerate, say, an RSAPrivateCrtKeySpec where most fields are null and + // silently make a public key out of it. This is probably a mistake, but would + // need to be a breaking change. bssl::UniquePtr rsa; if (!dBN) { rsa.reset(RSA_new_public_key(nBN.get(), eBN.get())); @@ -987,8 +995,10 @@ static jlong NativeCrypto_EVP_PKEY_new_RSA(JNIEnv* env, jclass, jbyteArray n, jb return 0; } OWNERSHIP_TRANSFERRED(rsa); - JNI_TRACE("EVP_PKEY_new_RSA(n=%p, e=%p, d=%p, p=%p, q=%p dmp1=%p, dmq1=%p, iqmp=%p) => %p", n, - e, d, p, q, dmp1, dmq1, iqmp, pkey.get()); + JNI_TRACE( + "EVP_PKEY_new_RSA(n=%p, e=%p, d=%p, p=%p, q=%p dmp1=%p, dmq1=%p, " + "iqmp=%p) => %p", + n, e, d, p, q, dmp1, dmq1, iqmp, pkey.get()); return reinterpret_cast(pkey.release()); } @@ -1208,9 +1218,9 @@ static jlong NativeCrypto_EVP_parse_private_key(JNIEnv* env, jclass, jbyteArray CBS cbs; CBS_init(&cbs, reinterpret_cast(bytes.get()), bytes.size()); bssl::UniquePtr pkey(EVP_parse_private_key(&cbs)); - // We intentionally do not check that cbs is exhausted, as JCA providers typically - // allow parsing keys from buffers that are larger than the contained key structure - // so we do the same for compatibility. + // We intentionally do not check that cbs is exhausted, as JCA providers + // typically allow parsing keys from buffers that are larger than the + // contained key structure so we do the same for compatibility. if (!pkey) { conscrypt::jniutil::throwParsingException(env, "Error parsing private key"); ERR_clear_error(); @@ -1657,9 +1667,9 @@ static jlong NativeCrypto_EVP_parse_public_key(JNIEnv* env, jclass, jbyteArray k CBS cbs; CBS_init(&cbs, reinterpret_cast(bytes.get()), bytes.size()); bssl::UniquePtr pkey(EVP_parse_public_key(&cbs)); - // We intentionally do not check that cbs is exhausted, as JCA providers typically - // allow parsing keys from buffers that are larger than the contained key structure - // so we do the same for compatibility. + // We intentionally do not check that cbs is exhausted, as JCA providers + // typically allow parsing keys from buffers that are larger than the + // contained key structure so we do the same for compatibility. if (!pkey) { conscrypt::jniutil::throwParsingException(env, "Error parsing public key"); ERR_clear_error(); @@ -1686,7 +1696,8 @@ static jlong NativeCrypto_getRSAPrivateKeyWrapper(JNIEnv* env, jclass, jobject j return 0; } - // TODO(crbug.com/boringssl/602): RSA_METHOD is not the ideal abstraction to use here. + // TODO(crbug.com/boringssl/602): RSA_METHOD is not the ideal abstraction to + // use here. bssl::UniquePtr rsa(RSA_new_method_no_e(g_engine, n.get())); if (rsa == nullptr) { conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate RSA key"); @@ -1782,7 +1793,8 @@ static jlong NativeCrypto_getECPrivateKeyWrapper(JNIEnv* env, jclass, jobject ja } /* - * public static native int RSA_generate_key(int modulusBits, byte[] publicExponent); + * public static native int RSA_generate_key(int modulusBits, byte[] + * publicExponent); */ static jlong NativeCrypto_RSA_generate_key_ex(JNIEnv* env, jclass, jint modulusBits, jbyteArray publicExponent) { @@ -2048,22 +2060,30 @@ static void NativeCrypto_chacha20_encrypt_decrypt(JNIEnv* env, jclass, jbyteArra JNI_TRACE("chacha20_encrypt_decrypt"); ScopedByteArrayRO in(env, inBytes); if (in.get() == nullptr) { - JNI_TRACE("chacha20_encrypt_decrypt => threw exception: could not read input bytes"); + JNI_TRACE( + "chacha20_encrypt_decrypt => threw exception: could not read input " + "bytes"); return; } ScopedByteArrayRW out(env, outBytes); if (out.get() == nullptr) { - JNI_TRACE("chacha20_encrypt_decrypt => threw exception: could not read output bytes"); + JNI_TRACE( + "chacha20_encrypt_decrypt => threw exception: could not read output " + "bytes"); return; } ScopedByteArrayRO key(env, keyBytes); if (key.get() == nullptr) { - JNI_TRACE("chacha20_encrypt_decrypt => threw exception: could not read key bytes"); + JNI_TRACE( + "chacha20_encrypt_decrypt => threw exception: could not read key " + "bytes"); return; } ScopedByteArrayRO nonce(env, nonceBytes); if (nonce.get() == nullptr) { - JNI_TRACE("chacha20_encrypt_decrypt => threw exception: could not read nonce bytes"); + JNI_TRACE( + "chacha20_encrypt_decrypt => threw exception: could not read nonce " + "bytes"); return; } @@ -2840,8 +2860,8 @@ static jint NativeCrypto_ECDSA_verify(JNIEnv* env, jclass, jbyteArray data, jint unsigned long error = ERR_peek_last_error(); if ((ERR_GET_LIB(error) == ERR_LIB_ECDSA) && (ERR_GET_REASON(error) == ECDSA_R_BAD_SIGNATURE)) { - // This error just means the signature didn't verify, so clear the error and return - // a failed verification + // This error just means the signature didn't verify, so clear the error + // and return a failed verification ERR_clear_error(); JNI_TRACE("ECDSA_verify(%p, %d, %p, %p) => %d", data, dataLen, sig, pkey, result); return 0; @@ -3240,6 +3260,108 @@ static jbyteArray NativeCrypto_XWING_public_key_from_seed(JNIEnv* env, jclass, return publicKeyRef.release(); } +static jbyteArray NativeCrypto_MLKEM768_public_key_from_seed(JNIEnv* env, jclass, + jbyteArray privateKeySeed) { + CHECK_ERROR_QUEUE_ON_RETURN; + + ScopedByteArrayRO seedArray(env, privateKeySeed); + if (seedArray.get() == nullptr) { + JNI_TRACE("MLKEM768_public_key_from_seed => privateKeySeed == null"); + return nullptr; + } + + if (seedArray.size() != MLKEM_SEED_BYTES) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "privateKeySeed length != 64"); + return nullptr; + } + + MLKEM768_private_key privateKey; + if (!MLKEM768_private_key_from_seed( + &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { + JNI_TRACE("MLKEM768_private_key_from_seed failed"); + conscrypt::jniutil::throwIllegalArgumentException(env, + "MLKEM768_private_key_from_seed failed"); + return nullptr; + } + + MLKEM768_public_key publicKey; + MLKEM768_public_from_private(&publicKey, &privateKey); + + ScopedLocalRef publicKeyRef( + env, env->NewByteArray(static_cast(MLKEM768_PUBLIC_KEY_BYTES))); + if (publicKeyRef.get() == nullptr) { + return nullptr; + } + ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); + if (publicKeyArray.get() == nullptr) { + return nullptr; + } + + CBB cbb; + size_t size; + if (!CBB_init_fixed(&cbb, reinterpret_cast(publicKeyArray.get()), + publicKeyArray.size()) || + !MLKEM768_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || + size != MLKEM768_PUBLIC_KEY_BYTES) { + JNI_TRACE("MLKEM768_marshal_public_key failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLKEM768_marshal_public_key"); + return nullptr; + } + return publicKeyRef.release(); +} + +static jbyteArray NativeCrypto_MLKEM1024_public_key_from_seed(JNIEnv* env, jclass, + jbyteArray privateKeySeed) { + CHECK_ERROR_QUEUE_ON_RETURN; + + ScopedByteArrayRO seedArray(env, privateKeySeed); + if (seedArray.get() == nullptr) { + JNI_TRACE("MLKEM1024_public_key_from_seed => privateKeySeed == null"); + return nullptr; + } + + if (seedArray.size() != MLKEM_SEED_BYTES) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "privateKeySeed length != 64"); + return nullptr; + } + + MLKEM1024_private_key privateKey; + if (!MLKEM1024_private_key_from_seed( + &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { + JNI_TRACE("MLKEM1024_private_key_from_seed failed"); + conscrypt::jniutil::throwIllegalArgumentException(env, + "MLKEM1024_private_key_from_seed failed"); + return nullptr; + } + + MLKEM1024_public_key publicKey; + MLKEM1024_public_from_private(&publicKey, &privateKey); + + ScopedLocalRef publicKeyRef( + env, env->NewByteArray(static_cast(MLKEM1024_PUBLIC_KEY_BYTES))); + if (publicKeyRef.get() == nullptr) { + return nullptr; + } + ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); + if (publicKeyArray.get() == nullptr) { + return nullptr; + } + + CBB cbb; + size_t size; + if (!CBB_init_fixed(&cbb, reinterpret_cast(publicKeyArray.get()), + publicKeyArray.size()) || + !MLKEM1024_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || + size != MLKEM1024_PUBLIC_KEY_BYTES) { + JNI_TRACE("MLKEM1024_marshal_public_key failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLKEM1024_marshal_public_key"); + return nullptr; + } + return publicKeyRef.release(); +} + static jlong NativeCrypto_EVP_MD_CTX_create(JNIEnv* env, jclass) { CHECK_ERROR_QUEUE_ON_RETURN; JNI_TRACE_MD("EVP_MD_CTX_create()"); @@ -3526,18 +3648,18 @@ static void evpUpdate(JNIEnv* env, jobject evpMdCtxRef, jbyteArray inJavaBytes, int update_func_result = -1; if (conscrypt::jniutil::isGetByteArrayElementsLikelyToReturnACopy(array_size)) { - // GetByteArrayElements is expected to return a copy. Use GetByteArrayRegion instead, to - // avoid copying the whole array. + // GetByteArrayElements is expected to return a copy. Use GetByteArrayRegion + // instead, to avoid copying the whole array. if (in_size <= 1024) { - // For small chunk, it's more efficient to use a bit more space on the stack instead of - // allocating a new buffer. + // For small chunk, it's more efficient to use a bit more space on the + // stack instead of allocating a new buffer. jbyte buf[1024]; env->GetByteArrayRegion(inJavaBytes, in_offset, in_size, buf); update_func_result = update_func(mdCtx, reinterpret_cast(buf), static_cast(in_size)); } else { - // For large chunk, allocate a 64 kB buffer and stream the chunk into update_func - // through the buffer, stopping as soon as update_func fails. + // For large chunk, allocate a 64 kB buffer and stream the chunk into + // update_func through the buffer, stopping as soon as update_func fails. jint remaining = in_size; jint buf_size = (remaining >= 65536) ? 65536 : remaining; std::unique_ptr buf(new jbyte[static_cast(buf_size)]); @@ -3560,9 +3682,10 @@ static void evpUpdate(JNIEnv* env, jobject evpMdCtxRef, jbyteArray inJavaBytes, } } } else { - // GetByteArrayElements is expected to not return a copy. Use GetByteArrayElements. - // We're not using ScopedByteArrayRO here because its an implementation detail whether it'll - // use GetByteArrayElements or another approach. + // GetByteArrayElements is expected to not return a copy. Use + // GetByteArrayElements. We're not using ScopedByteArrayRO here because its + // an implementation detail whether it'll use GetByteArrayElements or + // another approach. jbyte* array_elements = env->GetByteArrayElements(inJavaBytes, nullptr); if (array_elements == nullptr) { conscrypt::jniutil::throwOutOfMemory(env, "Unable to obtain elements of inBytes"); @@ -3708,8 +3831,9 @@ static jboolean NativeCrypto_EVP_DigestVerifyFinal(JNIEnv* env, jclass, jobject return 0; } - // If the signature did not verify, BoringSSL error queue contains an error (BAD_SIGNATURE). - // Clear the error queue to prevent its state from affecting future operations. + // If the signature did not verify, BoringSSL error queue contains an error + // (BAD_SIGNATURE). Clear the error queue to prevent its state from affecting + // future operations. ERR_clear_error(); JNI_TRACE("EVP_DigestVerifyFinal(%p) => %d", mdCtx, result); @@ -3839,8 +3963,9 @@ static jboolean NativeCrypto_EVP_DigestVerify(JNIEnv* env, jclass, jobject evpMd return 0; } - // If the signature did not verify, BoringSSL error queue contains an error (BAD_SIGNATURE). - // Clear the error queue to prevent its state from affecting future operations. + // If the signature did not verify, BoringSSL error queue contains an error + // (BAD_SIGNATURE). Clear the error queue to prevent its state from affecting + // future operations. ERR_clear_error(); JNI_TRACE("EVP_DigestVerify(%p) => %d", mdCtx, result); @@ -4180,8 +4305,8 @@ static void NativeCrypto_EVP_CipherInit_ex(JNIEnv* env, jclass, jobject ctxRef, } /* - * public static native int EVP_CipherUpdate(long ctx, byte[] out, int outOffset, byte[] in, - * int inOffset, int inLength); + * public static native int EVP_CipherUpdate(long ctx, byte[] out, int + * outOffset, byte[] in, int inOffset, int inLength); */ static jint NativeCrypto_EVP_CipherUpdate(JNIEnv* env, jclass, jobject ctxRef, jbyteArray outArray, jint outOffset, jbyteArray inArray, jint inOffset, @@ -4216,7 +4341,8 @@ static jint NativeCrypto_EVP_CipherUpdate(JNIEnv* env, jclass, jobject ctxRef, j } JNI_TRACE( - "ctx=%p EVP_CipherUpdate in=%p in.length=%zd inOffset=%d inLength=%d out=%p " + "ctx=%p EVP_CipherUpdate in=%p in.length=%zd inOffset=%d inLength=%d " + "out=%p " "out.length=%zd outOffset=%d", ctx, inBytes.get(), inBytes.size(), inOffset, inLength, outBytes.get(), outBytes.size(), outOffset); @@ -4354,7 +4480,8 @@ static void NativeCrypto_EVP_CIPHER_CTX_set_padding(JNIEnv* env, jclass, jobject return; } - EVP_CIPHER_CTX_set_padding(ctx, enablePadding); // Not void, but always returns 1. + EVP_CIPHER_CTX_set_padding(ctx, + enablePadding); // Not void, but always returns 1. JNI_TRACE("EVP_CIPHER_CTX_set_padding(%p, %d) => success", ctx, enablePadding); } @@ -4520,9 +4647,11 @@ static jint evp_aead_ctx_op(JNIEnv* env, jlong evpAeadRef, jbyteArray keyArray, } if (ARRAY_OFFSET_INVALID(outBytes, outOffset)) { - JNI_TRACE("evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p) => out offset invalid", - evpAead, keyArray, tagLen, outArray, outOffset, nonceArray, inArray, inOffset, - inLength, aadArray); + JNI_TRACE( + "evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p) => out offset " + "invalid", + evpAead, keyArray, tagLen, outArray, outOffset, nonceArray, inArray, inOffset, + inLength, aadArray); conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", "out"); return 0; } @@ -4534,7 +4663,8 @@ static jint evp_aead_ctx_op(JNIEnv* env, jlong evpAeadRef, jbyteArray keyArray, if (ARRAY_OFFSET_LENGTH_INVALID(inBytes, inOffset, inLength)) { JNI_TRACE( - "evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p) => in offset/length " + "evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p) => in " + "offset/length " "invalid", evpAead, keyArray, tagLen, outArray, outOffset, nonceArray, inArray, inOffset, inLength, aadArray); @@ -4741,14 +4871,15 @@ static jbyteArray NativeCrypto_EVP_HPKE_CTX_open(JNIEnv* env, jclass, jobject re size_t plaintextLen; std::vector plaintext(ciphertext.size()); - if (!EVP_HPKE_CTX_open(/* ctx= */ ctx, - /* out= */ plaintext.data(), - /* out_len= */ &plaintextLen, - /* max_out_len= */ plaintext.size(), - /* in= */ reinterpret_cast(ciphertext.get()), - /* in_len= */ ciphertext.size(), - /* aad= */ aad, - /* aad_len= */ aadLen)) { + if (!EVP_HPKE_CTX_open( + /* ctx= */ ctx, + /* out= */ plaintext.data(), + /* out_len= */ &plaintextLen, + /* max_out_len= */ plaintext.size(), + /* in= */ reinterpret_cast(ciphertext.get()), + /* in_len= */ ciphertext.size(), + /* aad= */ aad, + /* aad_len= */ aadLen)) { conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_open"); return {}; } @@ -4799,14 +4930,15 @@ static jbyteArray NativeCrypto_EVP_HPKE_CTX_seal(JNIEnv* env, jclass, jobject se std::vector encrypted(env->GetArrayLength(plaintextArray) + EVP_HPKE_CTX_max_overhead(ctx)); size_t encryptedLen; - if (!EVP_HPKE_CTX_seal(/* ctx= */ ctx, - /* out= */ encrypted.data(), - /* out_len= */ &encryptedLen, - /* max_out_len= */ encrypted.size(), - /* in= */ reinterpret_cast(plaintext.get()), - /* in_len= */ plaintext.size(), - /* aad= */ aad, - /* aad_len= */ aadLen)) { + if (!EVP_HPKE_CTX_seal( + /* ctx= */ ctx, + /* out= */ encrypted.data(), + /* out_len= */ &encryptedLen, + /* max_out_len= */ encrypted.size(), + /* in= */ reinterpret_cast(plaintext.get()), + /* in_len= */ plaintext.size(), + /* aad= */ aad, + /* aad_len= */ aadLen)) { conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_seal"); return {}; } @@ -4852,6 +4984,10 @@ const EVP_HPKE_KDF* getHpkeKdf(JNIEnv* env, jint kdfValue) { const EVP_HPKE_KEM* getHpkeKem(JNIEnv* env, jint kemValue) { if (kemValue == EVP_HPKE_DHKEM_X25519_HKDF_SHA256) { return EVP_hpke_x25519_hkdf_sha256(); + } else if (kemValue == EVP_HPKE_MLKEM768) { + return EVP_hpke_mlkem768(); + } else if (kemValue == EVP_HPKE_MLKEM1024) { + return EVP_hpke_mlkem1024(); } else if (kemValue == EVP_HPKE_XWING) { return EVP_hpke_xwing(); } else { @@ -4893,10 +5029,11 @@ static jobject NativeCrypto_EVP_HPKE_CTX_setup_base_mode_recipient( bssl::ScopedEVP_HPKE_KEY key; - if (!EVP_HPKE_KEY_init(/* key= */ key.get(), - /* kem= */ kem, - /* priv_key= */ reinterpret_cast(privateKey.get()), - /* priv_key_len= */ privateKey.size())) { + if (!EVP_HPKE_KEY_init( + /* key= */ key.get(), + /* kem= */ kem, + /* priv_key= */ reinterpret_cast(privateKey.get()), + /* priv_key_len= */ privateKey.size())) { conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_setup_recipient"); return nullptr; } @@ -4915,14 +5052,15 @@ static jobject NativeCrypto_EVP_HPKE_CTX_setup_base_mode_recipient( bssl::UniquePtr ctx(EVP_HPKE_CTX_new()); ScopedByteArrayRO enc(env, encArray); - if (!EVP_HPKE_CTX_setup_recipient(/* ctx= */ ctx.get(), - /* key= */ key.get(), - /* kdf= */ kdf, - /* aead= */ aead, - /* enc= */ reinterpret_cast(enc.get()), - /* enc_len= */ enc.size(), - /* info= */ info, - /* info_len= */ infoLen)) { + if (!EVP_HPKE_CTX_setup_recipient( + /* ctx= */ ctx.get(), + /* key= */ key.get(), + /* kdf= */ kdf, + /* aead= */ aead, + /* enc= */ reinterpret_cast(enc.get()), + /* enc_len= */ enc.size(), + /* info= */ info, + /* info_len= */ infoLen)) { conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_setup_recipient"); return nullptr; } @@ -4987,7 +5125,8 @@ static jobjectArray NativeCrypto_EVP_HPKE_CTX_setup_base_mode_sender(JNIEnv* env /* kem= */ kem, /* kdf= */ kdf, /* aead= */ aead, - /* peer_public_key= */ reinterpret_cast(peer_public_key.get()), + /* peer_public_key= */ + reinterpret_cast(peer_public_key.get()), /* peer_public_key_len= */ peer_public_key.size(), /* info= */ info, /* info_len= */ infoLen)) { @@ -5074,7 +5213,8 @@ static jobjectArray NativeCrypto_EVP_HPKE_CTX_setup_base_mode_sender_with_seed_f /* kem= */ kem, /* kdf= */ kdf, /* aead= */ aead, - /* peer_public_key= */ reinterpret_cast(peer_public_key.get()), + /* peer_public_key= */ + reinterpret_cast(peer_public_key.get()), /* peer_public_key_len= */ peer_public_key.size(), /* info= */ info, /* info_len= */ infoLen, @@ -5409,7 +5549,8 @@ static void NativeCrypto_HMAC_Reset(JNIEnv* env, jclass, jobject hmacCtxRef) { // HMAC_Init_ex with all nulls will reuse the existing key. This is slightly // more efficient than re-initializing the context with the key again. - if (!HMAC_Init_ex(hmacCtx, /*key=*/nullptr, /*key_len=*/0, /*md=*/nullptr, /*impl=*/nullptr)) { + if (!HMAC_Init_ex(hmacCtx, /*key=*/nullptr, /*key_len=*/0, /*md=*/nullptr, + /*impl=*/nullptr)) { JNI_TRACE("HMAC_Reset(%p) => threw exception", hmacCtx); conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "HMAC_Init_ex"); return; @@ -5669,8 +5810,10 @@ static jobjectArray NativeCrypto_get_X509_GENERAL_NAME_stack(JNIEnv* env, jclass GENERAL_NAME* gen = sk_GENERAL_NAME_value(gn_stack.get(), static_cast(i)); ScopedLocalRef val(env, GENERAL_NAME_to_jobject(env, gen)); if (env->ExceptionCheck()) { - JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => threw exception parsing gen name", - x509, type); + JNI_TRACE( + "get_X509_GENERAL_NAME_stack(%p, %d) => threw exception parsing gen " + "name", + x509, type); return nullptr; } @@ -5698,8 +5841,10 @@ static jobjectArray NativeCrypto_get_X509_GENERAL_NAME_stack(JNIEnv* env, jclass } if (count == 0) { - JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) shrunk from %d to 0; returning nullptr", - x509, type, origCount); + JNI_TRACE( + "get_X509_GENERAL_NAME_stack(%p, %d) shrunk from %d to 0; returning " + "nullptr", + x509, type, origCount); joa.reset(nullptr); } else if (origCount != count) { JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) shrunk from %d to %d", x509, type, origCount, @@ -5910,8 +6055,8 @@ static jbyteArray NativeCrypto_get_X509_tbs_cert_without_ext(JNIEnv* env, jclass return nullptr; } - // Remove the extension and re-encode the TBSCertificate. Note |i2d_re_X509_tbs| ignores the - // cached encoding. + // Remove the extension and re-encode the TBSCertificate. Note + // |i2d_re_X509_tbs| ignores the cached encoding. X509_EXTENSION_free(X509_delete_ext(copy.get(), extIndex)); return ASN1ToByteArray(env, copy.get(), i2d_re_X509_tbs); } @@ -5932,10 +6077,11 @@ static jint NativeCrypto_get_X509_ex_flags(JNIEnv* env, jclass, jlong x509Ref, // X509_get_extension_flags sometimes leaves values in the error queue. See // https://crbug.com/boringssl/382. // - // TODO(https://github.com/google/conscrypt/issues/916): This function is used to check - // EXFLAG_CA, but does not check EXFLAG_INVALID. Fold the two JNI calls in getBasicConstraints() - // together and handle errors. (See also NativeCrypto_get_X509_ex_pathlen.) From there, limit - // this JNI call to EXFLAG_CRITICAL. + // TODO(https://github.com/google/conscrypt/issues/916): This function is used + // to check EXFLAG_CA, but does not check EXFLAG_INVALID. Fold the two JNI + // calls in getBasicConstraints() together and handle errors. (See also + // NativeCrypto_get_X509_ex_pathlen.) From there, limit this JNI call to + // EXFLAG_CRITICAL. ERR_clear_error(); return flags; } @@ -6220,8 +6366,8 @@ static jbyteArray get_X509_ALGOR_parameter(JNIEnv* env, const X509_ALGOR* algor) return nullptr; } - // The OpenSSL 1.1.x API lacks a function to get the ASN1_TYPE out of X509_ALGOR directly, so - // recreate it from the returned components. + // The OpenSSL 1.1.x API lacks a function to get the ASN1_TYPE out of + // X509_ALGOR directly, so recreate it from the returned components. bssl::UniquePtr param(ASN1_TYPE_new()); if (!param || !ASN1_TYPE_set1(param.get(), param_type, param_value)) { conscrypt::jniutil::throwOutOfMemory(env, "Unable to serialize parameter"); @@ -6434,8 +6580,9 @@ static void NativeCrypto_X509_REVOKED_print(JNIEnv* env, jclass, jlong bioRef, j BIO_printf(bio, "\nRevocation Date: "); ASN1_TIME_print(bio, X509_REVOKED_get0_revocationDate(revoked)); BIO_printf(bio, "\n"); - // TODO(davidben): Should the flags parameter be |X509V3_EXT_DUMP_UNKNOWN| so we don't error on - // unknown extensions. Alternatively, maybe we can use a simpler toString() implementation. + // TODO(davidben): Should the flags parameter be |X509V3_EXT_DUMP_UNKNOWN| so + // we don't error on unknown extensions. Alternatively, maybe we can use a + // simpler toString() implementation. X509V3_extensions_print(bio, "CRL entry extensions", X509_REVOKED_get0_extensions(revoked), 0, 0); } @@ -7554,9 +7701,10 @@ static jbooleanArray NativeCrypto_get_X509_ex_kusage(JNIEnv* env, jclass, jlong return nullptr; } - // TODO(https://github.com/google/conscrypt/issues/916): Handle errors and remove - // |ERR_clear_error|. Note X509Certificate.getKeyUsage() cannot throw - // CertificateParsingException, so this needs to be checked earlier, e.g. in the constructor. + // TODO(https://github.com/google/conscrypt/issues/916): Handle errors and + // remove |ERR_clear_error|. Note X509Certificate.getKeyUsage() cannot throw + // CertificateParsingException, so this needs to be checked earlier, e.g. in + // the constructor. bssl::UniquePtr bitStr( static_cast(X509_get_ext_d2i(x509, NID_key_usage, nullptr, nullptr))); if (bitStr.get() == nullptr) { @@ -7620,14 +7768,16 @@ static jint NativeCrypto_get_X509_ex_pathlen(JNIEnv* env, jclass, jlong x509Ref, return 0; } - // Use |X509_get_ext_d2i| instead of |X509_get_pathlen| because the latter treats - // |EXFLAG_INVALID| as the error case. |EXFLAG_INVALID| is set if any built-in extension is - // invalid. For now, we preserve Conscrypt's historical behavior in accepting certificates in - // the constructor even if |EXFLAG_INVALID| is set. + // Use |X509_get_ext_d2i| instead of |X509_get_pathlen| because the latter + // treats |EXFLAG_INVALID| as the error case. |EXFLAG_INVALID| is set if any + // built-in extension is invalid. For now, we preserve Conscrypt's historical + // behavior in accepting certificates in the constructor even if + // |EXFLAG_INVALID| is set. // - // TODO(https://github.com/google/conscrypt/issues/916): Handle errors and remove - // |ERR_clear_error|. Note X509Certificate.getBasicConstraints() cannot throw - // CertificateParsingException, so this needs to be checked earlier, e.g. in the constructor. + // TODO(https://github.com/google/conscrypt/issues/916): Handle errors and + // remove |ERR_clear_error|. Note X509Certificate.getBasicConstraints() cannot + // throw CertificateParsingException, so this needs to be checked earlier, + // e.g. in the constructor. bssl::UniquePtr basic_constraints(static_cast( X509_get_ext_d2i(x509, NID_basic_constraints, nullptr, nullptr))); if (basic_constraints == nullptr) { @@ -7643,23 +7793,26 @@ static jint NativeCrypto_get_X509_ex_pathlen(JNIEnv* env, jclass, jlong x509Ref, if (!basic_constraints->ca) { // Path length constraints are only valid for CA certificates. - // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an error condition. + // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an + // error condition. JNI_TRACE("get_X509_ex_path(%p) => -1 (not a CA)", x509); return -1; } if (basic_constraints->pathlen->type == V_ASN1_NEG_INTEGER) { // Path length constraints may not be negative. - // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an error condition. + // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an + // error condition. JNI_TRACE("get_X509_ex_path(%p) => -1 (negative)", x509); return -1; } long pathlen = ASN1_INTEGER_get(basic_constraints->pathlen); if (pathlen == -1 || pathlen > INT_MAX) { - // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an error condition. - // If the integer overflows, the certificate is effectively unconstrained. Reporting no - // constraint is plausible, but Chromium rejects all values above 255. + // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an + // error condition. If the integer overflows, the certificate is effectively + // unconstrained. Reporting no constraint is plausible, but Chromium rejects + // all values above 255. JNI_TRACE("get_X509_ex_path(%p) => -1 (overflow)", x509); return -1; } @@ -7844,13 +7997,14 @@ static void info_callback_LOG(const SSL* s, int where, int ret) { * * @param env * @param type Either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE - * @param fdObject The FileDescriptor, since appData->fileDescriptor should be NULL + * @param fdObject The FileDescriptor, since appData->fileDescriptor should be + * NULL * @param appData The application data structure with mutex info etc. - * @param timeout_millis The timeout value for select call, with the special value - * 0 meaning no timeout at all (wait indefinitely). Note: This is - * the Java semantics of the timeout value, not the usual - * select() semantics. - * @return THROWN_EXCEPTION on close socket, 0 on timeout, -1 on error, and 1 on success + * @param timeout_millis The timeout value for select call, with the special + * value 0 meaning no timeout at all (wait indefinitely). Note: This is the Java + * semantics of the timeout value, not the usual select() semantics. + * @return THROWN_EXCEPTION on close socket, 0 on timeout, -1 on error, and 1 on + * success */ static int sslSelect(JNIEnv* env, int type, jobject fdObject, AppData* appData, int timeout_millis) { @@ -7919,7 +8073,8 @@ static int sslSelect(JNIEnv* env, int type, jobject fdObject, AppData* appData, * * @param env * @param type Either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE - * @param fdObject The FileDescriptor, since appData->fileDescriptor should be nullptr + * @param fdObject The FileDescriptor, since appData->fileDescriptor should be + * nullptr * @param appData The application data structure with mutex info etc. * @param timeout_millis The timeout value for poll call, with the special value * 0 meaning no timeout at all (wait indefinitely). Note: This is @@ -8057,8 +8212,10 @@ static ssl_verify_result_t cert_verify_callback(SSL* ssl, CONSCRYPT_UNUSED uint8 const SSL_CIPHER* cipher = SSL_get_pending_cipher(ssl); const char* authMethod = SSL_CIPHER_get_kx_name(cipher); - JNI_TRACE("ssl=%p cert_verify_callback calling verifyCertificateChain authMethod=%s", ssl, - authMethod); + JNI_TRACE( + "ssl=%p cert_verify_callback calling verifyCertificateChain " + "authMethod=%s", + ssl, authMethod); ScopedLocalRef authMethodString(env, env->NewStringUTF(authMethod)); env->CallVoidMethod(sslHandshakeCallbacks, methodID, array.get(), authMethodString.get()); @@ -8212,8 +8369,29 @@ static enum ssl_select_cert_result_t select_certificate_cb(const SSL_CLIENT_HELL jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_serverCertificateRequested; + const uint16_t* sigalgs = nullptr; + size_t sigalgs_num = SSL_get0_peer_verify_algorithms(ssl, &sigalgs); + + if (sigalgs_num > static_cast(INT_MAX)) { + conscrypt::jniutil::throwRuntimeException(env, "Too many signature algorithms"); + return ssl_select_cert_error; + } + jintArray signatureAlgs = env->NewIntArray(static_cast(sigalgs_num)); + if (signatureAlgs == nullptr) { + return ssl_select_cert_error; + } + { + ScopedIntArrayRW sigAlgsRW(env, signatureAlgs); + if (sigAlgsRW.get() == nullptr) { + return ssl_select_cert_error; + } + for (size_t i = 0; i < sigalgs_num; i++) { + sigAlgsRW[i] = sigalgs[i]; + } + } + JNI_TRACE("ssl=%p select_certificate_cb calling serverCertificateRequested", ssl); - env->CallVoidMethod(sslHandshakeCallbacks, methodID); + env->CallVoidMethod(sslHandshakeCallbacks, methodID, signatureAlgs); if (env->ExceptionCheck()) { JNI_TRACE("ssl=%p select_certificate_cb exception", ssl); @@ -8371,8 +8549,8 @@ static int new_session_callback(SSL* ssl, SSL_SESSION* session) { } JNI_TRACE("ssl=%p new_session_callback completed", ssl); - // Always returning 0 (not taking ownership). The Java code is responsible for incrementing - // the reference count. + // Always returning 0 (not taking ownership). The Java code is responsible for + // incrementing the reference count. return 0; } @@ -8380,8 +8558,8 @@ static SSL_SESSION* server_session_requested_callback(SSL* ssl, const uint8_t* i int* out_copy) { JNI_TRACE("ssl=%p server_session_requested_callback", ssl); - // Always set to out_copy to zero. The Java callback will be responsible for incrementing - // the reference count (and any required synchronization). + // Always set to out_copy to zero. The Java callback will be responsible for + // incrementing the reference count (and any required synchronization). *out_copy = 0; AppData* appData = toAppData(ssl); @@ -8419,8 +8597,7 @@ static SSL_SESSION* server_session_requested_callback(SSL* ssl, const uint8_t* i return ssl_session_ptr; } -static jint NativeCrypto_EVP_has_aes_hardware(JNIEnv* env, jclass) { - CHECK_ERROR_QUEUE_ON_RETURN; +static jint NativeCrypto_EVP_has_aes_hardware(CRITICAL_JNI_PARAMS) { int ret = 0; ret = EVP_has_aes_hardware(); JNI_TRACE("EVP_has_aes_hardware => %d", ret); @@ -8482,7 +8659,8 @@ static jlong NativeCrypto_SSL_CTX_new(JNIEnv* env, jclass) { SSL_OP_ALL // We also disable session tickets for better compatibility b/2682876 | SSL_OP_NO_TICKET - // We also disable compression for better compatibility b/2710492 b/2710497 + // We also disable compression for better compatibility b/2710492 + // b/2710497 | SSL_OP_NO_COMPRESSION // Generate a fresh ECDH keypair for each key exchange. | SSL_OP_SINGLE_ECDH_USE); @@ -8507,10 +8685,9 @@ static jlong NativeCrypto_SSL_CTX_new(JNIEnv* env, jclass) { // Enable False Start. mode |= SSL_MODE_ENABLE_FALSE_START; - // We need to enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER as the memory address may change - // between - // calls to wrap(...). - // See https://github.com/netty/netty-tcnative/issues/100 + // We need to enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER as the memory address + // may change between calls to wrap(...). See + // https://github.com/netty/netty-tcnative/issues/100 mode |= SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER; SSL_CTX_set_mode(sslCtx.get(), mode); @@ -8561,8 +8738,10 @@ static void NativeCrypto_SSL_CTX_set_session_id_context(JNIEnv* env, jclass, jlo ScopedByteArrayRO buf(env, sid_ctx); if (buf.get() == nullptr) { - JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_set_session_id_context => threw exception", - ssl_ctx); + JNI_TRACE( + "ssl_ctx=%p NativeCrypto_SSL_CTX_set_session_id_context => threw " + "exception", + ssl_ctx); return; } @@ -8661,8 +8840,8 @@ static jbyteArray NativeCrypto_SSL_get_tls_channel_id(JNIEnv* env, jclass, jlong return nullptr; } - // Channel ID is 64 bytes long. Unfortunately, OpenSSL doesn't declare this length - // as a constant anywhere. + // Channel ID is 64 bytes long. Unfortunately, OpenSSL doesn't declare this + // length as a constant anywhere. jbyteArray javaBytes = env->NewByteArray(64); ScopedByteArrayRW bytes(env, javaBytes); if (bytes.get() == nullptr) { @@ -8671,9 +8850,10 @@ static jbyteArray NativeCrypto_SSL_get_tls_channel_id(JNIEnv* env, jclass, jlong } unsigned char* tmp = reinterpret_cast(bytes.get()); - // Unfortunately, the SSL_get_tls_channel_id method below always returns 64 (upon success) - // regardless of the number of bytes copied into the output buffer "tmp". Thus, the correctness - // of this code currently relies on the "tmp" buffer being exactly 64 bytes long. + // Unfortunately, the SSL_get_tls_channel_id method below always returns 64 + // (upon success) regardless of the number of bytes copied into the output + // buffer "tmp". Thus, the correctness of this code currently relies on the + // "tmp" buffer being exactly 64 bytes long. size_t ret = SSL_get_tls_channel_id(ssl, tmp, 64); if (ret == 0) { // Channel ID either not set or did not verify @@ -8727,8 +8907,10 @@ static void NativeCrypto_setLocalCertsAndPrivateKey(JNIEnv* env, jclass, jlong s jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_chain_and_key certificates=%p, privateKey=%p", ssl, - encodedCertificatesJava, pkeyRef); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_set_chain_and_key certificates=%p, " + "privateKey=%p", + ssl, encodedCertificatesJava, pkeyRef); if (ssl == nullptr) { return; } @@ -8900,11 +9082,14 @@ static jint NativeCrypto_SSL_set_protocol_versions(JNIEnv* env, jclass, jlong ss int result = 1; if (!min_result || !max_result) { result = 0; - // The only possible error is an invalid version, so we don't need the details. + // The only possible error is an invalid version, so we don't need the + // details. ERR_clear_error(); } - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_protocol_versions => (min: %d, max: %d) == %d", ssl, - min_result, max_result, result); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_set_protocol_versions => (min: %d, max: %d) == " + "%d", + ssl, min_result, max_result, result); return result; } @@ -8952,7 +9137,8 @@ static jbyteArray NativeCrypto_SSL_get_signed_cert_timestamp_list( } /* - * public static native void SSL_set_signed_cert_timestamp_list(long ssl, byte[] response); + * public static native void SSL_set_signed_cert_timestamp_list(long ssl, byte[] + * response); */ static void NativeCrypto_SSL_set_signed_cert_timestamp_list(JNIEnv* env, jclass, jlong ssl_address, CONSCRYPT_UNUSED jobject ssl_holder, @@ -8966,7 +9152,10 @@ static void NativeCrypto_SSL_set_signed_cert_timestamp_list(JNIEnv* env, jclass, ScopedByteArrayRO listBytes(env, list); if (listBytes.get() == nullptr) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_signed_cert_timestamp_list => list == null", ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_set_signed_cert_timestamp_list => list == " + "null", + ssl); return; } @@ -9054,10 +9243,10 @@ static void NativeCrypto_SSL_set_ocsp_response(JNIEnv* env, jclass, jlong ssl_ad } } -// All verify_data values are currently 12 bytes long, but cipher suites are allowed -// to customize the length of their verify_data (with a default of 12 bytes). We accept -// up to 16 bytes so that we can check that the results are actually 12 bytes long in -// tests and update this value if necessary. +// All verify_data values are currently 12 bytes long, but cipher suites are +// allowed to customize the length of their verify_data (with a default of 12 +// bytes). We accept up to 16 bytes so that we can check that the results are +// actually 12 bytes long in tests and update this value if necessary. const size_t MAX_TLS_UNIQUE_LENGTH = 16; static jbyteArray NativeCrypto_SSL_get_tls_unique(JNIEnv* env, jclass, jlong ssl_address, @@ -9103,7 +9292,10 @@ static jbyteArray NativeCrypto_SSL_export_keying_material(JNIEnv* env, jclass, j } ScopedByteArrayRO labelBytes(env, label); if (labelBytes.get() == nullptr) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_export_keying_material label == null => exception", ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_export_keying_material label == null => " + "exception", + ssl); return nullptr; } std::unique_ptr out(new uint8_t[num_bytes]); @@ -9115,8 +9307,10 @@ static jbyteArray NativeCrypto_SSL_export_keying_material(JNIEnv* env, jclass, j } else { ScopedByteArrayRO contextBytes(env, context); if (contextBytes.get() == nullptr) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_export_keying_material context == null => exception", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_export_keying_material context == null => " + "exception", + ssl); return nullptr; } ret = SSL_export_keying_material( @@ -9133,7 +9327,10 @@ static jbyteArray NativeCrypto_SSL_export_keying_material(JNIEnv* env, jclass, j jbyteArray result = env->NewByteArray(static_cast(num_bytes)); if (result == nullptr) { conscrypt::jniutil::throwSSLExceptionStr(env, "Could not create result array"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_export_keying_material => could not create array", ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_export_keying_material => could not create " + "array", + ssl); return nullptr; } const jbyte* src = reinterpret_cast(out.get()); @@ -9250,7 +9447,10 @@ static void NativeCrypto_SSL_set_cipher_lists(JNIEnv* env, jclass, jlong ssl_add SSL_set_cipher_list(ssl, ""); ERR_clear_error(); if (sk_SSL_CIPHER_num(SSL_get_ciphers(ssl)) != 0) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_cipher_lists cipherSuites=empty => error", ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_set_cipher_lists cipherSuites=empty => " + "error", + ssl); conscrypt::jniutil::throwRuntimeException( env, "SSL_set_cipher_list did not update ciphers!"); ERR_clear_error(); @@ -9404,8 +9604,10 @@ static void NativeCrypto_SSL_set_session_creation_enabled(JNIEnv* env, jclass, j jboolean creation_enabled) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_session_creation_enabled creation_enabled=%d", ssl, - creation_enabled); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_set_session_creation_enabled " + "creation_enabled=%d", + ssl, creation_enabled); if (ssl == nullptr) { return; } @@ -9484,8 +9686,8 @@ static jstring NativeCrypto_SSL_get_servername(JNIEnv* env, jclass, jlong ssl_ad } /** - * Selects the ALPN protocol to use. The list of protocols in "primary" is considered the order - * which should take precedence. + * Selects the ALPN protocol to use. The list of protocols in "primary" is + * considered the order which should take precedence. */ static int selectApplicationProtocol(SSL* ssl, unsigned char** out, unsigned char* outLength, const unsigned char* primary, const unsigned int primaryLength, @@ -9693,8 +9895,10 @@ static void NativeCrypto_SSL_do_handshake(JNIEnv* env, jclass, jlong ssl_address } if (shc == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake sslHandshakeCallbacks == null => exception", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_do_handshake sslHandshakeCallbacks == null => " + "exception", + ssl); return; } @@ -9716,8 +9920,8 @@ static void NativeCrypto_SSL_do_handshake(JNIEnv* env, jclass, jlong ssl_address } /* - * Make socket non-blocking, so SSL_connect SSL_read() and SSL_write() don't hang - * forever and we can use select() to find out if the socket is ready. + * Make socket non-blocking, so SSL_connect SSL_read() and SSL_write() don't + * hang forever and we can use select() to find out if the socket is ready. */ if (!conscrypt::netutil::setBlocking(fd.get(), false)) { conscrypt::jniutil::throwSSLExceptionStr(env, "Unable to make socket non blocking"); @@ -9786,15 +9990,19 @@ static void NativeCrypto_SSL_do_handshake(JNIEnv* env, jclass, jlong ssl_address conscrypt::jniutil::throwSSLExceptionWithSslErrors( env, ssl, SSL_ERROR_SYSCALL, "handshake error", conscrypt::jniutil::throwSSLHandshakeExceptionStr); - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake selectResult == -1 => exception", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_do_handshake selectResult == -1 => " + "exception", + ssl); return; } if (selectResult == 0) { conscrypt::jniutil::throwSocketTimeoutException(env, "SSL handshake timed out"); ERR_clear_error(); - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake selectResult == 0 => exception", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_do_handshake selectResult == 0 => " + "exception", + ssl); return; } } else { @@ -10132,8 +10340,8 @@ static jint NativeCrypto_SSL_read(JNIEnv* env, jclass, jlong ssl_address, conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate chunk buffer"); return 0; } - // TODO(flooey): Fix cumulative read timeout? The effective timeout is the multiplied - // by the number of internal calls to sslRead() below. + // TODO(flooey): Fix cumulative read timeout? The effective timeout is the + // multiplied by the number of internal calls to sslRead() below. ret = 0; while (remaining > 0) { jint temp_ret; @@ -10142,8 +10350,8 @@ static jint NativeCrypto_SSL_read(JNIEnv* env, jclass, jlong ssl_address, chunk_size, &sslError, read_timeout_millis); if (temp_ret < 0) { if (ret > 0) { - // We've already read some bytes; attempt to preserve them if this is an - // "expected" error. + // We've already read some bytes; attempt to preserve them if this + // is an "expected" error. if (temp_ret == -1) { // EOF break; @@ -10397,7 +10605,8 @@ static void NativeCrypto_SSL_write(JNIEnv* env, jclass, jlong ssl_address, ret = sslWrite(env, ssl, fdObject, shc, reinterpret_cast(&buf[0]), len, &sslError, write_timeout_millis); } else { - // TODO(flooey): Similar safety concerns and questions here as in SSL_read. + // TODO(flooey): Similar safety concerns and questions here as in + // SSL_read. jint remaining = len; jint buf_size = (remaining >= 65536) ? 65536 : remaining; std::unique_ptr buf(new jbyte[static_cast(buf_size)]); @@ -10714,9 +10923,8 @@ static jlong NativeCrypto_SSL_get_timeout(JNIEnv* env, jclass, jlong ssl_address return result; } -static jint NativeCrypto_SSL_get_signature_algorithm_key_type(JNIEnv* env, jclass, - jint signatureAlg) { - CHECK_ERROR_QUEUE_ON_RETURN; +static jint NativeCrypto_SSL_get_signature_algorithm_key_type( + CRITICAL_JNI_PARAMS_COMMA jint signatureAlg) { return SSL_get_signature_algorithm_key_type(signatureAlg); } @@ -10735,7 +10943,8 @@ static jlong NativeCrypto_SSL_SESSION_get_timeout(JNIEnv* env, jclass, jlong ssl } /** - * Gets the ID for the SSL session, or null if no session is currently available. + * Gets the ID for the SSL session, or null if no session is currently + * available. */ static jbyteArray NativeCrypto_SSL_session_id(JNIEnv* env, jclass, jlong ssl_address, CONSCRYPT_UNUSED jobject ssl_holder) { @@ -10998,12 +11207,12 @@ static bool ocsp_cert_id_matches_certificate(CBS* cert_id, X509* x509, X509* iss } /** - * Get a SingleResponse whose CertID matches the given certificate and issuer from a - * SEQUENCE OF SingleResponse. + * Get a SingleResponse whose CertID matches the given certificate and issuer + * from a SEQUENCE OF SingleResponse. * - * If found, |out_single_response| is set to the response, and true is returned. Otherwise if an - * error occured or no response matches the certificate, false is returned and |out_single_response| - * is unchanged. + * If found, |out_single_response| is set to the response, and true is returned. + * Otherwise if an error occured or no response matches the certificate, false + * is returned and |out_single_response| is unchanged. */ static bool find_ocsp_single_response(CBS* responses, X509* x509, X509* issuerX509, CBS* out_single_response) { @@ -11037,8 +11246,8 @@ static bool find_ocsp_single_response(CBS* responses, X509* x509, X509* issuerX5 /** * Get the BasicOCSPResponse from an OCSPResponse. - * If parsing succeeds and the response is of type basic, |basic_response| is set to it, and true is - * returned. + * If parsing succeeds and the response is of type basic, |basic_response| is + * set to it, and true is returned. */ static bool get_ocsp_basic_response(CBS* ocsp_response, CBS* basic_response) { CBS tagged_response_bytes, response_bytes, response_type, response; @@ -11068,8 +11277,8 @@ static bool get_ocsp_basic_response(CBS* ocsp_response, CBS* basic_response) { /** * Get the SEQUENCE OF SingleResponse from a BasicOCSPResponse. - * If parsing succeeds, |single_responses| is set to point to the sequence of SingleResponse, and - * true is returned. + * If parsing succeeds, |single_responses| is set to point to the sequence of + * SingleResponse, and true is returned. */ static bool get_ocsp_single_responses(CBS* basic_response, CBS* single_responses) { // Parse the ResponseData out of the BasicOCSPResponse. Ignore the rest. @@ -11092,8 +11301,8 @@ static bool get_ocsp_single_responses(CBS* basic_response, CBS* single_responses /** * Get the SEQUENCE OF Extension from a SingleResponse. - * If parsing succeeds, |extensions| is set to point the the extension sequence and true is - * returned. + * If parsing succeeds, |extensions| is set to point the the extension sequence + * and true is returned. */ static bool get_ocsp_single_response_extensions(CBS* single_response, CBS* extensions) { // Skip the certID, certStatus, thisUpdate and optional nextUpdate fields. @@ -11111,8 +11320,8 @@ static bool get_ocsp_single_response_extensions(CBS* single_response, CBS* exten } /* - public static native byte[] get_ocsp_single_extension(byte[] ocspData, String oid, - long x509Ref, long issuerX509Ref); + public static native byte[] get_ocsp_single_extension(byte[] ocspData, + String oid, long x509Ref, long issuerX509Ref); */ static jbyteArray NativeCrypto_get_ocsp_single_extension( JNIEnv* env, jclass, jbyteArray ocspDataBytes, jstring oid, jlong x509Ref, @@ -11171,8 +11380,9 @@ static jbyteArray NativeCrypto_get_ocsp_single_extension( } static jlong NativeCrypto_getDirectBufferAddress(JNIEnv* env, jclass, jobject buffer) { - // The javadoc for NativeCrypto.getDirectBufferAddress(Buffer buf) defines the behaviour here, - // no throwing if the buffer is null or not a direct ByteBuffer. + // The javadoc for NativeCrypto.getDirectBufferAddress(Buffer buf) defines the + // behaviour here, no throwing if the buffer is null or not a direct + // ByteBuffer. if (!conscrypt::jniutil::isDirectByteBufferInstance(env, buffer)) { return 0; } @@ -11189,7 +11399,7 @@ static jint NativeCrypto_SSL_get_error(JNIEnv* env, jclass, jlong ssl_address, return SSL_get_error(ssl, ret); } -static void NativeCrypto_SSL_clear_error(JNIEnv*, jclass) { +static void NativeCrypto_SSL_clear_error(CRITICAL_JNI_PARAMS) { ERR_clear_error(); } @@ -11260,8 +11470,10 @@ static jint NativeCrypto_ENGINE_SSL_do_handshake(JNIEnv* env, jclass, jlong ssl_ if (shc == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_do_handshake => sslHandshakeCallbacks == null", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_do_handshake => sslHandshakeCallbacks " + "== null", + ssl); return 0; } @@ -11343,7 +11555,10 @@ static void NativeCrypto_ENGINE_SSL_shutdown(JNIEnv* env, jclass, jlong ssl_addr if (shc == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_shutdown => sslHandshakeCallbacks == null", ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_shutdown => sslHandshakeCallbacks == " + "null", + ssl); return; } @@ -11412,8 +11627,10 @@ static jint NativeCrypto_ENGINE_SSL_read_direct(JNIEnv* env, jclass, jlong ssl_a if (shc == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_read_direct => sslHandshakeCallbacks == null", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_read_direct => sslHandshakeCallbacks " + "== null", + ssl); return -1; } AppData* appData = toAppData(ssl); @@ -11434,7 +11651,8 @@ static jint NativeCrypto_ENGINE_SSL_read_direct(JNIEnv* env, jclass, jlong ssl_a int result = SSL_read(ssl, destPtr, length); appData->clearCallbackState(); if (env->ExceptionCheck()) { - // An exception was thrown by one of the callbacks. Just propagate that exception. + // An exception was thrown by one of the callbacks. Just propagate that + // exception. ERR_clear_error(); JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_read_direct => THROWN_EXCEPTION", ssl); return -1; @@ -11487,8 +11705,10 @@ static jint NativeCrypto_ENGINE_SSL_read_direct(JNIEnv* env, jclass, jlong ssl_a } } - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_read_direct address=%p length=%d shc=%p result=%d", - ssl, destPtr, length, shc, result); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_read_direct address=%p length=%d shc=%p " + "result=%d", + ssl, destPtr, length, shc, result); return result; } @@ -11514,8 +11734,8 @@ static int NativeCrypto_ENGINE_SSL_write_BIO_direct(JNIEnv* env, jclass, jlong s return -1; } if (len < 0 || BIO_ctrl_get_write_guarantee(bio) < static_cast(len)) { - // The network BIO couldn't handle the entire write. Don't write anything, so that we - // only process one packet at a time. + // The network BIO couldn't handle the entire write. Don't write anything, + // so that we only process one packet at a time. return 0; } const char* sourcePtr = reinterpret_cast(address); @@ -11539,7 +11759,8 @@ static int NativeCrypto_ENGINE_SSL_write_BIO_direct(JNIEnv* env, jclass, jlong s int result = BIO_write(bio, reinterpret_cast(sourcePtr), len); appData->clearCallbackState(); JNI_TRACE( - "ssl=%p NativeCrypto_ENGINE_SSL_write_BIO_direct bio=%p sourcePtr=%p len=%d shc=%p => " + "ssl=%p NativeCrypto_ENGINE_SSL_write_BIO_direct bio=%p sourcePtr=%p " + "len=%d shc=%p => " "ret=%d", ssl, bio, sourcePtr, len, shc, result); JNI_TRACE_PACKET_DATA(ssl, 'O', reinterpret_cast(sourcePtr), @@ -11558,8 +11779,10 @@ static int NativeCrypto_ENGINE_SSL_read_BIO_direct(JNIEnv* env, jclass, jlong ss } if (shc == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_read_BIO_direct => sslHandshakeCallbacks == null", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_read_BIO_direct => " + "sslHandshakeCallbacks == null", + ssl); return -1; } BIO* bio = to_BIO(env, bioRef); @@ -11591,7 +11814,8 @@ static int NativeCrypto_ENGINE_SSL_read_BIO_direct(JNIEnv* env, jclass, jlong ss int result = BIO_read(bio, destPtr, outputSize); appData->clearCallbackState(); JNI_TRACE( - "ssl=%p NativeCrypto_ENGINE_SSL_read_BIO_direct bio=%p destPtr=%p outputSize=%d shc=%p " + "ssl=%p NativeCrypto_ENGINE_SSL_read_BIO_direct bio=%p destPtr=%p " + "outputSize=%d shc=%p " "=> ret=%d", ssl, bio, destPtr, outputSize, shc, result); JNI_TRACE_PACKET_DATA(ssl, 'I', destPtr, static_cast(result)); @@ -11608,8 +11832,10 @@ static void NativeCrypto_ENGINE_SSL_force_read(JNIEnv* env, jclass, jlong ssl_ad JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_force_read shc=%p", ssl, shc); if (shc == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_force_read => sslHandshakeCallbacks == null", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_force_read => sslHandshakeCallbacks == " + "null", + ssl); return; } AppData* appData = toAppData(ssl); @@ -11628,7 +11854,8 @@ static void NativeCrypto_ENGINE_SSL_force_read(JNIEnv* env, jclass, jlong ssl_ad int result = SSL_peek(ssl, &c, 1); appData->clearCallbackState(); if (env->ExceptionCheck()) { - // An exception was thrown by one of the callbacks. Just propagate that exception. + // An exception was thrown by one of the callbacks. Just propagate that + // exception. ERR_clear_error(); JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_force_read => THROWN_EXCEPTION", ssl); return; @@ -11693,8 +11920,10 @@ static int NativeCrypto_ENGINE_SSL_write_direct(JNIEnv* env, jclass, jlong ssl_a sourcePtr, len, shc); if (shc == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_write_direct => sslHandshakeCallbacks == null", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_write_direct => sslHandshakeCallbacks " + "== null", + ssl); return -1; } @@ -11716,8 +11945,10 @@ static int NativeCrypto_ENGINE_SSL_write_direct(JNIEnv* env, jclass, jlong ssl_a int result = SSL_write(ssl, sourcePtr, len); appData->clearCallbackState(); - JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_write_direct address=%p length=%d shc=%p => ret=%d", - ssl, sourcePtr, len, shc, result); + JNI_TRACE( + "ssl=%p NativeCrypto_ENGINE_SSL_write_direct address=%p length=%d shc=%p " + "=> ret=%d", + ssl, sourcePtr, len, shc, result); return result; } @@ -11837,7 +12068,8 @@ static void NativeCrypto_SSL_CTX_set_spake_credential( /* pw_len= */ pw_bytes.size(), /* id_prover= */ reinterpret_cast(id_prover_bytes.get()), /* id_prover_len= */ id_prover_bytes.size(), - /* id_verifier= */ reinterpret_cast(id_verifier_bytes.get()), + /* id_verifier= */ + reinterpret_cast(id_verifier_bytes.get()), /* id_verifier_len= */ id_verifier_bytes.size()); if (ret != 1) { conscrypt::jniutil::throwExceptionFromBoringSSLError(env, @@ -11850,9 +12082,11 @@ static void NativeCrypto_SSL_CTX_set_spake_credential( bssl::UniquePtr creds_client(SSL_CREDENTIAL_new_spake2plusv1_client( /* context= */ reinterpret_cast(context_bytes.get()), /* context_len= */ context_bytes.size(), - /* client_identity= */ reinterpret_cast(id_prover_bytes.get()), + /* client_identity= */ + reinterpret_cast(id_prover_bytes.get()), /* client_identity_len= */ id_prover_bytes.size(), - /* server_identity= */ reinterpret_cast(id_verifier_bytes.get()), + /* server_identity= */ + reinterpret_cast(id_verifier_bytes.get()), /* server_identity_len= */ id_verifier_bytes.size(), /* attempts= */ handshake_limit, /* w0= */ pw_verifier_w0, @@ -11864,9 +12098,11 @@ static void NativeCrypto_SSL_CTX_set_spake_credential( bssl::UniquePtr creds_server(SSL_CREDENTIAL_new_spake2plusv1_server( /* context= */ reinterpret_cast(context_bytes.get()), /* context_len= */ context_bytes.size(), - /* client_identity= */ reinterpret_cast(id_prover_bytes.get()), + /* client_identity= */ + reinterpret_cast(id_prover_bytes.get()), /* client_identity_len= */ id_prover_bytes.size(), - /* server_identity= */ reinterpret_cast(id_verifier_bytes.get()), + /* server_identity= */ + reinterpret_cast(id_verifier_bytes.get()), /* server_identity_len= */ id_verifier_bytes.size(), /* attempts= */ handshake_limit, /* w0= */ pw_verifier_w0, @@ -12108,8 +12344,10 @@ static jbyteArray NativeCrypto_SSL_get0_ech_retry_configs(JNIEnv* env, jclass, j } jbyteArray result = env->NewByteArray(static_cast(retry_configs_len)); if (result == nullptr) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_ech_retry_configs() => creating byte array failed", - ssl); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_get0_ech_retry_configs() => creating byte " + "array failed", + ssl); return nullptr; } env->SetByteArrayRegion(result, 0, static_cast(retry_configs_len), @@ -12174,8 +12412,10 @@ static jboolean NativeCrypto_SSL_CTX_ech_enable_server(JNIEnv* env, jclass, jlon jbyteArray configJavaBytes) { CHECK_ERROR_QUEUE_ON_RETURN; SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true); - JNI_TRACE("NativeCrypto_SSL_CTX_ech_enable_server(keyJavaBytes=%p, configJavaBytes=%p)", - keyJavaBytes, configJavaBytes); + JNI_TRACE( + "NativeCrypto_SSL_CTX_ech_enable_server(keyJavaBytes=%p, " + "configJavaBytes=%p)", + keyJavaBytes, configJavaBytes); ScopedByteArrayRO keyBytes(env, keyJavaBytes); if (keyBytes.get() == nullptr) { conscrypt::jniutil::throwNullPointerException(env, "Null pointer, key bytes"); @@ -12318,6 +12558,8 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(X25519_keypair, "([B[B)V"), CONSCRYPT_NATIVE_METHOD(ED25519_keypair, "([B[B)V"), CONSCRYPT_NATIVE_METHOD(XWING_public_key_from_seed, "([B)[B"), + CONSCRYPT_NATIVE_METHOD(MLKEM768_public_key_from_seed, "([B)[B"), + CONSCRYPT_NATIVE_METHOD(MLKEM1024_public_key_from_seed, "([B)[B"), CONSCRYPT_NATIVE_METHOD(EVP_MD_CTX_create, "()J"), CONSCRYPT_NATIVE_METHOD(EVP_MD_CTX_cleanup, "(" REF_EVP_MD_CTX ")V"), CONSCRYPT_NATIVE_METHOD(EVP_MD_CTX_destroy, "(J)V"), diff --git a/common/src/jni/main/include/conscrypt/NetFd.h b/common/src/jni/main/include/conscrypt/NetFd.h index 522250c5e..2ed7982cf 100644 --- a/common/src/jni/main/include/conscrypt/NetFd.h +++ b/common/src/jni/main/include/conscrypt/NetFd.h @@ -20,7 +20,8 @@ #include /** - * Wraps access to the int inside a java.io.FileDescriptor, taking care of throwing exceptions. + * Wraps access to the int inside a java.io.FileDescriptor, taking care of + * throwing exceptions. */ class NetFd { public: @@ -51,9 +52,9 @@ class NetFd { }; /** - * Used to retry syscalls that can return EINTR. This differs from TEMP_FAILURE_RETRY in that - * it also considers the case where the reason for failure is that another thread called - * Socket.close. + * Used to retry syscalls that can return EINTR. This differs from + * TEMP_FAILURE_RETRY in that it also considers the case where the reason for + * failure is that another thread called Socket.close. */ #define NET_FAILURE_RETRY(fd, exp) \ ({ \ diff --git a/common/src/jni/main/include/conscrypt/app_data.h b/common/src/jni/main/include/conscrypt/app_data.h index cc8d72ffc..ac07ba610 100644 --- a/common/src/jni/main/include/conscrypt/app_data.h +++ b/common/src/jni/main/include/conscrypt/app_data.h @@ -87,13 +87,14 @@ namespace conscrypt { * * (5) the JNIEnv so we can invoke the Java callback * - * (6) a NativeCrypto.SSLHandshakeCallbacks instance for callbacks from native to Java + * (6) a NativeCrypto.SSLHandshakeCallbacks instance for callbacks from native + * to Java * * (7) a java.io.FileDescriptor wrapper to check for socket close * - * We store the ALPN protocols list so we can either send it (from the server) or - * select a protocol (on the client). We eagerly acquire a pointer to the array - * data so the callback doesn't need to acquire resources that it cannot + * We store the ALPN protocols list so we can either send it (from the server) + * or select a protocol (on the client). We eagerly acquire a pointer to the + * array data so the callback doesn't need to acquire resources that it cannot * release. * * Because renegotiation can be requested by the peer at any time, @@ -178,8 +179,10 @@ class AppData { e->GetByteArrayElements(applicationProtocolsJava, nullptr); if (applicationProtocols == nullptr) { clearCallbackState(); - JNI_TRACE("appData=%p setApplicationCallbackState => applicationProtocols == null", - this); + JNI_TRACE( + "appData=%p setApplicationCallbackState => applicationProtocols == " + "null", + this); return false; } applicationProtocolsLength = diff --git a/common/src/jni/main/include/conscrypt/compat.h b/common/src/jni/main/include/conscrypt/compat.h index b19e4d56b..e79621b7b 100644 --- a/common/src/jni/main/include/conscrypt/compat.h +++ b/common/src/jni/main/include/conscrypt/compat.h @@ -86,10 +86,9 @@ inline int asprintf(char** strp, const char* fmt, ...) { } inline int gettimeofday(struct timeval* tp, struct timezone*) { - // Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing - // zero's - // This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC) - // until 00:00:00 January 1, 1970 + // Note: some broken versions only have 8 trailing zero's, the correct epoch + // has 9 trailing zero's This magic number is the number of 100 nanosecond + // intervals since January 1, 1601 (UTC) until 00:00:00 January 1, 1970 static const uint64_t EPOCH = ((uint64_t)116444736000000000ULL); SYSTEMTIME system_time; diff --git a/common/src/jni/main/include/conscrypt/compatibility_close_monitor.h b/common/src/jni/main/include/conscrypt/compatibility_close_monitor.h index 70f6ad3ad..bc7c01328 100644 --- a/common/src/jni/main/include/conscrypt/compatibility_close_monitor.h +++ b/common/src/jni/main/include/conscrypt/compatibility_close_monitor.h @@ -22,14 +22,16 @@ namespace conscrypt { /* - * Where possible, this class hooks into the Android C API for AsynchronousCloseMonitor, - * allowing Java thread wakeup semantics during POSIX system calls. It is only used in sslSelect(). + * Where possible, this class hooks into the Android C API for + * AsynchronousCloseMonitor, allowing Java thread wakeup semantics during POSIX + * system calls. It is only used in sslSelect(). * * When unbundled, if the C API methods are not available, this class will fall * back to looking for the C++ API methods which existed on Android P and below. * - * On non-Android platforms, this class becomes a no-op as all of the function pointers - * to create and destroy AsynchronousCloseMonitor instances will be null. + * On non-Android platforms, this class becomes a no-op as all of the function + * pointers to create and destroy AsynchronousCloseMonitor instances will be + * null. */ class CompatibilityCloseMonitor { public: @@ -74,9 +76,9 @@ class CompatibilityCloseMonitor { // C++ API: Only available on Android P and below. Maintains pointers to // the C++ constructor and destructor methods, which will be null on // non-Android platforms. Calls them directly, passing in a pointer to - // objBuffer, which is large enough to fit an AsynchronousCloseMonitor object on - // Android versions where this class will be using this API. - // This is equivalent to placement new and explicit destruction. + // objBuffer, which is large enough to fit an AsynchronousCloseMonitor object + // on Android versions where this class will be using this API. This is + // equivalent to placement new and explicit destruction. typedef void (*acm_ctor_func)(void*, int); typedef void (*acm_dtor_func)(void*); diff --git a/common/src/jni/main/include/conscrypt/jniutil.h b/common/src/jni/main/include/conscrypt/jniutil.h index 70ae3965e..270211bdc 100644 --- a/common/src/jni/main/include/conscrypt/jniutil.h +++ b/common/src/jni/main/include/conscrypt/jniutil.h @@ -26,6 +26,14 @@ namespace conscrypt { namespace jniutil { +#ifdef __ANDROID__ +#define CRITICAL_JNI_PARAMS +#define CRITICAL_JNI_PARAMS_COMMA +#else +#define CRITICAL_JNI_PARAMS JNIEnv*, jclass +#define CRITICAL_JNI_PARAMS_COMMA JNIEnv*, jclass, +#endif + extern JavaVM* gJavaVM; extern jclass cryptoUpcallsClass; extern jclass openSslInputStreamClass; @@ -153,8 +161,8 @@ extern int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor); extern bool isDirectByteBufferInstance(JNIEnv* env, jobject buffer); /** - * Returns true if the VM's JNI GetByteArrayElements method is likely to create a copy when - * invoked on an array of the provided size. + * Returns true if the VM's JNI GetByteArrayElements method is likely to create + * a copy when invoked on an array of the provided size. */ extern bool isGetByteArrayElementsLikelyToReturnACopy(size_t size); @@ -281,7 +289,8 @@ extern int throwSSLHandshakeExceptionStr(JNIEnv* env, const char* message); extern int throwSSLExceptionStr(JNIEnv* env, const char* message); /** - * Throws a javax.net.ssl.SSLProcotolException with the given string as a message. + * Throws a javax.net.ssl.SSLProcotolException with the given string as a + * message. */ extern int throwSSLProtocolExceptionStr(JNIEnv* env, const char* message); @@ -302,9 +311,10 @@ extern int throwSSLExceptionWithSslErrors(JNIEnv* env, SSL* ssl, int sslErrorCod #ifdef CONSCRYPT_CHECK_ERROR_QUEUE /** - * Class that checks that the error queue is empty on destruction. It should only be used - * via the macro CHECK_ERROR_QUEUE_ON_RETURN, which can be placed at the top of a function to - * ensure that the error queue is empty whenever the function exits. + * Class that checks that the error queue is empty on destruction. It should + * only be used via the macro CHECK_ERROR_QUEUE_ON_RETURN, which can be placed + * at the top of a function to ensure that the error queue is empty whenever the + * function exits. */ class ErrorQueueChecker { public: @@ -319,7 +329,8 @@ class ErrorQueueChecker { char result[500]; snprintf(result, sizeof(result), "Error queue should have been empty but was (%s:%d) %s", file, line, message); - // If there's a pending exception, we want to throw the assertion error instead + // If there's a pending exception, we want to throw the assertion error + // instead env->ExceptionClear(); throwAssertionError(env, result); } diff --git a/common/src/jni/main/include/conscrypt/macros.h b/common/src/jni/main/include/conscrypt/macros.h index c7330ea39..f9197cb22 100644 --- a/common/src/jni/main/include/conscrypt/macros.h +++ b/common/src/jni/main/include/conscrypt/macros.h @@ -103,9 +103,10 @@ #endif /** - * Many OpenSSL APIs take ownership of an argument on success but don't free the argument - * on failure. This means we need to tell our scoped pointers when we've transferred ownership, - * without triggering a warning by not using the result of release(). + * Many OpenSSL APIs take ownership of an argument on success but don't free the + * argument on failure. This means we need to tell our scoped pointers when + * we've transferred ownership, without triggering a warning by not using the + * result of release(). */ #define OWNERSHIP_TRANSFERRED(obj) \ do { \ @@ -133,7 +134,8 @@ (len) > static_cast((array).size()) - (offset)) /** - * Check array bounds for arguments when an array length, chunk offset, and chunk length are given. + * Check array bounds for arguments when an array length, chunk offset, and + * chunk length are given. */ #define ARRAY_CHUNK_INVALID(array_len, chunk_offset, chunk_len) \ ((chunk_offset) < 0 || (chunk_offset) > static_cast(array_len) || (chunk_len) < 0 || \ diff --git a/common/src/jni/main/include/conscrypt/scoped_ssl_bio.h b/common/src/jni/main/include/conscrypt/scoped_ssl_bio.h index ecf7abd5c..7ca938c8e 100644 --- a/common/src/jni/main/include/conscrypt/scoped_ssl_bio.h +++ b/common/src/jni/main/include/conscrypt/scoped_ssl_bio.h @@ -22,8 +22,9 @@ namespace conscrypt { /* - * Sets the read and write BIO for an SSL connection and removes it when it goes out of scope. - * We hang on to BIO with a JNI GlobalRef and we want to remove them as soon as possible. + * Sets the read and write BIO for an SSL connection and removes it when it goes + * out of scope. We hang on to BIO with a JNI GlobalRef and we want to remove + * them as soon as possible. */ class ScopedSslBio { public: diff --git a/common/src/jni/main/include/conscrypt/trace.h b/common/src/jni/main/include/conscrypt/trace.h index 738f19c7e..018ba9995 100644 --- a/common/src/jni/main/include/conscrypt/trace.h +++ b/common/src/jni/main/include/conscrypt/trace.h @@ -38,7 +38,8 @@ constexpr std::size_t kWithJniTraceDataChunkSize = 512; * For example, if you were interested in ssl=0x12345678, you would do: * * address=0x12345678 - * awk "match(\$0,/ssl=$address SSL_DATA: (.*)\$/,a){print a[1]}" | text2pcap -T 443,1337 -t + * awk "match(\$0,/ssl=$address SSL_DATA: (.*)\$/,a){print a[1]}" | text2pcap + * -T 443,1337 -t * '%s.' -n -D - $address.pcapng */ constexpr bool kWithJniTracePackets = false; diff --git a/common/src/jni/unbundled/include/nativehelper/scoped_primitive_array.h b/common/src/jni/unbundled/include/nativehelper/scoped_primitive_array.h index 71e3733d2..7fad0e8ac 100644 --- a/common/src/jni/unbundled/include/nativehelper/scoped_primitive_array.h +++ b/common/src/jni/unbundled/include/nativehelper/scoped_primitive_array.h @@ -19,10 +19,11 @@ #include -// ScopedBooleanArrayRO, ScopedByteArrayRO, ScopedCharArrayRO, ScopedDoubleArrayRO, -// ScopedFloatArrayRO, ScopedIntArrayRO, ScopedLongArrayRO, and ScopedShortArrayRO provide -// convenient read-only access to Java arrays from JNI code. This is cheaper than read-write -// access and should be used by default. +// ScopedBooleanArrayRO, ScopedByteArrayRO, ScopedCharArrayRO, +// ScopedDoubleArrayRO, ScopedFloatArrayRO, ScopedIntArrayRO, ScopedLongArrayRO, +// and ScopedShortArrayRO provide convenient read-only access to Java arrays +// from JNI code. This is cheaper than read-write access and should be used by +// default. #define INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(PRIMITIVE_TYPE, NAME) \ class Scoped##NAME##ArrayRO { \ public: \ @@ -77,10 +78,11 @@ INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jshort, Short); #undef INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO -// ScopedBooleanArrayRW, ScopedByteArrayRW, ScopedCharArrayRW, ScopedDoubleArrayRW, -// ScopedFloatArrayRW, ScopedIntArrayRW, ScopedLongArrayRW, and ScopedShortArrayRW provide -// convenient read-write access to Java arrays from JNI code. These are more expensive, -// since they entail a copy back onto the Java heap, and should only be used when necessary. +// ScopedBooleanArrayRW, ScopedByteArrayRW, ScopedCharArrayRW, +// ScopedDoubleArrayRW, ScopedFloatArrayRW, ScopedIntArrayRW, ScopedLongArrayRW, +// and ScopedShortArrayRW provide convenient read-write access to Java arrays +// from JNI code. These are more expensive, since they entail a copy back onto +// the Java heap, and should only be used when necessary. #define INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(PRIMITIVE_TYPE, NAME) \ class Scoped##NAME##ArrayRW { \ public: \ diff --git a/common/src/main/java/org/conscrypt/ActiveSession.java b/common/src/main/java/org/conscrypt/ActiveSession.java index 0997b79b1..7fea51028 100644 --- a/common/src/main/java/org/conscrypt/ActiveSession.java +++ b/common/src/main/java/org/conscrypt/ActiveSession.java @@ -342,4 +342,25 @@ private void checkPeerCertificatesPresent() throws SSLPeerUnverifiedException { throw new SSLPeerUnverifiedException("No peer certificates"); } } + + private String[] peerSupportedSignatureAlgorithms = new String[0]; + + void onPeerSignatureAlgorithmsReceived(String[] algorithms) { + this.peerSupportedSignatureAlgorithms = + algorithms != null ? algorithms.clone() : new String[0]; + } + + @Override + public String[] getPeerSupportedSignatureAlgorithms() { + return peerSupportedSignatureAlgorithms.clone(); + } + + @Override + public String[] getLocalSupportedSignatureAlgorithms() { + return new String[] {// TLS 1.3 & modern TLS 1.2 + "RSASSA-PSS", "Ed25519", "SHA512withRSA", "SHA512withECDSA", + "SHA384withRSA", "SHA384withECDSA", "SHA256withRSA", "SHA256withECDSA", + // Legacy + "SHA224withRSA", "SHA224withECDSA", "SHA1withRSA", "SHA1withECDSA"}; + } } diff --git a/common/src/main/java/org/conscrypt/CertBlocklist.java b/common/src/main/java/org/conscrypt/CertBlocklist.java index 0ed68b0ab..056718f02 100644 --- a/common/src/main/java/org/conscrypt/CertBlocklist.java +++ b/common/src/main/java/org/conscrypt/CertBlocklist.java @@ -16,12 +16,15 @@ package org.conscrypt; +import org.conscrypt.Internal; + import java.math.BigInteger; import java.security.PublicKey; /** * A set of certificates that are blacklisted from trust. */ +@Internal public interface CertBlocklist { /** * Returns whether the given public key is in the blacklist. diff --git a/common/src/main/java/org/conscrypt/CertBlocklistEntry.java b/common/src/main/java/org/conscrypt/CertBlocklistEntry.java new file mode 100644 index 000000000..950d568d5 --- /dev/null +++ b/common/src/main/java/org/conscrypt/CertBlocklistEntry.java @@ -0,0 +1,37 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import org.conscrypt.Internal; + +/** + * An entry in the blocklist, for the purpose of reporting. + */ +@Internal +public interface CertBlocklistEntry { + enum Origin { SHA1_TEST, SHA1_BUILT_IN, SHA1_FILE, SHA256_TEST, SHA256_BUILT_IN, SHA256_FILE } + + /** + * Returns the origin of this entry. + */ + Origin getOrigin(); + + /** + * Returns the index of this entry in its blocklist. + */ + int getIndex(); +} diff --git a/common/src/main/java/org/conscrypt/ConscryptEngine.java b/common/src/main/java/org/conscrypt/ConscryptEngine.java index 8dd220e0d..696b5accd 100644 --- a/common/src/main/java/org/conscrypt/ConscryptEngine.java +++ b/common/src/main/java/org/conscrypt/ConscryptEngine.java @@ -1591,8 +1591,10 @@ public void onSSLStateChange(int type, int val) { } @Override - public void serverCertificateRequested() throws IOException { + public void serverCertificateRequested(int[] signatureAlgs) throws IOException { synchronized (ssl) { + String[] jsseAlgs = SSLUtils.mapSignatureAlgorithms(signatureAlgs); + activeSession.onPeerSignatureAlgorithmsReceived(jsseAlgs); ssl.configureServerCertificate(); } } @@ -1658,6 +1660,8 @@ public void verifyCertificateChain(byte[][] certChain, String authMethod) public void clientCertificateRequested(byte[] keyTypeBytes, int[] signatureAlgs, byte[][] asn1DerEncodedPrincipals) throws CertificateEncodingException, SSLException { + String[] jsseAlgs = SSLUtils.mapSignatureAlgorithms(signatureAlgs); + activeSession.onPeerSignatureAlgorithmsReceived(jsseAlgs); ssl.chooseClientCertificate(keyTypeBytes, signatureAlgs, asn1DerEncodedPrincipals); } diff --git a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java index ba5a525cb..65b296d38 100644 --- a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java +++ b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java @@ -309,6 +309,8 @@ public final void startHandshake() throws IOException { public final void clientCertificateRequested(byte[] keyTypeBytes, int[] signatureAlgs, byte[][] asn1DerEncodedPrincipals) throws CertificateEncodingException, SSLException { + String[] jsseAlgs = SSLUtils.mapSignatureAlgorithms(signatureAlgs); + activeSession.onPeerSignatureAlgorithmsReceived(jsseAlgs); ssl.chooseClientCertificate(keyTypeBytes, signatureAlgs, asn1DerEncodedPrincipals); } @@ -382,8 +384,10 @@ public final long serverSessionRequested(byte[] id) { } @Override - public final void serverCertificateRequested() throws IOException { + public final void serverCertificateRequested(int[] signatureAlgs) throws IOException { synchronized (ssl) { + String[] jsseAlgs = SSLUtils.mapSignatureAlgorithms(signatureAlgs); + activeSession.onPeerSignatureAlgorithmsReceived(jsseAlgs); ssl.configureServerCertificate(); } } diff --git a/common/src/main/java/org/conscrypt/ConscryptSession.java b/common/src/main/java/org/conscrypt/ConscryptSession.java index e25140598..77af0ff88 100644 --- a/common/src/main/java/org/conscrypt/ConscryptSession.java +++ b/common/src/main/java/org/conscrypt/ConscryptSession.java @@ -53,4 +53,8 @@ interface ConscryptSession extends SSLSession { @Override X509Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException; String getApplicationProtocol(); + + public String[] getPeerSupportedSignatureAlgorithms(); + + public String[] getLocalSupportedSignatureAlgorithms(); } diff --git a/common/src/main/java/org/conscrypt/ConscryptX509TrustManager.java b/common/src/main/java/org/conscrypt/ConscryptX509TrustManager.java new file mode 100644 index 000000000..f3586d6f9 --- /dev/null +++ b/common/src/main/java/org/conscrypt/ConscryptX509TrustManager.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * Interface for TrustManager methods implemented in Conscrypt but not part of + * the standard X509TrustManager or X509ExtendedTrustManager. + * + * These methods can be called by the Android framework. Extend + * X509TrustManagerExtensions if these need to be visible to apps. + */ +@Internal +public interface ConscryptX509TrustManager { + /** + * Verifies the given certificate chain. + * + *

See {@link X509TrustManager#checkServerTrusted(X509Certificate[], String)} for a + * description of the chain and authType parameters. The final parameter, host, should be the + * hostname of the server.

+ * + * @throws CertificateException if the chain does not verify correctly. + * @return the properly ordered chain used for verification as a list of X509Certificates. + */ + public List checkServerTrusted(X509Certificate[] chain, String authType, + String hostname) throws CertificateException; + + /** + * Verifies the given certificate chain. + * + *

See {@link X509TrustManager#checkServerTrusted(X509Certificate[], String)} for a + * description of the chain and authType parameters. The final parameter, host, should be the + * hostname of the server. + * + *

ocspData and tlsSctData may be provided to verify any Signed Certificate Timestamp (SCT) + * attached to the connection. These are ASN.1 octet strings (SignedCertificateTimestampList) as + * described in RFC 6962, Section 3.3. Note that SCTs embedded in the certificate chain will + * automatically be processed. + * + * @throws CertificateException if the chain does not verify correctly. + * @return the properly ordered chain used for verification as a list of X509Certificates. + */ + public List checkServerTrusted(X509Certificate[] chain, byte[] ocspData, + byte[] tlsSctData, String authType, + String hostname) throws CertificateException; +} diff --git a/common/src/main/java/org/conscrypt/DomainEncryptionMode.java b/common/src/main/java/org/conscrypt/DomainEncryptionMode.java new file mode 100644 index 000000000..ab878a468 --- /dev/null +++ b/common/src/main/java/org/conscrypt/DomainEncryptionMode.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +@Internal enum DomainEncryptionMode { UNKNOWN, DISABLED, OPPORTUNISTIC, ENABLED, REQUIRED } diff --git a/common/src/main/java/org/conscrypt/EchOptions.java b/common/src/main/java/org/conscrypt/EchOptions.java new file mode 100644 index 000000000..3715a4819 --- /dev/null +++ b/common/src/main/java/org/conscrypt/EchOptions.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +@Internal +class EchOptions { + private final byte[] configList; + private final boolean enableGrease; + + EchOptions(byte[] configList, boolean enableGrease) { + this.configList = configList; + this.enableGrease = enableGrease; + } + + byte[] getConfigList() { + return configList; + } + + boolean isGreaseEnabled() { + return enableGrease; + } +} diff --git a/common/src/main/java/org/conscrypt/ExternalSession.java b/common/src/main/java/org/conscrypt/ExternalSession.java index 1641a7f14..3e60c5d08 100644 --- a/common/src/main/java/org/conscrypt/ExternalSession.java +++ b/common/src/main/java/org/conscrypt/ExternalSession.java @@ -211,6 +211,16 @@ void removeValue(SSLSession session, String name) { } } + @Override + public String[] getPeerSupportedSignatureAlgorithms() { + return provider.provideSession().getPeerSupportedSignatureAlgorithms(); + } + + @Override + public String[] getLocalSupportedSignatureAlgorithms() { + return provider.provideSession().getLocalSupportedSignatureAlgorithms(); + } + /** * The provider of the current delegate session. */ diff --git a/common/src/main/java/org/conscrypt/HpkeImpl.java b/common/src/main/java/org/conscrypt/HpkeImpl.java index 5c7e19fae..0f97382a7 100644 --- a/common/src/main/java/org/conscrypt/HpkeImpl.java +++ b/common/src/main/java/org/conscrypt/HpkeImpl.java @@ -20,6 +20,8 @@ import static org.conscrypt.HpkeSuite.AEAD_CHACHA20POLY1305; import static org.conscrypt.HpkeSuite.KDF_HKDF_SHA256; import static org.conscrypt.HpkeSuite.KEM_DHKEM_X25519_HKDF_SHA256; +import static org.conscrypt.HpkeSuite.KEM_MLKEM_1024; +import static org.conscrypt.HpkeSuite.KEM_MLKEM_768; import static org.conscrypt.HpkeSuite.KEM_XWING; import java.security.GeneralSecurityException; @@ -267,4 +269,70 @@ public XwingHkdfSha256ChaCha20Poly1305() { super(new HpkeSuite(KEM_XWING, KDF_HKDF_SHA256, AEAD_CHACHA20POLY1305)); } } + + private static class HpkeMlKemImpl extends HpkeImpl { + HpkeMlKemImpl(HpkeSuite hpkeSuite) { + super(hpkeSuite); + } + + @Override + byte[] getRecipientPublicKeyBytes(PublicKey publicKey) throws InvalidKeyException { + if (!(publicKey instanceof OpenSslMlKemPublicKey)) { + throw new InvalidKeyException("Unsupported recipient key class: " + + publicKey.getClass()); + } + return ((OpenSslMlKemPublicKey) publicKey).getRaw(); + } + + @Override + byte[] getPrivateRecipientKeyBytes(PrivateKey recipientKey) throws InvalidKeyException { + if (!(recipientKey instanceof OpenSslMlKemPrivateKey)) { + throw new InvalidKeyException("Unsupported recipient private key class: " + + recipientKey.getClass()); + } + return ((OpenSslMlKemPrivateKey) recipientKey).getSeed(); + } + } + + /** Implementation of MLKEM_768/HKDF_SHA256/AES_128_GCM. */ + public static class MlKem768HkdfSha256Aes128Gcm extends HpkeMlKemImpl { + public MlKem768HkdfSha256Aes128Gcm() { + super(new HpkeSuite(KEM_MLKEM_768, KDF_HKDF_SHA256, AEAD_AES_128_GCM)); + } + } + + /** Implementation of MLKEM_768/HKDF_SHA256/AES_256_GCM. */ + public static class MlKem768HkdfSha256Aes256Gcm extends HpkeMlKemImpl { + public MlKem768HkdfSha256Aes256Gcm() { + super(new HpkeSuite(KEM_MLKEM_768, KDF_HKDF_SHA256, AEAD_AES_256_GCM)); + } + } + + /** Implementation of MLKEM_768/HKDF_SHA256/CHACHA20_POLY1305. */ + public static class MlKem768HkdfSha256ChaCha20Poly1305 extends HpkeMlKemImpl { + public MlKem768HkdfSha256ChaCha20Poly1305() { + super(new HpkeSuite(KEM_MLKEM_768, KDF_HKDF_SHA256, AEAD_CHACHA20POLY1305)); + } + } + + /** Implementation of MLKEM_1024/HKDF_SHA256/AES_128_GCM. */ + public static class MlKem1024HkdfSha256Aes128Gcm extends HpkeMlKemImpl { + public MlKem1024HkdfSha256Aes128Gcm() { + super(new HpkeSuite(KEM_MLKEM_1024, KDF_HKDF_SHA256, AEAD_AES_128_GCM)); + } + } + + /** Implementation of MLKEM_1024/HKDF_SHA256/AES_256_GCM. */ + public static class MlKem1024HkdfSha256Aes256Gcm extends HpkeMlKemImpl { + public MlKem1024HkdfSha256Aes256Gcm() { + super(new HpkeSuite(KEM_MLKEM_1024, KDF_HKDF_SHA256, AEAD_AES_256_GCM)); + } + } + + /** Implementation of MLKEM_1024/HKDF_SHA256/CHACHA20_POLY1305. */ + public static class MlKem1024HkdfSha256ChaCha20Poly1305 extends HpkeMlKemImpl { + public MlKem1024HkdfSha256ChaCha20Poly1305() { + super(new HpkeSuite(KEM_MLKEM_1024, KDF_HKDF_SHA256, AEAD_CHACHA20POLY1305)); + } + } } diff --git a/common/src/main/java/org/conscrypt/HpkeSuite.java b/common/src/main/java/org/conscrypt/HpkeSuite.java index 95bb6742b..bb70716a2 100644 --- a/common/src/main/java/org/conscrypt/HpkeSuite.java +++ b/common/src/main/java/org/conscrypt/HpkeSuite.java @@ -22,12 +22,15 @@ * *

    *
  • KEM
  • + * href="https://www.rfc-editor.org/rfc/rfc9180.html#name-key-encapsulation-mechanism">KEM *
  • KDF
  • + * href="https://www.rfc-editor.org/rfc/rfc9180.html#name-key-derivation-functions-kd">KDF *
  • AEAD
  • + * href="https://www.rfc-editor.org/rfc/rfc9180.html#name-authenticated-encryption-wi">AEAD *
+ * + *

Some of the constants are defined in IANA HPKE. */ public final class HpkeSuite { /** @@ -35,6 +38,12 @@ public final class HpkeSuite { */ public static final int KEM_DHKEM_X25519_HKDF_SHA256 = 0x0020; + /** KEM: 0x0041 ML-KEM-768 */ + public static final int KEM_MLKEM_768 = 0x0041; + + /** KEM: 0x0042 ML-KEM-1024 */ + public static final int KEM_MLKEM_1024 = 0x0042; + /** * KEM: 0x647a X-Wing */ @@ -144,6 +153,10 @@ public AEAD convertAead(int aead) { public enum KEM { DHKEM_X25519_HKDF_SHA256( /* id= */ 0x20, /* nSecret= */ 32, /* nEnc= */ 32, /* nPk= */ 32, /* nSk= */ 32), + MLKEM_768(/* id= */ 0x41, /* nSecret= */ 32, /* nEnc= */ 1088, /* nPk= */ 1184, + /* nSk= */ 64), + MLKEM_1024(/* id= */ 0x42, /* nSecret= */ 32, /* nEnc= */ 1568, /* nPk= */ 1568, + /* nSk= */ 64), XWING(/* id= */ 0x647a, /* nSecret= */ 32, /* nEnc= */ 1120, /* nPk= */ 1216, /* nSk= */ 32); diff --git a/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java b/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java index 431108790..879730422 100644 --- a/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java +++ b/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java @@ -28,15 +28,6 @@ * on Java 7+. */ class Java7ExtendedSSLSession extends ExtendedSSLSession implements ConscryptSession { - // TODO: use BoringSSL API to actually fetch the real data - private static final String[] LOCAL_SUPPORTED_SIGNATURE_ALGORITHMS = new String[] { - "SHA512withRSA", "SHA512withECDSA", "SHA384withRSA", "SHA384withECDSA", - "SHA256withRSA", "SHA256withECDSA", "SHA224withRSA", "SHA224withECDSA", - "SHA1withRSA", "SHA1withECDSA", - }; - // TODO: use BoringSSL API to actually fetch the real data - private static final String[] PEER_SUPPORTED_SIGNATURE_ALGORITHMS = - new String[] {"SHA1withRSA", "SHA1withECDSA"}; protected final ExternalSession delegate; Java7ExtendedSSLSession(ExternalSession delegate) { @@ -44,15 +35,15 @@ class Java7ExtendedSSLSession extends ExtendedSSLSession implements ConscryptSes } /* @Override */ - @SuppressWarnings("MissingOverride") // For Android backward-compatibility. + @SuppressWarnings("MissingOverride") public final String[] getLocalSupportedSignatureAlgorithms() { - return LOCAL_SUPPORTED_SIGNATURE_ALGORITHMS.clone(); + return delegate.getLocalSupportedSignatureAlgorithms(); } /* @Override */ - @SuppressWarnings("MissingOverride") // For Android backward-compatibility. + @SuppressWarnings("MissingOverride") public final String[] getPeerSupportedSignatureAlgorithms() { - return PEER_SUPPORTED_SIGNATURE_ALGORITHMS.clone(); + return delegate.getPeerSupportedSignatureAlgorithms(); } @Override diff --git a/common/src/main/java/org/conscrypt/MlKemAlgorithm.java b/common/src/main/java/org/conscrypt/MlKemAlgorithm.java new file mode 100644 index 000000000..2c06bffbd --- /dev/null +++ b/common/src/main/java/org/conscrypt/MlKemAlgorithm.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +/** ML-KEM algorithm. */ +public enum MlKemAlgorithm { + // Values from https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.203.pdf, table 3. + ML_KEM_768("ML-KEM-768", 1184), + ML_KEM_1024("ML-KEM-1024", 1568); + + private final String name; + private final int publicKeySize; + + private MlKemAlgorithm(String name, int publicKeySize) { + this.name = name; + this.publicKeySize = publicKeySize; + } + + @Override + public String toString() { + return name; + } + + public int publicKeySize() { + return publicKeySize; + } +} diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java index e939f43b7..e8cc264ba 100644 --- a/common/src/main/java/org/conscrypt/NativeCrypto.java +++ b/common/src/main/java/org/conscrypt/NativeCrypto.java @@ -16,6 +16,8 @@ package org.conscrypt; +// android-add: import dalvik.annotation.optimization.CriticalNative; +// android-add: import dalvik.annotation.optimization.FastNative; import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException; import java.io.FileDescriptor; @@ -61,6 +63,7 @@ public final class NativeCrypto { // --- OpenSSL library initialization -------------------------------------- private static final UnsatisfiedLinkError loadError; + static { UnsatisfiedLinkError error = null; try { @@ -75,8 +78,8 @@ public final class NativeCrypto { } /** - * Checks to see whether or not the native library was successfully loaded. If not, throws - * the {@link UnsatisfiedLinkError} that was encountered while attempting to load the library. + * Checks to see whether or not the native library was successfully loaded. If not, throws the + * {@link UnsatisfiedLinkError} that was encountered while attempting to load the library. */ static void checkAvailability() { if (loadError != null) { @@ -86,23 +89,32 @@ static void checkAvailability() { // --- DSA/RSA public/private key handling functions ----------------------- + // android-add: @FastNative static native long EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q, byte[] dmp1, byte[] dmq1, byte[] iqmp); + // android-add: @FastNative static native int EVP_PKEY_type(NativeRef.EVP_PKEY pkey); + // android-add: @FastNative static native String EVP_PKEY_print_public(NativeRef.EVP_PKEY pkeyRef); + // android-add: @FastNative static native String EVP_PKEY_print_params(NativeRef.EVP_PKEY pkeyRef); + // android-add: @FastNative static native void EVP_PKEY_free(long pkey); + // android-add: @FastNative static native int EVP_PKEY_cmp(NativeRef.EVP_PKEY pkey1, NativeRef.EVP_PKEY pkey2); + // android-add: @FastNative static native byte[] EVP_marshal_private_key(NativeRef.EVP_PKEY pkey); + // android-add: @FastNative static native long EVP_parse_private_key(byte[] data) throws ParsingException; + // android-add: @FastNative static native byte[] EVP_marshal_public_key(NativeRef.EVP_PKEY pkey); static native long EVP_PKEY_from_private_key_info(byte[] data, int[] algs) @@ -123,33 +135,44 @@ static native long EVP_PKEY_from_subject_public_key_info(byte[] data, int[] algs static native byte[] EVP_PKEY_get_private_seed(NativeRef.EVP_PKEY pkey); + // android-add: @FastNative static native byte[] EVP_raw_X25519_private_key(byte[] data) throws ParsingException, InvalidKeyException; + // android-add: @FastNative static native long EVP_parse_public_key(byte[] data) throws ParsingException; + // android-add: @FastNative static native long PEM_read_bio_PUBKEY(long bioCtx); + // android-add: @FastNative static native long PEM_read_bio_PrivateKey(long bioCtx); + // android-add: @FastNative static native long getRSAPrivateKeyWrapper(PrivateKey key, byte[] modulus); + // android-add: @FastNative static native long getECPrivateKeyWrapper(PrivateKey key, NativeRef.EC_GROUP ecGroupRef); static native long RSA_generate_key_ex(int modulusBits, byte[] publicExponent); + // android-add: @FastNative static native int RSA_size(NativeRef.EVP_PKEY pkey); + // android-add: @FastNative static native int RSA_private_encrypt(int flen, byte[] from, byte[] to, NativeRef.EVP_PKEY pkey, int padding); + // android-add: @FastNative static native int RSA_public_decrypt(int flen, byte[] from, byte[] to, NativeRef.EVP_PKEY pkey, int padding) throws BadPaddingException, SignatureException; + // android-add: @FastNative static native int RSA_public_encrypt(int flen, byte[] from, byte[] to, NativeRef.EVP_PKEY pkey, int padding); + // android-add: @FastNative static native int RSA_private_decrypt(int flen, byte[] from, byte[] to, NativeRef.EVP_PKEY pkey, int padding) throws BadPaddingException, SignatureException; @@ -157,11 +180,13 @@ static native int RSA_private_decrypt(int flen, byte[] from, byte[] to, NativeRe /* * Returns array of {n, e} */ + // android-add: @FastNative static native byte[][] get_RSA_public_params(NativeRef.EVP_PKEY rsa); /* * Returns array of {n, e, d, p, q, dmp1, dmq1, iqmp} */ + // android-add: @FastNative static native byte[][] get_RSA_private_params(NativeRef.EVP_PKEY rsa); // --- ChaCha20 ----------------------- @@ -169,248 +194,337 @@ static native int RSA_private_decrypt(int flen, byte[] from, byte[] to, NativeRe /* * Returns the encrypted or decrypted version of the data. */ + // android-add: @FastNative static native void chacha20_encrypt_decrypt(byte[] in, int inOffset, byte[] out, int outOffset, int length, byte[] key, byte[] nonce, int blockCounter); // --- EC functions -------------------------- + // android-add: @FastNative static native long EVP_PKEY_new_EC_KEY(NativeRef.EC_GROUP groupRef, NativeRef.EC_POINT pubkeyRef, byte[] privkey); + // android-add: @FastNative static native long EC_GROUP_new_by_curve_name(String curveName); + // android-add: @FastNative static native long EC_GROUP_new_arbitrary(byte[] p, byte[] a, byte[] b, byte[] x, byte[] y, byte[] order, int cofactor); + // android-add: @FastNative static native String EC_GROUP_get_curve_name(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native byte[][] EC_GROUP_get_curve(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native void EC_GROUP_clear_free(long groupRef); + // android-add: @FastNative static native long EC_GROUP_get_generator(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native byte[] EC_GROUP_get_order(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native int EC_GROUP_get_degree(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native byte[] EC_GROUP_get_cofactor(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native long EC_POINT_new(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native void EC_POINT_clear_free(long pointRef); + // android-add: @FastNative static native byte[][] EC_POINT_get_affine_coordinates(NativeRef.EC_GROUP groupRef, NativeRef.EC_POINT pointRef); + // android-add: @FastNative static native void EC_POINT_set_affine_coordinates(NativeRef.EC_GROUP groupRef, NativeRef.EC_POINT pointRef, byte[] x, byte[] y); static native long EC_KEY_generate_key(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native long EC_KEY_get1_group(NativeRef.EVP_PKEY pkeyRef); + // android-add: @FastNative static native byte[] EC_KEY_get_private_key(NativeRef.EVP_PKEY keyRef); + // android-add: @FastNative static native long EC_KEY_get_public_key(NativeRef.EVP_PKEY keyRef); + // android-add: @FastNative static native byte[] EC_KEY_marshal_curve_name(NativeRef.EC_GROUP groupRef) throws IOException; + // android-add: @FastNative static native long EC_KEY_parse_curve_name(byte[] encoded) throws IOException; + // android-add: @FastNative static native int ECDH_compute_key(byte[] out, int outOffset, NativeRef.EVP_PKEY publicKeyRef, NativeRef.EVP_PKEY privateKeyRef) throws InvalidKeyException, IndexOutOfBoundsException; + // android-add: @FastNative static native int ECDSA_size(NativeRef.EVP_PKEY pkey); + // android-add: @FastNative static native int ECDSA_sign(byte[] data, int dataLen, byte[] sig, NativeRef.EVP_PKEY pkey); + // android-add: @FastNative static native int ECDSA_verify(byte[] data, int dataLen, byte[] sig, NativeRef.EVP_PKEY pkey); // --- MLDSA65 -------------------------------------------------------------- + // android-add: @FastNative static native byte[] MLDSA65_public_key_from_seed(byte[] privateKeySeed); // --- MLDSA87 -------------------------------------------------------------- + // android-add: @FastNative static native byte[] MLDSA87_public_key_from_seed(byte[] privateKeySeed); // --- SLHDSA_SHA2_128S -------------------------------------------------------------- static native void SLHDSA_SHA2_128S_generate_key(byte[] outPublicKey, byte[] outPrivateKey); + // android-add: @FastNative static native byte[] SLHDSA_SHA2_128S_sign(byte[] data, int dataLen, byte[] privateKey); + // android-add: @FastNative static native int SLHDSA_SHA2_128S_verify(byte[] data, int dataLen, byte[] sig, byte[] publicKey); // --- Curve25519 -------------- + // android-add: @FastNative static native boolean X25519(byte[] out, byte[] privateKey, byte[] publicKey) throws InvalidKeyException; + // android-add: @FastNative static native void X25519_keypair(byte[] outPublicKey, byte[] outPrivateKey); + // android-add: @FastNative static native void ED25519_keypair(byte[] outPublicKey, byte[] outPrivateKey); // --- X-Wing -------------- static native byte[] XWING_public_key_from_seed(byte[] privateKeySeed); + // --- ML-KEM -------------- + + static native byte[] MLKEM768_public_key_from_seed(byte[] privateKeySeed); + static native byte[] MLKEM1024_public_key_from_seed(byte[] privateKeySeed); + // --- Message digest functions -------------- // These return const references + // android-add: @FastNative static native long EVP_get_digestbyname(String name); + // android-add: @FastNative static native int EVP_MD_size(long evp_md_const); // --- Message digest context functions -------------- + // android-add: @FastNative static native long EVP_MD_CTX_create(); + // android-add: @FastNative static native void EVP_MD_CTX_cleanup(NativeRef.EVP_MD_CTX ctx); + // android-add: @FastNative static native void EVP_MD_CTX_destroy(long ctx); + // android-add: @FastNative static native int EVP_MD_CTX_copy_ex(NativeRef.EVP_MD_CTX dst_ctx, NativeRef.EVP_MD_CTX src_ctx); // --- Digest handling functions ------------------------------------------- + // android-add: @FastNative static native int EVP_DigestInit_ex(NativeRef.EVP_MD_CTX ctx, long evp_md); + // android-add: @FastNative static native void EVP_DigestUpdate(NativeRef.EVP_MD_CTX ctx, byte[] buffer, int offset, int length); + // android-add: @FastNative static native void EVP_DigestUpdateDirect(NativeRef.EVP_MD_CTX ctx, long ptr, int length); + // android-add: @FastNative static native int EVP_DigestFinal_ex(NativeRef.EVP_MD_CTX ctx, byte[] hash, int offset); // --- Signature handling functions ---------------------------------------- + // android-add: @FastNative static native long EVP_DigestSignInit(NativeRef.EVP_MD_CTX ctx, long evpMdRef, NativeRef.EVP_PKEY key); + // android-add: @FastNative static native long EVP_DigestVerifyInit(NativeRef.EVP_MD_CTX ctx, long evpMdRef, NativeRef.EVP_PKEY key); + // android-add: @FastNative static native void EVP_DigestSignUpdate(NativeRef.EVP_MD_CTX ctx, byte[] buffer, int offset, int length); + // android-add: @FastNative static native void EVP_DigestSignUpdateDirect(NativeRef.EVP_MD_CTX ctx, long ptr, int length); + // android-add: @FastNative static native void EVP_DigestVerifyUpdate(NativeRef.EVP_MD_CTX ctx, byte[] buffer, int offset, int length); + // android-add: @FastNative static native void EVP_DigestVerifyUpdateDirect(NativeRef.EVP_MD_CTX ctx, long ptr, int length); + // android-add: @FastNative static native byte[] EVP_DigestSignFinal(NativeRef.EVP_MD_CTX ctx); + // android-add: @FastNative static native boolean EVP_DigestVerifyFinal(NativeRef.EVP_MD_CTX ctx, byte[] signature, int offset, int length) throws IndexOutOfBoundsException; + // android-add: @FastNative static native byte[] EVP_DigestSign(NativeRef.EVP_MD_CTX ctx, byte[] buffer, int offset, int length); + // android-add: @FastNative static native boolean EVP_DigestVerify(NativeRef.EVP_MD_CTX ctx, byte[] sigBuffer, int sigOffset, int sigLen, byte[] dataBuffer, int dataOffset, int dataLen); + // android-add: @FastNative static native long EVP_PKEY_encrypt_init(NativeRef.EVP_PKEY pkey) throws InvalidKeyException; + // android-add: @FastNative static native int EVP_PKEY_encrypt(NativeRef.EVP_PKEY_CTX ctx, byte[] out, int outOffset, byte[] input, int inOffset, int inLength) throws IndexOutOfBoundsException, BadPaddingException; + // android-add: @FastNative static native long EVP_PKEY_decrypt_init(NativeRef.EVP_PKEY pkey) throws InvalidKeyException; + // android-add: @FastNative static native int EVP_PKEY_decrypt(NativeRef.EVP_PKEY_CTX ctx, byte[] out, int outOffset, byte[] input, int inOffset, int inLength) throws IndexOutOfBoundsException, BadPaddingException; + // android-add: @FastNative static native void EVP_PKEY_CTX_free(long pkeyCtx); + // android-add: @FastNative static native void EVP_PKEY_CTX_set_rsa_padding(long ctx, int pad) throws InvalidAlgorithmParameterException; + // android-add: @FastNative static native void EVP_PKEY_CTX_set_rsa_pss_saltlen(long ctx, int len) throws InvalidAlgorithmParameterException; + // android-add: @FastNative static native void EVP_PKEY_CTX_set_rsa_mgf1_md(long ctx, long evpMdRef) throws InvalidAlgorithmParameterException; + // android-add: @FastNative static native void EVP_PKEY_CTX_set_rsa_oaep_md(long ctx, long evpMdRef) throws InvalidAlgorithmParameterException; + // android-add: @FastNative static native void EVP_PKEY_CTX_set_rsa_oaep_label(long ctx, byte[] label) throws InvalidAlgorithmParameterException; // --- Block ciphers ------------------------------------------------------- // These return const references + // android-add: @FastNative static native long EVP_get_cipherbyname(String string); + // android-add: @FastNative static native void EVP_CipherInit_ex(NativeRef.EVP_CIPHER_CTX ctx, long evpCipher, byte[] key, byte[] iv, boolean encrypting); + // android-add: @FastNative static native int EVP_CipherUpdate(NativeRef.EVP_CIPHER_CTX ctx, byte[] out, int outOffset, byte[] in, int inOffset, int inLength) throws IndexOutOfBoundsException; + // android-add: @FastNative static native int EVP_CipherFinal_ex(NativeRef.EVP_CIPHER_CTX ctx, byte[] out, int outOffset) throws BadPaddingException, IllegalBlockSizeException; + // android-add: @FastNative static native int EVP_CIPHER_iv_length(long evpCipher); + // android-add: @FastNative static native long EVP_CIPHER_CTX_new(); + // android-add: @FastNative static native int EVP_CIPHER_CTX_block_size(NativeRef.EVP_CIPHER_CTX ctx); + // android-add: @FastNative static native int get_EVP_CIPHER_CTX_buf_len(NativeRef.EVP_CIPHER_CTX ctx); + // android-add: @FastNative static native boolean get_EVP_CIPHER_CTX_final_used(NativeRef.EVP_CIPHER_CTX ctx); + // android-add: @FastNative static native void EVP_CIPHER_CTX_set_padding(NativeRef.EVP_CIPHER_CTX ctx, boolean enablePadding); + // android-add: @FastNative static native void EVP_CIPHER_CTX_set_key_length(NativeRef.EVP_CIPHER_CTX ctx, int keyBitSize); + // android-add: @FastNative static native void EVP_CIPHER_CTX_free(long ctx); // --- AEAD ---------------------------------------------------------------- + // android-add: @FastNative static native long EVP_aead_aes_128_gcm(); + // android-add: @FastNative static native long EVP_aead_aes_256_gcm(); + // android-add: @FastNative static native long EVP_aead_chacha20_poly1305(); + // android-add: @FastNative static native long EVP_aead_aes_128_gcm_siv(); + // android-add: @FastNative static native long EVP_aead_aes_256_gcm_siv(); + // android-add: @FastNative static native int EVP_AEAD_max_overhead(long evpAead); + // android-add: @FastNative static native int EVP_AEAD_nonce_length(long evpAead); + // android-add: @FastNative static native int EVP_AEAD_CTX_seal(long evpAead, byte[] key, int tagLengthInBytes, byte[] out, int outOffset, byte[] nonce, byte[] in, int inOffset, int inLength, byte[] ad) throws ShortBufferException, BadPaddingException; + // android-add: @FastNative static native int EVP_AEAD_CTX_seal_buf(long evpAead, byte[] key, int tagLengthInBytes, ByteBuffer out, byte[] nonce, ByteBuffer input, byte[] ad) throws ShortBufferException, BadPaddingException; + // android-add: @FastNative static native int EVP_AEAD_CTX_open(long evpAead, byte[] key, int tagLengthInBytes, byte[] out, int outOffset, byte[] nonce, byte[] in, int inOffset, int inLength, byte[] ad) throws ShortBufferException, BadPaddingException; + // android-add: @FastNative static native int EVP_AEAD_CTX_open_buf(long evpAead, byte[] key, int tagLengthInBytes, ByteBuffer out, byte[] nonce, ByteBuffer input, byte[] ad) @@ -418,48 +532,66 @@ static native int EVP_AEAD_CTX_open_buf(long evpAead, byte[] key, int tagLengthI // --- CMAC functions ------------------------------------------------------ + // android-add: @FastNative static native long CMAC_CTX_new(); + // android-add: @FastNative static native void CMAC_CTX_free(long ctx); + // android-add: @FastNative static native void CMAC_Init(NativeRef.CMAC_CTX ctx, byte[] key); + // android-add: @FastNative static native void CMAC_Update(NativeRef.CMAC_CTX ctx, byte[] in, int inOffset, int inLength); + // android-add: @FastNative static native void CMAC_UpdateDirect(NativeRef.CMAC_CTX ctx, long inPtr, int inLength); + // android-add: @FastNative static native byte[] CMAC_Final(NativeRef.CMAC_CTX ctx); + // android-add: @FastNative static native void CMAC_Reset(NativeRef.CMAC_CTX ctx); // --- HMAC functions ------------------------------------------------------ + // android-add: @FastNative static native long HMAC_CTX_new(); + // android-add: @FastNative static native void HMAC_CTX_free(long ctx); + // android-add: @FastNative static native void HMAC_Init_ex(NativeRef.HMAC_CTX ctx, byte[] key, long evp_md); + // android-add: @FastNative static native void HMAC_Update(NativeRef.HMAC_CTX ctx, byte[] in, int inOffset, int inLength); + // android-add: @FastNative static native void HMAC_UpdateDirect(NativeRef.HMAC_CTX ctx, long inPtr, int inLength); + // android-add: @FastNative static native byte[] HMAC_Final(NativeRef.HMAC_CTX ctx); + // android-add: @FastNative static native void HMAC_Reset(NativeRef.HMAC_CTX ctx); // --- HPKE functions ------------------------------------------------------ + // android-add: @FastNative static native byte[] EVP_HPKE_CTX_export(NativeRef.EVP_HPKE_CTX ctx, byte[] exporterCtx, int length); static native void EVP_HPKE_CTX_free(long ctx); + // android-add: @FastNative static native byte[] EVP_HPKE_CTX_open(NativeRef.EVP_HPKE_CTX ctx, byte[] ciphertext, byte[] aad) throws BadPaddingException; + // android-add: @FastNative static native byte[] EVP_HPKE_CTX_seal(NativeRef.EVP_HPKE_CTX ctx, byte[] plaintext, byte[] aad); + // android-add: @FastNative static native Object EVP_HPKE_CTX_setup_base_mode_recipient(int kem, int kdf, int aead, byte[] privateKey, byte[] enc, byte[] info); @@ -471,6 +603,7 @@ static Object EVP_HPKE_CTX_setup_base_mode_recipient(HpkeSuite suite, byte[] pri enc, info); } + // android-add: @FastNative static native Object[] EVP_HPKE_CTX_setup_base_mode_sender(int kem, int kdf, int aead, byte[] publicKey, byte[] info); @@ -479,6 +612,8 @@ static Object[] EVP_HPKE_CTX_setup_base_mode_sender(HpkeSuite suite, byte[] publ return EVP_HPKE_CTX_setup_base_mode_sender(suite.getKem().getId(), suite.getKdf().getId(), suite.getAead().getId(), publicKey, info); } + + // android-add: @FastNative static native Object[] EVP_HPKE_CTX_setup_base_mode_sender_with_seed_for_testing( int kem, int kdf, int aead, byte[] publicKey, byte[] info, byte[] seed); @@ -493,6 +628,7 @@ static Object[] EVP_HPKE_CTX_setup_base_mode_sender_with_seed_for_testing(HpkeSu // --- RAND ---------------------------------------------------------------- + // android-add: @FastNative static native void RAND_bytes(byte[] output); // --- X509_NAME ----------------------------------------------------------- @@ -504,6 +640,7 @@ static int X509_NAME_hash(X500Principal principal) { public static int X509_NAME_hash_old(X500Principal principal) { return X509_NAME_hash(principal, "MD5"); } + private static int X509_NAME_hash(X500Principal principal, String algorithm) { try { byte[] digest = MessageDigest.getInstance(algorithm).digest(principal.getEncoded()); @@ -520,102 +657,128 @@ private static int X509_NAME_hash(X500Principal principal, String algorithm) { /** Used to request get_X509_GENERAL_NAME_stack get the "altname" field. */ static final int GN_STACK_SUBJECT_ALT_NAME = 1; - /** - * Used to request get_X509_GENERAL_NAME_stack get the issuerAlternativeName - * extension. - */ + /** Used to request get_X509_GENERAL_NAME_stack get the issuerAlternativeName extension. */ static final int GN_STACK_ISSUER_ALT_NAME = 2; - /** - * Used to request only non-critical types in get_X509*_ext_oids. - */ + /** Used to request only non-critical types in get_X509*_ext_oids. */ static final int EXTENSION_TYPE_NON_CRITICAL = 0; - /** - * Used to request only critical types in get_X509*_ext_oids. - */ + /** Used to request only critical types in get_X509*_ext_oids. */ static final int EXTENSION_TYPE_CRITICAL = 1; + // android-add: @FastNative static native long d2i_X509_bio(long bioCtx); + // android-add: @FastNative static native long d2i_X509(byte[] encoded) throws ParsingException; + // android-add: @FastNative static native long PEM_read_bio_X509(long bioCtx); + // android-add: @FastNative static native byte[] i2d_X509(long x509ctx, OpenSSLX509Certificate holder); /** Takes an X509 context not an X509_PUBKEY context. */ + // android-add: @FastNative static native byte[] i2d_X509_PUBKEY(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native byte[] ASN1_seq_pack_X509(long[] x509CertRefs); + // android-add: @FastNative static native long[] ASN1_seq_unpack_X509_bio(long bioRef) throws ParsingException; static native void X509_free(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native int X509_cmp(long x509ctx1, OpenSSLX509Certificate holder, long x509ctx2, OpenSSLX509Certificate holder2); + // android-add: @FastNative static native void X509_print_ex(long bioCtx, long x509ctx, OpenSSLX509Certificate holder, long nmflag, long certflag); + // android-add: @FastNative static native byte[] X509_get_issuer_name(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native byte[] X509_get_subject_name(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native String get_X509_sig_alg_oid(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native byte[] get_X509_sig_alg_parameter(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native boolean[] get_X509_issuerUID(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native boolean[] get_X509_subjectUID(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native long X509_get_pubkey(long x509ctx, OpenSSLX509Certificate holder) throws NoSuchAlgorithmException, InvalidKeyException; + // android-add: @FastNative static native String get_X509_pubkey_oid(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native byte[] X509_get_ext_oid(long x509ctx, OpenSSLX509Certificate holder, String oid); + // android-add: @FastNative static native String[] get_X509_ext_oids(long x509ctx, OpenSSLX509Certificate holder, int critical); + // android-add: @FastNative static native Object[][] get_X509_GENERAL_NAME_stack(long x509ctx, OpenSSLX509Certificate holder, int type) throws CertificateParsingException; + // android-add: @FastNative static native boolean[] get_X509_ex_kusage(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native String[] get_X509_ex_xkusage(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native int get_X509_ex_pathlen(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native long X509_get_notBefore(long x509ctx, OpenSSLX509Certificate holder) throws ParsingException; + // android-add: @FastNative static native long X509_get_notAfter(long x509ctx, OpenSSLX509Certificate holder) throws ParsingException; + // android-add: @FastNative static native long X509_get_version(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native byte[] X509_get_serialNumber(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native void X509_verify(long x509ctx, OpenSSLX509Certificate holder, NativeRef.EVP_PKEY pkeyCtx) throws BadPaddingException, IllegalBlockSizeException; + // android-add: @FastNative static native byte[] get_X509_tbs_cert(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native byte[] get_X509_tbs_cert_without_ext(long x509ctx, OpenSSLX509Certificate holder, String oid); + // android-add: @FastNative static native byte[] get_X509_signature(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native int get_X509_ex_flags(long x509ctx, OpenSSLX509Certificate holder); // Used by Android platform TrustedCertificateStore. @SuppressWarnings("unused") + // android-add: @FastNative static native int X509_check_issued(long ctx, OpenSSLX509Certificate holder, long ctx2, OpenSSLX509Certificate holder2); @@ -628,104 +791,138 @@ static native int X509_check_issued(long ctx, OpenSSLX509Certificate holder, lon static final int PKCS7_CRLS = 2; /** Returns an array of X509 or X509_CRL pointers. */ + // android-add: @FastNative static native long[] d2i_PKCS7_bio(long bioCtx, int which) throws ParsingException; /** Returns an array of X509 or X509_CRL pointers. */ + // android-add: @FastNative static native byte[] i2d_PKCS7(long[] certs); /** Returns an array of X509 or X509_CRL pointers. */ + // android-add: @FastNative static native long[] PEM_read_bio_PKCS7(long bioCtx, int which); // --- X509_CRL ------------------------------------------------------------ + // android-add: @FastNative static native long d2i_X509_CRL_bio(long bioCtx); + // android-add: @FastNative static native long PEM_read_bio_X509_CRL(long bioCtx); + // android-add: @FastNative static native byte[] i2d_X509_CRL(long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native void X509_CRL_free(long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native void X509_CRL_print(long bioCtx, long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native String get_X509_CRL_sig_alg_oid(long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native byte[] get_X509_CRL_sig_alg_parameter(long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native byte[] X509_CRL_get_issuer_name(long x509CrlCtx, OpenSSLX509CRL holder); /** Returns X509_REVOKED reference that is not duplicated! */ + // android-add: @FastNative static native long X509_CRL_get0_by_cert(long x509CrlCtx, OpenSSLX509CRL holder, long x509Ctx, OpenSSLX509Certificate holder2); /** Returns X509_REVOKED reference that is not duplicated! */ + // android-add: @FastNative static native long X509_CRL_get0_by_serial(long x509CrlCtx, OpenSSLX509CRL holder, byte[] serial); /** Returns an array of X509_REVOKED that are owned by the caller. */ + // android-add: @FastNative static native long[] X509_CRL_get_REVOKED(long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native String[] get_X509_CRL_ext_oids(long x509Crlctx, OpenSSLX509CRL holder, int critical); + // android-add: @FastNative static native byte[] X509_CRL_get_ext_oid(long x509CrlCtx, OpenSSLX509CRL holder, String oid); + // android-add: @FastNative static native long X509_CRL_get_version(long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native long X509_CRL_get_ext(long x509CrlCtx, OpenSSLX509CRL holder, String oid); + // android-add: @FastNative static native byte[] get_X509_CRL_signature(long x509ctx, OpenSSLX509CRL holder); + // android-add: @FastNative static native void X509_CRL_verify(long x509CrlCtx, OpenSSLX509CRL holder, NativeRef.EVP_PKEY pkeyCtx) throws BadPaddingException, SignatureException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException; + // android-add: @FastNative static native byte[] get_X509_CRL_crl_enc(long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native long X509_CRL_get_lastUpdate(long x509CrlCtx, OpenSSLX509CRL holder) throws ParsingException; + // android-add: @FastNative static native long X509_CRL_get_nextUpdate(long x509CrlCtx, OpenSSLX509CRL holder) throws ParsingException; // --- X509_REVOKED -------------------------------------------------------- + // android-add: @FastNative static native long X509_REVOKED_dup(long x509RevokedCtx); + // android-add: @FastNative static native byte[] i2d_X509_REVOKED(long x509RevokedCtx, OpenSSLX509CRLEntry holder); + // android-add: @FastNative static native String[] get_X509_REVOKED_ext_oids(long x509ctx, int critical, OpenSSLX509CRLEntry holder); + // android-add: @FastNative static native byte[] X509_REVOKED_get_ext_oid(long x509RevokedCtx, String oid, OpenSSLX509CRLEntry holder); + // android-add: @FastNative static native byte[] X509_REVOKED_get_serialNumber(long x509RevokedCtx, OpenSSLX509CRLEntry holder); + // android-add: @FastNative static native long X509_REVOKED_get_ext(long x509RevokedCtx, String oid, OpenSSLX509CRLEntry holder); /** Returns ASN1_TIME reference. */ + // android-add: @FastNative static native long get_X509_REVOKED_revocationDate(long x509RevokedCtx, OpenSSLX509CRLEntry holder); + // android-add: @FastNative static native void X509_REVOKED_print(long bioRef, long x509RevokedCtx, OpenSSLX509CRLEntry holder); + // android-add: @FastNative static native void X509_REVOKED_free(long x509RevokedCtx, OpenSSLX509CRLEntry holder); // --- X509_EXTENSION ------------------------------------------------------ + // android-add: @FastNative static native int X509_supported_extension(long x509ExtensionRef); // --- SPAKE --------------------------------------------------------------- /** - * Sets the SPAKE credential for the given SSL context using a password. - * Used for both client and server. + * Sets the SPAKE credential for the given SSL context using a password. Used for both client + * and server. */ + // android-add: @FastNative static native void SSL_CTX_set_spake_credential(byte[] context, byte[] pw_array, byte[] id_prover_array, byte[] id_verifier_array, boolean is_client, @@ -735,6 +932,7 @@ static native void SSL_CTX_set_spake_credential(byte[] context, byte[] pw_array, // --- ASN1_TIME ----------------------------------------------------------- + // android-add: @FastNative static native void ASN1_TIME_to_Calendar(long asn1TimeCtx, Calendar cal) throws ParsingException; @@ -742,144 +940,151 @@ static native void ASN1_TIME_to_Calendar(long asn1TimeCtx, Calendar cal) /** * Allocates and returns an opaque reference to an object that can be used with other - * asn1_read_* functions to read the ASN.1-encoded data in val. The returned object must - * be freed after use by calling asn1_read_free. + * asn1_read_* functions to read the ASN.1-encoded data in val. The returned object must be + * freed after use by calling asn1_read_free. */ + // android-add: @FastNative static native long asn1_read_init(byte[] val) throws IOException; /** * Allocates and returns an opaque reference to an object that can be used with other - * asn1_read_* functions to read the ASN.1 sequence pointed to by cbsRef. The returned - * object must be freed after use by calling asn1_read_free. + * asn1_read_* functions to read the ASN.1 sequence pointed to by cbsRef. The returned object + * must be freed after use by calling asn1_read_free. */ + // android-add: @FastNative static native long asn1_read_sequence(long cbsRef) throws IOException; /** - * Returns whether the next object in the given reference is explicitly tagged with the - * given tag number. + * Returns whether the next object in the given reference is explicitly tagged with the given + * tag number. */ + // android-add: @FastNative static native boolean asn1_read_next_tag_is(long cbsRef, int tag) throws IOException; /** - * Allocates and returns an opaque reference to an object that can be used with - * other asn1_read_* functions to read the ASN.1 data pointed to by cbsRef. The returned - * object must be freed after use by calling asn1_read_free. + * Allocates and returns an opaque reference to an object that can be used with other + * asn1_read_* functions to read the ASN.1 data pointed to by cbsRef. The returned object must + * be freed after use by calling asn1_read_free. */ + // android-add: @FastNative static native long asn1_read_tagged(long cbsRef) throws IOException; - /** - * Returns the contents of an ASN.1 octet string from the given reference. - */ + /** Returns the contents of an ASN.1 octet string from the given reference. */ + // android-add: @FastNative static native byte[] asn1_read_octetstring(long cbsRef) throws IOException; /** - * Returns an ASN.1 integer from the given reference. If the integer doesn't fit - * in a uint64, this method will throw an IOException. + * Returns an ASN.1 integer from the given reference. If the integer doesn't fit in a uint64, + * this method will throw an IOException. */ + // android-add: @FastNative static native long asn1_read_uint64(long cbsRef) throws IOException; - /** - * Consumes an ASN.1 NULL from the given reference. - */ + /** Consumes an ASN.1 NULL from the given reference. */ + // android-add: @FastNative static native void asn1_read_null(long cbsRef) throws IOException; /** * Returns an ASN.1 OID in dotted-decimal notation (eg, "1.3.14.3.2.26" for SHA-1) from the * given reference. */ + // android-add: @FastNative static native String asn1_read_oid(long cbsRef) throws IOException; - /** - * Returns whether or not the given reference has been read completely. - */ + /** Returns whether or not the given reference has been read completely. */ + // android-add: @FastNative static native boolean asn1_read_is_empty(long cbsRef); /** - * Frees any resources associated with the given reference. After calling, the reference - * must not be used again. This may be called with a zero reference, in which case nothing - * will be done. + * Frees any resources associated with the given reference. After calling, the reference must + * not be used again. This may be called with a zero reference, in which case nothing will be + * done. */ + // android-add: @FastNative static native void asn1_read_free(long cbsRef); /** * Allocates and returns an opaque reference to an object that can be used with other - * asn1_write_* functions to write ASN.1-encoded data. The returned object must be finalized - * after use by calling either asn1_write_finish or asn1_write_cleanup, and its resources - * must be freed by calling asn1_write_free. + * asn1_write_* functions to write ASN.1-encoded data. The returned object must be finalized + * after use by calling either asn1_write_finish or asn1_write_cleanup, and its resources must + * be freed by calling asn1_write_free. */ + // android-add: @FastNative static native long asn1_write_init() throws IOException; /** * Allocates and returns an opaque reference to an object that can be used with other - * asn1_write_* functions to write an ASN.1 sequence into the given reference. The returned - * reference may only be used until the next call on the parent reference. The returned - * object must be freed after use by calling asn1_write_free. + * asn1_write_* functions to write an ASN.1 sequence into the given reference. The returned + * reference may only be used until the next call on the parent reference. The returned object + * must be freed after use by calling asn1_write_free. */ + // android-add: @FastNative static native long asn1_write_sequence(long cbbRef) throws IOException; /** * Allocates and returns an opaque reference to an object that can be used with other - * asn1_write_* functions to write a explicitly-tagged ASN.1 object with the given tag - * into the given reference. The returned reference may only be used until the next - * call on the parent reference. The returned object must be freed after use by - * calling asn1_write_free. + * asn1_write_* functions to write a explicitly-tagged ASN.1 object with the given tag into the + * given reference. The returned reference may only be used until the next call on the parent + * reference. The returned object must be freed after use by calling asn1_write_free. */ + // android-add: @FastNative static native long asn1_write_tag(long cbbRef, int tag) throws IOException; - /** - * Writes the given data into the given reference as an ASN.1-encoded octet string. - */ + /** Writes the given data into the given reference as an ASN.1-encoded octet string. */ + // android-add: @FastNative static native void asn1_write_octetstring(long cbbRef, byte[] data) throws IOException; - /** - * Writes the given value into the given reference as an ASN.1-encoded integer. - */ + /** Writes the given value into the given reference as an ASN.1-encoded integer. */ + // android-add: @FastNative static native void asn1_write_uint64(long cbbRef, long value) throws IOException; - /** - * Writes a NULL value into the given reference. - */ + /** Writes a NULL value into the given reference. */ + // android-add: @FastNative static native void asn1_write_null(long cbbRef) throws IOException; - /** - * Writes the given OID (which must be in dotted-decimal notation) into the given reference. - */ + /** Writes the given OID (which must be in dotted-decimal notation) into the given reference. */ + // android-add: @FastNative static native void asn1_write_oid(long cbbRef, String oid) throws IOException; /** * Flushes the given reference, invalidating any child references and completing their - * operations. This must be called if the child references are to be freed before - * asn1_write_finish is called on the ultimate parent. The child references must still - * be freed. + * operations. This must be called if the child references are to be freed before + * asn1_write_finish is called on the ultimate parent. The child references must still be freed. */ + // android-add: @FastNative static native void asn1_write_flush(long cbbRef) throws IOException; /** - * Completes any in-progress operations and returns the ASN.1-encoded data. Either this - * or asn1_write_cleanup must be called on any reference returned from asn1_write_init - * before it is freed. + * Completes any in-progress operations and returns the ASN.1-encoded data. Either this or + * asn1_write_cleanup must be called on any reference returned from asn1_write_init before it is + * freed. */ + // android-add: @FastNative static native byte[] asn1_write_finish(long cbbRef) throws IOException; /** - * Cleans up intermediate state in the given reference. Either this or asn1_write_finish - * must be called on any reference returned from asn1_write_init before it is freed. + * Cleans up intermediate state in the given reference. Either this or asn1_write_finish must be + * called on any reference returned from asn1_write_init before it is freed. */ + // android-add: @FastNative static native void asn1_write_cleanup(long cbbRef); /** - * Frees resources associated with the given reference. After calling, the reference - * must not be used again. This may be called with a zero reference, in which case nothing - * will be done. + * Frees resources associated with the given reference. After calling, the reference must not be + * used again. This may be called with a zero reference, in which case nothing will be done. */ + // android-add: @FastNative static native void asn1_write_free(long cbbRef); // --- BIO stream creation ------------------------------------------------- + // android-add: @FastNative static native long create_BIO_InputStream(OpenSSLBIOInputStream is, boolean isFinite); + // android-add: @FastNative static native long create_BIO_OutputStream(OutputStream os); + // android-add: @FastNative static native void BIO_free_all(long bioRef); // --- SSL handling -------------------------------------------------------- @@ -909,23 +1114,18 @@ static native void ASN1_TIME_to_Calendar(long asn1TimeCtx, Calendar cal) new HashSet(Arrays.asList(SUPPORTED_TLS_1_3_CIPHER_SUITES)); /** - * TLS_EMPTY_RENEGOTIATION_INFO_SCSV is RFC 5746's renegotiation - * indication signaling cipher suite value. It is not a real - * cipher suite. It is just an indication in the default and - * supported cipher suite lists indicates that the implementation - * supports secure renegotiation. - *

- * In the RI, its presence means that the SCSV is sent in the - * cipher suite list to indicate secure renegotiation support and - * its absense means to send an empty TLS renegotiation info + * TLS_EMPTY_RENEGOTIATION_INFO_SCSV is RFC 5746's renegotiation indication signaling cipher + * suite value. It is not a real cipher suite. It is just an indication in the default and + * supported cipher suite lists indicates that the implementation supports secure renegotiation. + * + *

In the RI, its presence means that the SCSV is sent in the cipher suite list to indicate + * secure renegotiation support and its absense means to send an empty TLS renegotiation info * extension instead. - *

- * However, OpenSSL doesn't provide an API to give this level of - * control, instead always sending the SCSV and always including - * the empty renegotiation info if TLS is used (as opposed to - * SSL). So we simply allow TLS_EMPTY_RENEGOTIATION_INFO_SCSV to - * be passed for compatibility as to provide the hint that we - * support secure renegotiation. + * + *

However, OpenSSL doesn't provide an API to give this level of control, instead always + * sending the SCSV and always including the empty renegotiation info if TLS is used (as opposed + * to SSL). So we simply allow TLS_EMPTY_RENEGOTIATION_INFO_SCSV to be passed for compatibility + * as to provide the hint that we support secure renegotiation. */ static final String TLS_EMPTY_RENEGOTIATION_INFO_SCSV = "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"; @@ -945,15 +1145,14 @@ static String cipherSuiteFromJava(String javaCipherSuite) { } /** - * TLS_FALLBACK_SCSV is from - * https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00 - * to indicate to the server that this is a fallback protocol - * request. + * TLS_FALLBACK_SCSV is from https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00 to + * indicate to the server that this is a fallback protocol request. */ private static final String TLS_FALLBACK_SCSV = "TLS_FALLBACK_SCSV"; private static final boolean HAS_AES_HARDWARE; private static final String[] SUPPORTED_TLS_1_2_CIPHER_SUITES; + static { if (loadError == null) { // If loadError is not null, it means the native code was not loaded, so @@ -987,11 +1186,13 @@ static String cipherSuiteFromJava(String javaCipherSuite) { } /** - * Returns 1 if the BoringSSL believes the CPU has AES accelerated hardware - * instructions. Used to determine cipher suite ordering. + * Returns 1 if the BoringSSL believes the CPU has AES accelerated hardware instructions. Used + * to determine cipher suite ordering. */ + // android-add: @FastNative static native int EVP_has_aes_hardware(); + // android-add: @FastNative static native long SSL_CTX_new(); // IMPLEMENTATION NOTE: The default list of cipher suites is a trade-off between what we'd like @@ -1013,39 +1214,40 @@ static String cipherSuiteFromJava(String javaCipherSuite) { // prevent apps from connecting to servers they were previously able to connect to. /** X.509 based cipher suites enabled by default (if requested), in preference order. */ - static final String[] DEFAULT_X509_CIPHER_SUITES = HAS_AES_HARDWARE ? - new String[] { - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_RSA_WITH_AES_256_CBC_SHA", - } : - new String[] { - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_RSA_WITH_AES_256_CBC_SHA", - }; + static final String[] DEFAULT_X509_CIPHER_SUITES = + HAS_AES_HARDWARE + ? new String[] { + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + } + : new String[] { + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + }; /** TLS-PSK cipher suites enabled by default (if requested), in preference order. */ static final String[] DEFAULT_PSK_CIPHER_SUITES = new String[] { @@ -1066,21 +1268,28 @@ static String[] getSupportedCipherSuites() { SUPPORTED_TLS_1_2_CIPHER_SUITES.clone()); } + // android-add: @FastNative static native void SSL_CTX_free(long ssl_ctx, AbstractSessionContext holder); + // android-add: @FastNative static native void SSL_CTX_set_session_id_context(long ssl_ctx, AbstractSessionContext holder, byte[] sid_ctx); + // android-add: @FastNative static native long SSL_CTX_set_timeout(long ssl_ctx, AbstractSessionContext holder, long seconds); + // android-add: @FastNative static native long SSL_new(long ssl_ctx, AbstractSessionContext holder) throws SSLException; + // android-add: @FastNative static native void SSL_enable_tls_channel_id(long ssl, NativeSsl ssl_holder) throws SSLException; + // android-add: @FastNative static native byte[] SSL_get_tls_channel_id(long ssl, NativeSsl ssl_holder) throws SSLException; + // android-add: @FastNative static native void SSL_set1_tls_channel_id(long ssl, NativeSsl ssl_holder, NativeRef.EVP_PKEY pkey); @@ -1092,48 +1301,65 @@ static native void SSL_set1_tls_channel_id(long ssl, NativeSsl ssl_holder, * @param pkey a reference to the private key. * @throws SSLException if a problem occurs setting the cert/key. */ + // android-add: @FastNative static native void setLocalCertsAndPrivateKey(long ssl, NativeSsl ssl_holder, byte[][] encodedCertificates, NativeRef.EVP_PKEY pkey) throws SSLException; + // android-add: @FastNative static native void SSL_set_client_CA_list(long ssl, NativeSsl ssl_holder, byte[][] asn1DerEncodedX500Principals) throws SSLException; + // android-add: @FastNative static native long SSL_set_mode(long ssl, NativeSsl ssl_holder, long mode); + // android-add: @FastNative static native long SSL_set_options(long ssl, NativeSsl ssl_holder, long options); + // android-add: @FastNative static native long SSL_clear_options(long ssl, NativeSsl ssl_holder, long options); + // android-add: @FastNative static native int SSL_set_protocol_versions(long ssl, NativeSsl ssl_holder, int min_version, int max_version); + // android-add: @FastNative static native void SSL_enable_signed_cert_timestamps(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native byte[] SSL_get_signed_cert_timestamp_list(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native void SSL_set_signed_cert_timestamp_list(long ssl, NativeSsl ssl_holder, byte[] list); + // android-add: @FastNative static native void SSL_enable_ocsp_stapling(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native byte[] SSL_get_ocsp_response(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native void SSL_set_ocsp_response(long ssl, NativeSsl ssl_holder, byte[] response); + // android-add: @FastNative static native byte[] SSL_get_tls_unique(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native byte[] SSL_export_keying_material(long ssl, NativeSsl ssl_holder, byte[] label, byte[] context, int num_bytes) throws SSLException; + // android-add: @FastNative static native void SSL_use_psk_identity_hint(long ssl, NativeSsl ssl_holder, String identityHint) throws SSLException; + // android-add: @FastNative static native void set_SSL_psk_client_callback_enabled(long ssl, NativeSsl ssl_holder, boolean enabled); + // android-add: @FastNative static native void set_SSL_psk_server_callback_enabled(long ssl, NativeSsl ssl_holder, boolean enabled); @@ -1205,6 +1431,7 @@ static String[] getSupportedProtocols() { private static class Range { public final String min; public final String max; + public Range(String min, String max) { this.min = min; this.max = max; @@ -1273,6 +1500,7 @@ static String[] checkEnabledProtocols(String[] protocols) { return protocols; } + // android-add: @FastNative static native void SSL_set_cipher_lists(long ssl, NativeSsl ssl_holder, String[] ciphers); /** @@ -1280,6 +1508,7 @@ static String[] checkEnabledProtocols(String[] protocols) { * * @return array of {@code SSL_CIPHER} references. */ + // android-add: @FastNative static native long[] SSL_get_ciphers(long ssl, NativeSsl ssl_holder); static void setEnabledCipherSuites(long ssl, NativeSsl ssl_holder, String[] cipherSuites, @@ -1341,101 +1570,130 @@ static String[] checkEnabledCipherSuites(String[] cipherSuites) { return cipherSuites; } + // android-add: @FastNative static native void SSL_set_accept_state(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native void SSL_set_connect_state(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native void SSL_set_verify(long ssl, NativeSsl ssl_holder, int mode); + // android-add: @FastNative static native void SSL_set_session(long ssl, NativeSsl ssl_holder, long sslSessionNativePointer) throws SSLException; + // android-add: @FastNative static native void SSL_set_session_creation_enabled(long ssl, NativeSsl ssl_holder, boolean creationEnabled) throws SSLException; + // android-add: @FastNative static native boolean SSL_session_reused(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native void SSL_accept_renegotiations(long ssl, NativeSsl ssl_holder) throws SSLException; + // android-add: @FastNative static native void SSL_set_tlsext_host_name(long ssl, NativeSsl ssl_holder, String hostname) throws SSLException; + + // android-add: @FastNative static native String SSL_get_servername(long ssl, NativeSsl ssl_holder); static native void SSL_do_handshake(long ssl, NativeSsl ssl_holder, FileDescriptor fd, SSLHandshakeCallbacks shc, int timeoutMillis) throws SSLException, SocketTimeoutException, CertificateException; + // android-add: @FastNative public static native String SSL_get_current_cipher(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative + public static native String SSL_get_version(long ssl, NativeSsl ssl_holder); + public static native void SSL_set1_groups(long ssl, NativeSsl sslHolder, int[] groups); public static native String SSL_get_curve_name(long ssl, NativeSsl sslHolder); - public static native String SSL_get_version(long ssl, NativeSsl ssl_holder); - - /** - * Returns the peer certificate chain. - */ + /** Returns the peer certificate chain. */ + // android-add: @FastNative static native byte[][] SSL_get0_peer_certificates(long ssl, NativeSsl ssl_holder); /** * Reads with the native SSL_read function from the encrypted data stream + * * @return -1 if error or the end of the stream is reached. */ static native int SSL_read(long ssl, NativeSsl ssl_holder, FileDescriptor fd, SSLHandshakeCallbacks shc, byte[] b, int off, int len, int readTimeoutMillis) throws IOException; - /** - * Writes with the native SSL_write function to the encrypted data stream. - */ + /** Writes with the native SSL_write function to the encrypted data stream. */ static native void SSL_write(long ssl, NativeSsl ssl_holder, FileDescriptor fd, SSLHandshakeCallbacks shc, byte[] b, int off, int len, int writeTimeoutMillis) throws IOException; + // android-add: @FastNative static native void SSL_interrupt(long ssl, NativeSsl ssl_holder); + static native void SSL_shutdown(long ssl, NativeSsl ssl_holder, FileDescriptor fd, SSLHandshakeCallbacks shc) throws IOException; + // android-add: @FastNative static native int SSL_get_shutdown(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native void SSL_free(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native long SSL_get_time(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native long SSL_set_timeout(long ssl, NativeSsl ssl_holder, long millis); + // android-add: @FastNative static native long SSL_get_timeout(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native int SSL_get_signature_algorithm_key_type(int signatureAlg); + // android-add: @FastNative static native byte[] SSL_session_id(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native byte[] SSL_SESSION_session_id(long sslSessionNativePointer); + // android-add: @FastNative static native long SSL_SESSION_get_time(long sslSessionNativePointer); + // android-add: @FastNative static native long SSL_SESSION_get_timeout(long sslSessionNativePointer); + // android-add: @FastNative static native String SSL_SESSION_get_version(long sslSessionNativePointer); + // android-add: @FastNative static native String SSL_SESSION_cipher(long sslSessionNativePointer); + // android-add: @FastNative static native boolean SSL_SESSION_should_be_single_use(long sslSessionNativePointer); + // android-add: @FastNative static native void SSL_SESSION_up_ref(long sslSessionNativePointer); + // android-add: @FastNative static native void SSL_SESSION_free(long sslSessionNativePointer); + // android-add: @FastNative static native byte[] i2d_SSL_SESSION(long sslSessionNativePointer); + // android-add: @FastNative static native long d2i_SSL_SESSION(byte[] data) throws IOException; /** - * A collection of callbacks from the native OpenSSL code that are - * related to the SSL handshake initiated by SSL_do_handshake. + * A collection of callbacks from the native OpenSSL code that are related to the SSL handshake + * initiated by SSL_do_handshake. */ interface SSLHandshakeCallbacks { /** @@ -1443,7 +1701,6 @@ interface SSLHandshakeCallbacks { * * @param certificateChain chain of X.509 certificates in their encoded form * @param authMethod auth algorithm name - * * @throws CertificateException if the certificate is untrusted */ @SuppressWarnings("unused") @@ -1464,26 +1721,26 @@ void clientCertificateRequested(byte[] keyTypes, int[] signatureAlgs, throws CertificateEncodingException, SSLException; /** - * Called when acting as a server during ClientHello processing before a decision - * to resume a session is made. This allows the selection of the correct server - * certificate based on things like Server Name Indication (SNI). + * Called when acting as a server during ClientHello processing before a decision to resume + * a session is made. This allows the selection of the correct server certificate based on + * things like Server Name Indication (SNI). * * @throws IOException if there was an error during certificate selection. */ - @SuppressWarnings("unused") void serverCertificateRequested() throws IOException; + @SuppressWarnings("unused") + void serverCertificateRequested(int[] signatureAlgs) throws IOException; /** * Gets the key to be used in client mode for this connection in Pre-Shared Key (PSK) key * exchange. * * @param identityHint PSK identity hint provided by the server or {@code null} if no hint - * provided. + * provided. * @param identity buffer to be populated with PSK identity (NULL-terminated modified UTF-8) - * by this method. This identity will be provided to the server. + * by this method. This identity will be provided to the server. * @param key buffer to be populated with key material by this method. - * * @return number of bytes this method stored in the {@code key} buffer or {@code 0} if an - * error occurred in which case the handshake will be aborted. + * error occurred in which case the handshake will be aborted. */ int clientPSKKeyRequested(String identityHint, byte[] identity, byte[] key); @@ -1491,33 +1748,30 @@ void clientCertificateRequested(byte[] keyTypes, int[] signatureAlgs, * Gets the key to be used in server mode for this connection in Pre-Shared Key (PSK) key * exchange. * - * @param identityHint PSK identity hint provided by this server to the client or - * {@code null} if no hint was provided. + * @param identityHint PSK identity hint provided by this server to the client or {@code + * null} if no hint was provided. * @param identity PSK identity provided by the client. * @param key buffer to be populated with key material by this method. - * * @return number of bytes this method stored in the {@code key} buffer or {@code 0} if an - * error occurred in which case the handshake will be aborted. + * error occurred in which case the handshake will be aborted. */ int serverPSKKeyRequested(String identityHint, String identity, byte[] key); - /** - * Called when SSL state changes. This could be handshake completion. - */ + /** Called when SSL state changes. This could be handshake completion. */ @SuppressWarnings("unused") void onSSLStateChange(int type, int val); /** - * Called when a new session has been established and may be added to the session cache. - * The callee is responsible for incrementing the reference count on the returned session. + * Called when a new session has been established and may be added to the session cache. The + * callee is responsible for incrementing the reference count on the returned session. */ @SuppressWarnings("unused") void onNewSessionEstablished(long sslSessionNativePtr); /** - * Called for servers where TLS < 1.3 (TLS 1.3 uses session tickets rather than - * application session caches). + * Called for servers where TLS < 1.3 (TLS 1.3 uses session tickets rather than application + * session caches). * - *

Looks up the session by ID in the application's session cache. If a valid session - * is returned, this callback is responsible for incrementing the reference count (and any + *

Looks up the session by ID in the application's session cache. If a valid session is + * returned, this callback is responsible for incrementing the reference count (and any * required synchronization). * * @param id the ID of the session to find. @@ -1527,7 +1781,7 @@ void clientCertificateRequested(byte[] keyTypes, int[] signatureAlgs, /** * Called when acting as a server, the socket has an {@link - * ApplicationProtocolSelectorAdapter} associated with it, and the application protocol + * ApplicationProtocolSelectorAdapter} associated with it, and the application protocol * needs to be selected. * * @param applicationProtocols list of application protocols in length-prefix format @@ -1536,10 +1790,13 @@ void clientCertificateRequested(byte[] keyTypes, int[] signatureAlgs, @SuppressWarnings("unused") int selectApplicationProtocol(byte[] applicationProtocols); } + // android-add: @FastNative static native String SSL_CIPHER_get_kx_name(long cipherAddress); + // android-add: @FastNative static native String[] get_cipher_names(String selection); + // android-add: @FastNative public static native byte[] get_ocsp_single_extension(byte[] ocspResponse, String oid, long x509Ref, OpenSSLX509Certificate holder, @@ -1547,59 +1804,67 @@ public static native byte[] get_ocsp_single_extension(byte[] ocspResponse, Strin OpenSSLX509Certificate holder2); /** - * Returns the starting address of the memory region referenced by the provided direct - * {@link Buffer} or {@code 0} if the provided buffer is not direct or if such access to direct - * buffers is not supported by the platform. + * Returns the starting address of the memory region referenced by the provided direct {@link + * Buffer} or {@code 0} if the provided buffer is not direct or if such access to direct buffers + * is not supported by the platform. * *

NOTE: This method ignores the buffer's current {@code position}. */ + // android-add: @FastNative static native long getDirectBufferAddress(Buffer buf); + // android-add: @FastNative static native long SSL_BIO_new(long ssl, NativeSsl ssl_holder) throws SSLException; + // android-add: @FastNative static native int SSL_get_error(long ssl, NativeSsl ssl_holder, int ret); + // android-add: @FastNative static native void SSL_clear_error(); + // android-add: @FastNative static native int SSL_pending_readable_bytes(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native int SSL_pending_written_bytes_in_BIO(long bio); - /** - * Returns the maximum overhead, in bytes, of sealing a record with SSL. - */ + /** Returns the maximum overhead, in bytes, of sealing a record with SSL. */ + // android-add: @FastNative static native int SSL_max_seal_overhead(long ssl, NativeSsl ssl_holder); /** * Enables ALPN for this TLS endpoint and sets the list of supported ALPN protocols in * wire-format (length-prefixed 8-bit strings). */ + // android-add: @FastNative static native void setApplicationProtocols(long ssl, NativeSsl ssl_holder, boolean client, byte[] protocols) throws IOException; /** * Called for a server endpoint only. Enables ALPN and indicates that the {@link - * SSLHandshakeCallbacks#selectApplicationProtocol} will be called to select the - * correct protocol during a handshake. Calling this method overrides - * {@link #setApplicationProtocols(long, NativeSsl, boolean, byte[])}. + * SSLHandshakeCallbacks#selectApplicationProtocol} will be called to select the correct + * protocol during a handshake. Calling this method overrides {@link + * #setApplicationProtocols(long, NativeSsl, boolean, byte[])}. */ + // android-add: @FastNative static native void setHasApplicationProtocolSelector(long ssl, NativeSsl ssl_holder, boolean hasSelector) throws IOException; /** - * Returns the selected ALPN protocol. If the server did not select a - * protocol, {@code null} will be returned. + * Returns the selected ALPN protocol. If the server did not select a protocol, {@code null} + * will be returned. */ + // android-add: @FastNative static native byte[] getApplicationProtocol(long ssl, NativeSsl ssl_holder); /** * Variant of the {@link #SSL_do_handshake} used by {@link ConscryptEngine}. This differs - * slightly from the raw BoringSSL API in that it returns the SSL error code from the - * operation, rather than the return value from {@code SSL_do_handshake}. This is done in - * order to allow to properly handle SSL errors and propagate useful exceptions. + * slightly from the raw BoringSSL API in that it returns the SSL error code from the operation, + * rather than the return value from {@code SSL_do_handshake}. This is done in order to allow to + * properly handle SSL errors and propagate useful exceptions. * * @return Returns the SSL error code for the operation when the error was {@code - * SSL_ERROR_NONE}, {@code SSL_ERROR_WANT_READ}, or {@code SSL_ERROR_WANT_WRITE}. + * SSL_ERROR_NONE}, {@code SSL_ERROR_WANT_READ}, or {@code SSL_ERROR_WANT_WRITE}. * @throws IOException when the error code is anything except those returned by this method. */ static native int ENGINE_SSL_do_handshake(long ssl, NativeSsl ssl_holder, @@ -1609,14 +1874,13 @@ static native int ENGINE_SSL_do_handshake(long ssl, NativeSsl ssl_holder, * Variant of the {@link #SSL_read} for a direct {@link java.nio.ByteBuffer} used by {@link * ConscryptEngine}. * - * @return if positive, represents the number of bytes read into the given buffer. - * Returns {@code -SSL_ERROR_WANT_READ} if more data is needed. Returns - * {@code -SSL_ERROR_WANT_WRITE} if data needs to be written out to flush the BIO. - * + * @return if positive, represents the number of bytes read into the given buffer. Returns + * {@code -SSL_ERROR_WANT_READ} if more data is needed. Returns {@code + * -SSL_ERROR_WANT_WRITE} if data needs to be written out to flush the BIO. * @throws java.io.InterruptedIOException if the read was interrupted. * @throws java.io.EOFException if the end of stream has been reached. * @throws CertificateException if the application's certificate verification callback failed. - * Only occurs during handshake processing. + * Only occurs during handshake processing. * @throws SSLException if any other error occurs. */ static native int ENGINE_SSL_read_direct(long ssl, NativeSsl ssl_holder, long address, @@ -1664,9 +1928,8 @@ static native void ENGINE_SSL_shutdown(long ssl, NativeSsl ssl_holder, static native byte[] Scrypt_generate_key(byte[] password, byte[] salt, int n, int r, int p, int key_len); - /** - * Return {@code true} if BoringSSL has been built in FIPS mode. - */ + /** Return {@code true} if BoringSSL has been built in FIPS mode. */ + // android-add: @FastNative static native boolean usesBoringSsl_FIPS_mode(); /* ECH */ @@ -1698,11 +1961,22 @@ static native boolean SSL_CTX_ech_enable_server(long sslCtx, AbstractSessionCont /** * Used for testing only. */ + // android-add: @FastNative static native int BIO_read(long bioRef, byte[] buffer) throws IOException; + + // android-add: @FastNative static native void BIO_write(long bioRef, byte[] buffer, int offset, int length) throws IOException, IndexOutOfBoundsException; + + // android-add: @FastNative static native long SSL_clear_mode(long ssl, NativeSsl ssl_holder, long mode); + + // android-add: @FastNative static native long SSL_get_mode(long ssl, NativeSsl ssl_holder); + + // android-add: @FastNative static native long SSL_get_options(long ssl, NativeSsl ssl_holder); + + // android-add: @FastNative static native long SSL_get1_session(long ssl, NativeSsl ssl_holder); } diff --git a/common/src/main/java/org/conscrypt/NativeSsl.java b/common/src/main/java/org/conscrypt/NativeSsl.java index 7fe7c49d7..02bc62496 100644 --- a/common/src/main/java/org/conscrypt/NativeSsl.java +++ b/common/src/main/java/org/conscrypt/NativeSsl.java @@ -363,6 +363,7 @@ void initialize(String hostname, OpenSSLKey channelIdPrivateKey) throws IOExcept if (parameters.isCTVerificationEnabled(hostname)) { NativeCrypto.SSL_enable_signed_cert_timestamps(ssl, this); } + enableEchBasedOnPolicy(hostname); } else { NativeCrypto.SSL_set_accept_state(ssl, this); @@ -584,6 +585,27 @@ private void setTlsChannelId(OpenSSLKey channelIdPrivateKey) throws SSLException } } + private void enableEchBasedOnPolicy(String hostname) throws SSLException { + EchOptions opts = parameters.getEchOptions(hostname); + if (opts == null) { + return; + } + + byte[] configList = opts.getConfigList(); + if (configList != null) { + try { + NativeCrypto.SSL_set1_ech_config_list(ssl, this, configList); + } catch (SSLException e) { + // The platform may provide a more specialized exception type for this error. + throw Platform.wrapInvalidEchDataException(e); + } + } + + if (opts.isGreaseEnabled()) { + NativeCrypto.SSL_set_enable_ech_grease(ssl, this, /* enable= */ true); + } + } + private void setCertificateValidation() throws SSLException { // setup peer certificate verification if (!isClient()) { diff --git a/common/src/main/java/org/conscrypt/NetworkSecurityPolicy.java b/common/src/main/java/org/conscrypt/NetworkSecurityPolicy.java new file mode 100644 index 000000000..499c5eda0 --- /dev/null +++ b/common/src/main/java/org/conscrypt/NetworkSecurityPolicy.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import org.conscrypt.metrics.CertificateTransparencyVerificationReason; + +/** + * A policy provided by the platform to decide on the behaviour of TrustManagerImpl. + * + * See the platform-specific implementations in PlatformNetworkSecurityPolicy. + */ +@Internal +public interface NetworkSecurityPolicy { + boolean isCertificateTransparencyVerificationRequired(String hostname); + + CertificateTransparencyVerificationReason getCertificateTransparencyVerificationReason( + String hostname); + + DomainEncryptionMode getDomainEncryptionMode(String hostname); +} diff --git a/common/src/main/java/org/conscrypt/OpenSSLProvider.java b/common/src/main/java/org/conscrypt/OpenSSLProvider.java index a583db102..6f6e56810 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLProvider.java +++ b/common/src/main/java/org/conscrypt/OpenSSLProvider.java @@ -225,6 +225,10 @@ public OpenSSLProvider(String providerName) { // We don't support SLH-DSA, because it's not clear which algorithm to use. put("KeyPairGenerator.SLH-DSA-SHA2-128S", PREFIX + "OpenSslSlhDsaKeyPairGenerator"); + put("KeyPairGenerator.ML-KEM", PREFIX + "OpenSslMlKemKeyPairGenerator$MlKem"); + put("KeyPairGenerator.ML-KEM-768", PREFIX + "OpenSslMlKemKeyPairGenerator$MlKem768"); + put("KeyPairGenerator.ML-KEM-1024", PREFIX + "OpenSslMlKemKeyPairGenerator$MlKem1024"); + put("KeyPairGenerator.XWING", PREFIX + "OpenSslXwingKeyPairGenerator"); /* == KeyFactory == */ @@ -256,6 +260,10 @@ public OpenSSLProvider(String providerName) { // We don't support SLH-DSA, because it's not clear which algorithm to use. put("KeyFactory.SLH-DSA-SHA2-128S", PREFIX + "OpenSslSlhDsaKeyFactory"); + put("KeyFactory.ML-KEM", PREFIX + "OpenSslMlKemKeyFactory$MlKem"); + put("KeyFactory.ML-KEM-768", PREFIX + "OpenSslMlKemKeyFactory$MlKem768"); + put("KeyFactory.ML-KEM-1024", PREFIX + "OpenSslMlKemKeyFactory$MlKem1024"); + put("KeyFactory.XWING", PREFIX + "OpenSslXwingKeyFactory"); /* == SecretKeyFactory == */ @@ -580,6 +588,20 @@ public OpenSSLProvider(String providerName) { baseClass + "$X25519_CHACHA20"); put("Alg.Alias.ConscryptHpke.DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_GhpkeCHACHA20POLY1305", "DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/CHACHA20POLY1305"); + + put("ConscryptHpke.MLKEM_768/HKDF_SHA256/AES_128_GCM", + baseClass + "$MlKem768HkdfSha256Aes128Gcm"); + put("ConscryptHpke.MLKEM_768/HKDF_SHA256/AES_256_GCM", + baseClass + "$MlKem768HkdfSha256Aes256Gcm"); + put("ConscryptHpke.MLKEM_768/HKDF_SHA256/CHACHA20POLY1305", + baseClass + "$MlKem768HkdfSha256ChaCha20Poly1305"); + put("ConscryptHpke.MLKEM_1024/HKDF_SHA256/AES_128_GCM", + baseClass + "$MlKem1024HkdfSha256Aes128Gcm"); + put("ConscryptHpke.MLKEM_1024/HKDF_SHA256/AES_256_GCM", + baseClass + "$MlKem1024HkdfSha256Aes256Gcm"); + put("ConscryptHpke.MLKEM_1024/HKDF_SHA256/CHACHA20POLY1305", + baseClass + "$MlKem1024HkdfSha256ChaCha20Poly1305"); + put("ConscryptHpke.XWING/HKDF_SHA256/AES_128_GCM", baseClass + "$XwingHkdfSha256Aes128Gcm"); put("ConscryptHpke.XWING/HKDF_SHA256/AES_256_GCM", baseClass + "$XwingHkdfSha256Aes256Gcm"); put("ConscryptHpke.XWING/HKDF_SHA256/CHACHA20POLY1305", diff --git a/common/src/main/java/org/conscrypt/OpenSSLX25519PrivateKey.java b/common/src/main/java/org/conscrypt/OpenSSLX25519PrivateKey.java index aa85e29bf..a49c2c735 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLX25519PrivateKey.java +++ b/common/src/main/java/org/conscrypt/OpenSSLX25519PrivateKey.java @@ -30,11 +30,16 @@ public class OpenSSLX25519PrivateKey implements OpenSSLX25519Key, PrivateKey { private static final long serialVersionUID = -3136201500221850916L; private static final byte[] PKCS8_PREAMBLE = new byte[] { - 0x30, 0x2e, // Sequence: 46 bytes - 0x02, 0x01, 0x00, // Integer: 0 (version) - 0x30, 0x05, // Sequence: 5 bytes - 0x06, 0x03, 0x2b, 0x65, 0x6e, // OID: 1.3.101.110 (X25519) - 0x04, 0x22, 0x04, 0x20, // Octet string: 32 bytes + 0x30, + 0x2e, // Sequence: 46 bytes + 0x02, 0x01, + 0x00, // Integer: 0 (version) + 0x30, + 0x05, // Sequence: 5 bytes + 0x06, 0x03, 0x2b, 0x65, + 0x6e, // OID: 1.3.101.110 (X25519) + 0x04, 0x22, 0x04, + 0x20, // Octet string: 32 bytes // Key bytes follow directly }; diff --git a/common/src/main/java/org/conscrypt/OpenSSLX25519PublicKey.java b/common/src/main/java/org/conscrypt/OpenSSLX25519PublicKey.java index 7ec4c87a6..0c09efe1b 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLX25519PublicKey.java +++ b/common/src/main/java/org/conscrypt/OpenSSLX25519PublicKey.java @@ -28,10 +28,14 @@ public class OpenSSLX25519PublicKey implements OpenSSLX25519Key, PublicKey { private static final long serialVersionUID = 453861992373478445L; private static final byte[] X509_PREAMBLE = new byte[] { - 0x30, 0x2a, // Sequence: 42 bytes - 0x30, 0x05, // Sequence: 5 bytes - 0x06, 0x03, 0x2b, 0x65, 0x6e, // OID: 1.3.101.110 (X25519) - 0x03, 0x21, 0x00, // Bit string: 256 bits + 0x30, + 0x2a, // Sequence: 42 bytes + 0x30, + 0x05, // Sequence: 5 bytes + 0x06, 0x03, 0x2b, 0x65, + 0x6e, // OID: 1.3.101.110 (X25519) + 0x03, 0x21, + 0x00, // Bit string: 256 bits // Key bytes follow directly }; diff --git a/common/src/main/java/org/conscrypt/OpenSslMlKemKeyFactory.java b/common/src/main/java/org/conscrypt/OpenSslMlKemKeyFactory.java new file mode 100644 index 000000000..c979811a4 --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslMlKemKeyFactory.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactorySpi; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; + +/** An implementation of a {@link KeyFactorySpi} for ML-KEM keys based on BoringSSL. */ +@Internal +public abstract class OpenSslMlKemKeyFactory extends KeyFactorySpi { + // X.509 format preamble for ML-KEM-768 from RFC 9935. + static final byte[] x509PreambleMlKem768 = new byte[] { + (byte) 0x30, (byte) 0x82, (byte) 0x04, (byte) 0xb2, (byte) 0x30, (byte) 0x0b, + (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01, + (byte) 0x65, (byte) 0x03, (byte) 0x04, (byte) 0x04, (byte) 0x02, (byte) 0x03, + (byte) 0x82, (byte) 0x04, (byte) 0xa1, (byte) 0x00}; + + // X.509 format preamble for ML-KEM-1024 from RFC 9935. + static final byte[] x509PreambleMlKem1024 = new byte[] { + (byte) 0x30, (byte) 0x82, (byte) 0x06, (byte) 0x32, (byte) 0x30, (byte) 0x0b, + (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01, + (byte) 0x65, (byte) 0x03, (byte) 0x04, (byte) 0x04, (byte) 0x03, (byte) 0x03, + (byte) 0x82, (byte) 0x06, (byte) 0x21, (byte) 0x00}; + + // PKCS#8 format preamble (seed format) for ML-KEM-768 from RFC 9935. + static final byte[] pkcs8PreambleMlKem768 = new byte[] { + (byte) 0x30, (byte) 0x54, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x30, + (byte) 0x0b, (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, + (byte) 0x01, (byte) 0x65, (byte) 0x03, (byte) 0x04, (byte) 0x04, (byte) 0x02, + (byte) 0x04, (byte) 0x42, (byte) 0x80, (byte) 0x40}; + + // PKCS#8 format preamble (seed format) for ML-KEM-1024 from RFC 9935. + static final byte[] pkcs8PreambleMlKem1024 = new byte[] { + (byte) 0x30, (byte) 0x54, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x30, + (byte) 0x0b, (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, + (byte) 0x01, (byte) 0x65, (byte) 0x03, (byte) 0x04, (byte) 0x04, (byte) 0x03, + (byte) 0x04, (byte) 0x42, (byte) 0x80, (byte) 0x40}; + + private final MlKemAlgorithm defaultAlgorithm; + + private OpenSslMlKemKeyFactory(MlKemAlgorithm defaultAlgorithm) { + this.defaultAlgorithm = defaultAlgorithm; + } + + abstract boolean supportsAlgorithm(MlKemAlgorithm algorithm); + + /** ML-KEM */ + public static class MlKem extends OpenSslMlKemKeyFactory { + public MlKem() { + super(MlKemAlgorithm.ML_KEM_768); + } + + @Override + boolean supportsAlgorithm(MlKemAlgorithm algorithm) { + return algorithm.equals(MlKemAlgorithm.ML_KEM_768) + || algorithm.equals(MlKemAlgorithm.ML_KEM_1024); + } + } + + /** ML-KEM-768 */ + public static class MlKem768 extends OpenSslMlKemKeyFactory { + public MlKem768() { + super(MlKemAlgorithm.ML_KEM_768); + } + + @Override + boolean supportsAlgorithm(MlKemAlgorithm algorithm) { + return algorithm.equals(MlKemAlgorithm.ML_KEM_768); + } + } + + /** ML-KEM-1024 */ + public static class MlKem1024 extends OpenSslMlKemKeyFactory { + public MlKem1024() { + super(MlKemAlgorithm.ML_KEM_1024); + } + + @Override + boolean supportsAlgorithm(MlKemAlgorithm algorithm) { + return algorithm.equals(MlKemAlgorithm.ML_KEM_1024); + } + } + + private OpenSslMlKemPublicKey makePublicKeyFromRaw(byte[] raw, MlKemAlgorithm algorithm) + throws InvalidKeySpecException { + if (!supportsAlgorithm(algorithm)) { + throw new InvalidKeySpecException("Unsupported algorithm: " + algorithm); + } + if (raw.length != algorithm.publicKeySize()) { + throw new InvalidKeySpecException("Invalid raw public key length: " + raw.length + + " != " + algorithm.publicKeySize()); + } + try { + return new OpenSslMlKemPublicKey(raw, algorithm); + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException("Invalid raw public key", e); + } + } + + @Override + protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException { + if (keySpec == null) { + throw new InvalidKeySpecException("keySpec == null"); + } + if (!(keySpec instanceof EncodedKeySpec)) { + throw new InvalidKeySpecException("Currently only EncodedKeySpec is supported; was " + + keySpec.getClass().getName()); + } + EncodedKeySpec encodedKeySpec = (EncodedKeySpec) keySpec; + if (encodedKeySpec.getFormat().equalsIgnoreCase("raw")) { + byte[] raw = encodedKeySpec.getEncoded(); + return makePublicKeyFromRaw(raw, defaultAlgorithm); + } + if (!encodedKeySpec.getFormat().equals("X.509")) { + throw new InvalidKeySpecException("Encoding must be in X.509 format"); + } + byte[] encoded = encodedKeySpec.getEncoded(); + if (ArrayUtils.startsWith(encoded, x509PreambleMlKem768)) { + byte[] raw = Arrays.copyOfRange(encoded, x509PreambleMlKem768.length, encoded.length); + return makePublicKeyFromRaw(raw, MlKemAlgorithm.ML_KEM_768); + } else if (ArrayUtils.startsWith(encoded, x509PreambleMlKem1024)) { + byte[] raw = Arrays.copyOfRange(encoded, x509PreambleMlKem1024.length, encoded.length); + return makePublicKeyFromRaw(raw, MlKemAlgorithm.ML_KEM_1024); + } else { + throw new InvalidKeySpecException( + "Only X.509 format for ML-KEM-768 and ML-KEM-1024 is supported"); + } + } + + private OpenSslMlKemPrivateKey makePrivateKeyFromSeed(byte[] seed, MlKemAlgorithm algorithm) + throws InvalidKeySpecException { + if (!supportsAlgorithm(algorithm)) { + throw new InvalidKeySpecException("Unsupported algorithm: " + algorithm); + } + if (seed.length != OpenSslMlKemPrivateKey.PRIVATE_KEY_SIZE_BYTES) { + throw new InvalidKeySpecException("Invalid raw private key"); + } + try { + return new OpenSslMlKemPrivateKey(seed, algorithm); + } catch (IllegalArgumentException e) { + throw new InvalidKeySpecException("Invalid raw private key", e); + } + } + + @Override + protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException { + if (keySpec == null) { + throw new InvalidKeySpecException("keySpec == null"); + } + if (!(keySpec instanceof EncodedKeySpec)) { + throw new InvalidKeySpecException("Currently only EncodedKeySpec is supported; was " + + keySpec.getClass().getName()); + } + EncodedKeySpec encodedKeySpec = (EncodedKeySpec) keySpec; + if (encodedKeySpec.getFormat().equalsIgnoreCase("raw")) { + byte[] raw = encodedKeySpec.getEncoded(); + return makePrivateKeyFromSeed(raw, defaultAlgorithm); + } + if (!encodedKeySpec.getFormat().equals("PKCS#8")) { + throw new InvalidKeySpecException("Encoding must be in PKCS#8 format"); + } + byte[] encoded = encodedKeySpec.getEncoded(); + if (ArrayUtils.startsWith(encoded, pkcs8PreambleMlKem768)) { + byte[] seed = Arrays.copyOfRange(encoded, pkcs8PreambleMlKem768.length, encoded.length); + return makePrivateKeyFromSeed(seed, MlKemAlgorithm.ML_KEM_768); + } else if (ArrayUtils.startsWith(encoded, pkcs8PreambleMlKem1024)) { + byte[] seed = + Arrays.copyOfRange(encoded, pkcs8PreambleMlKem1024.length, encoded.length); + return makePrivateKeyFromSeed(seed, MlKemAlgorithm.ML_KEM_1024); + } else { + throw new InvalidKeySpecException( + "Only PKCS#8 format for ML-KEM-768 and ML-KEM-1024 is supported"); + } + } + + @Override + protected T engineGetKeySpec(Key key, Class keySpec) + throws InvalidKeySpecException { + if (key == null) { + throw new InvalidKeySpecException("key == null"); + } + if (keySpec == null) { + throw new InvalidKeySpecException("keySpec == null"); + } + if (!key.getAlgorithm().equals("ML-KEM")) { + throw new InvalidKeySpecException("Key must be an ML-KEM key"); + } + if (key instanceof OpenSslMlKemPublicKey) { + OpenSslMlKemPublicKey conscryptKey = (OpenSslMlKemPublicKey) key; + if (!supportsAlgorithm(conscryptKey.getMlKemAlgorithm())) { + throw new InvalidKeySpecException("Key algorithm mismatch"); + } + if (X509EncodedKeySpec.class.isAssignableFrom(keySpec)) { + @SuppressWarnings("unchecked") + T result = (T) new X509EncodedKeySpec(key.getEncoded()); + return result; + } else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) { + return KeySpecUtil.makeRawKeySpec(conscryptKey.getRaw(), keySpec); + } + } else if (key instanceof OpenSslMlKemPrivateKey) { + OpenSslMlKemPrivateKey conscryptKey = (OpenSslMlKemPrivateKey) key; + if (!supportsAlgorithm(conscryptKey.getMlKemAlgorithm())) { + throw new InvalidKeySpecException("Key algorithm mismatch"); + } + if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec)) { + @SuppressWarnings("unchecked") + T result = (T) new PKCS8EncodedKeySpec(key.getEncoded()); + return result; + } else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) { + return KeySpecUtil.makeRawKeySpec(conscryptKey.getSeed(), keySpec); + } + } + throw new InvalidKeySpecException("Unsupported key type and key spec combination; key=" + + key.getClass().getName() + + ", keySpec=" + keySpec.getName()); + } + + @Override + protected Key engineTranslateKey(Key key) throws InvalidKeyException { + if ((key instanceof OpenSslMlKemPublicKey)) { + OpenSslMlKemPublicKey conscryptKey = (OpenSslMlKemPublicKey) key; + if (!supportsAlgorithm(conscryptKey.getMlKemAlgorithm())) { + throw new InvalidKeyException("Key algorithm mismatch"); + } + return conscryptKey; + } else if (key instanceof OpenSslMlKemPrivateKey) { + OpenSslMlKemPrivateKey conscryptKey = (OpenSslMlKemPrivateKey) key; + if (!supportsAlgorithm(conscryptKey.getMlKemAlgorithm())) { + throw new InvalidKeyException("Key algorithm mismatch"); + } + return key; + } else if ((key instanceof PrivateKey) && key.getFormat().equals("PKCS#8")) { + byte[] encoded = key.getEncoded(); + try { + return engineGeneratePrivate(new PKCS8EncodedKeySpec(encoded)); + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException(e); + } + } else if ((key instanceof PublicKey) && key.getFormat().equals("X.509")) { + byte[] encoded = key.getEncoded(); + try { + return engineGeneratePublic(new X509EncodedKeySpec(encoded)); + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException(e); + } + } else { + throw new InvalidKeyException("Unable to translate key into ML-KEM key"); + } + } +} diff --git a/common/src/main/java/org/conscrypt/OpenSslMlKemKeyPairGenerator.java b/common/src/main/java/org/conscrypt/OpenSslMlKemKeyPairGenerator.java new file mode 100644 index 000000000..412f2a31f --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslMlKemKeyPairGenerator.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import java.security.InvalidParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; + +/** + * An implementation of {@link KeyPairGenerator} for ML-KEM keys which uses BoringSSL to perform all + * the operations. It supports algorithms "ML-KEM", "ML-KEM-768" and "ML-KEM-1024". "ML-DSA" uses + * ML-DSA-768. + */ +@Internal +public class OpenSslMlKemKeyPairGenerator extends KeyPairGenerator { + private OpenSslMlKemKeyPairGenerator(String algorithm) { + super(algorithm); + } + + /** ML-KEM-768 */ + public static class MlKem768 extends OpenSslMlKemKeyPairGenerator { + public MlKem768() { + super("ML-KEM-768"); + } + + MlKem768(String algorithm) { + super(algorithm); + } + + @Override + public KeyPair generateKeyPair() { + byte[] privateKeyBytes = new byte[64]; + NativeCrypto.RAND_bytes(privateKeyBytes); + byte[] publicKeyBytes = NativeCrypto.MLKEM768_public_key_from_seed(privateKeyBytes); + return new KeyPair( + new OpenSslMlKemPublicKey(publicKeyBytes, MlKemAlgorithm.ML_KEM_768), + new OpenSslMlKemPrivateKey(privateKeyBytes, MlKemAlgorithm.ML_KEM_768)); + } + } + + /** ML-KEM uses ML-KEM-768. */ + public static class MlKem extends MlKem768 { + public MlKem() { + super("ML-KEM"); + } + } + + /** ML-KEM-1024 */ + public static final class MlKem1024 extends OpenSslMlKemKeyPairGenerator { + public MlKem1024() { + super("ML-KEM-1024"); + } + + @Override + public KeyPair generateKeyPair() { + byte[] privateKeyBytes = new byte[OpenSslMlKemPrivateKey.PRIVATE_KEY_SIZE_BYTES]; + NativeCrypto.RAND_bytes(privateKeyBytes); + byte[] publicKeyBytes = NativeCrypto.MLKEM1024_public_key_from_seed(privateKeyBytes); + return new KeyPair( + new OpenSslMlKemPublicKey(publicKeyBytes, MlKemAlgorithm.ML_KEM_1024), + new OpenSslMlKemPrivateKey(privateKeyBytes, MlKemAlgorithm.ML_KEM_1024)); + } + } + + @Override + public void initialize(int bits) throws InvalidParameterException { + if (bits != -1) { + throw new InvalidParameterException("ML-DSA only supports -1 for bits"); + } + } +} diff --git a/common/src/main/java/org/conscrypt/OpenSslMlKemPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSslMlKemPrivateKey.java new file mode 100644 index 000000000..e55dd4568 --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslMlKemPrivateKey.java @@ -0,0 +1,116 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.security.MessageDigest; +import java.security.PrivateKey; +import java.util.Arrays; + +/** An ML-KEM private key. */ +public class OpenSslMlKemPrivateKey implements PrivateKey { + private static final long serialVersionUID = 1L; + + static final int PRIVATE_KEY_SIZE_BYTES = 64; + + private byte[] seed; + private final MlKemAlgorithm algorithm; + + public OpenSslMlKemPrivateKey(byte[] seed, MlKemAlgorithm algorithm) { + if (seed.length != PRIVATE_KEY_SIZE_BYTES) { + throw new IllegalArgumentException("Invalid key size"); + } + this.seed = seed.clone(); + this.algorithm = algorithm; + } + + @Override + public String getAlgorithm() { + return "ML-KEM"; + } + + MlKemAlgorithm getMlKemAlgorithm() { + return algorithm; + } + + @Override + public String getFormat() { + return "PKCS#8"; + } + + private static byte[] getPkcs8Preamble(MlKemAlgorithm algorithm) { + switch (algorithm) { + case ML_KEM_768: + return OpenSslMlKemKeyFactory.pkcs8PreambleMlKem768; + case ML_KEM_1024: + return OpenSslMlKemKeyFactory.pkcs8PreambleMlKem1024; + default: + throw new IllegalArgumentException("Unsupported algorithm: " + algorithm); + } + } + + @Override + public byte[] getEncoded() { + return ArrayUtils.concat(getPkcs8Preamble(algorithm), seed); + } + + byte[] getSeed() { + if (seed == null) { + throw new IllegalStateException("key is destroyed"); + } + return seed.clone(); + } + + @Override + public void destroy() { + if (seed != null) { + Arrays.fill(seed, (byte) 0); + seed = null; + } + } + + @Override + public boolean isDestroyed() { + return seed == null; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof OpenSslMlKemPrivateKey)) { + return false; + } + OpenSslMlKemPrivateKey that = (OpenSslMlKemPrivateKey) o; + return MessageDigest.isEqual(seed, that.seed); + } + + @Override + public int hashCode() { + return Arrays.hashCode(seed) ^ algorithm.hashCode(); + } + + private void readObject(ObjectInputStream in) { + throw new UnsupportedOperationException("serialization not supported"); + } + + private void writeObject(ObjectOutputStream out) { + throw new UnsupportedOperationException("serialization not supported"); + } +} diff --git a/common/src/main/java/org/conscrypt/OpenSslMlKemPublicKey.java b/common/src/main/java/org/conscrypt/OpenSslMlKemPublicKey.java new file mode 100644 index 000000000..373ec5eef --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslMlKemPublicKey.java @@ -0,0 +1,111 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.security.PublicKey; +import java.util.Arrays; + +/** An ML-KEM public key. */ +public class OpenSslMlKemPublicKey implements PublicKey { + private static final long serialVersionUID = 1L; + + private final byte[] raw; + private final MlKemAlgorithm algorithm; + + public OpenSslMlKemPublicKey(byte[] raw, MlKemAlgorithm algorithm) { + if (!algorithm.equals(MlKemAlgorithm.ML_KEM_768) + && !algorithm.equals(MlKemAlgorithm.ML_KEM_1024)) { + throw new IllegalArgumentException("Unsupported algorithm"); + } + if (raw.length != algorithm.publicKeySize()) { + throw new IllegalArgumentException("Invalid raw key of length " + raw.length); + } + this.raw = raw.clone(); + this.algorithm = algorithm; + } + + @Override + public String getAlgorithm() { + return "ML-KEM"; + } + + MlKemAlgorithm getMlKemAlgorithm() { + return algorithm; + } + + @Override + public String getFormat() { + return "X.509"; + } + + private static byte[] getX509Preamble(MlKemAlgorithm algorithm) { + switch (algorithm) { + case ML_KEM_768: + return OpenSslMlKemKeyFactory.x509PreambleMlKem768; + case ML_KEM_1024: + return OpenSslMlKemKeyFactory.x509PreambleMlKem1024; + default: + throw new IllegalArgumentException("Unsupported algorithm: " + algorithm); + } + } + + @Override + public byte[] getEncoded() { + return ArrayUtils.concat(getX509Preamble(algorithm), raw); + } + + byte[] getRaw() { + if (raw == null) { + throw new IllegalStateException("key is destroyed"); + } + return raw.clone(); + } + + @Override + public boolean equals(Object o) { + if (raw == null) { + throw new IllegalStateException("key is destroyed"); + } + + if (this == o) { + return true; + } + if (!(o instanceof OpenSslMlKemPublicKey)) { + return false; + } + OpenSslMlKemPublicKey that = (OpenSslMlKemPublicKey) o; + return Arrays.equals(raw, that.raw); + } + + @Override + public int hashCode() { + if (raw == null) { + throw new IllegalStateException("key is destroyed"); + } + return Arrays.hashCode(raw) ^ algorithm.hashCode(); + } + + private void readObject(ObjectInputStream in) { + throw new UnsupportedOperationException("serialization not supported"); + } + + private void writeObject(ObjectOutputStream out) { + throw new UnsupportedOperationException("serialization not supported"); + } +} diff --git a/common/src/main/java/org/conscrypt/SSLNullSession.java b/common/src/main/java/org/conscrypt/SSLNullSession.java index 9a808cfdb..e6c8fad32 100644 --- a/common/src/main/java/org/conscrypt/SSLNullSession.java +++ b/common/src/main/java/org/conscrypt/SSLNullSession.java @@ -184,4 +184,14 @@ public void removeValue(String name) { throw new UnsupportedOperationException( "All calls to this method should be intercepted by ExternalSession."); } + + @Override + public String[] getPeerSupportedSignatureAlgorithms() { + return new String[0]; + } + + @Override + public String[] getLocalSupportedSignatureAlgorithms() { + return new String[0]; + } } diff --git a/common/src/main/java/org/conscrypt/SSLParametersImpl.java b/common/src/main/java/org/conscrypt/SSLParametersImpl.java index e1bb9e4de..e85cf203e 100644 --- a/common/src/main/java/org/conscrypt/SSLParametersImpl.java +++ b/common/src/main/java/org/conscrypt/SSLParametersImpl.java @@ -17,6 +17,8 @@ package org.conscrypt; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.security.AlgorithmConstraints; import java.security.KeyManagementException; import java.security.KeyStore; @@ -29,11 +31,13 @@ import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.logging.Logger; import javax.crypto.SecretKey; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SNIMatcher; +import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509KeyManager; @@ -49,6 +53,8 @@ * socket or not. */ final class SSLParametersImpl implements Cloneable { + private static final Logger logger = Logger.getLogger(SSLParametersImpl.class.getName()); + // default source of X.509 certificate based authentication keys private static volatile X509KeyManager defaultX509KeyManager; // default source of X.509 certificate based authentication trust decisions @@ -73,6 +79,8 @@ final class SSLParametersImpl implements Cloneable { private final Spake2PlusTrustManager spake2PlusTrustManager; // source of Spake authentication or null if not provided private final Spake2PlusKeyManager spake2PlusKeyManager; + // getNetworkSecurityPolicy reflected method for x509TrustManager + private final Method getNetworkSecurityPolicy; // protocols enabled for SSL connection String[] enabledProtocols; @@ -109,6 +117,7 @@ final class SSLParametersImpl implements Cloneable { byte[] applicationProtocols = EmptyArray.BYTE; ApplicationProtocolSelectorAdapter applicationProtocolSelector; boolean useSessionTickets; + byte[] echConfigList; private Boolean useSni; /** @@ -168,6 +177,8 @@ final class SSLParametersImpl implements Cloneable { "Spake2PlusTrustManager and Spake2PlusKeyManager should be set together"); } + getNetworkSecurityPolicy = getNetworkSecurityPolicyMethod(x509TrustManager); + // initialize the list of cipher suites and protocols enabled by default if (isSpake()) { enabledProtocols = new String[] {NativeCrypto.SUPPORTED_PROTOCOL_TLSV1_3}; @@ -213,6 +224,7 @@ private SSLParametersImpl(ClientSessionContext clientSessionContext, this.x509KeyManager = x509KeyManager; this.pskKeyManager = pskKeyManager; this.x509TrustManager = x509TrustManager; + this.getNetworkSecurityPolicy = getNetworkSecurityPolicyMethod(x509TrustManager); this.spake2PlusKeyManager = spake2PlusKeyManager; this.spake2PlusTrustManager = spake2PlusTrustManager; @@ -238,6 +250,8 @@ private SSLParametersImpl(ClientSessionContext clientSessionContext, : sslParams.applicationProtocols.clone(); this.applicationProtocolSelector = sslParams.applicationProtocolSelector; this.useSessionTickets = sslParams.useSessionTickets; + this.echConfigList = + (sslParams.echConfigList == null) ? null : sslParams.echConfigList.clone(); this.useSni = sslParams.useSni; this.channelIdEnabled = sslParams.channelIdEnabled; } @@ -253,6 +267,17 @@ void initSpake() throws KeyManagementException { } } + private Method getNetworkSecurityPolicyMethod(X509TrustManager tm) { + if (tm == null) { + return null; + } + try { + return tm.getClass().getMethod("getNetworkSecurityPolicy"); + } catch (NoSuchMethodException ignored) { + return null; + } + } + static SSLParametersImpl getDefault() throws KeyManagementException { SSLParametersImpl result = defaultParameters; if (result == null) { @@ -475,6 +500,10 @@ void setUseSessionTickets(boolean useSessionTickets) { this.useSessionTickets = useSessionTickets; } + void setEchConfigList(byte[] echConfigList) { + this.echConfigList = echConfigList; + } + /* * Whether connections using this SSL connection should use the TLS * extension Server Name Indication (SNI). @@ -813,6 +842,37 @@ private static String[] getDefaultCipherSuites(boolean x509CipherSuitesNeeded, } } + private NetworkSecurityPolicy getPolicy() { + // Google3-only: Skip getPolicy (b/477326565 b/450387911). + // + // If the TrustManager has a security policy attached, use it. We are using reflection here. + // The Android framework may provide a high-level TrustManager (e.g., RootTrustManager or + // NetworkSecurityTrustManager), which we need to query. + // if (getNetworkSecurityPolicy != null) { + // try { + // Object objPolicy = getNetworkSecurityPolicy.invoke(x509TrustManager); + // if (objPolicy instanceof NetworkSecurityPolicy) { + // return (NetworkSecurityPolicy) objPolicy; + // } + // } catch (IllegalAccessException | IllegalArgumentException e) { + // // This is the unlikely scenario where an external TrustManager is being used and + // it + // // defines a getNetworkSecurityPolicy method which does not match our + // expectations. logger.warning("Unable to call getNetworkSecurityPolicy on + // TrustManager: " + // + e.getMessage()); + // } catch (InvocationTargetException e) { + // // getNetworkSecurityPolicy raised an exception. Unwrap it. + // throw new RuntimeException( + // "Unable to retrieve the NetworkSecurityPolicy associated " + // + "with the TrustManager", + // e.getCause()); + // } + //} + // Otherwise, rely on the global platform policy. + return ConscryptNetworkSecurityPolicy.getDefault(); + } + /* * Checks whether SCT verification is enforced for a given hostname. */ @@ -825,7 +885,26 @@ boolean isCTVerificationEnabled(String hostname) { if (ctVerificationEnabled) { return true; } - return Platform.isCTVerificationRequired(hostname); + + return getPolicy().isCertificateTransparencyVerificationRequired(hostname); + } + + EchOptions getEchOptions(String hostname) throws SSLException { + switch (getPolicy().getDomainEncryptionMode(hostname)) { + case DISABLED: + return null; + case OPPORTUNISTIC: + return new EchOptions(echConfigList, /* enableGrease= */ false); + case ENABLED: + return new EchOptions(echConfigList, /* enableGrease= */ true); + case REQUIRED: + if (echConfigList == null) { + throw new SSLException("No ECH config provided when required"); + } + return new EchOptions(echConfigList, /* enableGrease= */ false); + default: + return null; + } } boolean isSpake() { diff --git a/common/src/main/java/org/conscrypt/SSLUtils.java b/common/src/main/java/org/conscrypt/SSLUtils.java index 71852b894..f96c6078a 100644 --- a/common/src/main/java/org/conscrypt/SSLUtils.java +++ b/common/src/main/java/org/conscrypt/SSLUtils.java @@ -47,9 +47,11 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import javax.net.ssl.SSLException; @@ -578,5 +580,66 @@ static String[] concat(String[]... arrays) { return result; } + /** + * Maps BoringSSL/TLS signature scheme identifiers to standard JSSE algorithm names. + * See RFC 8446, Section 4.2.3. + */ + static String[] mapSignatureAlgorithms(int[] algorithms) { + if (algorithms == null || algorithms.length == 0) { + // Fallback for TLS 1.2 peers that don't send the signature_algorithms extension + return new String[] {"SHA256withRSA", "SHA256withECDSA", "SHA384withRSA", + "SHA384withECDSA", "SHA512withRSA", "SHA512withECDSA", + "SHA1withRSA", "SHA1withECDSA"}; + } + List result = new ArrayList<>(); + for (int alg : algorithms) { + switch (alg) { + // TLS 1.3 and modern TLS 1.2 + case 0x0401: + result.add("SHA256withRSA"); + break; + case 0x0501: + result.add("SHA384withRSA"); + break; + case 0x0601: + result.add("SHA512withRSA"); + break; + case 0x0403: + result.add("SHA256withECDSA"); + break; + case 0x0503: + result.add("SHA384withECDSA"); + break; + case 0x0603: + result.add("SHA512withECDSA"); + break; + // RSASSA-PSS schemes + case 0x0804: + case 0x0805: + case 0x0806: + case 0x0809: + case 0x080a: + case 0x080b: + result.add("RSASSA-PSS"); + break; + // EdDSA schemes + case 0x0807: + result.add("Ed25519"); + break; + // Legacy TLS 1.2 + case 0x0201: + result.add("SHA1withRSA"); + break; + case 0x0203: + result.add("SHA1withECDSA"); + break; + default: + // Ignore unknown or unsupported algorithms + break; + } + } + return result.toArray(new String[0]); + } + private SSLUtils() {} } diff --git a/common/src/main/java/org/conscrypt/SessionSnapshot.java b/common/src/main/java/org/conscrypt/SessionSnapshot.java index d9e6465a3..5e92a059d 100644 --- a/common/src/main/java/org/conscrypt/SessionSnapshot.java +++ b/common/src/main/java/org/conscrypt/SessionSnapshot.java @@ -42,6 +42,8 @@ final class SessionSnapshot implements ConscryptSession { private final String peerHost; private final String applicationProtocol; private final int peerPort; + private final String[] peerSupportedSignatureAlgorithms; + private final String[] localSupportedSignatureAlgorithms; SessionSnapshot(ConscryptSession session) { sessionContext = session.getSessionContext(); @@ -56,6 +58,8 @@ final class SessionSnapshot implements ConscryptSession { peerHost = session.getPeerHost(); peerPort = session.getPeerPort(); applicationProtocol = session.getApplicationProtocol(); + peerSupportedSignatureAlgorithms = session.getPeerSupportedSignatureAlgorithms(); + localSupportedSignatureAlgorithms = session.getLocalSupportedSignatureAlgorithms(); } @Override @@ -196,4 +200,16 @@ public int getApplicationBufferSize() { public String getApplicationProtocol() { return applicationProtocol; } + + @Override + public String[] getPeerSupportedSignatureAlgorithms() { + return peerSupportedSignatureAlgorithms != null ? peerSupportedSignatureAlgorithms.clone() + : new String[0]; + } + + @Override + public String[] getLocalSupportedSignatureAlgorithms() { + return localSupportedSignatureAlgorithms != null ? localSupportedSignatureAlgorithms.clone() + : new String[0]; + } } diff --git a/common/src/main/java/org/conscrypt/TrustManagerImpl.java b/common/src/main/java/org/conscrypt/TrustManagerImpl.java index adb206e5b..352e0a75a 100644 --- a/common/src/main/java/org/conscrypt/TrustManagerImpl.java +++ b/common/src/main/java/org/conscrypt/TrustManagerImpl.java @@ -61,6 +61,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Supplier; import java.util.logging.Logger; import javax.net.ssl.HttpsURLConnection; @@ -72,14 +73,16 @@ /** * TrustManager implementation. The implementation is based on CertPathValidator - * PKIX and CertificateFactory X509 implementations. This implementations should + * PKIX and CertificateFactory X509 implementations. These implementations should * be provided by some certification provider. * * @see javax.net.ssl.X509ExtendedTrustManager + * @see org.conscrypt.ConscryptX509TrustManager */ @Internal @SuppressWarnings("CustomX509TrustManager") -public final class TrustManagerImpl extends X509ExtendedTrustManager { +public final class TrustManagerImpl + extends X509ExtendedTrustManager implements ConscryptX509TrustManager { private static final Logger logger = Logger.getLogger(TrustManagerImpl.class.getName()); /** @@ -102,6 +105,15 @@ public final class TrustManagerImpl extends X509ExtendedTrustManager { */ private final CertPinManager pinManager; + /** + * The ConscryptNetworkSecurityPolicy associated with this TrustManager. + * + * The policy is used to decide if various mechanisms should be enabled, + * mostly based on the process configuration and the hostname queried. The + * policy is aligned with Android's libcore NetworkSecurityPolicy. + */ + private ConscryptNetworkSecurityPolicy policy; + /** * The backing store for the AndroidCAStore if non-null. This will * be null when the rootKeyStore is null, implying we are not @@ -153,12 +165,6 @@ public TrustManagerImpl(KeyStore keyStore, CertPinManager manager) { public TrustManagerImpl(KeyStore keyStore, CertPinManager manager, ConscryptCertStore certStore) { - this(keyStore, manager, certStore, null, null); - } - - private TrustManagerImpl(KeyStore keyStore, CertPinManager manager, - ConscryptCertStore certStore, CertBlocklist blocklist, - org.conscrypt.ct.CertificateTransparency ct) { CertPathValidator validatorLocal = null; CertificateFactory factoryLocal = null; KeyStore rootKeyStoreLocal = null; @@ -188,13 +194,6 @@ private TrustManagerImpl(KeyStore keyStore, CertPinManager manager, errLocal = e; } - if (ct == null) { - ct = Platform.newDefaultCertificateTransparency(); - } - if (blocklist == null) { - blocklist = Platform.newDefaultBlocklist(); - } - this.pinManager = manager; this.rootKeyStore = rootKeyStoreLocal; this.trustedCertificateStore = trustedCertificateStoreLocal; @@ -204,8 +203,25 @@ private TrustManagerImpl(KeyStore keyStore, CertPinManager manager, this.intermediateIndex = new TrustedCertificateIndex(); this.acceptedIssuers = acceptedIssuersLocal; this.err = errLocal; - this.blocklist = blocklist; - this.ct = ct; + this.policy = ConscryptNetworkSecurityPolicy.getDefault(); + this.blocklist = Platform.newDefaultBlocklist(); + this.ct = Platform.newDefaultCertificateTransparency(new Supplier() { + @Override + public NetworkSecurityPolicy get() { + return policy; + } + }); + } + + /** + * Attach a ConscryptNetworkSecurityPolicy to this TrustManager. + */ + public void setNetworkSecurityPolicy(ConscryptNetworkSecurityPolicy policy) { + this.policy = policy; + } + + public ConscryptNetworkSecurityPolicy getNetworkSecurityPolicy() { + return policy; } @SuppressWarnings("JdkObsolete") // KeyStore#aliases is the only API available @@ -298,12 +314,24 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) /** * For backward compatibility with older Android API that used String for the hostname only. */ + @Override public List checkServerTrusted(X509Certificate[] chain, String authType, String hostname) throws CertificateException { return checkTrusted(chain, null /* ocspData */, null /* tlsSctData */, authType, hostname, false); } + /** + * For compatibility with network stacks that cannot provide an SSLSession nor a + * Socket (e.g., Cronet). + */ + @Override + public List checkServerTrusted(X509Certificate[] chain, byte[] ocspData, + byte[] tlsSctData, String authType, + String hostname) throws CertificateException { + return checkTrusted(chain, ocspData, tlsSctData, authType, hostname, false); + } + /** * Returns the full trusted certificate chain found from {@code certs}. *

@@ -662,7 +690,8 @@ private List verifyChain(List untrustedChain, } // Check Certificate Transparency (if required). - if (!clientAuth && host != null && ct != null && ct.isCTVerificationRequired(host)) { + if (!clientAuth && host != null && ct != null + && policy.isCertificateTransparencyVerificationRequired(host)) { ct.checkCT(wholeChain, ocspData, tlsSctData, host); } diff --git a/common/src/main/java/org/conscrypt/ct/CertificateEntry.java b/common/src/main/java/org/conscrypt/ct/CertificateEntry.java index eae9fbadb..d28b03cd2 100644 --- a/common/src/main/java/org/conscrypt/ct/CertificateEntry.java +++ b/common/src/main/java/org/conscrypt/ct/CertificateEntry.java @@ -143,4 +143,17 @@ public void encode(OutputStream output) throws SerializationException { } Serialization.writeVariableBytes(output, certificate, Constants.CERTIFICATE_LENGTH_BYTES); } + + /** + * Expected size of the encoded CertificateEntry structure. + */ + public int encodedLength() { + int size = Constants.LOG_ENTRY_TYPE_LENGTH; + if (entryType == LogEntryType.PRECERT_ENTRY) { + size += issuerKeyHash.length; + } + size += Constants.CERTIFICATE_LENGTH_BYTES; + size += certificate.length; + return size; + } } diff --git a/common/src/main/java/org/conscrypt/ct/CertificateTransparency.java b/common/src/main/java/org/conscrypt/ct/CertificateTransparency.java index 1e13cdf6d..502f33f1b 100644 --- a/common/src/main/java/org/conscrypt/ct/CertificateTransparency.java +++ b/common/src/main/java/org/conscrypt/ct/CertificateTransparency.java @@ -17,6 +17,7 @@ package org.conscrypt.ct; import org.conscrypt.Internal; +import org.conscrypt.NetworkSecurityPolicy; import org.conscrypt.Platform; import org.conscrypt.metrics.CertificateTransparencyVerificationReason; import org.conscrypt.metrics.StatsLog; @@ -25,6 +26,7 @@ import java.security.cert.X509Certificate; import java.util.List; import java.util.Objects; +import java.util.function.Supplier; /** * Certificate Transparency subsystem. The implementation contains references @@ -36,9 +38,11 @@ public class CertificateTransparency { private Verifier verifier; private Policy policy; private StatsLog statsLog; + private Supplier policySupplier; public CertificateTransparency(LogStore logStore, Policy policy, Verifier verifier, - StatsLog statsLog) { + StatsLog statsLog, + Supplier policySupplier) { Objects.requireNonNull(logStore); Objects.requireNonNull(policy); Objects.requireNonNull(verifier); @@ -48,14 +52,11 @@ public CertificateTransparency(LogStore logStore, Policy policy, Verifier verifi this.policy = policy; this.verifier = verifier; this.statsLog = statsLog; + this.policySupplier = policySupplier; } - public boolean isCTVerificationRequired(String host) { - return Platform.isCTVerificationRequired(host); - } - - public CertificateTransparencyVerificationReason reasonCTVerificationRequired(String host) { - return Platform.reasonCTVerificationRequired(host); + public CertificateTransparencyVerificationReason getVerificationReason(String host) { + return policySupplier.get().getCertificateTransparencyVerificationReason(host); } public void checkCT(List chain, byte[] ocspData, byte[] tlsData, String host) @@ -67,7 +68,7 @@ public void checkCT(List chain, byte[] ocspData, byte[] tlsData statsLog.reportCTVerificationResult(logStore, /* VerificationResult */ null, /* PolicyCompliance */ null, - reasonCTVerificationRequired(host)); + getVerificationReason(host)); return; } VerificationResult result = @@ -76,7 +77,7 @@ public void checkCT(List chain, byte[] ocspData, byte[] tlsData X509Certificate leaf = chain.get(0); PolicyCompliance compliance = policy.doesResultConformToPolicy(result, leaf); statsLog.reportCTVerificationResult(logStore, result, compliance, - reasonCTVerificationRequired(host)); + getVerificationReason(host)); if (compliance != PolicyCompliance.COMPLY) { throw new CertificateException( "Certificate chain does not conform to required transparency policy: " diff --git a/common/src/main/java/org/conscrypt/ct/LogInfo.java b/common/src/main/java/org/conscrypt/ct/LogInfo.java index 8c3b82638..6412f2fe7 100644 --- a/common/src/main/java/org/conscrypt/ct/LogInfo.java +++ b/common/src/main/java/org/conscrypt/ct/LogInfo.java @@ -29,7 +29,7 @@ /** * Properties about a Certificate Transparency Log. - * This object stores information about a CT log, its public key, description and URL. + * This object stores information about a CT log, its public key and URL. * It allows verification of SCTs against the log's public key. */ @Internal @@ -42,13 +42,16 @@ public class LogInfo { public static final int STATE_RETIRED = 5; public static final int STATE_REJECTED = 6; + public static final int TYPE_UNKNOWN = 0; + public static final int TYPE_RFC6962 = 1; + public static final int TYPE_STATIC_CT_API = 2; + private final byte[] logId; private final PublicKey publicKey; private final int state; private final long stateTimestamp; - private final String description; - private final String url; private final String operator; + private final int type; private LogInfo(Builder builder) { /* Based on the required fields for the log list schema v3. Notably, @@ -56,16 +59,14 @@ private LogInfo(Builder builder) { * is validated in the builder. */ Objects.requireNonNull(builder.logId); Objects.requireNonNull(builder.publicKey); - Objects.requireNonNull(builder.url); Objects.requireNonNull(builder.operator); this.logId = builder.logId; this.publicKey = builder.publicKey; this.state = builder.state; this.stateTimestamp = builder.stateTimestamp; - this.description = builder.description; - this.url = builder.url; this.operator = builder.operator; + this.type = builder.type; } public static class Builder { @@ -73,9 +74,8 @@ public static class Builder { private PublicKey publicKey; private int state; private long stateTimestamp; - private String description; - private String url; private String operator; + private int type; public Builder setPublicKey(PublicKey publicKey) { Objects.requireNonNull(publicKey); @@ -98,24 +98,20 @@ public Builder setState(int state, long timestamp) { return this; } - public Builder setDescription(String description) { - Objects.requireNonNull(description); - this.description = description; - return this; - } - - public Builder setUrl(String url) { - Objects.requireNonNull(url); - this.url = url; - return this; - } - public Builder setOperator(String operator) { Objects.requireNonNull(operator); this.operator = operator; return this; } + public Builder setType(int type) { + if (type < 0 || type > TYPE_STATIC_CT_API) { + throw new IllegalArgumentException("invalid type value"); + } + this.type = type; + return this; + } + public LogInfo build() { return new LogInfo(this); } @@ -132,14 +128,6 @@ public PublicKey getPublicKey() { return publicKey; } - public String getDescription() { - return description; - } - - public String getUrl() { - return url; - } - public int getState() { return state; } @@ -159,6 +147,10 @@ public String getOperator() { return operator; } + public int getType() { + return type; + } + @Override public boolean equals(Object other) { if (this == other) { @@ -169,16 +161,14 @@ public boolean equals(Object other) { } LogInfo that = (LogInfo) other; - return this.state == that.state && this.description.equals(that.description) - && this.url.equals(that.url) && this.operator.equals(that.operator) - && this.stateTimestamp == that.stateTimestamp + return this.state == that.state && this.operator.equals(that.operator) + && this.stateTimestamp == that.stateTimestamp && this.type == that.type && Arrays.equals(this.logId, that.logId); } @Override public int hashCode() { - return Objects.hash(Arrays.hashCode(logId), description, url, state, stateTimestamp, - operator); + return Objects.hash(Arrays.hashCode(logId), state, stateTimestamp, operator, type); } /** diff --git a/common/src/main/java/org/conscrypt/ct/PolicyCompliance.java b/common/src/main/java/org/conscrypt/ct/PolicyCompliance.java index 68cffbb6e..c84297937 100644 --- a/common/src/main/java/org/conscrypt/ct/PolicyCompliance.java +++ b/common/src/main/java/org/conscrypt/ct/PolicyCompliance.java @@ -18,4 +18,11 @@ import org.conscrypt.Internal; -@Internal public enum PolicyCompliance { COMPLY, NOT_ENOUGH_SCTS, NOT_ENOUGH_DIVERSE_SCTS } +@Internal +public enum PolicyCompliance { + COMPLY, + NOT_ENOUGH_SCTS, + + NOT_ENOUGH_DIVERSE_SCTS, + NO_RFC6962_LOG +} diff --git a/common/src/main/java/org/conscrypt/ct/SignedCertificateTimestamp.java b/common/src/main/java/org/conscrypt/ct/SignedCertificateTimestamp.java index f9a737f66..9e89f5ce9 100644 --- a/common/src/main/java/org/conscrypt/ct/SignedCertificateTimestamp.java +++ b/common/src/main/java/org/conscrypt/ct/SignedCertificateTimestamp.java @@ -135,11 +135,22 @@ public void encodeTBS(OutputStream output, CertificateEntry certEntry) Serialization.writeVariableBytes(output, extensions, Constants.EXTENSIONS_LENGTH_BYTES); } + private int encodedLength(CertificateEntry certEntry) { + int size = Constants.VERSION_LENGTH; + size += Constants.SIGNATURE_TYPE_LENGTH; + size += Constants.TIMESTAMP_LENGTH; + size += certEntry.encodedLength(); + size += Constants.EXTENSIONS_LENGTH_BYTES; + size += extensions.length; + return size; + } + /** * TLS encode the signed part of the SCT, as described by RFC6962 section 3.2. */ public byte[] encodeTBS(CertificateEntry certEntry) throws SerializationException { - ByteArrayOutputStream output = new ByteArrayOutputStream(); + int bufferSize = encodedLength(certEntry); + ByteArrayOutputStream output = new ByteArrayOutputStream(bufferSize); encodeTBS(output, certEntry); return output.toByteArray(); } diff --git a/common/src/main/java/org/conscrypt/metrics/CertificateTransparencyVerificationReason.java b/common/src/main/java/org/conscrypt/metrics/CertificateTransparencyVerificationReason.java index 0c7fab7aa..ef4a5b999 100644 --- a/common/src/main/java/org/conscrypt/metrics/CertificateTransparencyVerificationReason.java +++ b/common/src/main/java/org/conscrypt/metrics/CertificateTransparencyVerificationReason.java @@ -16,8 +16,10 @@ package org.conscrypt.metrics; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_DRY_RUN; import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_NSCONFIG_APP_OPT_IN; import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_NSCONFIG_DOMAIN_OPT_IN; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_SDK_TARGET_DEFAULT_ENABLED; import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_UNKNOWN; import org.conscrypt.Internal; @@ -30,7 +32,10 @@ public enum CertificateTransparencyVerificationReason { UNKNOWN(CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_UNKNOWN), APP_OPT_IN(CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_NSCONFIG_APP_OPT_IN), DOMAIN_OPT_IN( - CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_NSCONFIG_DOMAIN_OPT_IN); + CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_NSCONFIG_DOMAIN_OPT_IN), + SDK_TARGET_DEFAULT_ENABLED( + CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_SDK_TARGET_DEFAULT_ENABLED), + DRY_RUN(CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_DRY_RUN); final int id; diff --git a/common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java b/common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java index a94a4e760..489fd0d4e 100644 --- a/common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java +++ b/common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java @@ -66,6 +66,12 @@ public final class ConscryptStatsLog { */ public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED = 989; + /** + * CertificateBlocklistBlockReported certificate_blocklist_block_reported
+ * Usage: StatsLog.write(StatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED, int source, int index, int uid);
+ */ + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED = 1143; + // Constants for enum values. // Values for TlsHandshakeReported.protocol @@ -179,10 +185,22 @@ public final class ConscryptStatsLog { public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_SDK_TARGET_DEFAULT_ENABLED = 2; public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_NSCONFIG_APP_OPT_IN = 3; public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_NSCONFIG_DOMAIN_OPT_IN = 4; + public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_DRY_RUN = 5; + // Values for CertificateTransparencyVerificationReported.policy_compatibility_version public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__POLICY_COMPATIBILITY_VERSION__COMPAT_VERSION_UNKNOWN = 0; public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__POLICY_COMPATIBILITY_VERSION__COMPAT_VERSION_V1 = 1; + public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__POLICY_COMPATIBILITY_VERSION__COMPAT_VERSION_V2 = 2; + + // Values for CertificateBlocklistBlockReported.source + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_UNKNOWN = 0; + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_TEST = 1; + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_BUILT_IN = 2; + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_FILE = 3; + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_TEST = 4; + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_BUILT_IN = 5; + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_FILE = 6; // Write methods public static void write(int code, boolean arg1, int arg2, int arg3, int arg4, int arg5, int[] arg6) { @@ -224,7 +242,18 @@ public static void write(int code, int arg1, int arg2, int arg3, int arg4, int a ReflexiveStatsLog.write(builder.build()); } - public static void write(int code, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8) { + public static void write(int code, int arg1, int arg2, int arg3) { + final ReflexiveStatsEvent.Builder builder = ReflexiveStatsEvent.newBuilder(); + builder.setAtomId(code); + builder.writeInt(arg1); + builder.writeInt(arg2); + builder.writeInt(arg3); + + builder.usePooledBuffer(); + ReflexiveStatsLog.write(builder.build()); + } + + public static void write(int code, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9) { final ReflexiveStatsEvent.Builder builder = ReflexiveStatsEvent.newBuilder(); builder.setAtomId(code); builder.writeInt(arg1); @@ -235,6 +264,7 @@ public static void write(int code, int arg1, int arg2, int arg3, int arg4, int a builder.writeInt(arg6); builder.writeInt(arg7); builder.writeInt(arg8); + builder.writeInt(arg9); builder.usePooledBuffer(); ReflexiveStatsLog.write(builder.build()); diff --git a/common/src/main/java/org/conscrypt/metrics/NoopStatsLog.java b/common/src/main/java/org/conscrypt/metrics/NoopStatsLog.java index 1144e3350..b4456fea5 100644 --- a/common/src/main/java/org/conscrypt/metrics/NoopStatsLog.java +++ b/common/src/main/java/org/conscrypt/metrics/NoopStatsLog.java @@ -15,6 +15,7 @@ */ package org.conscrypt.metrics; +import org.conscrypt.CertBlocklistEntry; import org.conscrypt.Internal; import org.conscrypt.ct.LogStore; import org.conscrypt.ct.PolicyCompliance; @@ -38,4 +39,6 @@ public void updateCTLogListStatusChanged(LogStore logStore) {} public void reportCTVerificationResult(LogStore logStore, VerificationResult result, PolicyCompliance compliance, CertificateTransparencyVerificationReason reason) {} + + public void reportBlocklistHit(CertBlocklistEntry entry) {} } diff --git a/common/src/main/java/org/conscrypt/metrics/StatsLog.java b/common/src/main/java/org/conscrypt/metrics/StatsLog.java index 0779946d7..f5711cc4c 100644 --- a/common/src/main/java/org/conscrypt/metrics/StatsLog.java +++ b/common/src/main/java/org/conscrypt/metrics/StatsLog.java @@ -15,6 +15,7 @@ */ package org.conscrypt.metrics; +import org.conscrypt.CertBlocklistEntry; import org.conscrypt.Internal; import org.conscrypt.ct.LogStore; import org.conscrypt.ct.PolicyCompliance; @@ -30,4 +31,6 @@ public void countTlsHandshake(boolean success, String protocol, String cipherSui public void reportCTVerificationResult(LogStore logStore, VerificationResult result, PolicyCompliance compliance, CertificateTransparencyVerificationReason reason); + + public void reportBlocklistHit(CertBlocklistEntry entry); } diff --git a/common/src/main/java/org/conscrypt/metrics/StatsLogImpl.java b/common/src/main/java/org/conscrypt/metrics/StatsLogImpl.java index 9a5365ef1..e8f2f8b52 100644 --- a/common/src/main/java/org/conscrypt/metrics/StatsLogImpl.java +++ b/common/src/main/java/org/conscrypt/metrics/StatsLogImpl.java @@ -15,6 +15,14 @@ */ package org.conscrypt.metrics; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_BUILT_IN; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_FILE; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_TEST; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_BUILT_IN; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_FILE; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_TEST; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_UNKNOWN; import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_STATE_CHANGED; import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_STATE_CHANGED__STATUS__STATUS_EXPIRED; import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_STATE_CHANGED__STATUS__STATUS_NOT_FOUND; @@ -30,21 +38,77 @@ import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_UNKNOWN; import static org.conscrypt.metrics.ConscryptStatsLog.TLS_HANDSHAKE_REPORTED; +import org.conscrypt.CertBlocklistEntry; import org.conscrypt.Internal; import org.conscrypt.Platform; import org.conscrypt.ct.LogStore; import org.conscrypt.ct.PolicyCompliance; import org.conscrypt.ct.VerificationResult; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; + /** * Implements logging for Conscrypt metrics. */ @Internal public final class StatsLogImpl implements StatsLog { - private static final StatsLog INSTANCE = new StatsLogImpl(); - private StatsLogImpl() {} + private final BlockingQueue logQueue; + private final ExecutorService writerThreadExecutor; + private boolean running = false; + + private StatsLogImpl() { + this.logQueue = new LinkedBlockingQueue<>(100); + this.writerThreadExecutor = + Executors.newSingleThreadExecutor(new LowPriorityThreadFactory()); + startWriterThread(); + } public static StatsLog getInstance() { - return INSTANCE; + return new StatsLogImpl(); + } + + public void stop() { + running = false; + writerThreadExecutor.shutdownNow(); + try { + writerThreadExecutor.awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + private void startWriterThread() { + writerThreadExecutor.execute(() -> { + while (running) { + try { + // Blocks until a log task is available + Runnable logTask = logQueue.take(); + logTask.run(); // Execute the specific ConscryptStatsLog.write() call + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + running = false; + } + } + // Process remaining logs + while (!logQueue.isEmpty()) { + Runnable logTask = logQueue.poll(); + if (logTask != null) { + logTask.run(); + } + } + }); + } + + private static class LowPriorityThreadFactory implements ThreadFactory { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, "ConscryptStatsLogWriter"); + thread.setPriority(Thread.MIN_PRIORITY); + return thread; + } } @Override @@ -89,12 +153,21 @@ private static int policyComplianceToMetrics(VerificationResult result, } else if (result.getValidSCTs().size() == 0) { return CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_FAILURE_NO_SCTS_FOUND; } else if (compliance == PolicyCompliance.NOT_ENOUGH_SCTS - || compliance == PolicyCompliance.NOT_ENOUGH_DIVERSE_SCTS) { + || compliance == PolicyCompliance.NOT_ENOUGH_DIVERSE_SCTS + || compliance == PolicyCompliance.NO_RFC6962_LOG) { return CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_FAILURE_SCTS_NOT_COMPLIANT; } return CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_UNKNOWN; } + private static int getUid() { + int[] uids = Platform.getUids(); + if (uids != null && uids.length != 0) { + return uids[0]; + } + return 0; + } + @Override public void reportCTVerificationResult(LogStore store, VerificationResult result, PolicyCompliance compliance, @@ -103,17 +176,41 @@ public void reportCTVerificationResult(LogStore store, VerificationResult result || store.getState() == LogStore.State.MALFORMED) { write(CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED, CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_FAIL_OPEN_NO_LOG_LIST_AVAILABLE, - reason.getId(), 0, 0, 0, 0, 0, 0); + reason.getId(), 0, 0, 0, 0, 0, 0, getUid()); } else if (store.getState() == LogStore.State.NON_COMPLIANT) { write(CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED, CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_FAIL_OPEN_LOG_LIST_NOT_COMPLIANT, - reason.getId(), 0, 0, 0, 0, 0, 0); + reason.getId(), 0, 0, 0, 0, 0, 0, getUid()); } else if (store.getState() == LogStore.State.COMPLIANT) { int comp = policyComplianceToMetrics(result, compliance); write(CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED, comp, reason.getId(), store.getCompatVersion(), store.getMajorVersion(), store.getMinorVersion(), - result.numCertSCTs(), result.numOCSPSCTs(), result.numTlsSCTs()); + result.numCertSCTs(), result.numOCSPSCTs(), result.numTlsSCTs(), getUid()); + } + } + + private static int blocklistOriginToMetrics(CertBlocklistEntry.Origin origin) { + switch (origin) { + case SHA1_TEST: + return CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_TEST; + case SHA1_BUILT_IN: + return CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_BUILT_IN; + case SHA1_FILE: + return CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_FILE; + case SHA256_TEST: + return CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_TEST; + case SHA256_BUILT_IN: + return CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_BUILT_IN; + case SHA256_FILE: + return CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_FILE; } + return CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_UNKNOWN; + } + + @Override + public void reportBlocklistHit(CertBlocklistEntry entry) { + write(CERTIFICATE_BLOCKLIST_BLOCK_REPORTED, blocklistOriginToMetrics(entry.getOrigin()), + entry.getIndex(), getUid()); } private static final boolean sdkVersionBiggerThan32; @@ -137,7 +234,9 @@ private void write(int atomId, boolean success, int protocol, int cipherSuite, i builder.usePooledBuffer(); ReflexiveStatsLog.write(builder.build()); } else { - ConscryptStatsLog.write(atomId, success, protocol, cipherSuite, duration, source, uids); + logQueue.offer(() + -> ConscryptStatsLog.write(atomId, success, protocol, + cipherSuite, duration, source, uids)); } } @@ -149,9 +248,13 @@ private void write(int atomId, int status, int loadedCompatVersion, private void write(int atomId, int verificationResult, int verificationReason, int policyCompatVersion, int majorVersion, int minorVersion, - int numEmbeddedScts, int numOcspScts, int numTlsScts) { + int numEmbeddedScts, int numOcspScts, int numTlsScts, int uid) { ConscryptStatsLog.write(atomId, verificationResult, verificationReason, policyCompatVersion, majorVersion, minorVersion, numEmbeddedScts, numOcspScts, - numTlsScts); + numTlsScts, uid); + } + + private void write(int atomId, int origin, int index, int uid) { + ConscryptStatsLog.write(atomId, origin, index, uid); } } diff --git a/common/src/test/java/org/conscrypt/MlKemTest.java b/common/src/test/java/org/conscrypt/MlKemTest.java new file mode 100644 index 000000000..c1a832f7f --- /dev/null +++ b/common/src/test/java/org/conscrypt/MlKemTest.java @@ -0,0 +1,747 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import static com.google.common.truth.Truth.assertThat; + +import static org.conscrypt.HpkeSuite.AEAD_AES_128_GCM; +import static org.conscrypt.HpkeSuite.AEAD_AES_256_GCM; +import static org.conscrypt.HpkeSuite.AEAD_CHACHA20POLY1305; +import static org.conscrypt.HpkeSuite.KDF_HKDF_SHA256; +import static org.conscrypt.HpkeSuite.KEM_MLKEM_1024; +import static org.conscrypt.HpkeSuite.KEM_MLKEM_768; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.List; + +@RunWith(JUnit4.class) +public class MlKemTest { + private final Provider conscryptProvider = TestUtils.getConscryptProvider(); + + @BeforeClass + public static void setUp() { + TestUtils.assumeAllowsUnsignedCrypto(); + } + + public static final class RawKeySpec extends EncodedKeySpec { + public RawKeySpec(byte[] encoded) { + super(encoded); + } + + @Override + public String getFormat() { + return "raw"; + } + } + + public static MlKemAlgorithm toMlKemAlgorithm(String algorithm) { + switch (algorithm) { + case "ML-KEM": + case "ML-KEM-768": + return MlKemAlgorithm.ML_KEM_768; + case "ML-KEM-1024": + return MlKemAlgorithm.ML_KEM_1024; + default: + throw new IllegalArgumentException("Unsupported algorithm: " + algorithm); + } + } + + @Test + public void generateKeyPair_works() throws Exception { + for (String keyGenAlgorithm : new String[] {"ML-KEM", "ML-KEM-768", "ML-KEM-1024"}) { + MlKemAlgorithm expectedAlgorithm = toMlKemAlgorithm(keyGenAlgorithm); + + KeyPairGenerator keyGen = + KeyPairGenerator.getInstance(keyGenAlgorithm, conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + OpenSslMlKemPrivateKey privateKey = (OpenSslMlKemPrivateKey) keyPair.getPrivate(); + OpenSslMlKemPublicKey publicKey = (OpenSslMlKemPublicKey) keyPair.getPublic(); + + assertEquals(expectedAlgorithm, privateKey.getMlKemAlgorithm()); + assertEquals("ML-KEM", privateKey.getAlgorithm()); + byte[] seed = privateKey.getSeed(); + assertThat(seed).hasLength(64); + + assertEquals(expectedAlgorithm, publicKey.getMlKemAlgorithm()); + assertEquals("ML-KEM", publicKey.getAlgorithm()); + byte[] rawPublicKey = publicKey.getRaw(); + assertThat(rawPublicKey).hasLength(expectedAlgorithm.publicKeySize()); + } + } + + @Test + public void keyFactory_toAndFromRaw_works() throws Exception { + for (String factoryAlgorithm : new String[] {"ML-KEM", "ML-KEM-768", "ML-KEM-1024"}) { + MlKemAlgorithm algorithm = toMlKemAlgorithm(factoryAlgorithm); + // create random raw keys of the correct size. + int publicKeySize = algorithm.publicKeySize(); + byte[] rawPrivateKey = new byte[64]; + NativeCrypto.RAND_bytes(rawPrivateKey); + byte[] rawPublicKey = new byte[publicKeySize]; + NativeCrypto.RAND_bytes(rawPublicKey); + + KeyFactory keyFactory = KeyFactory.getInstance(factoryAlgorithm, conscryptProvider); + + // generatePrivate works. + OpenSslMlKemPrivateKey privateKey = (OpenSslMlKemPrivateKey) keyFactory.generatePrivate( + new RawKeySpec(rawPrivateKey)); + assertEquals(algorithm, privateKey.getMlKemAlgorithm()); + assertEquals("ML-KEM", privateKey.getAlgorithm()); + assertThat(privateKey.getSeed()).isEqualTo(rawPrivateKey); + + // generatePublic works. + OpenSslMlKemPublicKey publicKey = + (OpenSslMlKemPublicKey) keyFactory.generatePublic(new RawKeySpec(rawPublicKey)); + assertEquals(algorithm, publicKey.getMlKemAlgorithm()); + assertEquals("ML-KEM", publicKey.getAlgorithm()); + assertThat(publicKey.getRaw()).isEqualTo(rawPublicKey); + + // getKeySpec for private key with RawKeySpec works. + EncodedKeySpec privateKeySpec = keyFactory.getKeySpec(privateKey, RawKeySpec.class); + assertEquals("raw", privateKeySpec.getFormat()); + assertThat(privateKeySpec.getEncoded()).isEqualTo(rawPrivateKey); + + // getKeySpec for public key with RawKeySpec works. + EncodedKeySpec publicKeySpec = keyFactory.getKeySpec(publicKey, RawKeySpec.class); + assertEquals("raw", publicKeySpec.getFormat()); + assertThat(publicKeySpec.getEncoded()).isEqualTo(rawPublicKey); + + // generatePrivate and generatePublic for these keySpecs returns the same keys. + PrivateKey privateKey2 = keyFactory.generatePrivate(new RawKeySpec(rawPrivateKey)); + PublicKey publicKey2 = keyFactory.generatePublic(new RawKeySpec(rawPublicKey)); + assertEquals(publicKey, publicKey2); + assertEquals(privateKey, privateKey2); + + // check that generatePrivate and generatePublic reject keys of the wrong size. + RawKeySpec tooSmallPrivateKeySpec = new RawKeySpec(new byte[rawPrivateKey.length - 1]); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(tooSmallPrivateKeySpec)); + RawKeySpec tooLargePrivateKeySpec = new RawKeySpec(new byte[rawPrivateKey.length + 1]); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(tooLargePrivateKeySpec)); + RawKeySpec tooSmallPublicKeySpec = new RawKeySpec(new byte[rawPublicKey.length - 1]); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePublic(tooSmallPublicKeySpec)); + RawKeySpec tooLargePublicKeySpec = new RawKeySpec(new byte[rawPublicKey.length + 1]); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePublic(tooLargePublicKeySpec)); + } + } + + /** Helper class to test KeyFactory.translateKey. */ + private static class TestPublicKey implements PublicKey { + TestPublicKey(byte[] x509Encoded) { + this.x509Encoded = x509Encoded; + } + + private final byte[] x509Encoded; + + @Override + public String getAlgorithm() { + return "ML-KEM"; + } + + @Override + public String getFormat() { + return "X.509"; + } + + @Override + public byte[] getEncoded() { + return x509Encoded; + } + } + + /** Helper class to test KeyFactory.translateKey. */ + private static class TestPrivateKey implements PrivateKey { + TestPrivateKey(byte[] pkcs8Encoded) { + this.pkcs8Encoded = pkcs8Encoded; + } + + private final byte[] pkcs8Encoded; + + @Override + public String getAlgorithm() { + return "ML-KEM"; + } + + @Override + public String getFormat() { + return "PKCS#8"; + } + + @Override + public byte[] getEncoded() { + return pkcs8Encoded; + } + } + + @Test + public void mlKem768KeyPair_x509AndPkcs8_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-768", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + assertThat(keyPair.getPrivate().getFormat()).isEqualTo("PKCS#8"); + // 64 bytes for the seed + 22 bytes for the preamble. + assertThat(keyPair.getPrivate().getEncoded()).hasLength(86); + + assertThat(keyPair.getPublic().getFormat()).isEqualTo("X.509"); + // 1184 bytes for the raw key + 22 bytes for the preamble. + assertThat(keyPair.getPublic().getEncoded()).hasLength(1206); + + for (String algorithm : new String[] {"ML-KEM-768", "ML-KEM"}) { + KeyFactory keyFactory = KeyFactory.getInstance(algorithm, conscryptProvider); + + PKCS8EncodedKeySpec privateKeySpec = + keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class); + assertThat(privateKeySpec.getFormat()).isEqualTo("PKCS#8"); + assertThat(privateKeySpec.getEncoded()).isEqualTo(keyPair.getPrivate().getEncoded()); + + X509EncodedKeySpec publicKeySpec = + keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class); + assertThat(publicKeySpec.getFormat()).isEqualTo("X.509"); + assertThat(publicKeySpec.getEncoded()).isEqualTo(keyPair.getPublic().getEncoded()); + + PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + + assertThat(privateKey).isEqualTo(keyPair.getPrivate()); + assertThat(publicKey).isEqualTo(keyPair.getPublic()); + + assertThat(keyFactory.translateKey(keyPair.getPrivate())) + .isEqualTo(keyPair.getPrivate()); + assertThat( + keyFactory.translateKey(new TestPrivateKey(keyPair.getPrivate().getEncoded()))) + .isEqualTo(keyPair.getPrivate()); + assertThat(keyFactory.translateKey(keyPair.getPublic())).isEqualTo(keyPair.getPublic()); + assertThat(keyFactory.translateKey(new TestPublicKey(keyPair.getPublic().getEncoded()))) + .isEqualTo(keyPair.getPublic()); + } + + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-1024", conscryptProvider); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class)); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class)); + assertThrows(InvalidKeySpecException.class, + () + -> keyFactory.generatePrivate( + new RawKeySpec(keyPair.getPrivate().getEncoded()))); + assertThrows( + InvalidKeySpecException.class, + () -> keyFactory.generatePublic(new RawKeySpec(keyPair.getPublic().getEncoded()))); + + assertThrows(InvalidKeyException.class, + () -> keyFactory.translateKey(keyPair.getPrivate())); + assertThrows(InvalidKeyException.class, + () + -> keyFactory.translateKey( + new TestPrivateKey(keyPair.getPrivate().getEncoded()))); + assertThrows(InvalidKeyException.class, () -> keyFactory.translateKey(keyPair.getPublic())); + assertThrows( + InvalidKeyException.class, + () -> keyFactory.translateKey(new TestPublicKey(keyPair.getPublic().getEncoded()))); + } + + @Test + public void mlKem1024KeyPair_x509AndPkcs8_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-1024", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + assertThat(keyPair.getPrivate().getFormat()).isEqualTo("PKCS#8"); + // 64 bytes for the seed + 22 bytes for the preamble. + assertThat(keyPair.getPrivate().getEncoded()).hasLength(86); + + assertThat(keyPair.getPublic().getFormat()).isEqualTo("X.509"); + // 1568 bytes for the raw key + 22 bytes for the preamble. + assertThat(keyPair.getPublic().getEncoded()).hasLength(1590); + + for (String algorithm : new String[] {"ML-KEM-1024", "ML-KEM"}) { + KeyFactory keyFactory = KeyFactory.getInstance(algorithm, conscryptProvider); + + PKCS8EncodedKeySpec privateKeySpec = + keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class); + assertThat(privateKeySpec.getFormat()).isEqualTo("PKCS#8"); + assertThat(privateKeySpec.getEncoded()).isEqualTo(keyPair.getPrivate().getEncoded()); + + X509EncodedKeySpec publicKeySpec = + keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class); + assertThat(publicKeySpec.getFormat()).isEqualTo("X.509"); + assertThat(publicKeySpec.getEncoded()).isEqualTo(keyPair.getPublic().getEncoded()); + + PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + + assertThat(privateKey).isEqualTo(keyPair.getPrivate()); + assertThat(publicKey).isEqualTo(keyPair.getPublic()); + + assertThat(keyFactory.translateKey(keyPair.getPrivate())) + .isEqualTo(keyPair.getPrivate()); + assertThat( + keyFactory.translateKey(new TestPrivateKey(keyPair.getPrivate().getEncoded()))) + .isEqualTo(keyPair.getPrivate()); + assertThat(keyFactory.translateKey(keyPair.getPublic())).isEqualTo(keyPair.getPublic()); + assertThat(keyFactory.translateKey(new TestPublicKey(keyPair.getPublic().getEncoded()))) + .isEqualTo(keyPair.getPublic()); + } + + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-768", conscryptProvider); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class)); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class)); + assertThrows(InvalidKeySpecException.class, + () + -> keyFactory.generatePrivate( + new RawKeySpec(keyPair.getPrivate().getEncoded()))); + assertThrows( + InvalidKeySpecException.class, + () -> keyFactory.generatePublic(new RawKeySpec(keyPair.getPublic().getEncoded()))); + + assertThrows(InvalidKeyException.class, + () -> keyFactory.translateKey(keyPair.getPrivate())); + assertThrows(InvalidKeyException.class, + () + -> keyFactory.translateKey( + new TestPrivateKey(keyPair.getPrivate().getEncoded()))); + assertThrows(InvalidKeyException.class, () -> keyFactory.translateKey(keyPair.getPublic())); + assertThrows( + InvalidKeyException.class, + () -> keyFactory.translateKey(new TestPublicKey(keyPair.getPublic().getEncoded()))); + } + + @Test + public void mlKem768_pkcs8TestVector_works() throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-768", conscryptProvider); + + // Example from RFC 9935, C.1.2.1 + String pcks8Base64 = "MFQCAQAwCwYJYIZIAWUDBAQCBEKAQAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ" + + "GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8="; + String rawHex = "000102030405060708090a0b0c0d0e0f10111213141" + + "5161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343" + + "5363738393a3b3c3d3e3f"; + + byte[] seed = TestUtils.decodeHex(rawHex); + byte[] encoded = TestUtils.decodeBase64(pcks8Base64); + + PrivateKey privateKey = keyFactory.generatePrivate(new RawKeySpec(seed)); + assertThat(privateKey.getEncoded()).isEqualTo(encoded); + + EncodedKeySpec encodedKeySpec = + keyFactory.getKeySpec(privateKey, PKCS8EncodedKeySpec.class); + assertThat(encodedKeySpec.getFormat()).isEqualTo("PKCS#8"); + assertThat(encodedKeySpec.getEncoded()).isEqualTo(encoded); + + PrivateKey privateKey2 = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded)); + assertThat(privateKey2.getEncoded()).isEqualTo(encoded); + OpenSslMlKemPrivateKey mlKemPrivateKey = (OpenSslMlKemPrivateKey) privateKey2; + assertThat(mlKemPrivateKey.getMlKemAlgorithm()).isEqualTo(MlKemAlgorithm.ML_KEM_768); + assertThat(mlKemPrivateKey.getSeed()).isEqualTo(seed); + } + + @Test + public void mlKem1024_pkcs8TestVector_works() throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-1024", conscryptProvider); + + // Example from RFC 9935, C.1.3.1 + String pcks8Base64 = "MFQCAQAwCwYJYIZIAWUDBAQDBEKAQAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ" + + "GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8="; + String rawHex = "000102030405060708090a0b0c0d0e0f10111213141" + + "5161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343" + + "5363738393a3b3c3d3e3f"; + + byte[] seed = TestUtils.decodeHex(rawHex); + byte[] encoded = TestUtils.decodeBase64(pcks8Base64); + + PrivateKey privateKey = keyFactory.generatePrivate(new RawKeySpec(seed)); + assertThat(privateKey.getEncoded()).isEqualTo(encoded); + + EncodedKeySpec encodedKeySpec = + keyFactory.getKeySpec(privateKey, PKCS8EncodedKeySpec.class); + assertThat(encodedKeySpec.getFormat()).isEqualTo("PKCS#8"); + assertThat(encodedKeySpec.getEncoded()).isEqualTo(encoded); + + PrivateKey privateKey2 = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded)); + assertThat(privateKey2.getEncoded()).isEqualTo(encoded); + OpenSslMlKemPrivateKey mlKemPrivateKey = (OpenSslMlKemPrivateKey) privateKey2; + assertThat(mlKemPrivateKey.getMlKemAlgorithm()).isEqualTo(MlKemAlgorithm.ML_KEM_1024); + assertThat(mlKemPrivateKey.getSeed()).isEqualTo(seed); + } + + @Test + public void mlKem768_x509TestVector_works() throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-768", conscryptProvider); + + // Example from RFC 9935, C.2 + String x509Base64 = "MIIEsjALBglghkgBZQMEBAIDggShACmKoQ1CPI3aBp0CvFnmzfA6CWuLPaTKubgM" + + "pKFJB2cszvHsT68jSgvFt+nUc/KzEzs7JqHRdctnp4BZGWmcAvdlMbmcX4kYBwS7" + + "TKRTXFuJcmecZgoHxeUUuHAJyGLrj1FXaV77P8QKne9rgcHMAqJJrk8JStDZvTSF" + + "wcHGgIBSCnyMYyAyzuc4FU5cUXbAfaVgJHdqQw/nbqz2ZaP3uDIQIhW8gvEJOcg1" + + "VwQzao+sHYHkuwSFql18dNa1m75cXpcqDYusQRtVtdVVfNaAoaj3G064a8SMmgUJ" + + "cxpUvZ1ykLJ5Y+Q3Lcmxmc/crAsBrNKKYjlREuTENkjWIsSMgjTQFEDozDdskn8j" + + "pa/JrAR0xmInTkJFJchVLs47P+JlFt6QG8fVFb3olVjmJslcgLkzQvgBAATznmxs" + + "lIccXjRMqzlmyDX5qWpZr9McQChrOLHBp4RwurlHUYk0RTzoZzapGfH1ptUQqG9U" + + "VPw5gMtcdlvSvV97NrFBDWY1yM60fE3aDXaijqyTnHHDAkgEhmxxYmZYRCFjwsIh" + + "F+UKzvzmN4qYVlIwKk7wws4Mxxa3eW4ray43d9+hrD2iWaMbWptTD4y2OKgaYqww" + + "GEmrr5WnMBvaMAaJCb/bfmfbzLs4pVUaJbGjoPaFdIrVdT2IgPABbGJ0hhZjhMVX" + + "H+I2WQA2TQODEeLYdds2ZoaTK17GAkMKNp6Hpu9cM4eGZXglvUwFes65I+sJNeaQ" + + "XmO0ztf4CFenc91ksVDSZhLqmsEgUtsgF78YQ8y0sygbaQ3HKK36hcACgbjjwJKH" + + "M1+Fa0/CiS9povV5Ia2gGRTECYhmLVd2lmKnhjUbm2ZJPat5WU2YbeIQDWW6D/Tq" + + "WLgVONJKRDWiWPrCVASqf0H2WLE4UGXhWNy2ARVzJyD0BFmqrBXkBpU6kKxSmX0c" + + "zQcAYO/GXbnmUzVEZ/rVbscTyG51QMQjrPJmn1L6b0rGiI2HHvPoR8ApqKr7uS4X" + + "skqgebH0GbphdbRCr7EZCdSla3CgM1soc5IYqnyTSOLDwvPrPRWkHmQXwN2Uv+sh" + + "QZsxGnuxOhgLvoMyGKmmsXRHzIXyJYWVh6cwdwSay8/UTQ8CVDjhXRU4Jw1Ybhv4" + + "MZKpRZz2PA6XL4UpdnmDHs8SFQmFHLg0D28Qew+hoO/Rs2qBibwIXE9ct4TlU/Qb" + + "kY+AOXzhlW94W+43fKmqi+aZitowwmt8PYxrVSVMyWIDsgxCruCsTh67QI5JqeP4" + + "edCrB4XrcCVCXRMFoimcAV4SDRY7DhlJTOVyU9AkbRgnRcuBl6t0OLPBu3lyvsWj" + + "BuujVnhVwBRpn+9lrlTHcKDYXBhADPZCrtxmB3e6SxOFAr1aeBL2IfhKSClrmN1D" + + "IrbxWCi4qPDgCoukSlPDqLFDVxsHQKvVZ9rxzenHnCBLbV4lnRdmoxu7y05qBc9F" + + "AhdrMBwcL0Ekd1AVe87IXoCbMKTWDXdHzdD1uZqoyCaYdRd5OqqAgKCxJKhVjfcr" + + "vje3X07btr6CFtbGM/srIoDiURPYaV5DSBw+6zl+sZJQUim2eiAeqJPD4ssy2ovD" + + "QvpN6gV4"; + String rawHex = "298aa10d423c8dda069d02bc59e6cdf03a096b8b3da" + + "4cab9b80ca4a14907672ccef1ec4faf234a0bc5b7e9d473f2b3133b3b26a1d17" + + "5cb67a7805919699c02f76531b99c5f89180704bb4ca4535c5b8972679c660a0" + + "7c5e514b87009c862eb8f5157695efb3fc40a9def6b81c1cc02a249ae4f094ad" + + "0d9bd3485c1c1c68080520a7c8c632032cee738154e5c5176c07da56024776a4" + + "30fe76eacf665a3f7b832102215bc82f10939c8355704336a8fac1d81e4bb048" + + "5aa5d7c74d6b59bbe5c5e972a0d8bac411b55b5d5557cd680a1a8f71b4eb86bc" + + "48c9a0509731a54bd9d7290b27963e4372dc9b199cfdcac0b01acd28a6239511" + + "2e4c43648d622c48c8234d01440e8cc376c927f23a5afc9ac0474c662274e424" + + "525c8552ece3b3fe26516de901bc7d515bde89558e626c95c80b93342f801000" + + "4f39e6c6c94871c5e344cab3966c835f9a96a59afd31c40286b38b1c1a78470b" + + "ab947518934453ce86736a919f1f5a6d510a86f5454fc3980cb5c765bd2bd5f7" + + "b36b1410d6635c8ceb47c4dda0d76a28eac939c71c3024804866c71626658442" + + "163c2c22117e50acefce6378a985652302a4ef0c2ce0cc716b7796e2b6b2e377" + + "7dfa1ac3da259a31b5a9b530f8cb638a81a62ac301849abaf95a7301bda30068" + + "909bfdb7e67dbccbb38a5551a25b1a3a0f685748ad5753d8880f0016c6274861" + + "66384c5571fe2365900364d038311e2d875db366686932b5ec602430a369e87a" + + "6ef5c338786657825bd4c057aceb923eb0935e6905e63b4ced7f80857a773dd6" + + "4b150d26612ea9ac12052db2017bf1843ccb4b3281b690dc728adfa85c00281b" + + "8e3c09287335f856b4fc2892f69a2f57921ada01914c40988662d57769662a78" + + "6351b9b66493dab79594d986de2100d65ba0ff4ea58b81538d24a4435a258fac" + + "25404aa7f41f658b1385065e158dcb60115732720f40459aaac15e406953a90a" + + "c52997d1ccd070060efc65db9e653354467fad56ec713c86e7540c423acf2669" + + "f52fa6f4ac6888d871ef3e847c029a8aafbb92e17b24aa079b1f419ba6175b44" + + "2afb11909d4a56b70a0335b28739218aa7c9348e2c3c2f3eb3d15a41e6417c0d" + + "d94bfeb21419b311a7bb13a180bbe833218a9a6b17447cc85f225859587a7307" + + "7049acbcfd44d0f025438e15d1538270d586e1bf83192a9459cf63c0e972f852" + + "97679831ecf121509851cb8340f6f107b0fa1a0efd1b36a8189bc085c4f5cb78" + + "4e553f41b918f80397ce1956f785bee377ca9aa8be6998ada30c26b7c3d8c6b5" + + "5254cc96203b20c42aee0ac4e1ebb408e49a9e3f879d0ab0785eb7025425d130" + + "5a2299c015e120d163b0e19494ce57253d0246d182745cb8197ab7438b3c1bb7" + + "972bec5a306eba3567855c014699fef65ae54c770a0d85c18400cf642aedc660" + + "777ba4b138502bd5a7812f621f84a48296b98dd4322b6f15828b8a8f0e00a8ba" + + "44a53c3a8b143571b0740abd567daf1cde9c79c204b6d5e259d1766a31bbbcb4" + + "e6a05cf4502176b301c1c2f41247750157bcec85e809b30a4d60d7747cdd0f5b" + + "99aa8c826987517793aaa8080a0b124a8558df72bbe37b75f4edbb6be8216d6c" + + "633fb2b2280e25113d8695e43481c3eeb397eb192505229b67a201ea893c3e2c" + + "b32da8bc342fa4dea0578"; + + byte[] raw = TestUtils.decodeHex(rawHex); + byte[] encoded = TestUtils.decodeBase64(x509Base64); + + PublicKey publicKey = keyFactory.generatePublic(new RawKeySpec(raw)); + assertThat(publicKey.getEncoded()).isEqualTo(encoded); + + EncodedKeySpec encodedKeySpec = keyFactory.getKeySpec(publicKey, X509EncodedKeySpec.class); + assertThat(encodedKeySpec.getFormat()).isEqualTo("X.509"); + assertThat(encodedKeySpec.getEncoded()).isEqualTo(encoded); + + PublicKey publicKey2 = keyFactory.generatePublic(new X509EncodedKeySpec(encoded)); + assertThat(publicKey2.getEncoded()).isEqualTo(encoded); + OpenSslMlKemPublicKey mlKemPublicKey = (OpenSslMlKemPublicKey) publicKey2; + assertThat(mlKemPublicKey.getMlKemAlgorithm()).isEqualTo(MlKemAlgorithm.ML_KEM_768); + assertThat(mlKemPublicKey.getRaw()).isEqualTo(raw); + } + + @Test + public void mlKem1024_x509TestVector_works() throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-1024", conscryptProvider); + + // Example from RFC 9935, C.2 + String x509Base64 = "MIIGMjALBglghkgBZQMEBAMDggYhAEuUwpRQERGRgjs1FMmsHqPZglzLhjk6LfsE" + + "ZU+iGS03v60cSXxlAu7lyoCnO/zguvWlSohYWkATl6PSMvQmp6+wgrwhpEMXCQ6q" + + "x1ksLqiKZTxEkeoZOTEzX1LpiaPEzFbZxVNzLVfEcPtBq3WbZdLQREU4L82cTjRK" + + "ESj6nhHgQ1jhku0BSyMjKn7isi4jcX9EER7jNXU5nDdkbamBPsmyEq/pTl3FwjMK" + + "cpTMH0I0ptP7tPFoWriJLASssXzRwXDXsGEbanF2x5TMjGf1X8kjwq0gMQDzZZkY" + + "gsMCQ9d4E4Q7XsfJZAMiY3BgkuzwDHUWvmTkWYykImwGm7XmfkF1zyKGyN1cSIps" + + "WGHzG6oL0CaUcOi1Ud07zTjIbBL5zbF2x33ItsAqcB9HiQLIVT9pTA2CcntMSlws" + + "EEEhKqEnSAi4IRGzd+x1IU6bGXj3YATUE52YYT9LjpjSCve1NAc6UJqVm3p1ZPm0" + + "DKIYv2GCkyCoUCAXlU0yjXrGx2nsKXAHVuewaFs0DV4RgFlQSkmppQoQGY6xCleE" + + "Z460J9e0uruVUpM7BiiXlz4TGOrwoOrDdYSmVAGxcD4EKszYN1MUg/JBytzRwdN4" + + "EZ5pRCnbGZrIkeTFNDdXCFuzrng2ZzUMRFjZdnLoYegLHSZ5UQ6jpvI2DHekaULH" + + "oGpVTSKAgMhLR67xTbF2IMsWwGqzChvkzacIK+n4fpwhHEaRY0mluo6qUgHHKUo8" + + "CIW1O2V0UhCIJexkbJCgRhIyTufQMa/lNDEyy+9ntu+xpewoCbdzU4znez2LBOsL" + + "PCJWAR5McWwZqLoHUr9xSSEXZJ8GFcMpD8KaRv3kvVLbkobWAziCRCWcFaesK2QK" + + "YMwDN2pYQaP7ikc1aPqbGiZyFfNMAWl7Dw5icXXXIQW3cHwpueYUvcM6b2yBipU3" + + "C0J4gte0dnlqnsbrmTJ0zZsjkagrpF4zk9Lprpchyp1sG5iLWCdxP5CmWF3pQzUo" + + "wCsDzhC7X3IBOND7tMMMEma5GOUpJd/hezf5XSK8pU9HWRmshZCYwPDQisWHXvKb" + + "Vv0UHm7xX3AKC2bzlZXFiBdzc8RmmyG8Bx5MOqXwtKMbYljzXaJKw80px/IJJBDF" + + "B4NVsTj7U6a5rm4LnAgkPnuqRcRzduuMfxPUz1Gqc2+jFUDJJB83DaVEv5+cKNml" + + "fi8qfKlaTktGbmQas7zHat8ROdVnpvErUvOmXn7AquJryqjFWDOwTlmZjryaGTD7" + + "ttIjPFPSwfi5UY48Lec6Gd7ms4Clsylxz2ThKf1sH6bnXUojRQHpZt06VAr1yPTz" + + "SmtKJT7ihJJWbV5nxvVYVfywUG+wbBVnRNmgOjGib6lMrRTxV7fzA9B6acdzdo/L" + + "TQecCQWXA6DDqU3kuZ6jovFlg9D5Fwo5UNsHtPC8MIApJ/n3lhtiWYkmNqlQKicF" + + "MDY3eZ3TRNpFHBz3v2eEDOsweauMa4wZJ/ZAU8YSRQxFyeYDvBZmbllrNHHhA7bx" + + "VEdCTRcCIEgRH/vTfhxnD2TxS4p7MrlMGkm0XdL8OM1SidkQrWNgLPXhMELGSsZ5" + + "e4n7VRrQjgWpLSAMzLfnEu8jyTEss1DwKatTfihzR/0wdawQkGp4PxxsB8y4j0Ei" + + "jEvhxkD3kLXDpdXTynkklddLxGFWJljAesYAJ2uSSrW8m+HwSUy3b4L0YKdICXJm" + + "M4HhaZlgYdeZhZ7FTU9cpcQRwB2xWXsWWXdmneE6koo0r7rCWP6oxHZCOclCHcMR" + + "m/W0dpkgaXgyexxTRe90anmDhB8FbiU0EAqyTU6au9CxfGqVvUw8DkD2nhYSrO6y" + + "i5kIbJURbnIEJziTOQv0a4mbNihrDr8ZR7uYhPcyyifagrGbXcDMf4iFcUkQiIsj" + + "EMT5MZ1BCzTmQzuQA+IXa7mVJXRWEG6JUhY7i6WSUwzFqgrrQ605j+npe6pSPXpE" + + "MWd8PTrwcZ5HXbhcqVr1CJvqvrBbL6q0iWumD4HIhHKle0aoKIJqDN+0RvgYkYLS" + + "v16sTsHMXer1mcihPkgjVAbRf/3cg0S2xmmEqGiqkvoCInoIaVDrDIcB7VjcYod2" + + "uYOILhF1"; + String rawHex = "4b94c29450111191823b3514c9ac1ea3d9825ccb863" + + "93a2dfb04654fa2192d37bfad1c497c6502eee5ca80a73bfce0baf5a54a88585" + + "a401397a3d232f426a7afb082bc21a44317090eaac7592c2ea88a653c4491ea1" + + "93931335f52e989a3c4cc56d9c553732d57c470fb41ab759b65d2d04445382fc" + + "d9c4e344a1128fa9e11e04358e192ed014b23232a7ee2b22e23717f44111ee33" + + "575399c37646da9813ec9b212afe94e5dc5c2330a7294cc1f4234a6d3fbb4f16" + + "85ab8892c04acb17cd1c170d7b0611b6a7176c794cc8c67f55fc923c2ad20310" + + "0f365991882c30243d77813843b5ec7c964032263706092ecf00c7516be64e45" + + "98ca4226c069bb5e67e4175cf2286c8dd5c488a6c5861f31baa0bd0269470e8b" + + "551dd3bcd38c86c12f9cdb176c77dc8b6c02a701f478902c8553f694c0d82727" + + "b4c4a5c2c1041212aa1274808b82111b377ec75214e9b1978f76004d4139d986" + + "13f4b8e98d20af7b534073a509a959b7a7564f9b40ca218bf61829320a850201" + + "7954d328d7ac6c769ec29700756e7b0685b340d5e118059504a49a9a50a10198" + + "eb10a5784678eb427d7b4babb9552933b062897973e1318eaf0a0eac37584a65" + + "401b1703e042accd837531483f241cadcd1c1d378119e694429db199ac891e4c" + + "5343757085bb3ae783667350c4458d97672e861e80b1d2679510ea3a6f2360c7" + + "7a46942c7a06a554d228080c84b47aef14db17620cb16c06ab30a1be4cda7082" + + "be9f87e9c211c46916349a5ba8eaa5201c7294a3c0885b53b657452108825ec6" + + "46c90a04612324ee7d031afe5343132cbef67b6efb1a5ec2809b773538ce77b3" + + "d8b04eb0b3c2256011e4c716c19a8ba0752bf71492117649f0615c3290fc29a4" + + "6fde4bd52db9286d603388244259c15a7ac2b640a60cc03376a5841a3fb8a473" + + "568fa9b1a267215f34c01697b0f0e627175d72105b7707c29b9e614bdc33a6f6" + + "c818a95370b427882d7b476796a9ec6eb993274cd9b2391a82ba45e3393d2e9a" + + "e9721ca9d6c1b988b5827713f90a6585de9433528c02b03ce10bb5f720138d0f" + + "bb4c30c1266b918e52925dfe17b37f95d22bca54f475919ac859098c0f0d08ac" + + "5875ef29b56fd141e6ef15f700a0b66f39595c588177373c4669b21bc071e4c3" + + "aa5f0b4a31b6258f35da24ac3cd29c7f2092410c5078355b138fb53a6b9ae6e0" + + "b9c08243e7baa45c47376eb8c7f13d4cf51aa736fa31540c9241f370da544bf9" + + "f9c28d9a57e2f2a7ca95a4e4b466e641ab3bcc76adf1139d567a6f12b52f3a65" + + "e7ec0aae26bcaa8c55833b04e59998ebc9a1930fbb6d2233c53d2c1f8b9518e3" + + "c2de73a19dee6b380a5b32971cf64e129fd6c1fa6e75d4a234501e966dd3a540" + + "af5c8f4f34a6b4a253ee28492566d5e67c6f55855fcb0506fb06c156744d9a03" + + "a31a26fa94cad14f157b7f303d07a69c773768fcb4d079c09059703a0c3a94de" + + "4b99ea3a2f16583d0f9170a3950db07b4f0bc30802927f9f7961b6259892636a" + + "9502a2705303637799dd344da451c1cf7bf67840ceb3079ab8c6b8c1927f6405" + + "3c612450c45c9e603bc16666e596b3471e103b6f15447424d17022048111ffbd" + + "37e1c670f64f14b8a7b32b94c1a49b45dd2fc38cd5289d910ad63602cf5e1304" + + "2c64ac6797b89fb551ad08e05a92d200cccb7e712ef23c9312cb350f029ab537" + + "e287347fd3075ac10906a783f1c6c07ccb88f41228c4be1c640f790b5c3a5d5d" + + "3ca792495d74bc461562658c07ac600276b924ab5bc9be1f0494cb76f82f460a" + + "7480972663381e169996061d799859ec54d4f5ca5c411c01db1597b165977669" + + "de13a928a34afbac258fea8c4764239c9421dc3119bf5b47699206978327b1c5" + + "345ef746a7983841f056e2534100ab24d4e9abbd0b17c6a95bd4c3c0e40f69e1" + + "612aceeb28b99086c95116e7204273893390bf46b899b36286b0ebf1947bb988" + + "4f732ca27da82b19b5dc0cc7f8885714910888b2310c4f9319d410b34e6433b9" + + "003e2176bb995257456106e8952163b8ba592530cc5aa0aeb43ad398fe9e97ba" + + "a523d7a4431677c3d3af0719e475db85ca95af5089beabeb05b2faab4896ba60" + + "f81c88472a57b46a828826a0cdfb446f8189182d2bf5eac4ec1cc5deaf599c8a" + + "13e48235406d17ffddc8344b6c66984a868aa92fa02227a086950eb0c8701ed5" + + "8dc628776b983882e1175"; + + byte[] raw = TestUtils.decodeHex(rawHex); + byte[] encoded = TestUtils.decodeBase64(x509Base64); + + PublicKey publicKey = keyFactory.generatePublic(new RawKeySpec(raw)); + assertThat(publicKey.getEncoded()).isEqualTo(encoded); + + EncodedKeySpec encodedKeySpec = keyFactory.getKeySpec(publicKey, X509EncodedKeySpec.class); + assertThat(encodedKeySpec.getFormat()).isEqualTo("X.509"); + assertThat(encodedKeySpec.getEncoded()).isEqualTo(encoded); + + PublicKey publicKey2 = keyFactory.generatePublic(new X509EncodedKeySpec(encoded)); + assertThat(publicKey2.getEncoded()).isEqualTo(encoded); + OpenSslMlKemPublicKey mlKemPublicKey = (OpenSslMlKemPublicKey) publicKey2; + assertThat(mlKemPublicKey.getMlKemAlgorithm()).isEqualTo(MlKemAlgorithm.ML_KEM_1024); + assertThat(mlKemPublicKey.getRaw()).isEqualTo(raw); + } + + @Test + public void sealAndOpen_mlkem768_works() throws Exception { + byte[] info = TestUtils.decodeHex("aa"); + byte[] plaintext = TestUtils.decodeHex("bb"); + byte[] aad = TestUtils.decodeHex("cc"); + + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-768", conscryptProvider); + KeyPair keyPairRecipient = keyGen.generateKeyPair(); + + for (int aead : new int[] {AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_CHACHA20POLY1305}) { + HpkeSuite suite = new HpkeSuite(KEM_MLKEM_768, KDF_HKDF_SHA256, aead); + + HpkeContextSender ctxSender = + HpkeContextSender.getInstance(suite.name(), conscryptProvider); + ctxSender.init(keyPairRecipient.getPublic(), info); + + byte[] encapsulated = ctxSender.getEncapsulated(); + byte[] ciphertext = ctxSender.seal(plaintext, aad); + + HpkeContextRecipient contextRecipient = + HpkeContextRecipient.getInstance(suite.name(), conscryptProvider); + contextRecipient.init(encapsulated, keyPairRecipient.getPrivate(), info); + byte[] output = contextRecipient.open(ciphertext, aad); + + assertThat(output).isEqualTo(plaintext); + } + } + + @Test + public void sealAndOpen_mlkem1024_works() throws Exception { + byte[] info = TestUtils.decodeHex("aa"); + byte[] plaintext = TestUtils.decodeHex("bb"); + byte[] aad = TestUtils.decodeHex("cc"); + + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-1024", conscryptProvider); + KeyPair keyPairRecipient = keyGen.generateKeyPair(); + + for (int aead : new int[] {AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_CHACHA20POLY1305}) { + HpkeSuite suite = new HpkeSuite(KEM_MLKEM_1024, KDF_HKDF_SHA256, aead); + + HpkeContextSender ctxSender = + HpkeContextSender.getInstance(suite.name(), conscryptProvider); + ctxSender.init(keyPairRecipient.getPublic(), info); + + byte[] encapsulated = ctxSender.getEncapsulated(); + byte[] ciphertext = ctxSender.seal(plaintext, aad); + + HpkeContextRecipient contextRecipient = + HpkeContextRecipient.getInstance(suite.name(), conscryptProvider); + contextRecipient.init(encapsulated, keyPairRecipient.getPrivate(), info); + byte[] output = contextRecipient.open(ciphertext, aad); + + assertThat(output).isEqualTo(plaintext); + } + } + + @Test + public void wrongInfoORAad_fails() throws Exception { + byte[] info = TestUtils.decodeHex("aa"); + byte[] plaintext = TestUtils.decodeHex("bb"); + byte[] aad = TestUtils.decodeHex("cc"); + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-768", conscryptProvider); + KeyPair keyPairRecipient = keyGen.generateKeyPair(); + + String hpkeAlgorithm = "MLKEM_768/HKDF_SHA256/AES_128_GCM"; + + HpkeContextSender ctxSender = + HpkeContextSender.getInstance(hpkeAlgorithm, conscryptProvider); + ctxSender.init(keyPairRecipient.getPublic(), info); + + byte[] encapsulated = ctxSender.getEncapsulated(); + byte[] ciphertext = ctxSender.seal(plaintext, aad); + + HpkeContextRecipient contextRecipient = + HpkeContextRecipient.getInstance(hpkeAlgorithm, conscryptProvider); + contextRecipient.init(encapsulated, keyPairRecipient.getPrivate(), info); + + // with correct info and aad, it works. + assertThat(contextRecipient.open(ciphertext, aad)).isEqualTo(plaintext); + + // with correct info and wrong aad, it fails. + assertThrows(GeneralSecurityException.class, + () -> contextRecipient.open(ciphertext, TestUtils.decodeHex("ff"))); + + // with wrong info and correct aad, it fails. + HpkeContextRecipient contextRecipient2 = + HpkeContextRecipient.getInstance(hpkeAlgorithm, conscryptProvider); + contextRecipient2.init(encapsulated, keyPairRecipient.getPrivate(), + TestUtils.decodeHex("ff")); + assertThrows(GeneralSecurityException.class, () -> contextRecipient2.open(ciphertext, aad)); + } + + @Test + public void hpkeContextRecipient_openTestVectors_works() throws Exception { + List vectors = TestUtils.readTestVectors("crypto/mlkem.txt"); + + for (TestVector vector : vectors) { + String keyAlgorithm = vector.getString("key-algorithm"); + String hpkeAlgorithm = vector.getString("hpke-algorithm"); + byte[] info = vector.getBytes("info"); + byte[] pk = vector.getBytes("pk"); + byte[] sk = vector.getBytes("sk"); + byte[] enc = vector.getBytes("enc"); + byte[] ct = vector.getBytes("ct"); + byte[] pt = vector.getBytes("pt"); + byte[] aad = vector.getBytes("aad"); + + KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm, conscryptProvider); + PrivateKey privateKey = keyFactory.generatePrivate(new RawKeySpec(sk)); + PublicKey publicKey = keyFactory.generatePublic(new RawKeySpec(pk)); + + // Open enc/ct pair from test vector. + HpkeContextRecipient ctxRecipient = + HpkeContextRecipient.getInstance(hpkeAlgorithm, conscryptProvider); + + ctxRecipient.init(enc, privateKey, info); + byte[] decrypted = ctxRecipient.open(ct, aad); + + assertThat(decrypted).isEqualTo(pt); + + // Create new enc/ct pair and open it. + HpkeContextSender ctxSender = + HpkeContextSender.getInstance(hpkeAlgorithm, conscryptProvider); + ctxSender.init(publicKey, info); + + byte[] enc2 = ctxSender.getEncapsulated(); + byte[] ct2 = ctxSender.seal(pt, aad); + + HpkeContextRecipient contextRecipient = + HpkeContextRecipient.getInstance(hpkeAlgorithm, conscryptProvider); + contextRecipient.init(enc2, privateKey, info); + byte[] output = contextRecipient.open(ct2, aad); + + assertThat(output).isEqualTo(pt); + } + } + + @Test + public void serialize_throwsUnsupportedOperationException() throws Exception { + for (String algorithm : new String[] {"ML-KEM", "ML-KEM-768", "ML-KEM-1024"}) { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm, conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + + ObjectOutputStream oos = new ObjectOutputStream(new ByteArrayOutputStream(16384)); + PrivateKey privateKey = keyPair.getPrivate(); + assertThrows(UnsupportedOperationException.class, () -> oos.writeObject(privateKey)); + PublicKey publicKey = keyPair.getPublic(); + assertThrows(UnsupportedOperationException.class, () -> oos.writeObject(publicKey)); + } + } +} diff --git a/common/src/test/java/org/conscrypt/NativeCryptoArgTest.java b/common/src/test/java/org/conscrypt/NativeCryptoArgTest.java index e8a0f182f..cc36a0f56 100644 --- a/common/src/test/java/org/conscrypt/NativeCryptoArgTest.java +++ b/common/src/test/java/org/conscrypt/NativeCryptoArgTest.java @@ -66,12 +66,6 @@ public class NativeCryptoArgTest { private final Map> classCache = new HashMap<>(); private final Map methodMap = buildMethodMap(); - @AfterClass - public static void after() { - // TODO(prb): Temporary hacky check - remove - assertTrue(testedMethods.size() >= 190); - } - @Test public void ecMethods() throws Throwable { markTestRun(); diff --git a/common/src/test/java/org/conscrypt/ct/SerializationTest.java b/common/src/test/java/org/conscrypt/ct/SerializationTest.java index 5ce182e10..9151fa49c 100644 --- a/common/src/test/java/org/conscrypt/ct/SerializationTest.java +++ b/common/src/test/java/org/conscrypt/ct/SerializationTest.java @@ -20,6 +20,8 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +// android-add: import libcore.test.annotation.NonCts; +// android-add: import libcore.test.reasons.NonCtsReasons; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -30,6 +32,7 @@ @RunWith(JUnit4.class) public class SerializationTest { @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_decode_SignedCertificateTimestamp() throws Exception { byte[] in = new byte[] { 0x00, // version @@ -57,6 +60,7 @@ public void test_decode_SignedCertificateTimestamp() throws Exception { } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_decode_invalid_SignedCertificateTimestamp() throws Exception { byte[] sct = new byte[] { 0x00, // version @@ -91,6 +95,7 @@ public void test_decode_invalid_SignedCertificateTimestamp() throws Exception { } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_decode_DigitallySigned() throws Exception { byte[] in = new byte[] { 0x04, 0x03, // hash & signature algorithm @@ -105,6 +110,7 @@ public void test_decode_DigitallySigned() throws Exception { } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_decode_invalid_DigitallySigned() throws Exception { try { DigitallySigned.decode(new byte[] { @@ -147,6 +153,7 @@ public void test_decode_invalid_DigitallySigned() throws Exception { } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_encode_CertificateEntry_X509Certificate() throws Exception { // Use a dummy certificate. It doesn't matter, CertificateEntry doesn't care about the // contents. @@ -165,6 +172,7 @@ public void test_encode_CertificateEntry_X509Certificate() throws Exception { } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_encode_CertificateEntry_PreCertificate() throws Exception { // Use a dummy certificate and issuer key hash. It doesn't matter, // CertificateEntry doesn't care about the contents. @@ -187,6 +195,7 @@ public void test_encode_CertificateEntry_PreCertificate() throws Exception { } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_readDEROctetString() throws Exception { byte[] in, expected; diff --git a/common/src/test/java/org/conscrypt/ct/VerifierTest.java b/common/src/test/java/org/conscrypt/ct/VerifierTest.java index 02e8280d4..d612bedcb 100644 --- a/common/src/test/java/org/conscrypt/ct/VerifierTest.java +++ b/common/src/test/java/org/conscrypt/ct/VerifierTest.java @@ -20,6 +20,8 @@ import static org.conscrypt.TestUtils.readTestFile; import static org.junit.Assert.assertEquals; +// android-add: import libcore.test.annotation.NonCts; +// android-add: import libcore.test.reasons.NonCtsReasons; import org.conscrypt.OpenSSLX509Certificate; import org.conscrypt.TestUtils; import org.junit.Before; @@ -48,8 +50,7 @@ public void setUp() throws Exception { final LogInfo log = new LogInfo.Builder() .setPublicKey(key) - .setDescription("Test Log") - .setUrl("http://example.com") + .setType(LogInfo.TYPE_RFC6962) .setOperator("LogOperator") .setState(LogInfo.STATE_USABLE, 1643709600000L) .build(); @@ -98,6 +99,7 @@ public LogInfo getKnownLog(byte[] logId) { } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withOCSPResponse() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; @@ -109,6 +111,7 @@ public void test_verifySignedCertificateTimestamps_withOCSPResponse() throws Exc } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withTLSExtension() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; @@ -120,6 +123,7 @@ public void test_verifySignedCertificateTimestamps_withTLSExtension() throws Exc } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withEmbeddedExtension() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {certEmbedded, ca}; @@ -129,6 +133,7 @@ public void test_verifySignedCertificateTimestamps_withEmbeddedExtension() throw } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withoutTimestamp() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; @@ -138,6 +143,7 @@ public void test_verifySignedCertificateTimestamps_withoutTimestamp() throws Exc } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withInvalidSignature() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; @@ -152,6 +158,7 @@ public void test_verifySignedCertificateTimestamps_withInvalidSignature() throws } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withUnknownLog() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; @@ -165,6 +172,7 @@ public void test_verifySignedCertificateTimestamps_withUnknownLog() throws Excep } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withInvalidEncoding() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; @@ -178,6 +186,7 @@ public void test_verifySignedCertificateTimestamps_withInvalidEncoding() throws } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withInvalidOCSPResponse() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; @@ -191,6 +200,7 @@ public void test_verifySignedCertificateTimestamps_withInvalidOCSPResponse() thr } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withMultipleTimestamps() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java index 67fc09d45..5ab143c96 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java @@ -50,9 +50,11 @@ import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Enumeration; +import java.util.List; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -83,6 +85,18 @@ private TestKeyStore getTestKeyStore() throws Exception { return testKeyStore; } + // Remove legacy EC_EC type, which Jdk now considers invalid. (may or may not be a bug: + // https://bugs.openjdk.org/browse/JDK-8379191) + private static String[] removeLegacyEcTypes(String[] input) { + List list = new ArrayList<>(); + for (String s : input) { + if (s != null && !s.equals("EC_EC")) { + list.add(s); + } + } + return list.toArray(new String[0]); + } + @Test public void test_KeyManagerFactory_getDefaultAlgorithm() throws Exception { String algorithm = KeyManagerFactory.getDefaultAlgorithm(); @@ -193,7 +207,7 @@ private void test_KeyManagerFactory_getKeyManagers(KeyManagerFactory kmf, boolea private void test_X509KeyManager(X509KeyManager km, boolean empty, String algorithm) throws Exception { - String[] keyTypes = keyTypes(algorithm); + String[] keyTypes = removeLegacyEcTypes(keyTypes(algorithm)); for (String keyType : keyTypes) { String[] aliases = km.getClientAliases(keyType, null); if (empty || keyType == null || keyType.isEmpty()) { @@ -239,7 +253,7 @@ private void test_X509KeyManager(X509KeyManager km, boolean empty, String algori private void test_X509ExtendedKeyManager(X509ExtendedKeyManager km, boolean empty, String algorithm) throws Exception { - String[] keyTypes = keyTypes(algorithm); + String[] keyTypes = removeLegacyEcTypes(keyTypes(algorithm)); String[][] rotatedTypes = rotate(nonEmpty(keyTypes)); for (String[] keyList : rotatedTypes) { String alias = km.chooseEngineClientAlias(keyList, null, null); diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java index 86de4dfbd..114a7ef21 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java @@ -352,7 +352,7 @@ public void test_SSLSession_getProtocol() { assertEquals("NONE", s.invalid.getProtocol()); assertNotNull(s.server.getProtocol()); assertNotNull(s.client.getProtocol()); - assertEquals(s.server.getProtocol(), s.client.getProtocol()); + assertEquals("TLSv1.3", s.client.getProtocol()); assertTrue(StandardNames.SSL_SOCKET_PROTOCOLS.contains(s.server.getProtocol())); s.close(); } diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java index 1fec266de..6fae78e61 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java @@ -20,7 +20,6 @@ import static org.conscrypt.TestUtils.isLinux; import static org.conscrypt.TestUtils.isOsx; import static org.conscrypt.TestUtils.isTlsV1Deprecated; -import static org.conscrypt.TestUtils.isTlsV1Filtered; import static org.conscrypt.TestUtils.isTlsV1Supported; import static org.conscrypt.TestUtils.isWindows; import static org.conscrypt.TestUtils.osName; diff --git a/common/src/test/resources/crypto/mlkem.txt b/common/src/test/resources/crypto/mlkem.txt new file mode 100644 index 000000000..6f80d3772 --- /dev/null +++ b/common/src/test/resources/crypto/mlkem.txt @@ -0,0 +1,54 @@ +name = Test case A.1 for ML-KEM-768 from https://www.ietf.org/archive/id/draft-ietf-hpke-pq-01.html +key-algorithm = ML-KEM-768 +hpke-algorithm = MLKEM_768/HKDF_SHA256/AES_128_GCM +info = 34663634363532303666366532303631323034373732363536333639363136653230353537323665 +sk = 06f7d4f1495a828789f5543cb847369e10751ca5369a473c74e46043080f94f525f2f8cb7d8cfbf3cf8496728611a6567afd446a6ed1d22f6d32f74ef266a97e +pk = 33e49cea9c631f102595637291548f7220782f498bca5073b8039759f5582f45aa59245f41e5516da2a779b325e039651c348f57502602a3b7b8482b4b1576c93aa8b8c5155e5bc5dbc6a08d57103bc970b016ab7ab22320c430291ba1e8085564c4a13686aafefb510ce76e36876f2fdb1b7ba49550c15f5d366abe420e7ff2459cd7af36f5c285981adcf84020b04a66a0bc58172f8a34280fc32497a56308238a9f27ae95f0a593a70ae7594260e60930695f5f803eb2210fe3ac61c8bc20ec2566dafc399cea81a3834619420e5d85476cd573c7a08eaec4c60cf7999acc98724b934e259cda793dfda761cfa4289530b1f75b6a592730b8f5607eba701a150c38ac0e21038bfef496d2cb808f7342317789d1581b6f8565e3018d796013fed26b59fb226b5988633923a72a9acf42960c228f1e25b84f18ba4fd87620679d6c7a9e5f4340dadc3af252afd28231e3d52cb924209e50ba1ca632de811ae1097cd89803884a8c750663baec78c90254f038574fdcb74c527610e21469398a20abcce9f50e1537867aa036bf1452cbf70850e8a5f5464eccc8a2af260a45507e2da24c4868699065c95e3946ace90a655696f654892d8470a535cda5d58c44c8c0cb7bcc7104a847bc4cb1f03b8872143f5491c9e8c8ed46a6a6703c07d42ab8fabc4e25477245754d718ad8c88ce753c9756c2403948c06c1345c570fbd9a5932982f326a2f2ac69ebf5a58bc788fdb72a4d90865d3169d7752579cc49ad5e27479260dbba4800b521d21d22eb633168632b5209c4b81f32026a8111e07b148babf740297af87239bc760b976862c551622416a6fb23825112308867ba70a75ba33179c2373b97492235a27c1f266cbc18f71c0596e47a285bc2e09397a632309ff67739cb132d8c4007d2926fafc5eb56a6cf1960e87b4488c4895aacc6fcbbc4b6dd2b56cc69616653836550bbb9663b678370c3916fb832e17ba753d04787d78b0ddf11f6410bbe847cd6b1abdcee2835d50867a40137723433fdb6e94f902fda88b96d95af872000447c3649a93d3e0013dab5350dbb45c6190089c56cd4c1fce162daba74abfe8ae673105e0ebb94e52b37967b609741eb4608723e9858937b4996269033ca2cb503c4d3ccda0e15137cbc602728cc9bb5cca55b2cbc4579d02bdca430a052bbfc68bb5c8eb7c0139272c545d09f345b49a800314ca63c5097ef24cd793946410a931d5437becb557197affa37071954eee9c093a3486492362595761c83aac2ce46cdaebaa07ab3c3c1363c15bb8c4c869f54c6fdfa78924b954e0b480d3a128a2f64f7527beb2ec92ea6994dddc5eac10bde8a45609784e7948b6acc7c5da8526dd970870f46812bb0b53867668e17183fa6389261fa5da9a3ad8451094cd8ddb28683c1ce853c0e1cb3a4f79a168502242f662172bc21609492fa57a9f077a1889c42cb2aa3bc2583275ce6b171c3e3aa4279483b204cb3a1a98535a2fe7c1b3d322c52dfb9e9ec46f926562ef1c8992a2bc5fc047961898d1eb3d35cb1a018cb440d84de5047e50a4abd891c84f9431e36bb8134642e6fa144c15307a5122f07252b1e65b0e9ab3c2a184826c9a9cd938bdd588138a731c1b51787f166ca6205dadaaffc05c609f747b99fc3b918359e2ac28c8 +enc = 3d857eb5cb43c7ff163af7fb9ec804af496e4983f139bcf54a5b91a32399f63e57d79e8a80489c5355f4d2ce61613cc152da4a30ceef8ba9e36846f54a6dc3cd45f0191ba914e7da0e2b82aef47328972509157706f71a101685522602b9e19cb0574d2ba630a4b1d6cd140c8906d27c8d9573a59cab066acf2aad92a44d2a031b74d64dd1f1dead0612238a470aa089979a7b0e881cb7655b2182a760e4b4a595f82de5dfc4913bf0326bf66791ed7193e2c3b214d679ab1065823be1c90fce2539359338b253d3dd00585c9e4febf87afc2d0cba105e37771cc4b0f5606c09c03e10f3c41f5f06273e2e4d7e85a7a7c0fa7aab5b785af951034f0e4cd06da1b7b64becb1b2e670a6cea15d5e8cdfe97d401ad9c8411805eb4943cba446c88f243650ce185a77e16bf9c8c76b7ab77584462dd68d86cc752f8ca4b4314b6b8cee563b65d47409abfa3c9f71533cd4799d4a0b437525ea0d9f34a76b1aee5903f81079486d2cd759326dc3e385903f64c237847449638dc42b4b3af9d2c9c4d38624a7f766de56f82167caf480dfaadc8565b501711f2500d3cd3ced261f1c97a3268c2d501f76c4b84d5c81fca4158d439e0357e2aed6c706955e57aaeb633c1490225dcf7160b5d8acc4982a9c5a93fe856dddb86da2d4d68fea777b07f4a050a248876c8750f72c4b0e09a53f98c0b6ea0f62d455c25d200eff9d6c41e95ab4d5b402f63bf40720af8d8f5204c2b53c6916e345e38072c7716e6e54261894f55a6c0ded181e9edfdaac22282266e6fbbc476654e3502a1056949c28a1e0f06b5372f9826a5723e86bbfe2e74e72028ca2ab0172f5c77c403ac8a5afb650e19eb8580922eba51f8ec89272b15f207cbe443a25d9a3c3b2c3a547caebd0cb1d262dd5635ba96e0e83ffead58fed63cd3a44d993138367049b706168c649f5d655edffface55ec2ff190baaa437bfd373c2cadcb330ebd6e7ebc416d249a76d4bfd871a63d238b64aaf1b7cb8e9007075a9f48fde7338418d3231c3954ec3cb1922ebd531d54927630b1166c5a5d33886242016adc74730e7a95c315964eb9d10a7d5c3a2dc3dad3bef04f26cc2ce98decf709cfe2343dcfbb27ff588e0abb5725d0c749888aa3369331f389a4b82b427ca8245f4d5555d63067178841c8304cf57ed595acc2e0aaefc82bb9234da182498cf33a5936fd075e02f2df1f31d8bd4292e7fbb32bce4d1e7839958213c7bef1e2032a4ed049defca29166aff2efc90ce527f24352c3c87de024a561cc97000679fc9b86a5bde16c5b1613e4cf19845e71aa4a7ea9fa41137b075573fc22549b51ddf15eeb5f3fbd5365975e4342d35c976f0bc4c13d8208760b8bccffc5fbe3a979a05c5be681e31821180fe564f20aaa064dec3b9ba3ee158b62a42da3905338714778030d09ff5f3862da161b18ce32243e0bee3e56014883d3391a3f2b37fd382f5bda0efa28ebfd64c12b12c3a0341fd4c615e18f1d35c1c18f09835cfeb61e2d0ac29901cb2999507dcd55e5 +ct = 066fbfe3b5a6a046c2a91150ae385a5567b9b71dd9f1b806c0ab581ff2291fe9b1ac414293fb2423bc4c0dedb0dcbd43950d1f039e5f4f56e15fba325d76ee47b53e4e8ee68e23bb7a0b +aad = 436f756e742d30 +pt = 34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739 + +name = Test case for ML-KEM-768 generated with boringSSL. Empty inputs. +key-algorithm = ML-KEM-768 +hpke-algorithm = MLKEM_768/HKDF_SHA256/AES_128_GCM +info = +sk = 7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d8626ed79d451140800e03b59b956f8210e556067407d13dc90fa9e8b872bfb8f +pk = a8e651a1e685f22478a8954f007bc7711b930772c78f092e82878e3e937f367967532913a8d53dfdf4bfb1f8846746596705cf345142b972a3f16325c40c2952a37b25897e5ef35fbaeb73a4acbeb6a0b89942ceb195531cfc0a07993954483e6cbc87c06aa74ff0cac5207e535b260aa98d1198c07da605c4d11020f6c9f7bb68bb3456c73a01b710bc99d17739a51716aa01660c8b628b2f5602ba65f07ea993336e896e83f2c5731bbf03460c5b6c8afecb748ee391e98934a2c57d4d069f50d88b30d6966f38c37bc649b82634ce7722645ccd625063364646d6d699db57b45eb67465e16de4d406a818b9eae1ca916a2594489708a43cea88b02a4c03d09b44815c97101caf5048bbcb247ae2366cdc254ba22129f45b3b0eb399ca91a303402830ec01db7b2ca480cf350409b216094b7b0c3ae33ce10a9124e89651ab901ea253c8415bd7825f02bb229369af972028f22875ea55af16d3bc69f70c2ee8b75f28b47dd391f989ade314729c331fa04c1917b278c3eb602868512821adc825c64577ce1e63b1d9644a612948a3483c7f1b9a258000e30196944a403627609c76c7ea6b5de01764d24379117b9ea29848dc555c454bceae1ba5cc72c74ab96b9c91b910d26b88b25639d4778ae26c7c6151a19c6cd7938454372465e4c5ec29245acb3db5379de3dabfa629a7c04a8353a8530c95acb732bb4bb81932bb2ca7a848cd366801444abe23c83b366a87d6a3cf360924c002bae90af65c48060b3752f2badf1ab2722072554a5059753594e6a702761fc97684c8c4a7540a6b07fbc9de87c974aa8809d928c7f4cbbf8045aea5bc667825fd05a521f1a4bf539210c7113bc37b3e58b0cbfc53c841cbb0371de2e511b989cb7c70c023366d78f9c37ef047f8720be1c759a8d96b93f65a94114ffaf60d9a81795e995c71152a4691a5a602a9e1f3599e37c768c7bc108994c0669f3adc957d46b4b6256968e290d7892ea85464ee7a750f39c5e3152c2dfc56d8b0c924ba8a959a68096547f66423c838982a5794b9e1533771331a9a656c28828beb9126a60e95e8c5d906832c7710705576b1fb9507269ddaf8c95ce9719b2ca8dd112be10bcc9f4a37bd1b1eeeb33ecda76ae9f69a5d4b2923a86957671d619335be1c4c2c77ce87c41f98a8cc466460fa300aaf5b301f0a1d09c88e65da4d8ee64f68c02189bbb3584baff716c85db654048a004333489393a07427cd3e217e6a345f6c2c2b13c27b337271c0b27b2dbaa00d237600b5b594e8cf2dd625ea76cf0ed899122c9796b4b0187004258049a477cd11d68c49b9a0e7b00bce8cac7864cbb375140084744c93062694ca795c4f40e7acc9c5a1884072d8c38dafb501ee4184dd5a819ec24ec1651261f962b17a7215aa4a748c15836c389137678204838d7195a85b4f98a1b574c4cd7909cd1f833effd1485543229d3748d9b5cd6c17b9b3b84aef8bce13e683733659c79542d615782a71cdeee792bab51bdc4bbfe8308e663144ede8491830ad98b4634f64aba8b9c042272653920f380c1a17ca87ced7aac41c82888793181a6f76e197b7b90ef90943bb3844912911d8551e5466c5767ab0bc61a1a3f736162ec098a900b12dd8fabbfb3fe8cb1dc4e8315f2af0d32f0017ae136e19f028 +enc = 9c9b4648259be6f50344250f21ccce64a2a997f4ba061e780e781012d68cda7afdc22b5f1f670800333fbc434c62e0182801747228f3bfbe46eb5aab821154aa912acd2a8e76f832e538e0de647057e0ad164c59f1a016716bfe5dd8a2c96fac418d7bbe902c6bbd6970da7e05c7a2d5c1d388f504587f229cd0ff11d7090b4003e08424048ccad7fd712cb48bffe6bf97e0459df7444836ab37432774a14670f6eb184f9d34c7390b7884370bb65088290217ac540243690fa7d91f1fae35c809ccbbdab475d4907b46f774a6f9a4fe39d5d0cc12288085ef81ded2f44dde78099a5e7758141b0209b08369231ba4181a62537e237a6e67982426d979a8e5c9577265e5337b4cd42a5af59a6d10226ddb7548e0a1998c328c82e23f2ccf526aacd533502042a0d4938d7af73d054db927862b23eadeaa587535b6bb967184062c6767dad2cc2bf12bf9539b750cbe02e42cbf375449b1f73d69ae3c39e7a0b92e80b2ee97c27f0bd5fbd92541a3368c847788ed212ebd5e56b67c4b6ac126d32c3be7d1d2b195b5569260961f472195cebbbb6b531d46b89ad45af7c898daa7165f42cf13db3898567b9ff06f5902105a04f7b92ca0199b73e20b08b321772c85fcc6adc7b604ca8899728de7c0ba581b5d0c6516ca34204872dcb01bff277702ec6b4b0937be03beefc35602445313e57dd867f9a37fd03b16e30acaec7da3d36ea542b2f7c6db0fd5fa2cf292c6c85971fa21ebd635c19ddc95c7c91299cd83f1dfdc1dded29ddbcb3b9742427423502f4904753033aff7483731e93ccb6c1358dc13b42defb10dc59662ff7200fe02ca8fe6f53471b05b4c1389f44ce04e20d9a9cfe8c25acc7178f2977c0195bb2f360a7c74e29122bae32f6b6f6de84dc0263befc4bdd95a4483592388329ee652f6bec22287b5f9f5a100daccda2337bf6ae6c75a91f9a2ba2e67e931b4dddc0ad30d9a33d0bfe09f24c29e83802e540c5c29fd040912a1c806c1aa0bc699eb55e56156f9e617d62f1e4af9ffe5e09d3d16942205f909a49d4d07b27da58ecef5ab5e10e7ed510a6117e7a5c74591a4a6effa10c1a0178e2e9c8fb952f46a967f1261752f9af873db9388e7fdfbaebb5427ead0a88edbc1f01bf3163a503e124a5f1f7638f4ca6053f18201ddc1fb435599917531593d2f42a4c3bf64d414d1d50ac4415dcf100ffbe1216917b7a12529ec50126d2292161bb7c7feb3b2a36a24b07b187f473c9df6c29db4a42da49602a6b67d6e5ee5af1a9f845ebfd064f6ddd651b74abbe166c0ed54429f7c4c8459acb25a4ea802049247db81735a2ba6f01e15b35b43fe29e25d8a0cf2772c461528609dd399228fb020063a895589cb1319d426ea3f925930f1782255156221b7258261830e48c3e0081075ca0efa05c7ef7c192f1185573460540a08e52c886745ace8d57e9fec941c7830acd7e447d799597ec6acac25977aeb9e8ec0fbf12475dad23d20d5b31a4461f8af8cba0d96410b5dc8352ecbf26f78f626d359fd +ct = 0f0007caa9ba3401e631f949b5fc6d3e +aad = +pt = + +name = Test case for ML-KEM-768 generated with boringSSL. Empty aad. +key-algorithm = ML-KEM-768 +hpke-algorithm = MLKEM_768/HKDF_SHA256/AES_128_GCM +info = b254a656608933b934b3f81e8f810214c8135eda92a0614c2b926c4a3075b9f939e6a3c61309f53e +sk = 7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d8626ed79d451140800e03b59b956f8210e556067407d13dc90fa9e8b872bfb8f +pk = a8e651a1e685f22478a8954f007bc7711b930772c78f092e82878e3e937f367967532913a8d53dfdf4bfb1f8846746596705cf345142b972a3f16325c40c2952a37b25897e5ef35fbaeb73a4acbeb6a0b89942ceb195531cfc0a07993954483e6cbc87c06aa74ff0cac5207e535b260aa98d1198c07da605c4d11020f6c9f7bb68bb3456c73a01b710bc99d17739a51716aa01660c8b628b2f5602ba65f07ea993336e896e83f2c5731bbf03460c5b6c8afecb748ee391e98934a2c57d4d069f50d88b30d6966f38c37bc649b82634ce7722645ccd625063364646d6d699db57b45eb67465e16de4d406a818b9eae1ca916a2594489708a43cea88b02a4c03d09b44815c97101caf5048bbcb247ae2366cdc254ba22129f45b3b0eb399ca91a303402830ec01db7b2ca480cf350409b216094b7b0c3ae33ce10a9124e89651ab901ea253c8415bd7825f02bb229369af972028f22875ea55af16d3bc69f70c2ee8b75f28b47dd391f989ade314729c331fa04c1917b278c3eb602868512821adc825c64577ce1e63b1d9644a612948a3483c7f1b9a258000e30196944a403627609c76c7ea6b5de01764d24379117b9ea29848dc555c454bceae1ba5cc72c74ab96b9c91b910d26b88b25639d4778ae26c7c6151a19c6cd7938454372465e4c5ec29245acb3db5379de3dabfa629a7c04a8353a8530c95acb732bb4bb81932bb2ca7a848cd366801444abe23c83b366a87d6a3cf360924c002bae90af65c48060b3752f2badf1ab2722072554a5059753594e6a702761fc97684c8c4a7540a6b07fbc9de87c974aa8809d928c7f4cbbf8045aea5bc667825fd05a521f1a4bf539210c7113bc37b3e58b0cbfc53c841cbb0371de2e511b989cb7c70c023366d78f9c37ef047f8720be1c759a8d96b93f65a94114ffaf60d9a81795e995c71152a4691a5a602a9e1f3599e37c768c7bc108994c0669f3adc957d46b4b6256968e290d7892ea85464ee7a750f39c5e3152c2dfc56d8b0c924ba8a959a68096547f66423c838982a5794b9e1533771331a9a656c28828beb9126a60e95e8c5d906832c7710705576b1fb9507269ddaf8c95ce9719b2ca8dd112be10bcc9f4a37bd1b1eeeb33ecda76ae9f69a5d4b2923a86957671d619335be1c4c2c77ce87c41f98a8cc466460fa300aaf5b301f0a1d09c88e65da4d8ee64f68c02189bbb3584baff716c85db654048a004333489393a07427cd3e217e6a345f6c2c2b13c27b337271c0b27b2dbaa00d237600b5b594e8cf2dd625ea76cf0ed899122c9796b4b0187004258049a477cd11d68c49b9a0e7b00bce8cac7864cbb375140084744c93062694ca795c4f40e7acc9c5a1884072d8c38dafb501ee4184dd5a819ec24ec1651261f962b17a7215aa4a748c15836c389137678204838d7195a85b4f98a1b574c4cd7909cd1f833effd1485543229d3748d9b5cd6c17b9b3b84aef8bce13e683733659c79542d615782a71cdeee792bab51bdc4bbfe8308e663144ede8491830ad98b4634f64aba8b9c042272653920f380c1a17ca87ced7aac41c82888793181a6f76e197b7b90ef90943bb3844912911d8551e5466c5767ab0bc61a1a3f736162ec098a900b12dd8fabbfb3fe8cb1dc4e8315f2af0d32f0017ae136e19f028 +enc = bd48b97bc2c9ef55bcdf65e5c705aad0c190fb3e4271ca78b567a8d3d7070c6e73e4637cd3341ece8858335b3fec417a3671720717d15546eafeb2d3b72e04f87064e2819e90c046085e0704c6589f97d911bf18baf54ca0d07a4ccd954ee62226d760750d9a908142d19109e7dca776be514bb851eb33ce34a533244a5d12300df204b82484f08696588361a0e93f7295d617e7a8d453d78d940a251a440d74b4130801765d05edf5aa70dc8e7d5718bc9d9914e241c928cdc799d4485572ebfa8c8c25a5d055b2317a9aa53f760a995e370c11f8117b213d5b129579ea5d959433bf01feb3bf93f42e047daa1e4f108ae82c66887e65fa8f43995251d55d0ba82df473efaaac43d0a777339f2fa40deb812c7a6ceb1a15817e9408a0a80164fda5d80194cf6ffbbc9fcd21cceac5c091c718486afd22f046c4d62d9e93c3bccee6f3c7629d5f30e4c7509cc1ba70dcc1d12d4609aa1525c5a2ad75135a4fdc044ba72e77282a7ff89e976a0d7ee81def2df82eb96e7057ff9ee94a598376d16b65b918e845be331fea83f391b7eb504e6f1c98a17867dec5829eac0e63672f09b9ccf3b9b489e191bd5cfceb1da0bb4d18ae6e9a15d794f81f36c3148dd288c6a3ebd705a2f601acce37551dda6cc1f8a4a7ea11a2c86254ceca99486d6634251cbda030b9642f68a6f46dcbef3c94c65f772ecb8341703c30068a3c9fcd263a6149d5a6dbeff9b569f7f68d23733f498fbee12d402f980532f3d1ec83b9828ceafb54518447bc2393c973fff668ff54fa7958205e3be4eca40fd2523327568b5245355c828a9f6294ed3ef179b5eb16bc1e43f61ab069d10c89f59cf447d5a61c8498ff84f48a7cd76832c8d694e5b090c176caf81be027215cdfaadf5e05e2c80155ffc6eebb1589f33abaee00ba8bc7f4794561e9a54616bd505896517940ce8adb2d9e2d0d778175a19c987f5791b41af272e89e43c73436fbfb2ead8e8624acf81d81694f21a13cd1e13ea818f46f66ddce1e93e20f4c3b2fa820ebd224937053c4ed1404802634a0b1cd3763970ba6f66e13c6e1d833c9e80e4c041cd1c4947cc9cf0b70fc78b55b2244527bbedc6e618bf96261bec109320ee1a94e84dedd003d37deca5bc6ac6cd3bb8fa6a92fd62b331179f1557632f91cfb5cd327e643aa867dab7801e5e91317191b3ddcec231aa6c1c07c371b5b5a02340cba092605e38642aa190275d8324757d1330a0bfef15841e39c9430cb89ce596e0b715b26aa7c6c4b642f914a9da8ae77f045a112dfd3a0aa9616a817a09c5449b8831ad12bd17af81af7a5808e572b28c75591bcf69ab8077f8e067df75f4365fa9b70de24ed1422ea3229d85d0bfb503bc2bffd89c74055b76758cea078a05bd12c793a1f9c78f5e90f44d89d5bd14a211ae149c91da17b6ac46774cc3c73a807edbfdb3e33002daa2dc2758824435dfc6a0ddfe0bd1db083bf5f5d8e7b18abd1de1a8706f5c0c8a762d0a3d3f1ce02813345937034b973f94b6114651a58aacf +ct = be50e7d9e0aac571eac64b27b296bae1254505ec797a79b772fa60bc8f81bfe367af4ee02107c090f6a60bf8e9f547fd7937ad607ece7d7791817f55411c551f2de6f1a5b6662868d290dc884377a8a225 +aad = +pt = 86526d8f8d975a50785055b1f6120e6e76e1088730919310d486016a1c62b9a797c5f8842c16260f959c1620d43632975a6c3f309b6891398c8c5a4d31481180de + +name = Test case A.2 for ML-KEM-1024, but using HKDF_SHA256. Generated with boringSSL. +key-algorithm = ML-KEM-1024 +hpke-algorithm = MLKEM_1024/HKDF_SHA256/AES_256_GCM +info = 34663634363532303666366532303631323034373732363536333639363136653230353537323665 +sk = 870150f8c622ea6866db299c3348c737f0e8da17c1e7f721029b5e035db5942168522e0bea336dd93031199ab74b3acd684cbd03d6e56f304e5c28e7a9cba3bc +pk = daa944e375a5c36c1f3771be499c297aba61d8762694256ef840460d10bed594641d58446e3795792c51f272383e78aa517112197230c3604993182d046679a870495f1c44a6e2b30da7c251034f127561e02a234d0bbbb4ccc5a15224d0643108c44ad2973085371c80a064eaf524ebd90d5ad6057d45326a8b4ef532a7dfa93ff83783ece423106c8b1ed2627b59ca34d71a3b4014698c9969db604589270b492124a80eba50909c2847a8bc9881b0788de95ad718a9c71a33b7ba98ab8ac31ed744ffca6d6e08a170084c072bafaf7c25d5f56c519a6e3bdb30b0906192e2671da765ef53af8ac0197af2c9ac794a41038599766323718347f8821f3888ce250b84335eb8c43de4d9720f26b92531685af7576f62c6f39249c6645433387a8c617fdeb0650f77580de2af30019eb9959fb39ca2ed5364fc0a54e4809ac6a2424d284113e25179427b6a22936dc5839f3a9cb8bb49a5239ecd09278e432f6559bba0d56f9a9b87af068369c207b040022306a5fcc64d3264338a0789d7a833ad280b9ef2619c75769ed124a19c89a9b00a43c934b0042f24e28d2c353f2718ae9eb556bcd7c408f01e07bc7ad1d563b411133899312e5c6125f3cf39448ba64c8a904414c32a8fc233649a2a69b98ab658094908f89c7089765e8211e5c75d949c6e53c6445d11902b25b452da79db853b78c8a629f1c5ef762f34f2126a2092c85b618c1a1bb506cd60e0c8b39b3c9aec088494c966878db1c3772b0992de4cb3a4958d2c43878f681a53cc207f446c6f95addb7047116bb4d5124b5fc53220401a7b5531fbb49bafa345479c620bfa8529f2a73f830c975b820b465a97453483a746ec573289a8c02b53906c7423dac584495b6c771a60db498ae478a8f9a9833c81666a55a47a01adcd189b0e8c90aaa382a1d2301a873d9370c12ecc6104516e6b2957e3a7806c551d96aabe3b5cc47f918c467732dbe3a138305490c38b33760d4b164bd509595415a3b560082a9b688b386c20f05c0667631555015eba61e87174600c1fd0971b4e776e8452ad50748fd5a783ade73d80a97c8b07a374ca53ba95c89157b5e2bb333518adce58c5ad90734ee20cc4646382f4bb91d9431968895f541a25da9a892c7086cb361e4240ae996afa5445c0cba188d13e4a8863c6918a7e247aa893c055a052bbaa3bd1b2731207006e5403e8742c844605d4aa64963b0af4978fbb7a1e409ca5470395dc24a700a850b189039f87bd2c7858f28a9e2c624ecea50f1f7889f6a99cfcfacb0c5861586c3e97a41d855319d5930e0aba9eb1b1bf013b32f6cb0d1570243eda540538a288d13bad128907d07925153925e9253c117e82ab0c27c49fc68162e465b0e3fb7c1671c0965c71b5836862301a85606122896e8d9a6d848c1818580ddc7955f3446f148338ca14149a205a3c6bb43e30abcf20a69f36a7a5e60efdf69b5922cd6a7744e420311f3b9cb2148ab6ea7e11b522c81ab0209a43f8da598f580fc8536b2838bb8ca0c266905270609304ab78d6c40fa8a7015862b80ebc34a9ea25959a3c3fc4abd56a79616215f2caaacb51033e02ba4b446c0fd47e515ca9ad44c7e3a2739b9b69242a6cc528880491c72c7858ac13804db088a97541ed11208b369bfab3c8e4890ff1c73f2d783004c482458980eeb21f40c73d5d5282a78279d6f71bad650843c83d0323a7e90748b555853a50c091c31a9655b2388226e4524cdcbabb61543eb7caa88396933e1a5986986e41da209f131a9bbc9a44d66ba077229c499386a4418b990e20707c323846c861bcf227b2ff6ca3ee6511b37c190618c1e610be4672507ff7c7f5486930670d99bb6c2c28afaca54b815144aed10f82c27ad8922d5a9c86b15c9da866ad0ff96a2f569b42d52cdec8793d0414cfc35ddcebca8066b25e930b36743db6b05a6ec6641b9403f88a5320842522f388683c72eb60bfe1db314a0b78a0a0cc76a692137c9613bb47c3f54911e67c1ba48a46081fcaf0974783adddc88492943d8b5934a41b46f95120fce5a21e0223dee7b140a924ce711e0e93c434616b58b02c2e30352599380ca04127f40a4134c5862c3bab08a3f265a4679c989cfc1f24c9c553cabb37ab2767f5ab2d7a20e57b0b65fb6e4a6119ba33a736a9568de2ee95c312aad4c14639282831a3461d6e08800b592c8aa9d7ef6028 +enc = a9c43b27ed53d789ac6f74a96a8843ad20379d89a792d887ee6ebb09b271fb22177d0a8c261c6f0ec7b7b1cd22c61a189a82aca4aaab9d789510b69356ef3bd8607742931e7067d9d4f5bdc1d932fc51f15242f223d1f17b036d8acc7014864c002b0f9a3c4c5dc8d5baa8f9752f122d528f2873dd5180ff5c95797952aa89ba072dfc6afe3c866b11409837d2d89d2097e3435682a0444cd5d4ea76a5e1b64986ce51e5f0a40e0bfb1b869a35ac7b4992f2c1aae2af1e4d96b257053cae9f57279ce9c7e472aab074b2aa74ff50cdc455bf9e2b737795f16bbdeb3b46ad61020a79b4f64c4eb727e25eccd9be9cb1a3b593352b6a6314f37834856218dcad1a84af88711d96acea2c2ee7ff3aa9c4fd2b37456fc83c039c1b2918d286382ca9a38a967a27db7eea947ca1c9137e9d53f0a5e2ead709355f4afac7b7ad0bc4b17427dcae0b45e4f8991347f1a715b2910e637a671d9ffd5453ca0eaf79324528cee54c4d0972eb0f21cca55d496a096a53c416a8e44d2647d814355be8b88b4a6a464451410a5ad3f7df1a7af6bf49c1e4523664853e443b446e5c527c779e9423f24f33c6a479a8b3bf679505340db41041c7826b83e678880513f3714fdb351343064ac33746a251c15b9291394dc91ad20ad106979d31aa1a0ae9e176e0a85f4f987d9950b9220a74f83453475c28133f8ebaebf5d6c93ff8a30fefcae937c879be2e1df73f95aa75243ebd7966ada80d2be6828db5328d0aba396dcb6595f60798c177bf6b4e72023c4e84fc8eb4788af12d9f40fb79519263fb7ee99511aecffec5916e55da81bff24a3fa6e8e5f9e43d130383e18402af68eb20fa099d0c3ba69bb5d7211720c49cbb8fb50ab29fcabae1af183c0a045a6c39944d901b80fccb23c18465a10b7a04d9a4c6094ea9eace3a6880b0a73d2d4629f2de056f677174adac30e9e972b2fd442729d777056032dd89227a152c4eae7e95059dcd364de5bee0263abfb9ed2bc91e1f8b595e4a6944c00f1d82fef62b629148486853fc5ae2c9a5b4a29bbc7b56c8eb214391aac7dde7992e70a957441b6efef25b62228dccc32b700b9b82d63cc4bf61c2cee22fb9a30832765428a3f90b092fae9c9fc930a2c7c8aeed444c9c1ffad2ca0c8ef9f7905a7d33a9582158ef860951f8243006b390027d437096bd8617ca49c11a62a82ff7a2b7d9b69fbe0b1269ced4a764ed33e0c9525301c1f98a46dc30cc8f74104e50dc09f1c47beb0880ebc4b7c70786d58f217a794ff0e8f628596b5cc431f0b514464521507aa6f564b59d485f5cdeacdbca1ac96859fc5a3e8d3875ef88c36f2733dfafbd02de8c3e90d300cb62bdbc835f3a25f56f1a0a51a45c06b99aca7abc4b49a9eb90731139df1fdb4e29bfebd72371534f31a56c3c9c30b4921a87e58daed16f61df36f2b8f870118ba47d8d275600e5a50a3bff58bd7c1779bf4ea20fb5e8697eddc9af0d53a703a6f2bf7ce62a7393b97087af108d4c6f4ee938188fc9401a65c1d96a9acfccbb3c766dd192d2f646c67798fe97a39a1a0756ad18ecfefd52ae72d1416b10e28dbeb17004750798593c322fd4ad4136cd4de7fdad1105e06f5c3616dd9e35a57c7f54b2c91fdee4345dc73538cbe9a5f58ccf93574fe3a2984d6da76707c6ab7641b422f7cb60bf2bdaaa119aa9accdb8f60fd78ef6bd60f83d823d994644ad5cf719999b9ca8af6eba716fd70650a34ebd58420c4d218cad6f03ae108aa45a77a04afd95f1e54c356f74794a44bb9a463b11743acc2cd5a11ec5c48cfc15d0942981cf20ac14ef825528c932bb6f1e043da43f0151e481dcdbe74816457bf5168fd3b71a8bbf202b6324fd0e564e8fced1d7d9b2ab1167791736d4689b3767ab1f9d09536a20a774374c142deacaf6d4f4b2e7a2b50ea49c0fbb85207e6fe381e9594ecb27262577ea1d1a9a992ba8d57ae6c37e0783059b255377fe6e1b5d4e29ee63e1de1ecc5d55aa7f8729abe867a8de54360cdecf56885b7ddd282658a5ef84d464d119697fc9fb3a37772e01c11a2d89a5c434eec978ce6512627c008642ab71d4218e0385d5905f7b786a3371c429afa17293abf08a1cbe4424d27689aac3c5ac1ceba9ab6b8187eae97d78978191d2c1928250e18d95629531538e3b2e397d07cfb34a6a489e42b5c1d6897bfa121b9fe920ab8c262c26165b8a56 +ct = fa71be185be45b4734cc6df293a7ce0c66c1d8383dbfb87c8af6306e602b78c4e661fdd15a1747092fe72946e7e764d97e06cb8a6f4d74aa5228f808affa8c15f6e3e348627c7ecfd3a5 +aad = 436f756e742d30 +pt = 34323635363137353734373932303639373332303734373237353734363832633230373437323735373436383230363236353631373537343739 + +name = Test case for ML-KEM-1024 generated with boringSSL. Empty aad. +key-algorithm = ML-KEM-1024 +hpke-algorithm = MLKEM_1024/HKDF_SHA256/AES_256_GCM +info = b9e8c67f3a2d1e0c4b59f7d6a5c3b2e18d9f0a786e5c4d3b2a1f0987e6d5c4b3 +sk = 7c9935a0b07694aa0c6d10e4db6b1add2fd81a25ccb148032dcd739936737f2d8626ed79d451140800e03b59b956f8210e556067407d13dc90fa9e8b872bfb8f +pk = 537911957c125148a87f41589cb222d0d19229e2cb55e1a044791e7ca61192a46460c3183d2bcd6de08a5e7651603acc349ca16cba18abb23a3e8c330d7421598a6278ec7ebfabca0ef488b2290554753499c0452e453815309955b8150fa1a1e393386dc12fdb27b38c6745f2944016ec457f39b18d604a07a1abe07bc844050ffa8a06fa154a49d88fac775452d6a7c0e589bfb5c370c2c4b6201dda80c9ab2076ecc08b44522fda3326f033806dd2693f319739f40c4f42b24aca7098fb8ff5f9ac20292d02b56ac746801acccc84863dee32878497b69438bf991776286650482c8d9d9587bc6a55b85c4d7fa74d02656b421c9e23e03a48d4b74425c26e4a20dd9562a4da0793f3a352ccc0f18217d868c7f5002abe768b1fc73f05744e7cc28f10344062c10e08eccced3c1f7d392c01d979dd718d8398374665a16a9870585c39d5589a50e133389c9b9a276c024260d9fc7711c81b6337b57da3c376d0cd74e14c73727b276656b9d8a4eb71896ff589d4b893e7110f3bb948ece291dd86c0b7468a678c746980c12aa6b95e2b0cbe4331bb24a33a270153aa472c47312382ca365c5f35259d025746fc6595fe636c767510a69c1e8a176b7949958f2697399497a2fc7364a12c8198295239c826cb5082086077282ed628651fc04c639b438522a9de309b14b086d6e923c551623bd72a733cb0dabc54a9416a99e72c9fda1cb3fb9ba06b8adb2422d68cadc553c98202a17656478ac044ef3456378abce9991e0141ba79094fa8f77a300805d2d32ffc62bf0ca4554c330c2bb7042db35102f68b1a0062583865381c74dd913af70b26cf0923d0c4cb971692222552a8f4b788b4afd1341a9df415cf203900f5ccf7f65988949a75580d049639853100854b21f4018003502bb1ba95f556a5d67c7eb52410eba288a6d0635ca8a4f6d696d0a020c826938d34943c3808c79cc007768533216bc1b29da6c812eff3340baa8d2e65344f09bd47894f5a3a4118715b3c5020679327f9189f7e10856b238bb9b0ab4ca85abf4b21f5c76bccd71850b22e045928276a0f2e951db0707c6a116dc19113fa762dc5f20bd5d2ab5be71744dc9cbdb51ea757963aac56a90a0d8023bed1f5cae8a64da047279b353a096a835b0b2b023b6aa048989233079aeb467e522fa27a5822921e5c551b4f537536e46f3a6a97e72c3b063104e09a040598940d872f6d871f5ef9b4355073b54769e45454e6a0819599408621ab4413b35507b0df578ce2d511d52058d5749df38b29d6cc58870caf92f69a75161406e71c5ff92451a77522b8b2967a2d58a49a81661aa65ac09b08c9fe45abc3851f99c730c45003aca2bf0f8424a19b7408a537d541c16f5682bfe3a7faea564f1298611a7f5f60922ba19de73b1917f1853273555199a649318b50773345c997460856972acb43fc81ab6321b1c33c2bb5098bd489d696a0f70679c1213873d08bdad42844927216047205633212310ee9a06cb10016c805503c341a36d87e56072eabe23731e34af7e2328f85cdb370ccaf00515b64c9c54bc837578447aacfaed5969aa351e7da4efa7b115c4c51f4a699779850295ca72d781ad41bc680532b89e710e2189eb3c50817ba255c7474c95ca9110cc43b8ba8e682c7fb7b0fdc265c0483a65ca4514ee4b832aac5800c3b08e74f563951c1fbb210353efa1aa866856bc1e034733b0485dab1d020c6bf765ff60b3b801984a90c2fe970bf1de97004a6cf44b4984ab58258b4af71221cd17530a700c32959c9436344b5316f09ccca7029a230d639dcb022d8ba79ba91cd6ab12ae1579c50c7bb10e30301a65cae3101d40c7ba927bb553148d1647024d4a06c8166d0b0b81269b7d5f4b34fb022f69152f514004a7c685368552343bb60360fbb9945edf446d345bdcaa7455c74ba0a551e184620fef97688773d50b6433ca7a7ac5cb6b7f671a15376e5a6747a623fa7bc6630373f5b1b512690a661377870a60a7a189683f9b0cf0466e1f750762631c4ab09f505c42dd28633569472735442851e321616d4009810777b6bd46fa7224461a5cc27405dfbac0d39b002cab33433f2a86eb8ce91c134a6386f860a1994eb4b6875a46d195581d173854b53d2293df3e9a822756cd8f212b325ca29b4f9f8cfbadf2e41869abfbad10738ad04cc752bc20c394746850e0c4847db +enc = 35ab4e29f97d2828aeb7f746f1eaf17fd41a20542cfc6df50009f6558dfb5ca5325e1aeed36a8d51bf4eed0244b655b8a5b1434325424669d01f6133b4ac96b1e9e46dd34fede463f92255bd55c76269858fbbebfc05d69b2873a16f53da8f16d82070ff41a8094b457e0d6695f3b01aa10764fe164a5b88ab822b7d996056ebb29a979cecb9a06965181337f60d7690fefb6fc5e37617c6beee692ac9777bfa0cb792c6ccc2fff66dc986e2df0e94ec88ec3d06a4fdbee5d56a53800b6c286e683b1cb603184754414ee5e459a86d3800b435c6d593c045487cd18c33b1131011d4e390c8417a6b2480a077ddff5b25c48efe4b79d0b0c97ee8546946b52e59a95ea55058a6f265b39c62402831bd9cff4736b7602e799d9501e1e134ae33f63d820a144dad11ed2ef598ae648b425fb95600a7a4a7007bd639a110b3583d65c5e224f699681971eeeccdbf49f2ef22e72c4b14a23034ea16c4e0ca61fc1a0a1d25081110a5dcebd5d421d90b64ba00ac0015bc1b3ec8d59402b351a7099fc6e6f1f1484bb614d3ad7c02af2dbac41615d0c14de681f65a229228782f9f692a2d9fd4ed04bceaa429dcc0bc4610c275487d74fa8dd08d1ac0f2242bb0387ac980088f6187834717cfc856d32a99d7e68ff318d5c562202ef766274a76c1e6ed5c42ae7fa9140907102a11ca4a1ecf35e133b41788e7362a8a3eb6bff94523ff1315b076d5e1a81a593cac13235ebd8d95942026bdda9e0d7cd8ba186344a7d8c407fe05522ff0026d47fd759dc09f843cf424ffcce3a18423f0c0b17a816dbaeb950cfcfdc9bc150b51bdf019022c95cd03940f3b6611e76487962f64e18c1026cdaa74d24f390942ec77fc9cfb45260153a68fa22ed7d283306539b66aa0dc03abeeb79ac99d9bc84a7c822b57c41b7eeadd0037380724b1780f24b265e09988fa40e6f59d6dfb3903e4c55b6f0e0c204fe6b7cfc0d172f58614cf1f76ada5b4e1fa27606c182970fd032a8d81917fe5a11efad9dd41cfc805b3211c2c2eb59f65fe0c7af68a393a12c78de044a9c2678afff346f7fc6c69427bbe1f9068fef9478c788912ac87340297aa1d685e06a7c86ac141127cc1f6dc7c20da5b7f61289b4e881390e4ec28d5ef64da41c2701cc74a24e9212f17388102224cefe260ece85faceb3d1f5e67a2af99fbd10eb951d038cf455901f8996dc1bc091f41d8543d4440684872742ea50ebb21cf4ac21d5148e1588f9173943010ab0d00cd2a04c72c68a897768cf6ef195c3f7650462a7b3bc5b9edeba690dcc3ba8e818cb5e5c2b8a7c57905f07c711e587e33bedba755cff2f2c41177a0ea984d377aea7b0148c8def3c515924351b30eadf795610d386e10e37001594fa66be43a1d39a83a4737b31d04fb4310fd62fa56fbaa4a2c6dbb7029be979887bb2635f2863bb92aa6f145b44170fe476c8969c7de535deaf7f302673144c90c24dbc6df6bb81b72e24a091aa120f75cbb659caaae465b66ea64db727cead53d65f167a3cab1e97e4c7f3e9332500ae9d1745a725289f1327c8cfd6d9380d45eb7bf03e37825fa8e438b462e8db54351528f9550b763a003a46335f79f3ada75f916b028a364a16c527d2ea9a21fd587e8a34339d66156d6a0055a15a51d1206a824d2ae94e95382e888150ada59d606ac4b17f855563c737f50ec449a7d7c1a7cf2801a81578a5d0c6afbecf25cde3b333bc1d25af54c67a5b212933388140810a1dad0895add3452c0ad0e4cd83b055c5ded7c8f463a28154948acc99e6695dc523a8255c1ffb29b86d3330ad153776bb340dad43c5ea59a69009d3dff057e019cddcd0ac055ba22ba04cb241f3b082c05695a5b07602194d714e84bad39e37017eacd7ca7026c307c67a4f63d08a0b0f5155a6b9a79564a10cd23cecf28659a04a7732aebfb9edaa486bcdf6fbd1a115477f221027a21f9e3a1db0ce995fd1b92759cfa3778d202a43cbc767a2cd4bf7cf7461e74de3e2aeaeb1a30884563c3f44928085427f6205b43d2a93cb7927bd0eca44783d1c65cad1d968e434daf6b851ab2b8d9f1516afb9ea65dac8715a1e83eee0be7c31a82d33d581a910baae404c06e954cf3281d19c3665756cf262f6d028832a6bf16cfc860f7a7538a19e1558e2fdaa56bd26ecba4c7342f28250f498cbc1075b181c762dd3afbdaa42f3267f736faecdc +ct = 81f49b1a4d2eaff96b635a505f1ae8b5210ca44fba8f1c0f2de96a38b35e64305d58c0be8f3cff0aa51f769139b6308b9ec4da6844709cda17d47cfa7deef634b8fa3215f2c38b1d3057cd3c +aad = +pt = c8a4153f9b7e2d06c5478f1a3e6d9c0b25748f3e9a6c1b4d8e7f5a2b0d9e8c1f7a4b6d3e9f2a7c5b8e1d4f0a3c7b8e2d5f1a4c9b8d2e6f3a7c1b5d9e diff --git a/openjdk/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java b/openjdk/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java new file mode 100644 index 000000000..1e516c8fc --- /dev/null +++ b/openjdk/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import org.conscrypt.metrics.CertificateTransparencyVerificationReason; + +/** + * A default NetworkSecurityPolicy for OpenJDK. + */ +@Internal +public class ConscryptNetworkSecurityPolicy implements NetworkSecurityPolicy { + public static ConscryptNetworkSecurityPolicy getDefault() { + return new ConscryptNetworkSecurityPolicy(); + } + + @Override + public boolean isCertificateTransparencyVerificationRequired(String hostname) { + return false; + } + + @Override + public CertificateTransparencyVerificationReason getCertificateTransparencyVerificationReason( + String hostname) { + return CertificateTransparencyVerificationReason.UNKNOWN; + } + + @Override + public DomainEncryptionMode getDomainEncryptionMode(String hostname) { + return DomainEncryptionMode.UNKNOWN; + } +} diff --git a/openjdk/src/main/java/org/conscrypt/Platform.java b/openjdk/src/main/java/org/conscrypt/Platform.java index 58a88d848..4be4c871f 100644 --- a/openjdk/src/main/java/org/conscrypt/Platform.java +++ b/openjdk/src/main/java/org/conscrypt/Platform.java @@ -77,9 +77,11 @@ import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.function.Supplier; import javax.crypto.spec.GCMParameterSpec; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; @@ -648,57 +650,8 @@ static boolean supportsX509ExtendedTrustManager() { return true; } - /** - * Check if SCT verification is required for a given hostname. - * - * SCT Verification is enabled using {@code Security} properties. - * The "conscrypt.ct.enable" property must be true, as well as a per domain property. - * The reverse notation of the domain name, prefixed with "conscrypt.ct.enforce." - * is used as the property name. - * Basic globbing is also supported. - * - * For example, for the domain foo.bar.com, the following properties will be - * looked up, in order of precedence. - * - conscrypt.ct.enforce.com.bar.foo - * - conscrypt.ct.enforce.com.bar.* - * - conscrypt.ct.enforce.com.* - * - conscrypt.ct.enforce.* - */ - public static boolean isCTVerificationRequired(String hostname) { - if (hostname == null) { - return false; - } - - String property = Security.getProperty("conscrypt.ct.enable"); - if (property == null || !Boolean.parseBoolean(property.toLowerCase(Locale.ROOT))) { - return false; - } - - List parts = Arrays.asList(hostname.split("\\.")); - Collections.reverse(parts); - - boolean enable = false; - StringBuilder propertyName = new StringBuilder("conscrypt.ct.enforce"); - // The loop keeps going on even once we've found a match - // This allows for finer grained settings on subdomains - for (String part : parts) { - property = Security.getProperty(propertyName + ".*"); - if (property != null) { - enable = Boolean.parseBoolean(property.toLowerCase(Locale.ROOT)); - } - propertyName.append(".").append(part); - } - - property = Security.getProperty(propertyName.toString()); - if (property != null) { - enable = Boolean.parseBoolean(property.toLowerCase(Locale.ROOT)); - } - return enable; - } - - public static CertificateTransparencyVerificationReason reasonCTVerificationRequired( - String hostname) { - return CertificateTransparencyVerificationReason.UNKNOWN; + static SSLException wrapInvalidEchDataException(SSLException e) { + return e; } static boolean supportsConscryptCertStore() { @@ -765,7 +718,8 @@ static CertBlocklist newDefaultBlocklist() { return null; } - static CertificateTransparency newDefaultCertificateTransparency() { + static CertificateTransparency newDefaultCertificateTransparency( + Supplier policySupplier) { return null; } diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java b/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java index 22c292d8c..16a1bbe47 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java @@ -87,6 +87,7 @@ MlDsaTest.class, NativeCryptoArgTest.class, NativeCryptoTest.class, + NativeSslTest.class, NativeRefTest.class, NativeSslSessionTest.class, OpenSSLKeyTest.class, diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java index 350f4b33d..b4e8708b3 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java @@ -103,6 +103,7 @@ HpkeTestVectorsTest.class, KeySpecUtilTest.class, MlDsaTest.class, + MlKemTest.class, NativeCryptoArgTest.class, NativeCryptoTest.class, NativeSslTest.class, diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java index d7ca7b3ac..c4f508a9f 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java @@ -570,18 +570,6 @@ public long beforeHandshake(long c) throws SSLException { assertTrue(serverCallback.serverCertificateRequestedInvoked); } - /** Convenient debug print for ECH Config Lists */ - private void printEchConfigList(String msg, byte[] buf) { - int blen = buf.length; - System.out.print(msg + " (" + blen + "):\n "); - for (int i = 0; i < blen; i++) { - if ((i != 0) && (i % 16 == 0)) - System.out.print("\n "); - System.out.print(String.format("%02x:", Byte.toUnsignedInt(buf[i]))); - } - System.out.print("\n"); - } - @Test public void test_SSL_do_handshake_ech_client_server() throws Exception { final ServerSocket listener = newServerSocket(); @@ -1203,10 +1191,12 @@ public long serverSessionRequested(byte[] id) { } private boolean serverCertificateRequestedInvoked; + private int[] serverSignatureAlgs; @Override - public void serverCertificateRequested() { + public void serverCertificateRequested(int[] signatureAlgs) { serverCertificateRequestedInvoked = true; + this.serverSignatureAlgs = signatureAlgs; } @Override @@ -1439,6 +1429,8 @@ public void test_SSL_do_handshake_normal() throws Exception { assertTrue(serverCallback.handshakeCompletedCalled); assertFalse(clientCallback.serverCertificateRequestedInvoked); assertTrue(serverCallback.serverCertificateRequestedInvoked); + assertNotNull(serverCallback.serverSignatureAlgs); + assertTrue(serverCallback.serverSignatureAlgs.length > 0); } @Test @@ -3954,6 +3946,129 @@ public void test_ecdsaSignVerify_works() throws Exception { () -> NativeCrypto.ECDSA_verify(data, invalidDataLen, signature, publicKey)); } + @Test + public void mlKem768PublicKeyFromSeed_returnsPublicKeyIfPrivateKeyIsValid() throws Exception { + // test vector from https://www.ietf.org/archive/id/draft-ietf-hpke-pq-01.html, Section A.1 + byte[] privateKey = + decodeHex("06f7d4f1495a828789f5543cb847369e10751ca5369a473c74e46043080f94f5" + + "25f2f8cb7d8cfbf3cf8496728611a6567afd446a6ed1d22f6d32f74ef266a97e"); + byte[] expectedPublicKey = + decodeHex("33e49cea9c631f102595637291548f7220782f498bca5073b8039759f5582f45" + + "aa59245f41e5516da2a779b325e039651c348f57502602a3b7b8482b4b1576c9" + + "3aa8b8c5155e5bc5dbc6a08d57103bc970b016ab7ab22320c430291ba1e80855" + + "64c4a13686aafefb510ce76e36876f2fdb1b7ba49550c15f5d366abe420e7ff2" + + "459cd7af36f5c285981adcf84020b04a66a0bc58172f8a34280fc32497a56308" + + "238a9f27ae95f0a593a70ae7594260e60930695f5f803eb2210fe3ac61c8bc20" + + "ec2566dafc399cea81a3834619420e5d85476cd573c7a08eaec4c60cf7999acc" + + "98724b934e259cda793dfda761cfa4289530b1f75b6a592730b8f5607eba701a" + + "150c38ac0e21038bfef496d2cb808f7342317789d1581b6f8565e3018d796013" + + "fed26b59fb226b5988633923a72a9acf42960c228f1e25b84f18ba4fd8762067" + + "9d6c7a9e5f4340dadc3af252afd28231e3d52cb924209e50ba1ca632de811ae1" + + "097cd89803884a8c750663baec78c90254f038574fdcb74c527610e21469398a" + + "20abcce9f50e1537867aa036bf1452cbf70850e8a5f5464eccc8a2af260a4550" + + "7e2da24c4868699065c95e3946ace90a655696f654892d8470a535cda5d58c44" + + "c8c0cb7bcc7104a847bc4cb1f03b8872143f5491c9e8c8ed46a6a6703c07d42a" + + "b8fabc4e25477245754d718ad8c88ce753c9756c2403948c06c1345c570fbd9a" + + "5932982f326a2f2ac69ebf5a58bc788fdb72a4d90865d3169d7752579cc49ad5" + + "e27479260dbba4800b521d21d22eb633168632b5209c4b81f32026a8111e07b1" + + "48babf740297af87239bc760b976862c551622416a6fb23825112308867ba70a" + + "75ba33179c2373b97492235a27c1f266cbc18f71c0596e47a285bc2e09397a63" + + "2309ff67739cb132d8c4007d2926fafc5eb56a6cf1960e87b4488c4895aacc6f" + + "cbbc4b6dd2b56cc69616653836550bbb9663b678370c3916fb832e17ba753d04" + + "787d78b0ddf11f6410bbe847cd6b1abdcee2835d50867a40137723433fdb6e94" + + "f902fda88b96d95af872000447c3649a93d3e0013dab5350dbb45c6190089c56" + + "cd4c1fce162daba74abfe8ae673105e0ebb94e52b37967b609741eb4608723e9" + + "858937b4996269033ca2cb503c4d3ccda0e15137cbc602728cc9bb5cca55b2cb" + + "c4579d02bdca430a052bbfc68bb5c8eb7c0139272c545d09f345b49a800314ca" + + "63c5097ef24cd793946410a931d5437becb557197affa37071954eee9c093a34" + + "86492362595761c83aac2ce46cdaebaa07ab3c3c1363c15bb8c4c869f54c6fdf" + + "a78924b954e0b480d3a128a2f64f7527beb2ec92ea6994dddc5eac10bde8a456" + + "09784e7948b6acc7c5da8526dd970870f46812bb0b53867668e17183fa638926" + + "1fa5da9a3ad8451094cd8ddb28683c1ce853c0e1cb3a4f79a168502242f66217" + + "2bc21609492fa57a9f077a1889c42cb2aa3bc2583275ce6b171c3e3aa4279483" + + "b204cb3a1a98535a2fe7c1b3d322c52dfb9e9ec46f926562ef1c8992a2bc5fc0" + + "47961898d1eb3d35cb1a018cb440d84de5047e50a4abd891c84f9431e36bb813" + + "4642e6fa144c15307a5122f07252b1e65b0e9ab3c2a184826c9a9cd938bdd588" + + "138a731c1b51787f166ca6205dadaaffc05c609f747b99fc3b918359e2ac28c8"); + + byte[] publicKey = NativeCrypto.MLKEM768_public_key_from_seed(privateKey); + assertArrayEquals(expectedPublicKey, publicKey); + + byte[] privateKeyTooShort = Arrays.copyOf(privateKey, privateKey.length - 1); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLKEM768_public_key_from_seed(privateKeyTooShort)); + byte[] privateKeyTooLong = Arrays.copyOf(privateKey, privateKey.length + 1); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLKEM768_public_key_from_seed(privateKeyTooLong)); + } + + @Test + public void mlKem1024PublicKeyFromSeed_returnsPublicKeyIfPrivateKeyIsValid() throws Exception { + // test vector from https://www.ietf.org/archive/id/draft-ietf-hpke-pq-01.html, Section A.2 + byte[] privateKey = + decodeHex("870150f8c622ea6866db299c3348c737f0e8da17c1e7f721029b5e035db59421" + + "68522e0bea336dd93031199ab74b3acd684cbd03d6e56f304e5c28e7a9cba3bc"); + byte[] expectedPublicKey = + decodeHex("daa944e375a5c36c1f3771be499c297aba61d8762694256ef840460d10bed594" + + "641d58446e3795792c51f272383e78aa517112197230c3604993182d046679a8" + + "70495f1c44a6e2b30da7c251034f127561e02a234d0bbbb4ccc5a15224d06431" + + "08c44ad2973085371c80a064eaf524ebd90d5ad6057d45326a8b4ef532a7dfa9" + + "3ff83783ece423106c8b1ed2627b59ca34d71a3b4014698c9969db604589270b" + + "492124a80eba50909c2847a8bc9881b0788de95ad718a9c71a33b7ba98ab8ac3" + + "1ed744ffca6d6e08a170084c072bafaf7c25d5f56c519a6e3bdb30b0906192e2" + + "671da765ef53af8ac0197af2c9ac794a41038599766323718347f8821f3888ce" + + "250b84335eb8c43de4d9720f26b92531685af7576f62c6f39249c6645433387a" + + "8c617fdeb0650f77580de2af30019eb9959fb39ca2ed5364fc0a54e4809ac6a2" + + "424d284113e25179427b6a22936dc5839f3a9cb8bb49a5239ecd09278e432f65" + + "59bba0d56f9a9b87af068369c207b040022306a5fcc64d3264338a0789d7a833" + + "ad280b9ef2619c75769ed124a19c89a9b00a43c934b0042f24e28d2c353f2718" + + "ae9eb556bcd7c408f01e07bc7ad1d563b411133899312e5c6125f3cf39448ba6" + + "4c8a904414c32a8fc233649a2a69b98ab658094908f89c7089765e8211e5c75d" + + "949c6e53c6445d11902b25b452da79db853b78c8a629f1c5ef762f34f2126a20" + + "92c85b618c1a1bb506cd60e0c8b39b3c9aec088494c966878db1c3772b0992de" + + "4cb3a4958d2c43878f681a53cc207f446c6f95addb7047116bb4d5124b5fc532" + + "20401a7b5531fbb49bafa345479c620bfa8529f2a73f830c975b820b465a9745" + + "3483a746ec573289a8c02b53906c7423dac584495b6c771a60db498ae478a8f9" + + "a9833c81666a55a47a01adcd189b0e8c90aaa382a1d2301a873d9370c12ecc61" + + "04516e6b2957e3a7806c551d96aabe3b5cc47f918c467732dbe3a138305490c3" + + "8b33760d4b164bd509595415a3b560082a9b688b386c20f05c0667631555015e" + + "ba61e87174600c1fd0971b4e776e8452ad50748fd5a783ade73d80a97c8b07a3" + + "74ca53ba95c89157b5e2bb333518adce58c5ad90734ee20cc4646382f4bb91d9" + + "431968895f541a25da9a892c7086cb361e4240ae996afa5445c0cba188d13e4a" + + "8863c6918a7e247aa893c055a052bbaa3bd1b2731207006e5403e8742c844605" + + "d4aa64963b0af4978fbb7a1e409ca5470395dc24a700a850b189039f87bd2c78" + + "58f28a9e2c624ecea50f1f7889f6a99cfcfacb0c5861586c3e97a41d855319d5" + + "930e0aba9eb1b1bf013b32f6cb0d1570243eda540538a288d13bad128907d079" + + "25153925e9253c117e82ab0c27c49fc68162e465b0e3fb7c1671c0965c71b583" + + "6862301a85606122896e8d9a6d848c1818580ddc7955f3446f148338ca14149a" + + "205a3c6bb43e30abcf20a69f36a7a5e60efdf69b5922cd6a7744e420311f3b9c" + + "b2148ab6ea7e11b522c81ab0209a43f8da598f580fc8536b2838bb8ca0c26690" + + "5270609304ab78d6c40fa8a7015862b80ebc34a9ea25959a3c3fc4abd56a7961" + + "6215f2caaacb51033e02ba4b446c0fd47e515ca9ad44c7e3a2739b9b69242a6c" + + "c528880491c72c7858ac13804db088a97541ed11208b369bfab3c8e4890ff1c7" + + "3f2d783004c482458980eeb21f40c73d5d5282a78279d6f71bad650843c83d03" + + "23a7e90748b555853a50c091c31a9655b2388226e4524cdcbabb61543eb7caa8" + + "8396933e1a5986986e41da209f131a9bbc9a44d66ba077229c499386a4418b99" + + "0e20707c323846c861bcf227b2ff6ca3ee6511b37c190618c1e610be4672507f" + + "f7c7f5486930670d99bb6c2c28afaca54b815144aed10f82c27ad8922d5a9c86" + + "b15c9da866ad0ff96a2f569b42d52cdec8793d0414cfc35ddcebca8066b25e93" + + "0b36743db6b05a6ec6641b9403f88a5320842522f388683c72eb60bfe1db314a" + + "0b78a0a0cc76a692137c9613bb47c3f54911e67c1ba48a46081fcaf0974783ad" + + "ddc88492943d8b5934a41b46f95120fce5a21e0223dee7b140a924ce711e0e93" + + "c434616b58b02c2e30352599380ca04127f40a4134c5862c3bab08a3f265a467" + + "9c989cfc1f24c9c553cabb37ab2767f5ab2d7a20e57b0b65fb6e4a6119ba33a7" + + "36a9568de2ee95c312aad4c14639282831a3461d6e08800b592c8aa9d7ef6028"); + byte[] publicKey = NativeCrypto.MLKEM1024_public_key_from_seed(privateKey); + assertArrayEquals(expectedPublicKey, publicKey); + + byte[] privateKeyTooShort = Arrays.copyOf(privateKey, privateKey.length - 1); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLKEM1024_public_key_from_seed(privateKeyTooShort)); + byte[] privateKeyTooLong = Arrays.copyOf(privateKey, privateKey.length + 1); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLKEM1024_public_key_from_seed(privateKeyTooLong)); + } + @Test public void xwingPublicKeyFromSeed_returnsPublicKeyIfPrivateKeyIsValid() throws Exception { // test vector from @@ -3983,6 +4098,8 @@ public void xwingPublicKeyFromSeed_returnsPublicKeyIfPrivateKeyIsValid() throws private static final int DHKEM_X25519_HKDF_SHA256 = 0x0020; private static final int DHKEM_X448_HKDF_SHA256 = 0x0021; private static final int XWING = 0x647a; + private static final int MLKEM768 = 0x0041; + private static final int MLKEM1024 = 0x0042; // KDF IDs private static final int HKDF_SHA256 = 0x0001; private static final int HKDF_SHA384 = 0x0002; @@ -4032,7 +4149,7 @@ public void hpkeWithXwing_publicKeyFromSeedSealOpen_success() throws Exception { byte[] aad = decodeHex("cc"); Object[] result = NativeCrypto.EVP_HPKE_CTX_setup_base_mode_sender( - XWING, HKDF_SHA256, HKDF_SHA256, publicKey, info); + XWING, HKDF_SHA256, AES_128_GCM, publicKey, info); NativeRef.EVP_HPKE_CTX ctxSender = (NativeRef.EVP_HPKE_CTX) result[0]; byte[] encapsulated = (byte[]) result[1]; assertEquals(1120, encapsulated.length); @@ -4041,8 +4158,61 @@ public void hpkeWithXwing_publicKeyFromSeedSealOpen_success() throws Exception { NativeRef.EVP_HPKE_CTX ctxRecipient = (NativeRef.EVP_HPKE_CTX) NativeCrypto.EVP_HPKE_CTX_setup_base_mode_recipient( - /* kem= */ 0x647a, /*kdf=*/0x0001, /* aead= */ 0x0001, privateKey, - encapsulated, info); + XWING, HKDF_SHA256, AES_128_GCM, privateKey, encapsulated, info); + byte[] output = NativeCrypto.EVP_HPKE_CTX_open(ctxRecipient, ciphertext, aad); + + assertArrayEquals(plaintext, output); + } + + @Test + public void hpkeWithMLKEM768_publicKeyFromSeedSealOpen_success() throws Exception { + byte[] privateKey = decodeHex( + "06f7d4f1495a828789f5543cb847369e10751ca5369a473c74e46043080f94f525f2f8cb7d8cfbf3" + + "cf8496728611a6567afd446a6ed1d22f6d32f74ef266a97e"); + byte[] publicKey = NativeCrypto.MLKEM768_public_key_from_seed(privateKey); + + byte[] info = decodeHex("aa"); + byte[] plaintext = decodeHex("bb"); + byte[] aad = decodeHex("cc"); + + Object[] result = NativeCrypto.EVP_HPKE_CTX_setup_base_mode_sender( + MLKEM768, HKDF_SHA256, AES_128_GCM, publicKey, info); + NativeRef.EVP_HPKE_CTX ctxSender = (NativeRef.EVP_HPKE_CTX) result[0]; + byte[] encapsulated = (byte[]) result[1]; + assertEquals(1088, encapsulated.length); + + byte[] ciphertext = NativeCrypto.EVP_HPKE_CTX_seal(ctxSender, plaintext, aad); + + NativeRef.EVP_HPKE_CTX ctxRecipient = + (NativeRef.EVP_HPKE_CTX) NativeCrypto.EVP_HPKE_CTX_setup_base_mode_recipient( + MLKEM768, HKDF_SHA256, AES_128_GCM, privateKey, encapsulated, info); + byte[] output = NativeCrypto.EVP_HPKE_CTX_open(ctxRecipient, ciphertext, aad); + + assertArrayEquals(plaintext, output); + } + + @Test + public void hpkeWithMLKEM1024_publicKeyFromSeedSealOpen_success() throws Exception { + byte[] privateKey = + decodeHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"); + byte[] publicKey = NativeCrypto.MLKEM1024_public_key_from_seed(privateKey); + + byte[] info = decodeHex("aa"); + byte[] plaintext = decodeHex("bb"); + byte[] aad = decodeHex("cc"); + + Object[] result = NativeCrypto.EVP_HPKE_CTX_setup_base_mode_sender( + MLKEM1024, HKDF_SHA256, AES_128_GCM, publicKey, info); + NativeRef.EVP_HPKE_CTX ctxSender = (NativeRef.EVP_HPKE_CTX) result[0]; + byte[] encapsulated = (byte[]) result[1]; + assertEquals(1568, encapsulated.length); + + byte[] ciphertext = NativeCrypto.EVP_HPKE_CTX_seal(ctxSender, plaintext, aad); + + NativeRef.EVP_HPKE_CTX ctxRecipient = + (NativeRef.EVP_HPKE_CTX) NativeCrypto.EVP_HPKE_CTX_setup_base_mode_recipient( + MLKEM1024, HKDF_SHA256, AES_128_GCM, privateKey, encapsulated, info); byte[] output = NativeCrypto.EVP_HPKE_CTX_open(ctxRecipient, ciphertext, aad); assertArrayEquals(plaintext, output); diff --git a/platform/src/main/ct_log_store.fbs b/platform/src/main/ct_log_store.fbs new file mode 100644 index 000000000..9a15f998a --- /dev/null +++ b/platform/src/main/ct_log_store.fbs @@ -0,0 +1,38 @@ +namespace com.android.org.conscrypt.ct.fbs; + +file_identifier "CTFB"; +file_extension "ctfb"; + +enum LogType:byte { + Unknown = 0, + Rfc6962 = 1, + Static = 2 +} + +enum LogState:byte { + Unknown = 0, + Pending = 1, + Qualified = 2, + Usable = 3, + Readonly = 4, + Retired = 5, + Rejected = 6 +} + +table Log { + log_id:string (key); + public_key:[byte]; + operator:string; + type:LogType; + state:LogState; + state_timestamp:long; +} + +table LogList { + version_major:long; + version_minor:long; + timestamp:long; + logs:[Log]; +} + +root_type LogList; diff --git a/platform/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java b/platform/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java new file mode 100644 index 000000000..ff0f9a625 --- /dev/null +++ b/platform/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import org.conscrypt.metrics.CertificateTransparencyVerificationReason; + +/** + * ConscryptNetworkSecurityPolicy for the platform (mainline). + * + * The Conscrypt-internal interface NetworkSecurityPolicy is ignored when exporting the API. + */ +@SuppressWarnings("HiddenSuperclass") +public class ConscryptNetworkSecurityPolicy implements NetworkSecurityPolicy { + private final libcore.net.NetworkSecurityPolicy policy; + + public static ConscryptNetworkSecurityPolicy getDefault() { + return new ConscryptNetworkSecurityPolicy(libcore.net.NetworkSecurityPolicy.getInstance()); + } + + public ConscryptNetworkSecurityPolicy(libcore.net.NetworkSecurityPolicy policy) { + this.policy = policy; + } + + @Override + public boolean isCertificateTransparencyVerificationRequired(String hostname) { + return policy.isCertificateTransparencyVerificationRequired(hostname); + } + + @Override + public CertificateTransparencyVerificationReason getCertificateTransparencyVerificationReason( + String hostname) { + if (Platform.isSdkGreater(33) + && com.android.libcore.Flags.networkSecurityPolicyReasonCtEnabledApi()) { + CertificateTransparencyVerificationReason reason = plaformCtReasonToConscryptReason( + policy.getCertificateTransparencyVerificationReason(hostname)); + if (reason != CertificateTransparencyVerificationReason.UNKNOWN) { + return reason; + } + } + if (policy.isCertificateTransparencyVerificationRequired("")) { + return CertificateTransparencyVerificationReason.APP_OPT_IN; + } else if (policy.isCertificateTransparencyVerificationRequired(hostname)) { + return CertificateTransparencyVerificationReason.DOMAIN_OPT_IN; + } + return CertificateTransparencyVerificationReason.UNKNOWN; + } + + private static CertificateTransparencyVerificationReason plaformCtReasonToConscryptReason( + int platformReason) { + switch (platformReason) { + case libcore.net.NetworkSecurityPolicy.CERTIFICATE_TRANSPARENCY_REASON_APP_OPT_IN: + return CertificateTransparencyVerificationReason.APP_OPT_IN; + case libcore.net.NetworkSecurityPolicy.CERTIFICATE_TRANSPARENCY_REASON_DOMAIN_OPT_IN: + return CertificateTransparencyVerificationReason.DOMAIN_OPT_IN; + case libcore.net.NetworkSecurityPolicy + .CERTIFICATE_TRANSPARENCY_REASON_SDK_TARGET_DEFAULT_ENABLED: + return CertificateTransparencyVerificationReason.SDK_TARGET_DEFAULT_ENABLED; + default: + return CertificateTransparencyVerificationReason.UNKNOWN; + } + } + + @Override + public DomainEncryptionMode getDomainEncryptionMode(String hostname) { + // Domain encryption is enabled if it is supported by the platform AND + // the API is available in libcore. + if (org.conscrypt.net.flags.Flags.encryptedClientHelloPlatform() + && com.android.libcore.Flags.networkSecurityPolicyEchApi()) { + return platformToConscryptEncryptionMode(policy.getDomainEncryptionMode(hostname)); + } + return DomainEncryptionMode.UNKNOWN; + } + + private static DomainEncryptionMode platformToConscryptEncryptionMode(int platformMode) { + switch (platformMode) { + case libcore.net.NetworkSecurityPolicy.DOMAIN_ENCRYPTION_MODE_DISABLED: + return DomainEncryptionMode.DISABLED; + case libcore.net.NetworkSecurityPolicy.DOMAIN_ENCRYPTION_MODE_OPPORTUNISTIC: + return DomainEncryptionMode.OPPORTUNISTIC; + case libcore.net.NetworkSecurityPolicy.DOMAIN_ENCRYPTION_MODE_ENABLED: + return DomainEncryptionMode.ENABLED; + case libcore.net.NetworkSecurityPolicy.DOMAIN_ENCRYPTION_MODE_REQUIRED: + return DomainEncryptionMode.REQUIRED; + default: + return DomainEncryptionMode.UNKNOWN; + } + } +} diff --git a/platform/src/main/java/org/conscrypt/Hex.java b/platform/src/main/java/org/conscrypt/Hex.java index d88205e49..7cdfe0479 100644 --- a/platform/src/main/java/org/conscrypt/Hex.java +++ b/platform/src/main/java/org/conscrypt/Hex.java @@ -20,24 +20,12 @@ * Helper class for dealing with hexadecimal strings. */ @Internal -// public for testing by TrustedCertificateStoreTest -// TODO(nathanmittler): Move to InternalUtil? public final class Hex { private Hex() {} private final static char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - public static String bytesToHexString(byte[] bytes) { - char[] buf = new char[bytes.length * 2]; - int c = 0; - for (byte b : bytes) { - buf[c++] = DIGITS[(b >> 4) & 0xf]; - buf[c++] = DIGITS[b & 0xf]; - } - return new String(buf); - } - public static String intToHexString(int i, int minWidth) { int bufLen = 8; // Max number of hex digits in an int char[] buf = new char[bufLen]; @@ -49,4 +37,33 @@ public static String intToHexString(int i, int minWidth) { return new String(buf, cursor, bufLen - cursor); } + + public static byte[] decodeHex(String encoded) throws IllegalArgumentException { + if ((encoded.length() % 2) != 0) { + throw new IllegalArgumentException("Invalid input length: " + encoded.length()); + } + + int resultLengthBytes = encoded.length() / 2; + byte[] result = new byte[resultLengthBytes]; + + int resultOffset = 0; + int i = 0; + for (int len = encoded.length(); i < len; i += 2) { + result[resultOffset++] = + (byte) ((toDigit(encoded.charAt(i)) << 4) | toDigit(encoded.charAt(i + 1))); + } + + return result; + } + + private static int toDigit(char pseudoCodePoint) throws IllegalArgumentException { + if ('0' <= pseudoCodePoint && pseudoCodePoint <= '9') { + return pseudoCodePoint - '0'; + } else if ('a' <= pseudoCodePoint && pseudoCodePoint <= 'f') { + return 10 + (pseudoCodePoint - 'a'); + } else if ('A' <= pseudoCodePoint && pseudoCodePoint <= 'F') { + return 10 + (pseudoCodePoint - 'A'); + } + throw new IllegalArgumentException("Illegal char: " + pseudoCodePoint); + } } diff --git a/platform/src/main/java/org/conscrypt/Platform.java b/platform/src/main/java/org/conscrypt/Platform.java index ad7fb182a..33a7aa485 100644 --- a/platform/src/main/java/org/conscrypt/Platform.java +++ b/platform/src/main/java/org/conscrypt/Platform.java @@ -26,8 +26,7 @@ import dalvik.system.BlockGuard; import dalvik.system.CloseGuard; import dalvik.system.VMRuntime; - -import libcore.net.NetworkSecurityPolicy; +import dalvik.system.ZygoteHooks; import org.conscrypt.NativeCrypto; import org.conscrypt.ct.CertificateTransparency; @@ -37,12 +36,14 @@ import org.conscrypt.ct.PolicyImpl; import org.conscrypt.flags.Flags; import org.conscrypt.metrics.CertificateTransparencyVerificationReason; +import org.conscrypt.metrics.NoopStatsLog; import org.conscrypt.metrics.OptionalMethod; import org.conscrypt.metrics.Source; import org.conscrypt.metrics.StatsLog; import org.conscrypt.metrics.StatsLogImpl; import java.io.FileDescriptor; +import java.io.FileReader; import java.io.IOException; import java.lang.System; import java.lang.reflect.Field; @@ -66,6 +67,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.function.Supplier; import javax.crypto.spec.GCMParameterSpec; import javax.net.ssl.HttpsURLConnection; @@ -73,6 +75,7 @@ import javax.net.ssl.SNIMatcher; import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; @@ -90,8 +93,13 @@ private static class NoPreloadHolder { private static boolean DEPRECATED_TLS_V1 = true; private static boolean ENABLED_TLS_V1 = false; private static boolean FILTERED_TLS_V1 = true; + private static boolean RUNNING_IN_ZYGOTE = true; + private static final boolean canProbeZygote; + private static final boolean canCallZygoteMethod; static { + canProbeZygote = isSdkGreater(32); + canCallZygoteMethod = isSdkGreater(36); NativeCrypto.setTlsV1DeprecationStatus(DEPRECATED_TLS_V1, ENABLED_TLS_V1); } @@ -104,6 +112,7 @@ public static synchronized void setup(boolean deprecatedTlsV1, boolean enabledTl FILTERED_TLS_V1 = !enabledTlsV1; NoPreloadHolder.MAPPER.ping(); NativeCrypto.setTlsV1DeprecationStatus(DEPRECATED_TLS_V1, ENABLED_TLS_V1); + RUNNING_IN_ZYGOTE = inZygote(); } /** @@ -172,7 +181,8 @@ static void setSSLParameters(SSLParameters params, SSLParametersImpl impl, try { Method getNamedGroupsMethod = params.getClass().getMethod("getNamedGroups"); impl.setNamedGroups((String[]) getNamedGroupsMethod.invoke(params)); - } catch (NoSuchMethodException | IllegalArgumentException e) { + } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException + | InvocationTargetException e) { // Do nothing. } @@ -216,9 +226,11 @@ static void setSSLParameters(SSLParameters params, SSLParametersImpl impl, try { Method getNamedGroupsMethod = params.getClass().getMethod("getNamedGroups"); impl.setNamedGroups((String[]) getNamedGroupsMethod.invoke(params)); - } catch (NoSuchMethodException | IllegalArgumentException e) { + } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException + | InvocationTargetException e) { // Do nothing. } + List serverNames = params.getServerNames(); if (serverNames != null) { for (SNIServerName serverName : serverNames) { @@ -243,6 +255,7 @@ static void getSSLParameters(SSLParameters params, SSLParametersImpl impl, } catch (NoSuchMethodException | IllegalArgumentException e) { // Do nothing. } + if (impl.getUseSni() && AddressUtils.isValidSniHostname(engine.getHostname())) { params.setServerNames(Collections.singletonList( new SNIHostName(engine.getHostname()))); @@ -533,23 +546,8 @@ static boolean supportsX509ExtendedTrustManager() { return true; } - public static boolean isCTVerificationRequired(String hostname) { - if (Flags.certificateTransparencyPlatform()) { - return NetworkSecurityPolicy.getInstance() - .isCertificateTransparencyVerificationRequired(hostname); - } - return false; - } - - public static CertificateTransparencyVerificationReason reasonCTVerificationRequired( - String hostname) { - if (NetworkSecurityPolicy.getInstance().isCertificateTransparencyVerificationRequired("")) { - return CertificateTransparencyVerificationReason.APP_OPT_IN; - } else if (NetworkSecurityPolicy.getInstance() - .isCertificateTransparencyVerificationRequired(hostname)) { - return CertificateTransparencyVerificationReason.DOMAIN_OPT_IN; - } - return CertificateTransparencyVerificationReason.UNKNOWN; + static SSLException wrapInvalidEchDataException(SSLException e) { + return new android.net.ssl.InvalidEchDataException(e.getMessage()); } static boolean supportsConscryptCertStore() { @@ -574,11 +572,13 @@ static CertBlocklist newDefaultBlocklist() { return CertBlocklistImpl.getDefault(); } - static CertificateTransparency newDefaultCertificateTransparency() { + static CertificateTransparency newDefaultCertificateTransparency( + Supplier policySupplier) { org.conscrypt.ct.Policy policy = new org.conscrypt.ct.PolicyImpl(); org.conscrypt.ct.LogStore logStore = new org.conscrypt.ct.LogStoreImpl(policy); org.conscrypt.ct.Verifier verifier = new org.conscrypt.ct.Verifier(logStore); - return new CertificateTransparency(logStore, policy, verifier, getStatsLog()); + return new CertificateTransparency(logStore, policy, verifier, getStatsLog(), + policySupplier); } static boolean serverNamePermitted(SSLParametersImpl parameters, String serverName) { @@ -610,7 +610,14 @@ static long getMillisSinceBoot() { } public static StatsLog getStatsLog() { - return StatsLogImpl.getInstance(); + if (!RUNNING_IN_ZYGOTE) { + return StatsLogImpl.getInstance(); + } + if (!inZygote()) { + RUNNING_IN_ZYGOTE = false; + return StatsLogImpl.getInstance(); + } + return NoopStatsLog.getInstance(); } public static Source getStatsSource() { @@ -645,6 +652,31 @@ public static boolean isPakeSupported() { return true; } + private static boolean inZygote() { + if (canCallZygoteMethod) { + return ZygoteHooks.isInZygote(); + } + if (canProbeZygote) { + try { + Class zygoteHooksClass = Class.forName("dalvik.system.ZygoteHooks"); + Method inZygoteMethod = zygoteHooksClass.getDeclaredMethod("inZygote"); + Object inZygote = inZygoteMethod.invoke(null); + if (inZygote == null) { + return true; + } + return (boolean) inZygote; + } catch (IllegalAccessException | NullPointerException | InvocationTargetException + | ClassNotFoundException | NoSuchMethodException e) { + return true; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + // For previous releases, we have no mechanism to test if we are in Zygote. + // Assume we are not, to conserve the existing behaviour. + return false; + } + static Object getTargetSdkVersion() { try { Class vmRuntimeClass = Class.forName("dalvik.system.VMRuntime"); diff --git a/platform/src/main/java/org/conscrypt/ct/LogStoreImpl.java b/platform/src/main/java/org/conscrypt/ct/LogStoreImpl.java index fc875e907..e18c3ef3b 100644 --- a/platform/src/main/java/org/conscrypt/ct/LogStoreImpl.java +++ b/platform/src/main/java/org/conscrypt/ct/LogStoreImpl.java @@ -37,32 +37,29 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; @Internal public class LogStoreImpl implements LogStore { private static final Logger logger = Logger.getLogger(LogStoreImpl.class.getName()); - private static final String BASE_PATH = "misc/keychain/ct"; - private static final int COMPAT_VERSION = 1; - private static final String CURRENT = "current"; - private static final String LOG_LIST_FILENAME = "log_list.json"; - private static final Path DEFAULT_LOG_LIST; + private static final int COMPAT_VERSION = 2; + private static final Path logListPrefix; + private static final Path logListSuffix; + private static final long LOG_LIST_CHECK_INTERVAL_IN_MS = 10L * 60 * 1_000; // 10 minutes static { String androidData = System.getenv("ANDROID_DATA"); - String compatVersion = String.format("v%d", COMPAT_VERSION); - DEFAULT_LOG_LIST = - Paths.get(androidData, BASE_PATH, compatVersion, CURRENT, LOG_LIST_FILENAME); + // /data/misc/keychain/ct/v1/current/log_list.json + logListPrefix = Paths.get(androidData, "misc", "keychain", "ct"); + logListSuffix = Paths.get("current", "log_list.json"); } private final Path logList; @@ -73,20 +70,34 @@ public class LogStoreImpl implements LogStore { private int minorVersion; private long timestamp; private Map logs; + private long logListLastModified; + private Supplier clock; + private long logListLastChecked; - public LogStoreImpl(Policy policy) { - this(policy, DEFAULT_LOG_LIST); + /* We do not have access to InstantSource. Implement a similar pattern using Supplier. */ + static class SystemTimeSupplier implements Supplier { + @Override + public Long get() { + return System.currentTimeMillis(); + } } - public LogStoreImpl(Policy policy, Path logList) { - this(policy, logList, Platform.getStatsLog()); + private static Path getPathForCompatVersion(int compatVersion) { + String version = String.format("v%d", compatVersion); + return logListPrefix.resolve(version).resolve(logListSuffix); + } + + public LogStoreImpl(Policy policy) { + this(policy, getPathForCompatVersion(COMPAT_VERSION), Platform.getStatsLog(), + new SystemTimeSupplier()); } - public LogStoreImpl(Policy policy, Path logList, StatsLog metrics) { + public LogStoreImpl(Policy policy, Path logList, StatsLog metrics, Supplier clock) { this.state = State.UNINITIALIZED; this.policy = policy; this.logList = logList; this.metrics = metrics; + this.clock = clock; } @Override @@ -112,8 +123,7 @@ public int getMinorVersion() { @Override public int getCompatVersion() { - // Currently, there is only one compatibility version supported. If we - // are loaded or initialized, it means the expected compatibility + // If we are loaded or initialized, it means the expected compatibility // version was found. if (state == State.LOADED || state == State.COMPLIANT || state == State.NON_COMPLIANT) { return COMPAT_VERSION; @@ -123,6 +133,9 @@ public int getCompatVersion() { @Override public int getMinCompatVersionAvailable() { + if (Files.exists(getPathForCompatVersion(1))) { + return 1; + } return getCompatVersion(); } @@ -145,26 +158,55 @@ public LogInfo getKnownLog(byte[] logId) { /* Ensures the log list is loaded. * Returns true if the log list is usable. */ - private boolean ensureLogListIsLoaded() { - synchronized (this) { - State previousState = state; - if (state == State.UNINITIALIZED) { - state = loadLogList(); - } - if (state == State.LOADED && policy != null) { - state = policy.isLogStoreCompliant(this) ? State.COMPLIANT : State.NON_COMPLIANT; + private synchronized boolean ensureLogListIsLoaded() { + resetLogListIfRequired(); + State previousState = state; + if (state == State.UNINITIALIZED) { + state = loadLogList(); + } + if (state == State.LOADED && policy != null) { + state = policy.isLogStoreCompliant(this) ? State.COMPLIANT : State.NON_COMPLIANT; + } + if (state != previousState) { + metrics.updateCTLogListStatusChanged(this); + } + return state == State.COMPLIANT; + } + + private synchronized void resetLogListIfRequired() { + long now = clock.get(); + if (now >= this.logListLastChecked + && now < this.logListLastChecked + LOG_LIST_CHECK_INTERVAL_IN_MS) { + return; + } + this.logListLastChecked = now; + try { + long lastModified = Files.getLastModifiedTime(logList).toMillis(); + if (this.logListLastModified == lastModified) { + // The log list has the same last modified timestamp. Keep our + // current cached value. + return; } - if (state != previousState) { - metrics.updateCTLogListStatusChanged(this); + } catch (IOException e) { + if (this.logListLastModified == 0) { + // The log list is not accessible now and it has never been + // previously, there is nothing to do. + return; } - return state == State.COMPLIANT; } + this.state = State.UNINITIALIZED; + this.logs = null; + this.timestamp = 0; + this.majorVersion = 0; + this.minorVersion = 0; } private State loadLogList() { byte[] content; + long lastModified; try { content = Files.readAllBytes(logList); + lastModified = Files.getLastModifiedTime(logList).toMillis(); } catch (IOException e) { return State.NOT_FOUND; } @@ -182,39 +224,18 @@ private State loadLogList() { try { majorVersion = parseMajorVersion(json.getString("version")); minorVersion = parseMinorVersion(json.getString("version")); - timestamp = parseTimestamp(json.getString("log_list_timestamp")); + timestamp = json.getLong("log_list_timestamp"); JSONArray operators = json.getJSONArray("operators"); for (int i = 0; i < operators.length(); i++) { JSONObject operator = operators.getJSONObject(i); String operatorName = operator.getString("name"); - JSONArray logs = operator.getJSONArray("logs"); - for (int j = 0; j < logs.length(); j++) { - JSONObject log = logs.getJSONObject(j); - - LogInfo.Builder builder = - new LogInfo.Builder() - .setDescription(log.getString("description")) - .setPublicKey(parsePubKey(log.getString("key"))) - .setUrl(log.getString("url")) - .setOperator(operatorName); - JSONObject stateObject = log.optJSONObject("state"); - if (stateObject != null) { - String state = stateObject.keys().next(); - String stateTimestamp = - stateObject.getJSONObject(state).getString("timestamp"); - builder.setState(parseState(state), parseTimestamp(stateTimestamp)); - } - - LogInfo logInfo = builder.build(); - byte[] logId = Base64.getDecoder().decode(log.getString("log_id")); - - // The logId computed using the public key should match the log_id field. - if (!Arrays.equals(logInfo.getID(), logId)) { - throw new IllegalArgumentException("logId does not match publicKey"); - } + JSONArray logs = operator.getJSONArray("logs"); + addLogsToMap(logs, operatorName, LogInfo.TYPE_RFC6962, logsMap); - logsMap.put(new ByteArray(logId), logInfo); + JSONArray tiledLogs = operator.optJSONArray("tiled_logs"); + if (tiledLogs != null) { + addLogsToMap(tiledLogs, operatorName, LogInfo.TYPE_STATIC_CT_API, logsMap); } } } catch (JSONException | IllegalArgumentException e) { @@ -222,9 +243,45 @@ private State loadLogList() { return State.MALFORMED; } this.logs = Collections.unmodifiableMap(logsMap); + this.logListLastModified = lastModified; return State.LOADED; } + private void addLogsToMap(JSONArray logs, String operatorName, int logType, + Map logsMap) throws JSONException { + for (int j = 0; j < logs.length(); j++) { + JSONObject log = logs.getJSONObject(j); + LogInfo.Builder builder = new LogInfo.Builder() + .setPublicKey(parsePubKey(log.getString("key"))) + .setType(logType) + .setOperator(operatorName); + JSONObject stateObject = log.optJSONObject("state"); + if (stateObject != null) { + String state = stateObject.keys().next(); + long stateTimestamp = stateObject.getJSONObject(state).getLong("timestamp"); + builder.setState(parseState(state), stateTimestamp); + } + LogInfo logInfo = builder.build(); + + String logIdFromList = log.getString("log_id"); + // The logId computed using the public key should match the log_id field. + byte[] logId = Base64.getDecoder().decode(logIdFromList); + if (!Arrays.equals(logInfo.getID(), logId)) { + throw new IllegalArgumentException("logId does not match publicKey"); + } + + // Verify that the log is in a known state now. This might fail if + // there is an issue with the device's clock which can cause false + // positives when validating SCTs. + if (logInfo.getStateAt(clock.get()) == LogInfo.STATE_UNKNOWN) { + throw new IllegalArgumentException("Log current state is " + + "unknown, logId: " + logIdFromList); + } + + logsMap.put(new ByteArray(logId), logInfo); + } + } + private static int parseMajorVersion(String version) { int pos = version.indexOf("."); if (pos == -1) { @@ -268,19 +325,6 @@ private static int parseState(String state) { } } - // ISO 8601 - private static DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"); - - @SuppressWarnings("JavaUtilDate") - private static long parseTimestamp(String timestamp) { - try { - Date date = dateFormatter.parse(timestamp); - return date.getTime(); - } catch (ParseException e) { - throw new IllegalArgumentException(e); - } - } - private static PublicKey parsePubKey(String key) { byte[] pem = ("-----BEGIN PUBLIC KEY-----\n" + key + "\n-----END PUBLIC KEY-----") .getBytes(US_ASCII); diff --git a/platform/src/main/java/org/conscrypt/ct/PolicyImpl.java b/platform/src/main/java/org/conscrypt/ct/PolicyImpl.java index a606557fa..dd48cf971 100644 --- a/platform/src/main/java/org/conscrypt/ct/PolicyImpl.java +++ b/platform/src/main/java/org/conscrypt/ct/PolicyImpl.java @@ -178,7 +178,7 @@ private PolicyCompliance conformEmbeddedSCTs(Set embeddedValidSCTs, return PolicyCompliance.NOT_ENOUGH_SCTS; } - /* 3. Among the SCTs satisfying requirements 1 and 2, at least two SCTs + /* 3. Among the SCTs satisfying requirements 2, at least two SCTs * must be issued from distinct CT Log Operators as recognized by * Chrome. */ @@ -190,6 +190,20 @@ private PolicyCompliance conformEmbeddedSCTs(Set embeddedValidSCTs, return PolicyCompliance.NOT_ENOUGH_DIVERSE_SCTS; } + /* 4. Among the SCTs satisfying requirement 2, at least one SCT must be + * issued from a log recognized by Chrome as being RFC6962-compliant. + */ + boolean foundRfc6962Log = false; + for (LogInfo logInfo : validLogs) { + if (logInfo.getType() == LogInfo.TYPE_RFC6962) { + foundRfc6962Log = true; + break; + } + } + if (!foundRfc6962Log) { + return PolicyCompliance.NO_RFC6962_LOG; + } + return PolicyCompliance.COMPLY; } @@ -223,6 +237,20 @@ private PolicyCompliance conformOCSPorTLSSCTs(Set ocspOrTLSValidSCT return PolicyCompliance.NOT_ENOUGH_DIVERSE_SCTS; } + /* 3. Among the SCTs satisfying requirement 1, at least one SCT must be + * issued from a log recognized by Chrome as being RFC6962-compliant. + */ + boolean foundRfc6962Log = false; + for (LogInfo logInfo : validLogs) { + if (logInfo.getType() == LogInfo.TYPE_RFC6962) { + foundRfc6962Log = true; + break; + } + } + if (!foundRfc6962Log) { + return PolicyCompliance.NO_RFC6962_LOG; + } + return PolicyCompliance.COMPLY; } } diff --git a/platform/src/test/java/org/conscrypt/CertBlocklistTest.java b/platform/src/test/java/org/conscrypt/CertBlocklistTest.java index 31341ae9a..e37730b3b 100644 --- a/platform/src/test/java/org/conscrypt/CertBlocklistTest.java +++ b/platform/src/test/java/org/conscrypt/CertBlocklistTest.java @@ -16,9 +16,13 @@ package org.conscrypt; +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import org.conscrypt.metrics.NoopStatsLog; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -29,6 +33,7 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Collection; import javax.net.ssl.X509TrustManager; @@ -102,6 +107,44 @@ public void testBlocklistedIntermediateFallback() throws Exception { assertUntrusted(chain, getTrustManager(blocklistedCa)); } + static class FakeStatsLog extends NoopStatsLog { + public ArrayList entries = new ArrayList<>(); + + @Override + public void reportBlocklistHit(CertBlocklistEntry entry) { + entries.add(entry); + } + } + + @Test + public void isPublicKeyBlockListed_hitBlocklist_hitIsReported() throws Exception { + X509Certificate blocklistedCa = loadCertificate(BLOCKLIST_CA); + FakeStatsLog fakeMetrics = new FakeStatsLog(); + CertBlocklist blocklist = + new CertBlocklistImpl.Builder().loadAllDefaults().setMetrics(fakeMetrics).build(); + + blocklist.isPublicKeyBlockListed(blocklistedCa.getPublicKey()); + + assertThat(fakeMetrics.entries).hasSize(1); + CertBlocklistEntry entry = fakeMetrics.entries.get(0); + assertThat(entry.getOrigin()).isEqualTo(CertBlocklistEntry.Origin.SHA1_TEST); + assertThat(entry.getIndex()).isEqualTo(0); + } + + @Test + public void isPublicKeyBlockListed_hitBlocklistTwiceWithSameKey_hitIsReportedTwice() + throws Exception { + X509Certificate blocklistedCa = loadCertificate(BLOCKLIST_CA); + FakeStatsLog fakeMetrics = new FakeStatsLog(); + CertBlocklist blocklist = + new CertBlocklistImpl.Builder().loadAllDefaults().setMetrics(fakeMetrics).build(); + + blocklist.isPublicKeyBlockListed(blocklistedCa.getPublicKey()); + blocklist.isPublicKeyBlockListed(blocklistedCa.getPublicKey()); + + assertThat(fakeMetrics.entries).hasSize(2); + } + private static X509Certificate loadCertificate(String file) throws Exception { return loadCertificates(file)[0]; } diff --git a/platform/src/test/java/org/conscrypt/ct/LogStoreImplTest.java b/platform/src/test/java/org/conscrypt/ct/LogStoreImplTest.java index 1af49b4ef..a27812a2e 100644 --- a/platform/src/test/java/org/conscrypt/ct/LogStoreImplTest.java +++ b/platform/src/test/java/org/conscrypt/ct/LogStoreImplTest.java @@ -24,25 +24,24 @@ import org.conscrypt.OpenSSLKey; import org.conscrypt.metrics.NoopStatsLog; +import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FileWriter; import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.security.PublicKey; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Base64; +import java.util.function.Supplier; @RunWith(JUnit4.class) public class LogStoreImplTest { + /** FakeStatsLog captures the events being reported */ static class FakeStatsLog extends NoopStatsLog { public ArrayList states = new ArrayList(); @@ -76,13 +75,31 @@ public PolicyCompliance doesResultConformToPolicy(VerificationResult result, } }; - @Test - public void test_loadValidLogList() throws Exception { - // clang-format off - String content = "" + + /* Time supplier that can be set to any arbitrary time */ + static class TimeSupplier implements Supplier { + private long currentTimeInMs; + + TimeSupplier(long currentTimeInMs) { + this.currentTimeInMs = currentTimeInMs; + } + + @Override + public Long get() { + return currentTimeInMs; + } + + public void setCurrentTimeInMs(long currentTimeInMs) { + this.currentTimeInMs = currentTimeInMs; + } + } + + private static final long JAN2024 = 1704103200000L; + private static final long JAN2022 = 1641031200000L; + // clang-format off + static final String validLogList = "" + "{" + " \"version\": \"1.1\"," + -" \"log_list_timestamp\": \"2024-01-01T11:55:12Z\"," + +" \"log_list_timestamp\": 1704070861000," + " \"operators\": [" + " {" + " \"name\": \"Operator 1\"," + @@ -96,12 +113,12 @@ public void test_loadValidLogList() throws Exception { " \"mmd\": 86400," + " \"state\": {" + " \"usable\": {" + -" \"timestamp\": \"2022-11-01T18:54:00Z\"" + +" \"timestamp\": 1667328840000" + " }" + " }," + " \"temporal_interval\": {" + -" \"start_inclusive\": \"2024-01-01T00:00:00Z\"," + -" \"end_exclusive\": \"2025-01-01T00:00:00Z\"" + +" \"start_inclusive\": 1704070861000," + +" \"end_exclusive\": 1735693261000" + " }" + " }," + " {" + @@ -112,12 +129,12 @@ public void test_loadValidLogList() throws Exception { " \"mmd\": 86400," + " \"state\": {" + " \"usable\": {" + -" \"timestamp\": \"2023-11-26T12:00:00Z\"" + +" \"timestamp\": 1700960461000" + " }" + " }," + " \"temporal_interval\": {" + -" \"start_inclusive\": \"2025-01-01T00:00:00Z\"," + -" \"end_exclusive\": \"2025-07-01T00:00:00Z\"" + +" \"start_inclusive\": 1735693261000," + +" \"end_exclusive\": 1751331661000" + " }" + " }" + " ]" + @@ -134,26 +151,58 @@ public void test_loadValidLogList() throws Exception { " \"mmd\": 86400," + " \"state\": {" + " \"usable\": {" + -" \"timestamp\": \"2022-11-30T17:00:00Z\"" + +" \"timestamp\": 1669770061000" + " }" + " }," + " \"temporal_interval\": {" + -" \"start_inclusive\": \"2024-01-01T00:00:00Z\"," + -" \"end_exclusive\": \"2025-01-01T00:00:00Z\"" + +" \"start_inclusive\": 1704070861000," + +" \"end_exclusive\": 1735693261000" + +" }" + +" }" + +" ]," + +" \"tiled_logs\": [" + +" {" + +" \"description\": \"Operator 2 'Test2025' log\"," + +" \"log_id\": \"DleUvPOuqT4zGyyZB7P3kN+bwj1xMiXdIaklrGHFTiE=\"," + +" \"key\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEB/we6GOO/xwxivy4HhkrYFAAPo6e2nc346Wo2o2U+GvoPWSPJz91s/xrEvA3Bk9kWHUUXVZS5morFEzsgdHqPg==\"," + +" \"submission_url\": \"https://operator2.example.com/tiled/test2025\"," + +" \"monitoring_url\": \"https://operator2.exmaple.com/tiled_monitor/test2025\"," + +" \"mmd\": 86400," + +" \"state\": {" + +" \"usable\": {" + +" \"timestamp\": 1667328840000" + +" }" + +" }," + +" \"temporal_interval\": {" + +" \"start_inclusive\": 1767225600000," + +" \"end_exclusive\": 1782864000000" + " }" + " }" + " ]" + " }" + " ]" + "}"; - // clang-format on + // clang-format on - FakeStatsLog metrics = new FakeStatsLog(); - File logList = writeFile(content); - LogStore store = new LogStoreImpl(alwaysCompliantStorePolicy, logList.toPath(), metrics); + Path grandparentDir; + Path parentDir; + Path logList; - assertNull("A null logId should return null", store.getKnownLog(null)); + @After + public void tearDown() throws Exception { + if (logList != null) { + Files.deleteIfExists(logList); + Files.deleteIfExists(parentDir); + Files.deleteIfExists(grandparentDir); + } + } + @Test + public void loadValidLogList_returnsCompliantState() throws Exception { + FakeStatsLog metrics = new FakeStatsLog(); + logList = writeLogList(validLogList); + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2024); + LogStore store = new LogStoreImpl(alwaysCompliantStorePolicy, logList, metrics, fakeTime); byte[] pem = ("-----BEGIN PUBLIC KEY-----\n" + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHblsqctplMVc5ramA7vSuNxUQxcomQwGAVAdnW" + "TAWUYr" @@ -161,55 +210,163 @@ public void test_loadValidLogList() throws Exception { + "\n-----END PUBLIC KEY-----\n") .getBytes(US_ASCII); ByteArrayInputStream is = new ByteArrayInputStream(pem); - LogInfo log1 = new LogInfo.Builder() .setPublicKey(OpenSSLKey.fromPublicKeyPemInputStream(is).getPublicKey()) - .setDescription("Operator 1 'Test2024' log") - .setUrl("https://operator1.example.com/logs/test2024/") + .setType(LogInfo.TYPE_RFC6962) .setState(LogInfo.STATE_USABLE, 1667328840000L) .setOperator("Operator 1") .build(); byte[] log1Id = Base64.getDecoder().decode("7s3QZNXbGs7FXLedtM0TojKHRny87N7DUUhZRnEftZs="); + + assertNull("A null logId should return null", store.getKnownLog(/* logId= */ null)); assertEquals("An existing logId should be returned", log1, store.getKnownLog(log1Id)); - assertEquals("One metric update should be emitted", metrics.states.size(), 1); + assertEquals("One metric update should be emitted", 1, metrics.states.size()); assertEquals("The metric update for log list state should be compliant", - metrics.states.get(0), LogStore.State.COMPLIANT); + LogStore.State.COMPLIANT, metrics.states.get(0)); } @Test - public void test_loadMalformedLogList() throws Exception { + public void loadMalformedLogList_returnsMalformedState() throws Exception { FakeStatsLog metrics = new FakeStatsLog(); String content = "}}"; - File logList = writeFile(content); - LogStore store = new LogStoreImpl(alwaysCompliantStorePolicy, logList.toPath(), metrics); + logList = writeLogList(content); + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2024); + LogStore store = new LogStoreImpl(alwaysCompliantStorePolicy, logList, metrics, fakeTime); + + assertEquals("The log state should be malformed", LogStore.State.MALFORMED, + store.getState()); + assertEquals("One metric update should be emitted", 1, metrics.states.size()); + assertEquals("The metric update for log list state should be malformed", + LogStore.State.MALFORMED, metrics.states.get(0)); + } - assertEquals("The log state should be malformed", store.getState(), - LogStore.State.MALFORMED); - assertEquals("One metric update should be emitted", metrics.states.size(), 1); + @Test + public void loadFutureLogList_returnsMalformedState() throws Exception { + FakeStatsLog metrics = new FakeStatsLog(); + logList = writeLogList(validLogList); // The logs are usable from 2024 onwards. + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2022); + LogStore store = new LogStoreImpl(alwaysCompliantStorePolicy, logList, metrics, fakeTime); + + assertEquals("The log state should be malformed", LogStore.State.MALFORMED, + store.getState()); + assertEquals("One metric update should be emitted", 1, metrics.states.size()); assertEquals("The metric update for log list state should be malformed", - metrics.states.get(0), LogStore.State.MALFORMED); + LogStore.State.MALFORMED, metrics.states.get(0)); } @Test - public void test_loadMissingLogList() throws Exception { + public void loadMissingLogList_returnsNotFoundState() throws Exception { FakeStatsLog metrics = new FakeStatsLog(); - File logList = new File("does_not_exist"); - LogStore store = new LogStoreImpl(alwaysCompliantStorePolicy, logList.toPath(), metrics); + Path missingLogList = Paths.get("missing_dir", "missing_subdir", "does_not_exist_log_list"); + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2024); + LogStore store = + new LogStoreImpl(alwaysCompliantStorePolicy, missingLogList, metrics, fakeTime); - assertEquals("The log state should be not found", store.getState(), - LogStore.State.NOT_FOUND); - assertEquals("One metric update should be emitted", metrics.states.size(), 1); + assertEquals("The log state should be not found", LogStore.State.NOT_FOUND, + store.getState()); + assertEquals("One metric update should be emitted", 1, metrics.states.size()); assertEquals("The metric update for log list state should be not found", - metrics.states.get(0), LogStore.State.NOT_FOUND); + LogStore.State.NOT_FOUND, metrics.states.get(0)); } - private File writeFile(String content) throws IOException { - File file = File.createTempFile("test", null); - file.deleteOnExit(); - try (FileWriter fw = new FileWriter(file)) { - fw.write(content); - } + @Test + public void loadMissingAndThenFoundLogList_logListIsLoaded() throws Exception { + // Arrange + FakeStatsLog metrics = new FakeStatsLog(); + // Allocate a temporary file path and delete it. We keep the temporary + // path so that we can add a valid log list later on. + logList = writeLogList(""); + Files.deleteIfExists(logList); + Files.deleteIfExists(parentDir); + Files.deleteIfExists(grandparentDir); + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2024); + LogStore store = new LogStoreImpl(alwaysCompliantStorePolicy, logList, metrics, fakeTime); + assertEquals("The log state should be not found", LogStore.State.NOT_FOUND, + store.getState()); + + // Act + Files.createDirectory(grandparentDir); + Files.createDirectory(parentDir); + Files.write(logList, validLogList.getBytes()); + + // Assert + // 5min < 10min, we should not check the log list yet. + fakeTime.setCurrentTimeInMs(JAN2024 + 5L * 60 * 1000); + assertEquals("The log state should be not found", LogStore.State.NOT_FOUND, + store.getState()); + + // 12min, the log list should be reloadable. + fakeTime.setCurrentTimeInMs(JAN2024 + 12L * 60 * 1000); + assertEquals("The log state should be compliant", LogStore.State.COMPLIANT, + store.getState()); + } + + @Test + public void loadMissingThenTimeTravelBackwardsAndThenFoundLogList_logListIsLoaded() + throws Exception { + FakeStatsLog metrics = new FakeStatsLog(); + // Allocate a temporary file path and delete it. We keep the temporary + // path so that we can add a valid log list later on. + logList = writeLogList(""); + Files.deleteIfExists(logList); + Files.deleteIfExists(parentDir); + Files.deleteIfExists(grandparentDir); + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2024 + 100L * 60 * 1000); + LogStore store = new LogStoreImpl(alwaysCompliantStorePolicy, logList, metrics, fakeTime); + assertEquals("The log state should be not found", LogStore.State.NOT_FOUND, + store.getState()); + + Files.createDirectory(grandparentDir); + Files.createDirectory(parentDir); + Files.write(logList, validLogList.getBytes()); + // Move back in time. + fakeTime.setCurrentTimeInMs(JAN2024); + + assertEquals("The log state should be compliant", LogStore.State.COMPLIANT, + store.getState()); + } + + @Test + public void loadExistingAndThenRemovedLogList_logListIsNotFound() throws Exception { + FakeStatsLog metrics = new FakeStatsLog(); + logList = writeLogList(validLogList); + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2024); + LogStore store = new LogStoreImpl(alwaysCompliantStorePolicy, logList, metrics, fakeTime); + assertEquals("The log should be loaded", LogStore.State.COMPLIANT, store.getState()); + + Files.delete(logList); + // 12min, the log list should be reloadable. + fakeTime.setCurrentTimeInMs(JAN2024 + 12L * 60 * 1000); + + assertEquals("The log should have been refreshed", LogStore.State.NOT_FOUND, + store.getState()); + } + + @Test + public void loadExistingLogListAndThenMoveDirectory_logListIsNotFound() throws Exception { + FakeStatsLog metrics = new FakeStatsLog(); + logList = writeLogList(validLogList); + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2024); + LogStore store = new LogStoreImpl(alwaysCompliantStorePolicy, logList, metrics, fakeTime); + assertEquals("The log should be loaded", LogStore.State.COMPLIANT, store.getState()); + + Path oldParentDir = parentDir; + parentDir = grandparentDir.resolve("more_current"); + Files.move(oldParentDir, parentDir); + logList = parentDir.resolve("log_list.json"); + // 12min, the log list should be reloadable. + fakeTime.setCurrentTimeInMs(JAN2024 + 12L * 60 * 1000); + + assertEquals("The log should have been refreshed", LogStore.State.NOT_FOUND, + store.getState()); + } + + private Path writeLogList(String content) throws IOException { + grandparentDir = Files.createTempDirectory("v1"); + parentDir = Files.createDirectory(grandparentDir.resolve("current")); + Path file = Files.createFile(parentDir.resolve("log_list.json")); + Files.write(file, content.getBytes()); return file; } } diff --git a/platform/src/test/java/org/conscrypt/ct/PolicyImplTest.java b/platform/src/test/java/org/conscrypt/ct/PolicyImplTest.java index cc3b40b42..7a1a13712 100644 --- a/platform/src/test/java/org/conscrypt/ct/PolicyImplTest.java +++ b/platform/src/test/java/org/conscrypt/ct/PolicyImplTest.java @@ -36,9 +36,11 @@ public class PolicyImplTest { private static final String OPERATOR2 = "operator 2"; private static LogInfo usableOp1Log1; private static LogInfo usableOp1Log2; + private static LogInfo usableStaticOp1Log; private static LogInfo retiredOp1LogOld; private static LogInfo retiredOp1LogNew; private static LogInfo usableOp2Log; + private static LogInfo usableStaticOp2Log; private static LogInfo retiredOp2Log; private static SignedCertificateTimestamp embeddedSCT; private static SignedCertificateTimestamp ocspSCT; @@ -89,37 +91,49 @@ public static void setUp() { */ usableOp1Log1 = new LogInfo.Builder() .setPublicKey(new FakePublicKey(new byte[] {0x01})) - .setUrl("") + .setType(LogInfo.TYPE_RFC6962) .setOperator(OPERATOR1) .setState(LogInfo.STATE_USABLE, JAN2022) .build(); usableOp1Log2 = new LogInfo.Builder() .setPublicKey(new FakePublicKey(new byte[] {0x02})) - .setUrl("") + .setType(LogInfo.TYPE_RFC6962) .setOperator(OPERATOR1) .setState(LogInfo.STATE_USABLE, JAN2022) .build(); + usableStaticOp1Log = new LogInfo.Builder() + .setPublicKey(new FakePublicKey(new byte[] {0x07})) + .setType(LogInfo.TYPE_STATIC_CT_API) + .setOperator(OPERATOR1) + .setState(LogInfo.STATE_USABLE, JAN2022) + .build(); retiredOp1LogOld = new LogInfo.Builder() .setPublicKey(new FakePublicKey(new byte[] {0x03})) - .setUrl("") + .setType(LogInfo.TYPE_RFC6962) .setOperator(OPERATOR1) .setState(LogInfo.STATE_RETIRED, JAN2022) .build(); retiredOp1LogNew = new LogInfo.Builder() .setPublicKey(new FakePublicKey(new byte[] {0x06})) - .setUrl("") + .setType(LogInfo.TYPE_RFC6962) .setOperator(OPERATOR1) .setState(LogInfo.STATE_RETIRED, JUN2023) .build(); usableOp2Log = new LogInfo.Builder() .setPublicKey(new FakePublicKey(new byte[] {0x04})) - .setUrl("") + .setType(LogInfo.TYPE_RFC6962) .setOperator(OPERATOR2) .setState(LogInfo.STATE_USABLE, JAN2022) .build(); + usableStaticOp2Log = new LogInfo.Builder() + .setPublicKey(new FakePublicKey(new byte[] {0x08})) + .setType(LogInfo.TYPE_STATIC_CT_API) + .setOperator(OPERATOR2) + .setState(LogInfo.STATE_USABLE, JAN2022) + .build(); retiredOp2Log = new LogInfo.Builder() .setPublicKey(new FakePublicKey(new byte[] {0x05})) - .setUrl("") + .setType(LogInfo.TYPE_RFC6962) .setOperator(OPERATOR2) .setState(LogInfo.STATE_RETIRED, JAN2022) .build(); @@ -373,6 +387,71 @@ public void invalidOneEmbeddedOneOCSPVerificationResult() throws Exception { p.doesResultConformToPolicyAt(result, leaf, JAN2024)); } + public void validVerificationResultPartialStatic(SignedCertificateTimestamp sct) + throws Exception { + PolicyImpl p = new PolicyImpl(); + + VerifiedSCT vsct1 = new VerifiedSCT.Builder(sct) + .setStatus(VerifiedSCT.Status.VALID) + .setLogInfo(usableOp1Log1) + .build(); + + VerifiedSCT vsct2 = new VerifiedSCT.Builder(sct) + .setStatus(VerifiedSCT.Status.VALID) + .setLogInfo(usableStaticOp2Log) + .build(); + + VerificationResult result = new VerificationResult(); + result.add(vsct1); + result.add(vsct2); + + X509Certificate leaf = new FakeX509Certificate(); + assertEquals("Two valid SCTs from different operators", PolicyCompliance.COMPLY, + p.doesResultConformToPolicyAt(result, leaf, JAN2024)); + } + + @Test + public void validEmbeddedVerificationResultPartialStatic() throws Exception { + validVerificationResultPartialStatic(embeddedSCT); + } + + @Test + public void validOCSPVerificationResultPartialStatic() throws Exception { + validVerificationResultPartialStatic(ocspSCT); + } + + public void invalidTwoSctsAllStatic(SignedCertificateTimestamp sct) throws Exception { + PolicyImpl p = new PolicyImpl(); + + VerifiedSCT vsct1 = new VerifiedSCT.Builder(sct) + .setStatus(VerifiedSCT.Status.VALID) + .setLogInfo(usableStaticOp1Log) + .build(); + + VerifiedSCT vsct2 = new VerifiedSCT.Builder(sct) + .setStatus(VerifiedSCT.Status.VALID) + .setLogInfo(usableStaticOp2Log) + .build(); + + VerificationResult result = new VerificationResult(); + result.add(vsct1); + result.add(vsct2); + + X509Certificate leaf = new FakeX509Certificate(); + assertEquals("Two static SCTs", PolicyCompliance.NO_RFC6962_LOG, + p.doesResultConformToPolicyAt(result, leaf, JAN2024)); + } + + @Test + public void invalidEmbeddedTwoSctsAllStaticsVerificationResult() throws Exception { + invalidTwoSctsAllStatic(embeddedSCT); + } + + @Test + public void invalidOCSPTwoSctsAllStaticsVerificationResult() throws Exception { + invalidTwoSctsAllStatic(ocspSCT); + } + @Test public void validRecentLogStore() throws Exception { PolicyImpl p = new PolicyImpl(); diff --git a/testing/src/main/java/org/conscrypt/java/security/TestKeyStore.java b/testing/src/main/java/org/conscrypt/java/security/TestKeyStore.java index f3acc3ff0..3f0df8d16 100644 --- a/testing/src/main/java/org/conscrypt/java/security/TestKeyStore.java +++ b/testing/src/main/java/org/conscrypt/java/security/TestKeyStore.java @@ -98,7 +98,7 @@ public final class TestKeyStore { private static final int EC_KEY_SIZE_BITS = 256; /** Size of RSA keys to generate for testing. */ - private static final int RSA_KEY_SIZE_BITS = 1024; + private static final int RSA_KEY_SIZE_BITS = 2048; // Generated with: openssl dhparam -C 1024 private static final BigInteger DH_PARAMS_P = new BigInteger(