From 94d538afc6ff729489b4a38b42d54e76e8cb8f01 Mon Sep 17 00:00:00 2001 From: Miguel Aranda Date: Wed, 18 Mar 2026 15:57:00 +0000 Subject: [PATCH] Project import generated by Copybara. PiperOrigin-RevId: 885622312 --- .../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 | 742 ++++++++++++++++++ .../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 +- 77 files changed, 4278 insertions(+), 853 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/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..c744e6724 --- /dev/null +++ b/common/src/test/java/org/conscrypt/MlKemTest.java @@ -0,0 +1,742 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import static org.conscrypt.HpkeSuite.AEAD_AES_128_GCM; +import static org.conscrypt.HpkeSuite.AEAD_AES_256_GCM; +import static org.conscrypt.HpkeSuite.AEAD_CHACHA20POLY1305; +import static org.conscrypt.HpkeSuite.KDF_HKDF_SHA256; +import static org.conscrypt.HpkeSuite.KEM_MLKEM_1024; +import static org.conscrypt.HpkeSuite.KEM_MLKEM_768; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.List; + +@RunWith(JUnit4.class) +public class MlKemTest { + private final Provider conscryptProvider = TestUtils.getConscryptProvider(); + + @BeforeClass + public static void setUp() { + TestUtils.assumeAllowsUnsignedCrypto(); + } + + public static final class RawKeySpec extends EncodedKeySpec { + public RawKeySpec(byte[] encoded) { + super(encoded); + } + + @Override + public String getFormat() { + return "raw"; + } + } + + public static MlKemAlgorithm toMlKemAlgorithm(String algorithm) { + switch (algorithm) { + case "ML-KEM": + case "ML-KEM-768": + return MlKemAlgorithm.ML_KEM_768; + case "ML-KEM-1024": + return MlKemAlgorithm.ML_KEM_1024; + default: + throw new IllegalArgumentException("Unsupported algorithm: " + algorithm); + } + } + + @Test + public void generateKeyPair_works() throws Exception { + for (String keyGenAlgorithm : new String[] {"ML-KEM", "ML-KEM-768", "ML-KEM-1024"}) { + MlKemAlgorithm expectedAlgorithm = toMlKemAlgorithm(keyGenAlgorithm); + + KeyPairGenerator keyGen = + KeyPairGenerator.getInstance(keyGenAlgorithm, conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + OpenSslMlKemPrivateKey privateKey = (OpenSslMlKemPrivateKey) keyPair.getPrivate(); + OpenSslMlKemPublicKey publicKey = (OpenSslMlKemPublicKey) keyPair.getPublic(); + + assertEquals(expectedAlgorithm, privateKey.getMlKemAlgorithm()); + assertEquals("ML-KEM", privateKey.getAlgorithm()); + byte[] seed = privateKey.getSeed(); + assertEquals(64, seed.length); + + assertEquals(expectedAlgorithm, publicKey.getMlKemAlgorithm()); + assertEquals("ML-KEM", publicKey.getAlgorithm()); + byte[] rawPublicKey = publicKey.getRaw(); + assertEquals(expectedAlgorithm.publicKeySize(), rawPublicKey.length); + } + } + + @Test + public void keyFactory_toAndFromRaw_works() throws Exception { + for (String factoryAlgorithm : new String[] {"ML-KEM", "ML-KEM-768", "ML-KEM-1024"}) { + MlKemAlgorithm algorithm = toMlKemAlgorithm(factoryAlgorithm); + // create random raw keys of the correct size. + int publicKeySize = algorithm.publicKeySize(); + byte[] rawPrivateKey = new byte[64]; + NativeCrypto.RAND_bytes(rawPrivateKey); + byte[] rawPublicKey = new byte[publicKeySize]; + NativeCrypto.RAND_bytes(rawPublicKey); + + KeyFactory keyFactory = KeyFactory.getInstance(factoryAlgorithm, conscryptProvider); + + // generatePrivate works. + OpenSslMlKemPrivateKey privateKey = (OpenSslMlKemPrivateKey) keyFactory.generatePrivate( + new RawKeySpec(rawPrivateKey)); + assertEquals(algorithm, privateKey.getMlKemAlgorithm()); + assertEquals("ML-KEM", privateKey.getAlgorithm()); + assertArrayEquals(rawPrivateKey, privateKey.getSeed()); + + // generatePublic works. + OpenSslMlKemPublicKey publicKey = + (OpenSslMlKemPublicKey) keyFactory.generatePublic(new RawKeySpec(rawPublicKey)); + assertEquals(algorithm, publicKey.getMlKemAlgorithm()); + assertEquals("ML-KEM", publicKey.getAlgorithm()); + assertArrayEquals(rawPublicKey, publicKey.getRaw()); + + // getKeySpec for private key with RawKeySpec works. + EncodedKeySpec privateKeySpec = keyFactory.getKeySpec(privateKey, RawKeySpec.class); + assertEquals("raw", privateKeySpec.getFormat()); + assertArrayEquals(rawPrivateKey, privateKeySpec.getEncoded()); + + // getKeySpec for public key with RawKeySpec works. + EncodedKeySpec publicKeySpec = keyFactory.getKeySpec(publicKey, RawKeySpec.class); + assertEquals("raw", publicKeySpec.getFormat()); + assertArrayEquals(rawPublicKey, publicKeySpec.getEncoded()); + + // generatePrivate and generatePublic for these keySpecs returns the same keys. + PrivateKey privateKey2 = keyFactory.generatePrivate(new RawKeySpec(rawPrivateKey)); + PublicKey publicKey2 = keyFactory.generatePublic(new RawKeySpec(rawPublicKey)); + assertEquals(publicKey, publicKey2); + assertEquals(privateKey, privateKey2); + + // check that generatePrivate and generatePublic reject keys of the wrong size. + RawKeySpec tooSmallPrivateKeySpec = new RawKeySpec(new byte[rawPrivateKey.length - 1]); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(tooSmallPrivateKeySpec)); + RawKeySpec tooLargePrivateKeySpec = new RawKeySpec(new byte[rawPrivateKey.length + 1]); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(tooLargePrivateKeySpec)); + RawKeySpec tooSmallPublicKeySpec = new RawKeySpec(new byte[rawPublicKey.length - 1]); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePublic(tooSmallPublicKeySpec)); + RawKeySpec tooLargePublicKeySpec = new RawKeySpec(new byte[rawPublicKey.length + 1]); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePublic(tooLargePublicKeySpec)); + } + } + + /** Helper class to test KeyFactory.translateKey. */ + private static class TestPublicKey implements PublicKey { + TestPublicKey(byte[] x509Encoded) { + this.x509Encoded = x509Encoded; + } + + private final byte[] x509Encoded; + + @Override + public String getAlgorithm() { + return "ML-KEM"; + } + + @Override + public String getFormat() { + return "X.509"; + } + + @Override + public byte[] getEncoded() { + return x509Encoded; + } + } + + /** Helper class to test KeyFactory.translateKey. */ + private static class TestPrivateKey implements PrivateKey { + TestPrivateKey(byte[] pkcs8Encoded) { + this.pkcs8Encoded = pkcs8Encoded; + } + + private final byte[] pkcs8Encoded; + + @Override + public String getAlgorithm() { + return "ML-KEM"; + } + + @Override + public String getFormat() { + return "PKCS#8"; + } + + @Override + public byte[] getEncoded() { + return pkcs8Encoded; + } + } + + @Test + public void mlKem768KeyPair_x509AndPkcs8_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-768", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + assertEquals("PKCS#8", keyPair.getPrivate().getFormat()); + // 64 bytes for the seed + 22 bytes for the preamble. + assertEquals(86, keyPair.getPrivate().getEncoded().length); + + assertEquals("X.509", keyPair.getPublic().getFormat()); + // 1184 bytes for the raw key + 22 bytes for the preamble. + assertEquals(1206, keyPair.getPublic().getEncoded().length); + + for (String algorithm : new String[] {"ML-KEM-768", "ML-KEM"}) { + KeyFactory keyFactory = KeyFactory.getInstance(algorithm, conscryptProvider); + + PKCS8EncodedKeySpec privateKeySpec = + keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class); + assertEquals("PKCS#8", privateKeySpec.getFormat()); + assertArrayEquals(keyPair.getPrivate().getEncoded(), privateKeySpec.getEncoded()); + + X509EncodedKeySpec publicKeySpec = + keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class); + assertEquals("X.509", publicKeySpec.getFormat()); + assertArrayEquals(keyPair.getPublic().getEncoded(), publicKeySpec.getEncoded()); + + PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + + assertEquals(keyPair.getPrivate(), privateKey); + assertEquals(keyPair.getPublic(), publicKey); + + assertEquals(keyPair.getPrivate(), keyFactory.translateKey(keyPair.getPrivate())); + assertEquals(keyPair.getPrivate(), + keyFactory.translateKey(new TestPrivateKey(keyPair.getPrivate().getEncoded()))); + assertEquals(keyPair.getPublic(), keyFactory.translateKey(keyPair.getPublic())); + assertEquals(keyPair.getPublic(), + keyFactory.translateKey(new TestPublicKey(keyPair.getPublic().getEncoded()))); + } + + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-1024", conscryptProvider); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class)); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class)); + assertThrows(InvalidKeySpecException.class, + () + -> keyFactory.generatePrivate( + new RawKeySpec(keyPair.getPrivate().getEncoded()))); + assertThrows( + InvalidKeySpecException.class, + () -> keyFactory.generatePublic(new RawKeySpec(keyPair.getPublic().getEncoded()))); + + assertThrows(InvalidKeyException.class, + () -> keyFactory.translateKey(keyPair.getPrivate())); + assertThrows(InvalidKeyException.class, + () + -> keyFactory.translateKey( + new TestPrivateKey(keyPair.getPrivate().getEncoded()))); + assertThrows(InvalidKeyException.class, () -> keyFactory.translateKey(keyPair.getPublic())); + assertThrows( + InvalidKeyException.class, + () -> keyFactory.translateKey(new TestPublicKey(keyPair.getPublic().getEncoded()))); + } + + @Test + public void mlKem1024KeyPair_x509AndPkcs8_works() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-1024", conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + assertEquals("PKCS#8", keyPair.getPrivate().getFormat()); + // 64 bytes for the seed + 22 bytes for the preamble. + assertEquals(86, keyPair.getPrivate().getEncoded().length); + + assertEquals("X.509", keyPair.getPublic().getFormat()); + // 1568 bytes for the raw key + 22 bytes for the preamble. + assertEquals(1590, keyPair.getPublic().getEncoded().length); + + for (String algorithm : new String[] {"ML-KEM-1024", "ML-KEM"}) { + KeyFactory keyFactory = KeyFactory.getInstance(algorithm, conscryptProvider); + + PKCS8EncodedKeySpec privateKeySpec = + keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class); + assertEquals("PKCS#8", privateKeySpec.getFormat()); + assertArrayEquals(keyPair.getPrivate().getEncoded(), privateKeySpec.getEncoded()); + + X509EncodedKeySpec publicKeySpec = + keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class); + assertEquals("X.509", publicKeySpec.getFormat()); + assertArrayEquals(keyPair.getPublic().getEncoded(), publicKeySpec.getEncoded()); + + PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + + assertEquals(keyPair.getPrivate(), privateKey); + assertEquals(keyPair.getPublic(), publicKey); + + assertEquals(keyPair.getPrivate(), keyFactory.translateKey(keyPair.getPrivate())); + assertEquals(keyPair.getPrivate(), + keyFactory.translateKey(new TestPrivateKey(keyPair.getPrivate().getEncoded()))); + assertEquals(keyPair.getPublic(), keyFactory.translateKey(keyPair.getPublic())); + assertEquals(keyPair.getPublic(), + keyFactory.translateKey(new TestPublicKey(keyPair.getPublic().getEncoded()))); + } + + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-768", conscryptProvider); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.getKeySpec(keyPair.getPrivate(), PKCS8EncodedKeySpec.class)); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.getKeySpec(keyPair.getPublic(), X509EncodedKeySpec.class)); + assertThrows(InvalidKeySpecException.class, + () + -> keyFactory.generatePrivate( + new RawKeySpec(keyPair.getPrivate().getEncoded()))); + assertThrows( + InvalidKeySpecException.class, + () -> keyFactory.generatePublic(new RawKeySpec(keyPair.getPublic().getEncoded()))); + + assertThrows(InvalidKeyException.class, + () -> keyFactory.translateKey(keyPair.getPrivate())); + assertThrows(InvalidKeyException.class, + () + -> keyFactory.translateKey( + new TestPrivateKey(keyPair.getPrivate().getEncoded()))); + assertThrows(InvalidKeyException.class, () -> keyFactory.translateKey(keyPair.getPublic())); + assertThrows( + InvalidKeyException.class, + () -> keyFactory.translateKey(new TestPublicKey(keyPair.getPublic().getEncoded()))); + } + + @Test + public void mlKem768_pkcs8TestVector_works() throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-768", conscryptProvider); + + // Example from RFC 9935, C.1.2.1 + String pcks8Base64 = "MFQCAQAwCwYJYIZIAWUDBAQCBEKAQAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ" + + "GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8="; + String rawHex = "000102030405060708090a0b0c0d0e0f10111213141" + + "5161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343" + + "5363738393a3b3c3d3e3f"; + + byte[] seed = TestUtils.decodeHex(rawHex); + byte[] encoded = TestUtils.decodeBase64(pcks8Base64); + + PrivateKey privateKey = keyFactory.generatePrivate(new RawKeySpec(seed)); + assertArrayEquals(encoded, privateKey.getEncoded()); + + EncodedKeySpec encodedKeySpec = + keyFactory.getKeySpec(privateKey, PKCS8EncodedKeySpec.class); + assertEquals("PKCS#8", encodedKeySpec.getFormat()); + assertArrayEquals(encoded, encodedKeySpec.getEncoded()); + + PrivateKey privateKey2 = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded)); + assertArrayEquals(encoded, privateKey2.getEncoded()); + OpenSslMlKemPrivateKey mlKemPrivateKey = (OpenSslMlKemPrivateKey) privateKey2; + assertEquals(MlKemAlgorithm.ML_KEM_768, mlKemPrivateKey.getMlKemAlgorithm()); + assertArrayEquals(seed, mlKemPrivateKey.getSeed()); + } + + @Test + public void mlKem1024_pkcs8TestVector_works() throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-1024", conscryptProvider); + + // Example from RFC 9935, C.1.3.1 + String pcks8Base64 = "MFQCAQAwCwYJYIZIAWUDBAQDBEKAQAABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZ" + + "GhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8="; + String rawHex = "000102030405060708090a0b0c0d0e0f10111213141" + + "5161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343" + + "5363738393a3b3c3d3e3f"; + + byte[] seed = TestUtils.decodeHex(rawHex); + byte[] encoded = TestUtils.decodeBase64(pcks8Base64); + + PrivateKey privateKey = keyFactory.generatePrivate(new RawKeySpec(seed)); + assertArrayEquals(encoded, privateKey.getEncoded()); + + EncodedKeySpec encodedKeySpec = + keyFactory.getKeySpec(privateKey, PKCS8EncodedKeySpec.class); + assertEquals("PKCS#8", encodedKeySpec.getFormat()); + assertArrayEquals(encoded, encodedKeySpec.getEncoded()); + + PrivateKey privateKey2 = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded)); + assertArrayEquals(encoded, privateKey2.getEncoded()); + OpenSslMlKemPrivateKey mlKemPrivateKey = (OpenSslMlKemPrivateKey) privateKey2; + assertEquals(MlKemAlgorithm.ML_KEM_1024, mlKemPrivateKey.getMlKemAlgorithm()); + assertArrayEquals(seed, mlKemPrivateKey.getSeed()); + } + + @Test + public void mlKem768_x509TestVector_works() throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-768", conscryptProvider); + + // Example from RFC 9935, C.2 + String x509Base64 = "MIIEsjALBglghkgBZQMEBAIDggShACmKoQ1CPI3aBp0CvFnmzfA6CWuLPaTKubgM" + + "pKFJB2cszvHsT68jSgvFt+nUc/KzEzs7JqHRdctnp4BZGWmcAvdlMbmcX4kYBwS7" + + "TKRTXFuJcmecZgoHxeUUuHAJyGLrj1FXaV77P8QKne9rgcHMAqJJrk8JStDZvTSF" + + "wcHGgIBSCnyMYyAyzuc4FU5cUXbAfaVgJHdqQw/nbqz2ZaP3uDIQIhW8gvEJOcg1" + + "VwQzao+sHYHkuwSFql18dNa1m75cXpcqDYusQRtVtdVVfNaAoaj3G064a8SMmgUJ" + + "cxpUvZ1ykLJ5Y+Q3Lcmxmc/crAsBrNKKYjlREuTENkjWIsSMgjTQFEDozDdskn8j" + + "pa/JrAR0xmInTkJFJchVLs47P+JlFt6QG8fVFb3olVjmJslcgLkzQvgBAATznmxs" + + "lIccXjRMqzlmyDX5qWpZr9McQChrOLHBp4RwurlHUYk0RTzoZzapGfH1ptUQqG9U" + + "VPw5gMtcdlvSvV97NrFBDWY1yM60fE3aDXaijqyTnHHDAkgEhmxxYmZYRCFjwsIh" + + "F+UKzvzmN4qYVlIwKk7wws4Mxxa3eW4ray43d9+hrD2iWaMbWptTD4y2OKgaYqww" + + "GEmrr5WnMBvaMAaJCb/bfmfbzLs4pVUaJbGjoPaFdIrVdT2IgPABbGJ0hhZjhMVX" + + "H+I2WQA2TQODEeLYdds2ZoaTK17GAkMKNp6Hpu9cM4eGZXglvUwFes65I+sJNeaQ" + + "XmO0ztf4CFenc91ksVDSZhLqmsEgUtsgF78YQ8y0sygbaQ3HKK36hcACgbjjwJKH" + + "M1+Fa0/CiS9povV5Ia2gGRTECYhmLVd2lmKnhjUbm2ZJPat5WU2YbeIQDWW6D/Tq" + + "WLgVONJKRDWiWPrCVASqf0H2WLE4UGXhWNy2ARVzJyD0BFmqrBXkBpU6kKxSmX0c" + + "zQcAYO/GXbnmUzVEZ/rVbscTyG51QMQjrPJmn1L6b0rGiI2HHvPoR8ApqKr7uS4X" + + "skqgebH0GbphdbRCr7EZCdSla3CgM1soc5IYqnyTSOLDwvPrPRWkHmQXwN2Uv+sh" + + "QZsxGnuxOhgLvoMyGKmmsXRHzIXyJYWVh6cwdwSay8/UTQ8CVDjhXRU4Jw1Ybhv4" + + "MZKpRZz2PA6XL4UpdnmDHs8SFQmFHLg0D28Qew+hoO/Rs2qBibwIXE9ct4TlU/Qb" + + "kY+AOXzhlW94W+43fKmqi+aZitowwmt8PYxrVSVMyWIDsgxCruCsTh67QI5JqeP4" + + "edCrB4XrcCVCXRMFoimcAV4SDRY7DhlJTOVyU9AkbRgnRcuBl6t0OLPBu3lyvsWj" + + "BuujVnhVwBRpn+9lrlTHcKDYXBhADPZCrtxmB3e6SxOFAr1aeBL2IfhKSClrmN1D" + + "IrbxWCi4qPDgCoukSlPDqLFDVxsHQKvVZ9rxzenHnCBLbV4lnRdmoxu7y05qBc9F" + + "AhdrMBwcL0Ekd1AVe87IXoCbMKTWDXdHzdD1uZqoyCaYdRd5OqqAgKCxJKhVjfcr" + + "vje3X07btr6CFtbGM/srIoDiURPYaV5DSBw+6zl+sZJQUim2eiAeqJPD4ssy2ovD" + + "QvpN6gV4"; + String rawHex = "298aa10d423c8dda069d02bc59e6cdf03a096b8b3da" + + "4cab9b80ca4a14907672ccef1ec4faf234a0bc5b7e9d473f2b3133b3b26a1d17" + + "5cb67a7805919699c02f76531b99c5f89180704bb4ca4535c5b8972679c660a0" + + "7c5e514b87009c862eb8f5157695efb3fc40a9def6b81c1cc02a249ae4f094ad" + + "0d9bd3485c1c1c68080520a7c8c632032cee738154e5c5176c07da56024776a4" + + "30fe76eacf665a3f7b832102215bc82f10939c8355704336a8fac1d81e4bb048" + + "5aa5d7c74d6b59bbe5c5e972a0d8bac411b55b5d5557cd680a1a8f71b4eb86bc" + + "48c9a0509731a54bd9d7290b27963e4372dc9b199cfdcac0b01acd28a6239511" + + "2e4c43648d622c48c8234d01440e8cc376c927f23a5afc9ac0474c662274e424" + + "525c8552ece3b3fe26516de901bc7d515bde89558e626c95c80b93342f801000" + + "4f39e6c6c94871c5e344cab3966c835f9a96a59afd31c40286b38b1c1a78470b" + + "ab947518934453ce86736a919f1f5a6d510a86f5454fc3980cb5c765bd2bd5f7" + + "b36b1410d6635c8ceb47c4dda0d76a28eac939c71c3024804866c71626658442" + + "163c2c22117e50acefce6378a985652302a4ef0c2ce0cc716b7796e2b6b2e377" + + "7dfa1ac3da259a31b5a9b530f8cb638a81a62ac301849abaf95a7301bda30068" + + "909bfdb7e67dbccbb38a5551a25b1a3a0f685748ad5753d8880f0016c6274861" + + "66384c5571fe2365900364d038311e2d875db366686932b5ec602430a369e87a" + + "6ef5c338786657825bd4c057aceb923eb0935e6905e63b4ced7f80857a773dd6" + + "4b150d26612ea9ac12052db2017bf1843ccb4b3281b690dc728adfa85c00281b" + + "8e3c09287335f856b4fc2892f69a2f57921ada01914c40988662d57769662a78" + + "6351b9b66493dab79594d986de2100d65ba0ff4ea58b81538d24a4435a258fac" + + "25404aa7f41f658b1385065e158dcb60115732720f40459aaac15e406953a90a" + + "c52997d1ccd070060efc65db9e653354467fad56ec713c86e7540c423acf2669" + + "f52fa6f4ac6888d871ef3e847c029a8aafbb92e17b24aa079b1f419ba6175b44" + + "2afb11909d4a56b70a0335b28739218aa7c9348e2c3c2f3eb3d15a41e6417c0d" + + "d94bfeb21419b311a7bb13a180bbe833218a9a6b17447cc85f225859587a7307" + + "7049acbcfd44d0f025438e15d1538270d586e1bf83192a9459cf63c0e972f852" + + "97679831ecf121509851cb8340f6f107b0fa1a0efd1b36a8189bc085c4f5cb78" + + "4e553f41b918f80397ce1956f785bee377ca9aa8be6998ada30c26b7c3d8c6b5" + + "5254cc96203b20c42aee0ac4e1ebb408e49a9e3f879d0ab0785eb7025425d130" + + "5a2299c015e120d163b0e19494ce57253d0246d182745cb8197ab7438b3c1bb7" + + "972bec5a306eba3567855c014699fef65ae54c770a0d85c18400cf642aedc660" + + "777ba4b138502bd5a7812f621f84a48296b98dd4322b6f15828b8a8f0e00a8ba" + + "44a53c3a8b143571b0740abd567daf1cde9c79c204b6d5e259d1766a31bbbcb4" + + "e6a05cf4502176b301c1c2f41247750157bcec85e809b30a4d60d7747cdd0f5b" + + "99aa8c826987517793aaa8080a0b124a8558df72bbe37b75f4edbb6be8216d6c" + + "633fb2b2280e25113d8695e43481c3eeb397eb192505229b67a201ea893c3e2c" + + "b32da8bc342fa4dea0578"; + + byte[] raw = TestUtils.decodeHex(rawHex); + byte[] encoded = TestUtils.decodeBase64(x509Base64); + + PublicKey publicKey = keyFactory.generatePublic(new RawKeySpec(raw)); + assertArrayEquals(encoded, publicKey.getEncoded()); + + EncodedKeySpec encodedKeySpec = keyFactory.getKeySpec(publicKey, X509EncodedKeySpec.class); + assertEquals("X.509", encodedKeySpec.getFormat()); + assertArrayEquals(encoded, encodedKeySpec.getEncoded()); + + PublicKey publicKey2 = keyFactory.generatePublic(new X509EncodedKeySpec(encoded)); + assertArrayEquals(encoded, publicKey2.getEncoded()); + OpenSslMlKemPublicKey mlKemPublicKey = (OpenSslMlKemPublicKey) publicKey2; + assertEquals(MlKemAlgorithm.ML_KEM_768, mlKemPublicKey.getMlKemAlgorithm()); + assertArrayEquals(raw, mlKemPublicKey.getRaw()); + } + + @Test + public void mlKem1024_x509TestVector_works() throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance("ML-KEM-1024", conscryptProvider); + + // Example from RFC 9935, C.2 + String x509Base64 = "MIIGMjALBglghkgBZQMEBAMDggYhAEuUwpRQERGRgjs1FMmsHqPZglzLhjk6LfsE" + + "ZU+iGS03v60cSXxlAu7lyoCnO/zguvWlSohYWkATl6PSMvQmp6+wgrwhpEMXCQ6q" + + "x1ksLqiKZTxEkeoZOTEzX1LpiaPEzFbZxVNzLVfEcPtBq3WbZdLQREU4L82cTjRK" + + "ESj6nhHgQ1jhku0BSyMjKn7isi4jcX9EER7jNXU5nDdkbamBPsmyEq/pTl3FwjMK" + + "cpTMH0I0ptP7tPFoWriJLASssXzRwXDXsGEbanF2x5TMjGf1X8kjwq0gMQDzZZkY" + + "gsMCQ9d4E4Q7XsfJZAMiY3BgkuzwDHUWvmTkWYykImwGm7XmfkF1zyKGyN1cSIps" + + "WGHzG6oL0CaUcOi1Ud07zTjIbBL5zbF2x33ItsAqcB9HiQLIVT9pTA2CcntMSlws" + + "EEEhKqEnSAi4IRGzd+x1IU6bGXj3YATUE52YYT9LjpjSCve1NAc6UJqVm3p1ZPm0" + + "DKIYv2GCkyCoUCAXlU0yjXrGx2nsKXAHVuewaFs0DV4RgFlQSkmppQoQGY6xCleE" + + "Z460J9e0uruVUpM7BiiXlz4TGOrwoOrDdYSmVAGxcD4EKszYN1MUg/JBytzRwdN4" + + "EZ5pRCnbGZrIkeTFNDdXCFuzrng2ZzUMRFjZdnLoYegLHSZ5UQ6jpvI2DHekaULH" + + "oGpVTSKAgMhLR67xTbF2IMsWwGqzChvkzacIK+n4fpwhHEaRY0mluo6qUgHHKUo8" + + "CIW1O2V0UhCIJexkbJCgRhIyTufQMa/lNDEyy+9ntu+xpewoCbdzU4znez2LBOsL" + + "PCJWAR5McWwZqLoHUr9xSSEXZJ8GFcMpD8KaRv3kvVLbkobWAziCRCWcFaesK2QK" + + "YMwDN2pYQaP7ikc1aPqbGiZyFfNMAWl7Dw5icXXXIQW3cHwpueYUvcM6b2yBipU3" + + "C0J4gte0dnlqnsbrmTJ0zZsjkagrpF4zk9Lprpchyp1sG5iLWCdxP5CmWF3pQzUo" + + "wCsDzhC7X3IBOND7tMMMEma5GOUpJd/hezf5XSK8pU9HWRmshZCYwPDQisWHXvKb" + + "Vv0UHm7xX3AKC2bzlZXFiBdzc8RmmyG8Bx5MOqXwtKMbYljzXaJKw80px/IJJBDF" + + "B4NVsTj7U6a5rm4LnAgkPnuqRcRzduuMfxPUz1Gqc2+jFUDJJB83DaVEv5+cKNml" + + "fi8qfKlaTktGbmQas7zHat8ROdVnpvErUvOmXn7AquJryqjFWDOwTlmZjryaGTD7" + + "ttIjPFPSwfi5UY48Lec6Gd7ms4Clsylxz2ThKf1sH6bnXUojRQHpZt06VAr1yPTz" + + "SmtKJT7ihJJWbV5nxvVYVfywUG+wbBVnRNmgOjGib6lMrRTxV7fzA9B6acdzdo/L" + + "TQecCQWXA6DDqU3kuZ6jovFlg9D5Fwo5UNsHtPC8MIApJ/n3lhtiWYkmNqlQKicF" + + "MDY3eZ3TRNpFHBz3v2eEDOsweauMa4wZJ/ZAU8YSRQxFyeYDvBZmbllrNHHhA7bx" + + "VEdCTRcCIEgRH/vTfhxnD2TxS4p7MrlMGkm0XdL8OM1SidkQrWNgLPXhMELGSsZ5" + + "e4n7VRrQjgWpLSAMzLfnEu8jyTEss1DwKatTfihzR/0wdawQkGp4PxxsB8y4j0Ei" + + "jEvhxkD3kLXDpdXTynkklddLxGFWJljAesYAJ2uSSrW8m+HwSUy3b4L0YKdICXJm" + + "M4HhaZlgYdeZhZ7FTU9cpcQRwB2xWXsWWXdmneE6koo0r7rCWP6oxHZCOclCHcMR" + + "m/W0dpkgaXgyexxTRe90anmDhB8FbiU0EAqyTU6au9CxfGqVvUw8DkD2nhYSrO6y" + + "i5kIbJURbnIEJziTOQv0a4mbNihrDr8ZR7uYhPcyyifagrGbXcDMf4iFcUkQiIsj" + + "EMT5MZ1BCzTmQzuQA+IXa7mVJXRWEG6JUhY7i6WSUwzFqgrrQ605j+npe6pSPXpE" + + "MWd8PTrwcZ5HXbhcqVr1CJvqvrBbL6q0iWumD4HIhHKle0aoKIJqDN+0RvgYkYLS" + + "v16sTsHMXer1mcihPkgjVAbRf/3cg0S2xmmEqGiqkvoCInoIaVDrDIcB7VjcYod2" + + "uYOILhF1"; + String rawHex = "4b94c29450111191823b3514c9ac1ea3d9825ccb863" + + "93a2dfb04654fa2192d37bfad1c497c6502eee5ca80a73bfce0baf5a54a88585" + + "a401397a3d232f426a7afb082bc21a44317090eaac7592c2ea88a653c4491ea1" + + "93931335f52e989a3c4cc56d9c553732d57c470fb41ab759b65d2d04445382fc" + + "d9c4e344a1128fa9e11e04358e192ed014b23232a7ee2b22e23717f44111ee33" + + "575399c37646da9813ec9b212afe94e5dc5c2330a7294cc1f4234a6d3fbb4f16" + + "85ab8892c04acb17cd1c170d7b0611b6a7176c794cc8c67f55fc923c2ad20310" + + "0f365991882c30243d77813843b5ec7c964032263706092ecf00c7516be64e45" + + "98ca4226c069bb5e67e4175cf2286c8dd5c488a6c5861f31baa0bd0269470e8b" + + "551dd3bcd38c86c12f9cdb176c77dc8b6c02a701f478902c8553f694c0d82727" + + "b4c4a5c2c1041212aa1274808b82111b377ec75214e9b1978f76004d4139d986" + + "13f4b8e98d20af7b534073a509a959b7a7564f9b40ca218bf61829320a850201" + + "7954d328d7ac6c769ec29700756e7b0685b340d5e118059504a49a9a50a10198" + + "eb10a5784678eb427d7b4babb9552933b062897973e1318eaf0a0eac37584a65" + + "401b1703e042accd837531483f241cadcd1c1d378119e694429db199ac891e4c" + + "5343757085bb3ae783667350c4458d97672e861e80b1d2679510ea3a6f2360c7" + + "7a46942c7a06a554d228080c84b47aef14db17620cb16c06ab30a1be4cda7082" + + "be9f87e9c211c46916349a5ba8eaa5201c7294a3c0885b53b657452108825ec6" + + "46c90a04612324ee7d031afe5343132cbef67b6efb1a5ec2809b773538ce77b3" + + "d8b04eb0b3c2256011e4c716c19a8ba0752bf71492117649f0615c3290fc29a4" + + "6fde4bd52db9286d603388244259c15a7ac2b640a60cc03376a5841a3fb8a473" + + "568fa9b1a267215f34c01697b0f0e627175d72105b7707c29b9e614bdc33a6f6" + + "c818a95370b427882d7b476796a9ec6eb993274cd9b2391a82ba45e3393d2e9a" + + "e9721ca9d6c1b988b5827713f90a6585de9433528c02b03ce10bb5f720138d0f" + + "bb4c30c1266b918e52925dfe17b37f95d22bca54f475919ac859098c0f0d08ac" + + "5875ef29b56fd141e6ef15f700a0b66f39595c588177373c4669b21bc071e4c3" + + "aa5f0b4a31b6258f35da24ac3cd29c7f2092410c5078355b138fb53a6b9ae6e0" + + "b9c08243e7baa45c47376eb8c7f13d4cf51aa736fa31540c9241f370da544bf9" + + "f9c28d9a57e2f2a7ca95a4e4b466e641ab3bcc76adf1139d567a6f12b52f3a65" + + "e7ec0aae26bcaa8c55833b04e59998ebc9a1930fbb6d2233c53d2c1f8b9518e3" + + "c2de73a19dee6b380a5b32971cf64e129fd6c1fa6e75d4a234501e966dd3a540" + + "af5c8f4f34a6b4a253ee28492566d5e67c6f55855fcb0506fb06c156744d9a03" + + "a31a26fa94cad14f157b7f303d07a69c773768fcb4d079c09059703a0c3a94de" + + "4b99ea3a2f16583d0f9170a3950db07b4f0bc30802927f9f7961b6259892636a" + + "9502a2705303637799dd344da451c1cf7bf67840ceb3079ab8c6b8c1927f6405" + + "3c612450c45c9e603bc16666e596b3471e103b6f15447424d17022048111ffbd" + + "37e1c670f64f14b8a7b32b94c1a49b45dd2fc38cd5289d910ad63602cf5e1304" + + "2c64ac6797b89fb551ad08e05a92d200cccb7e712ef23c9312cb350f029ab537" + + "e287347fd3075ac10906a783f1c6c07ccb88f41228c4be1c640f790b5c3a5d5d" + + "3ca792495d74bc461562658c07ac600276b924ab5bc9be1f0494cb76f82f460a" + + "7480972663381e169996061d799859ec54d4f5ca5c411c01db1597b165977669" + + "de13a928a34afbac258fea8c4764239c9421dc3119bf5b47699206978327b1c5" + + "345ef746a7983841f056e2534100ab24d4e9abbd0b17c6a95bd4c3c0e40f69e1" + + "612aceeb28b99086c95116e7204273893390bf46b899b36286b0ebf1947bb988" + + "4f732ca27da82b19b5dc0cc7f8885714910888b2310c4f9319d410b34e6433b9" + + "003e2176bb995257456106e8952163b8ba592530cc5aa0aeb43ad398fe9e97ba" + + "a523d7a4431677c3d3af0719e475db85ca95af5089beabeb05b2faab4896ba60" + + "f81c88472a57b46a828826a0cdfb446f8189182d2bf5eac4ec1cc5deaf599c8a" + + "13e48235406d17ffddc8344b6c66984a868aa92fa02227a086950eb0c8701ed5" + + "8dc628776b983882e1175"; + + byte[] raw = TestUtils.decodeHex(rawHex); + byte[] encoded = TestUtils.decodeBase64(x509Base64); + + PublicKey publicKey = keyFactory.generatePublic(new RawKeySpec(raw)); + assertArrayEquals(encoded, publicKey.getEncoded()); + + EncodedKeySpec encodedKeySpec = keyFactory.getKeySpec(publicKey, X509EncodedKeySpec.class); + assertEquals("X.509", encodedKeySpec.getFormat()); + assertArrayEquals(encoded, encodedKeySpec.getEncoded()); + + PublicKey publicKey2 = keyFactory.generatePublic(new X509EncodedKeySpec(encoded)); + assertArrayEquals(encoded, publicKey2.getEncoded()); + OpenSslMlKemPublicKey mlKemPublicKey = (OpenSslMlKemPublicKey) publicKey2; + assertEquals(MlKemAlgorithm.ML_KEM_1024, mlKemPublicKey.getMlKemAlgorithm()); + assertArrayEquals(raw, mlKemPublicKey.getRaw()); + } + + @Test + public void sealAndOpen_mlkem768_works() throws Exception { + byte[] info = TestUtils.decodeHex("aa"); + byte[] plaintext = TestUtils.decodeHex("bb"); + byte[] aad = TestUtils.decodeHex("cc"); + + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-768", conscryptProvider); + KeyPair keyPairRecipient = keyGen.generateKeyPair(); + + for (int aead : new int[] {AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_CHACHA20POLY1305}) { + HpkeSuite suite = new HpkeSuite(KEM_MLKEM_768, KDF_HKDF_SHA256, aead); + + HpkeContextSender ctxSender = + HpkeContextSender.getInstance(suite.name(), conscryptProvider); + ctxSender.init(keyPairRecipient.getPublic(), info); + + byte[] encapsulated = ctxSender.getEncapsulated(); + byte[] ciphertext = ctxSender.seal(plaintext, aad); + + HpkeContextRecipient contextRecipient = + HpkeContextRecipient.getInstance(suite.name(), conscryptProvider); + contextRecipient.init(encapsulated, keyPairRecipient.getPrivate(), info); + byte[] output = contextRecipient.open(ciphertext, aad); + + assertArrayEquals(plaintext, output); + } + } + + @Test + public void sealAndOpen_mlkem1024_works() throws Exception { + byte[] info = TestUtils.decodeHex("aa"); + byte[] plaintext = TestUtils.decodeHex("bb"); + byte[] aad = TestUtils.decodeHex("cc"); + + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-1024", conscryptProvider); + KeyPair keyPairRecipient = keyGen.generateKeyPair(); + + for (int aead : new int[] {AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_CHACHA20POLY1305}) { + HpkeSuite suite = new HpkeSuite(KEM_MLKEM_1024, KDF_HKDF_SHA256, aead); + + HpkeContextSender ctxSender = + HpkeContextSender.getInstance(suite.name(), conscryptProvider); + ctxSender.init(keyPairRecipient.getPublic(), info); + + byte[] encapsulated = ctxSender.getEncapsulated(); + byte[] ciphertext = ctxSender.seal(plaintext, aad); + + HpkeContextRecipient contextRecipient = + HpkeContextRecipient.getInstance(suite.name(), conscryptProvider); + contextRecipient.init(encapsulated, keyPairRecipient.getPrivate(), info); + byte[] output = contextRecipient.open(ciphertext, aad); + + assertArrayEquals(plaintext, output); + } + } + + @Test + public void wrongInfoORAad_fails() throws Exception { + byte[] info = TestUtils.decodeHex("aa"); + byte[] plaintext = TestUtils.decodeHex("bb"); + byte[] aad = TestUtils.decodeHex("cc"); + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("ML-KEM-768", conscryptProvider); + KeyPair keyPairRecipient = keyGen.generateKeyPair(); + + String hpkeAlgorithm = "MLKEM_768/HKDF_SHA256/AES_128_GCM"; + + HpkeContextSender ctxSender = + HpkeContextSender.getInstance(hpkeAlgorithm, conscryptProvider); + ctxSender.init(keyPairRecipient.getPublic(), info); + + byte[] encapsulated = ctxSender.getEncapsulated(); + byte[] ciphertext = ctxSender.seal(plaintext, aad); + + HpkeContextRecipient contextRecipient = + HpkeContextRecipient.getInstance(hpkeAlgorithm, conscryptProvider); + contextRecipient.init(encapsulated, keyPairRecipient.getPrivate(), info); + + // with correct info and aad, it works. + assertArrayEquals(plaintext, contextRecipient.open(ciphertext, aad)); + + // with correct info and wrong aad, it fails. + assertThrows(GeneralSecurityException.class, + () -> contextRecipient.open(ciphertext, TestUtils.decodeHex("ff"))); + + // with wrong info and correct aad, it fails. + HpkeContextRecipient contextRecipient2 = + HpkeContextRecipient.getInstance(hpkeAlgorithm, conscryptProvider); + contextRecipient2.init(encapsulated, keyPairRecipient.getPrivate(), + TestUtils.decodeHex("ff")); + assertThrows(GeneralSecurityException.class, () -> contextRecipient2.open(ciphertext, aad)); + } + + @Test + public void hpkeContextRecipient_openTestVectors_works() throws Exception { + List vectors = TestUtils.readTestVectors("crypto/mlkem.txt"); + + for (TestVector vector : vectors) { + String keyAlgorithm = vector.getString("key-algorithm"); + String hpkeAlgorithm = vector.getString("hpke-algorithm"); + byte[] info = vector.getBytes("info"); + byte[] pk = vector.getBytes("pk"); + byte[] sk = vector.getBytes("sk"); + byte[] enc = vector.getBytes("enc"); + byte[] ct = vector.getBytes("ct"); + byte[] pt = vector.getBytes("pt"); + byte[] aad = vector.getBytes("aad"); + + KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm, conscryptProvider); + PrivateKey privateKey = keyFactory.generatePrivate(new RawKeySpec(sk)); + PublicKey publicKey = keyFactory.generatePublic(new RawKeySpec(pk)); + + // Open enc/ct pair from test vector. + HpkeContextRecipient ctxRecipient = + HpkeContextRecipient.getInstance(hpkeAlgorithm, conscryptProvider); + + ctxRecipient.init(enc, privateKey, info); + byte[] decrypted = ctxRecipient.open(ct, aad); + + assertArrayEquals(pt, decrypted); + + // Create new enc/ct pair and open it. + HpkeContextSender ctxSender = + HpkeContextSender.getInstance(hpkeAlgorithm, conscryptProvider); + ctxSender.init(publicKey, info); + + byte[] enc2 = ctxSender.getEncapsulated(); + byte[] ct2 = ctxSender.seal(pt, aad); + + HpkeContextRecipient contextRecipient = + HpkeContextRecipient.getInstance(hpkeAlgorithm, conscryptProvider); + contextRecipient.init(enc2, privateKey, info); + byte[] output = contextRecipient.open(ct2, aad); + + assertArrayEquals(pt, output); + } + } + + @Test + public void serialize_throwsUnsupportedOperationException() throws Exception { + for (String algorithm : new String[] {"ML-KEM", "ML-KEM-768", "ML-KEM-1024"}) { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm, conscryptProvider); + KeyPair keyPair = keyGen.generateKeyPair(); + + ObjectOutputStream oos = new ObjectOutputStream(new ByteArrayOutputStream(16384)); + PrivateKey privateKey = keyPair.getPrivate(); + assertThrows(UnsupportedOperationException.class, () -> oos.writeObject(privateKey)); + PublicKey publicKey = keyPair.getPublic(); + assertThrows(UnsupportedOperationException.class, () -> oos.writeObject(publicKey)); + } + } +} diff --git a/common/src/test/java/org/conscrypt/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..ed1e039fc 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java @@ -570,18 +570,6 @@ public long beforeHandshake(long c) throws SSLException { assertTrue(serverCallback.serverCertificateRequestedInvoked); } - /** Convenient debug print for ECH Config Lists */ - private void printEchConfigList(String msg, byte[] buf) { - int blen = buf.length; - System.out.print(msg + " (" + blen + "):\n "); - for (int i = 0; i < blen; i++) { - if ((i != 0) && (i % 16 == 0)) - System.out.print("\n "); - System.out.print(String.format("%02x:", Byte.toUnsignedInt(buf[i]))); - } - System.out.print("\n"); - } - @Test public void test_SSL_do_handshake_ech_client_server() throws Exception { final ServerSocket listener = newServerSocket(); @@ -1203,10 +1191,12 @@ public long serverSessionRequested(byte[] id) { } private boolean serverCertificateRequestedInvoked; + private int[] serverSignatureAlgs; @Override - public void serverCertificateRequested() { + public void serverCertificateRequested(int[] signatureAlgs) { serverCertificateRequestedInvoked = true; + this.serverSignatureAlgs = signatureAlgs; } @Override @@ -1439,6 +1429,8 @@ public void test_SSL_do_handshake_normal() throws Exception { assertTrue(serverCallback.handshakeCompletedCalled); assertFalse(clientCallback.serverCertificateRequestedInvoked); assertTrue(serverCallback.serverCertificateRequestedInvoked); + assertNotNull(serverCallback.serverSignatureAlgs); + // g3-add: assertTrue(serverCallback.serverSignatureAlgs.length > 0); } @Test @@ -3954,6 +3946,129 @@ public void test_ecdsaSignVerify_works() throws Exception { () -> NativeCrypto.ECDSA_verify(data, invalidDataLen, signature, publicKey)); } + @Test + public void mlKem768PublicKeyFromSeed_returnsPublicKeyIfPrivateKeyIsValid() throws Exception { + // test vector from https://www.ietf.org/archive/id/draft-ietf-hpke-pq-01.html, Section A.1 + byte[] privateKey = + decodeHex("06f7d4f1495a828789f5543cb847369e10751ca5369a473c74e46043080f94f5" + + "25f2f8cb7d8cfbf3cf8496728611a6567afd446a6ed1d22f6d32f74ef266a97e"); + byte[] expectedPublicKey = + decodeHex("33e49cea9c631f102595637291548f7220782f498bca5073b8039759f5582f45" + + "aa59245f41e5516da2a779b325e039651c348f57502602a3b7b8482b4b1576c9" + + "3aa8b8c5155e5bc5dbc6a08d57103bc970b016ab7ab22320c430291ba1e80855" + + "64c4a13686aafefb510ce76e36876f2fdb1b7ba49550c15f5d366abe420e7ff2" + + "459cd7af36f5c285981adcf84020b04a66a0bc58172f8a34280fc32497a56308" + + "238a9f27ae95f0a593a70ae7594260e60930695f5f803eb2210fe3ac61c8bc20" + + "ec2566dafc399cea81a3834619420e5d85476cd573c7a08eaec4c60cf7999acc" + + "98724b934e259cda793dfda761cfa4289530b1f75b6a592730b8f5607eba701a" + + "150c38ac0e21038bfef496d2cb808f7342317789d1581b6f8565e3018d796013" + + "fed26b59fb226b5988633923a72a9acf42960c228f1e25b84f18ba4fd8762067" + + "9d6c7a9e5f4340dadc3af252afd28231e3d52cb924209e50ba1ca632de811ae1" + + "097cd89803884a8c750663baec78c90254f038574fdcb74c527610e21469398a" + + "20abcce9f50e1537867aa036bf1452cbf70850e8a5f5464eccc8a2af260a4550" + + "7e2da24c4868699065c95e3946ace90a655696f654892d8470a535cda5d58c44" + + "c8c0cb7bcc7104a847bc4cb1f03b8872143f5491c9e8c8ed46a6a6703c07d42a" + + "b8fabc4e25477245754d718ad8c88ce753c9756c2403948c06c1345c570fbd9a" + + "5932982f326a2f2ac69ebf5a58bc788fdb72a4d90865d3169d7752579cc49ad5" + + "e27479260dbba4800b521d21d22eb633168632b5209c4b81f32026a8111e07b1" + + "48babf740297af87239bc760b976862c551622416a6fb23825112308867ba70a" + + "75ba33179c2373b97492235a27c1f266cbc18f71c0596e47a285bc2e09397a63" + + "2309ff67739cb132d8c4007d2926fafc5eb56a6cf1960e87b4488c4895aacc6f" + + "cbbc4b6dd2b56cc69616653836550bbb9663b678370c3916fb832e17ba753d04" + + "787d78b0ddf11f6410bbe847cd6b1abdcee2835d50867a40137723433fdb6e94" + + "f902fda88b96d95af872000447c3649a93d3e0013dab5350dbb45c6190089c56" + + "cd4c1fce162daba74abfe8ae673105e0ebb94e52b37967b609741eb4608723e9" + + "858937b4996269033ca2cb503c4d3ccda0e15137cbc602728cc9bb5cca55b2cb" + + "c4579d02bdca430a052bbfc68bb5c8eb7c0139272c545d09f345b49a800314ca" + + "63c5097ef24cd793946410a931d5437becb557197affa37071954eee9c093a34" + + "86492362595761c83aac2ce46cdaebaa07ab3c3c1363c15bb8c4c869f54c6fdf" + + "a78924b954e0b480d3a128a2f64f7527beb2ec92ea6994dddc5eac10bde8a456" + + "09784e7948b6acc7c5da8526dd970870f46812bb0b53867668e17183fa638926" + + "1fa5da9a3ad8451094cd8ddb28683c1ce853c0e1cb3a4f79a168502242f66217" + + "2bc21609492fa57a9f077a1889c42cb2aa3bc2583275ce6b171c3e3aa4279483" + + "b204cb3a1a98535a2fe7c1b3d322c52dfb9e9ec46f926562ef1c8992a2bc5fc0" + + "47961898d1eb3d35cb1a018cb440d84de5047e50a4abd891c84f9431e36bb813" + + "4642e6fa144c15307a5122f07252b1e65b0e9ab3c2a184826c9a9cd938bdd588" + + "138a731c1b51787f166ca6205dadaaffc05c609f747b99fc3b918359e2ac28c8"); + + byte[] publicKey = NativeCrypto.MLKEM768_public_key_from_seed(privateKey); + assertArrayEquals(expectedPublicKey, publicKey); + + byte[] privateKeyTooShort = Arrays.copyOf(privateKey, privateKey.length - 1); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLKEM768_public_key_from_seed(privateKeyTooShort)); + byte[] privateKeyTooLong = Arrays.copyOf(privateKey, privateKey.length + 1); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLKEM768_public_key_from_seed(privateKeyTooLong)); + } + + @Test + public void mlKem1024PublicKeyFromSeed_returnsPublicKeyIfPrivateKeyIsValid() throws Exception { + // test vector from https://www.ietf.org/archive/id/draft-ietf-hpke-pq-01.html, Section A.2 + byte[] privateKey = + decodeHex("870150f8c622ea6866db299c3348c737f0e8da17c1e7f721029b5e035db59421" + + "68522e0bea336dd93031199ab74b3acd684cbd03d6e56f304e5c28e7a9cba3bc"); + byte[] expectedPublicKey = + decodeHex("daa944e375a5c36c1f3771be499c297aba61d8762694256ef840460d10bed594" + + "641d58446e3795792c51f272383e78aa517112197230c3604993182d046679a8" + + "70495f1c44a6e2b30da7c251034f127561e02a234d0bbbb4ccc5a15224d06431" + + "08c44ad2973085371c80a064eaf524ebd90d5ad6057d45326a8b4ef532a7dfa9" + + "3ff83783ece423106c8b1ed2627b59ca34d71a3b4014698c9969db604589270b" + + "492124a80eba50909c2847a8bc9881b0788de95ad718a9c71a33b7ba98ab8ac3" + + "1ed744ffca6d6e08a170084c072bafaf7c25d5f56c519a6e3bdb30b0906192e2" + + "671da765ef53af8ac0197af2c9ac794a41038599766323718347f8821f3888ce" + + "250b84335eb8c43de4d9720f26b92531685af7576f62c6f39249c6645433387a" + + "8c617fdeb0650f77580de2af30019eb9959fb39ca2ed5364fc0a54e4809ac6a2" + + "424d284113e25179427b6a22936dc5839f3a9cb8bb49a5239ecd09278e432f65" + + "59bba0d56f9a9b87af068369c207b040022306a5fcc64d3264338a0789d7a833" + + "ad280b9ef2619c75769ed124a19c89a9b00a43c934b0042f24e28d2c353f2718" + + "ae9eb556bcd7c408f01e07bc7ad1d563b411133899312e5c6125f3cf39448ba6" + + "4c8a904414c32a8fc233649a2a69b98ab658094908f89c7089765e8211e5c75d" + + "949c6e53c6445d11902b25b452da79db853b78c8a629f1c5ef762f34f2126a20" + + "92c85b618c1a1bb506cd60e0c8b39b3c9aec088494c966878db1c3772b0992de" + + "4cb3a4958d2c43878f681a53cc207f446c6f95addb7047116bb4d5124b5fc532" + + "20401a7b5531fbb49bafa345479c620bfa8529f2a73f830c975b820b465a9745" + + "3483a746ec573289a8c02b53906c7423dac584495b6c771a60db498ae478a8f9" + + "a9833c81666a55a47a01adcd189b0e8c90aaa382a1d2301a873d9370c12ecc61" + + "04516e6b2957e3a7806c551d96aabe3b5cc47f918c467732dbe3a138305490c3" + + "8b33760d4b164bd509595415a3b560082a9b688b386c20f05c0667631555015e" + + "ba61e87174600c1fd0971b4e776e8452ad50748fd5a783ade73d80a97c8b07a3" + + "74ca53ba95c89157b5e2bb333518adce58c5ad90734ee20cc4646382f4bb91d9" + + "431968895f541a25da9a892c7086cb361e4240ae996afa5445c0cba188d13e4a" + + "8863c6918a7e247aa893c055a052bbaa3bd1b2731207006e5403e8742c844605" + + "d4aa64963b0af4978fbb7a1e409ca5470395dc24a700a850b189039f87bd2c78" + + "58f28a9e2c624ecea50f1f7889f6a99cfcfacb0c5861586c3e97a41d855319d5" + + "930e0aba9eb1b1bf013b32f6cb0d1570243eda540538a288d13bad128907d079" + + "25153925e9253c117e82ab0c27c49fc68162e465b0e3fb7c1671c0965c71b583" + + "6862301a85606122896e8d9a6d848c1818580ddc7955f3446f148338ca14149a" + + "205a3c6bb43e30abcf20a69f36a7a5e60efdf69b5922cd6a7744e420311f3b9c" + + "b2148ab6ea7e11b522c81ab0209a43f8da598f580fc8536b2838bb8ca0c26690" + + "5270609304ab78d6c40fa8a7015862b80ebc34a9ea25959a3c3fc4abd56a7961" + + "6215f2caaacb51033e02ba4b446c0fd47e515ca9ad44c7e3a2739b9b69242a6c" + + "c528880491c72c7858ac13804db088a97541ed11208b369bfab3c8e4890ff1c7" + + "3f2d783004c482458980eeb21f40c73d5d5282a78279d6f71bad650843c83d03" + + "23a7e90748b555853a50c091c31a9655b2388226e4524cdcbabb61543eb7caa8" + + "8396933e1a5986986e41da209f131a9bbc9a44d66ba077229c499386a4418b99" + + "0e20707c323846c861bcf227b2ff6ca3ee6511b37c190618c1e610be4672507f" + + "f7c7f5486930670d99bb6c2c28afaca54b815144aed10f82c27ad8922d5a9c86" + + "b15c9da866ad0ff96a2f569b42d52cdec8793d0414cfc35ddcebca8066b25e93" + + "0b36743db6b05a6ec6641b9403f88a5320842522f388683c72eb60bfe1db314a" + + "0b78a0a0cc76a692137c9613bb47c3f54911e67c1ba48a46081fcaf0974783ad" + + "ddc88492943d8b5934a41b46f95120fce5a21e0223dee7b140a924ce711e0e93" + + "c434616b58b02c2e30352599380ca04127f40a4134c5862c3bab08a3f265a467" + + "9c989cfc1f24c9c553cabb37ab2767f5ab2d7a20e57b0b65fb6e4a6119ba33a7" + + "36a9568de2ee95c312aad4c14639282831a3461d6e08800b592c8aa9d7ef6028"); + byte[] publicKey = NativeCrypto.MLKEM1024_public_key_from_seed(privateKey); + assertArrayEquals(expectedPublicKey, publicKey); + + byte[] privateKeyTooShort = Arrays.copyOf(privateKey, privateKey.length - 1); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLKEM1024_public_key_from_seed(privateKeyTooShort)); + byte[] privateKeyTooLong = Arrays.copyOf(privateKey, privateKey.length + 1); + assertThrows(RuntimeException.class, + () -> NativeCrypto.MLKEM1024_public_key_from_seed(privateKeyTooLong)); + } + @Test public void xwingPublicKeyFromSeed_returnsPublicKeyIfPrivateKeyIsValid() throws Exception { // test vector from @@ -3983,6 +4098,8 @@ public void xwingPublicKeyFromSeed_returnsPublicKeyIfPrivateKeyIsValid() throws private static final int DHKEM_X25519_HKDF_SHA256 = 0x0020; private static final int DHKEM_X448_HKDF_SHA256 = 0x0021; private static final int XWING = 0x647a; + private static final int MLKEM768 = 0x0041; + private static final int MLKEM1024 = 0x0042; // KDF IDs private static final int HKDF_SHA256 = 0x0001; private static final int HKDF_SHA384 = 0x0002; @@ -4032,7 +4149,7 @@ public void hpkeWithXwing_publicKeyFromSeedSealOpen_success() throws Exception { byte[] aad = decodeHex("cc"); Object[] result = NativeCrypto.EVP_HPKE_CTX_setup_base_mode_sender( - XWING, HKDF_SHA256, HKDF_SHA256, publicKey, info); + XWING, HKDF_SHA256, AES_128_GCM, publicKey, info); NativeRef.EVP_HPKE_CTX ctxSender = (NativeRef.EVP_HPKE_CTX) result[0]; byte[] encapsulated = (byte[]) result[1]; assertEquals(1120, encapsulated.length); @@ -4041,8 +4158,61 @@ public void hpkeWithXwing_publicKeyFromSeedSealOpen_success() throws Exception { NativeRef.EVP_HPKE_CTX ctxRecipient = (NativeRef.EVP_HPKE_CTX) NativeCrypto.EVP_HPKE_CTX_setup_base_mode_recipient( - /* kem= */ 0x647a, /*kdf=*/0x0001, /* aead= */ 0x0001, privateKey, - encapsulated, info); + XWING, HKDF_SHA256, AES_128_GCM, privateKey, encapsulated, info); + byte[] output = NativeCrypto.EVP_HPKE_CTX_open(ctxRecipient, ciphertext, aad); + + assertArrayEquals(plaintext, output); + } + + @Test + public void hpkeWithMLKEM768_publicKeyFromSeedSealOpen_success() throws Exception { + byte[] privateKey = decodeHex( + "06f7d4f1495a828789f5543cb847369e10751ca5369a473c74e46043080f94f525f2f8cb7d8cfbf3" + + "cf8496728611a6567afd446a6ed1d22f6d32f74ef266a97e"); + byte[] publicKey = NativeCrypto.MLKEM768_public_key_from_seed(privateKey); + + byte[] info = decodeHex("aa"); + byte[] plaintext = decodeHex("bb"); + byte[] aad = decodeHex("cc"); + + Object[] result = NativeCrypto.EVP_HPKE_CTX_setup_base_mode_sender( + MLKEM768, HKDF_SHA256, AES_128_GCM, publicKey, info); + NativeRef.EVP_HPKE_CTX ctxSender = (NativeRef.EVP_HPKE_CTX) result[0]; + byte[] encapsulated = (byte[]) result[1]; + assertEquals(1088, encapsulated.length); + + byte[] ciphertext = NativeCrypto.EVP_HPKE_CTX_seal(ctxSender, plaintext, aad); + + NativeRef.EVP_HPKE_CTX ctxRecipient = + (NativeRef.EVP_HPKE_CTX) NativeCrypto.EVP_HPKE_CTX_setup_base_mode_recipient( + MLKEM768, HKDF_SHA256, AES_128_GCM, privateKey, encapsulated, info); + byte[] output = NativeCrypto.EVP_HPKE_CTX_open(ctxRecipient, ciphertext, aad); + + assertArrayEquals(plaintext, output); + } + + @Test + public void hpkeWithMLKEM1024_publicKeyFromSeedSealOpen_success() throws Exception { + byte[] privateKey = + decodeHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f" + + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"); + byte[] publicKey = NativeCrypto.MLKEM1024_public_key_from_seed(privateKey); + + byte[] info = decodeHex("aa"); + byte[] plaintext = decodeHex("bb"); + byte[] aad = decodeHex("cc"); + + Object[] result = NativeCrypto.EVP_HPKE_CTX_setup_base_mode_sender( + MLKEM1024, HKDF_SHA256, AES_128_GCM, publicKey, info); + NativeRef.EVP_HPKE_CTX ctxSender = (NativeRef.EVP_HPKE_CTX) result[0]; + byte[] encapsulated = (byte[]) result[1]; + assertEquals(1568, encapsulated.length); + + byte[] ciphertext = NativeCrypto.EVP_HPKE_CTX_seal(ctxSender, plaintext, aad); + + NativeRef.EVP_HPKE_CTX ctxRecipient = + (NativeRef.EVP_HPKE_CTX) NativeCrypto.EVP_HPKE_CTX_setup_base_mode_recipient( + MLKEM1024, HKDF_SHA256, AES_128_GCM, privateKey, encapsulated, info); byte[] output = NativeCrypto.EVP_HPKE_CTX_open(ctxRecipient, ciphertext, aad); assertArrayEquals(plaintext, output); diff --git a/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(