[automerger skipped] Make XDH key sizes 255 within Android12L-tests-dev am: de47c1cc82 -s ours am: e1ff9647ed -s ours

am skip reason: Merged-In I538534009f98306db6a65f2dcef84cbb98045aa0 with SHA-1 82ed88b565 is already in history

Original change: https://android-review.googlesource.com/c/platform/external/conscrypt/+/3003268

Change-Id: I9079e73bef15fc6ce4598f1be504a09bf311bdcd
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index af41a1a..bc1be6a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -282,6 +282,7 @@
     visibility: [
         "//art/build",
         "//external/robolectric-shadows",
+        "//frameworks/layoutlib",
     ],
     static_libs: [
         "conscrypt",
@@ -328,8 +329,8 @@
 java_sdk_library {
     name: "conscrypt.module.platform.api",
     visibility: [
+        "//build/soong/java/core-libraries",
         "//external/wycheproof",
-        "//libcore:__subpackages__",
         // Visibility for prebuilt conscrypt-module-sdk from the prebuilt of
         // this module.
         // TODO(b/155921753): Restrict this when prebuilts are in their proper
@@ -347,7 +348,6 @@
     droiddoc_options: [
         "--hide-annotation libcore.api.Hide",
         "--show-single-annotation libcore.api.CorePlatformApi\\(status=libcore.api.CorePlatformApi.Status.STABLE\\)",
-        "--skip-annotation-instance-methods=false",
     ],
     hostdex: true,
 
@@ -364,10 +364,10 @@
 java_sdk_library {
     name: "conscrypt.module.public.api",
     visibility: [
-        "//packages/modules/common/sdk",
+        "//build/soong/java/core-libraries",
         "//frameworks/base",
         "//frameworks/base/api",
-        "//libcore",
+        "//packages/modules/common/sdk",
         // TODO(b/165823103): Remove visiblity for IPsec once CorePlatformApi is available
         "//packages/modules/IPsec",
         // Visibility for prebuilt art-module-host-exports from the prebuilt of
@@ -387,12 +387,21 @@
     // version of the API.
     dist_stem: "conscrypt",
 
+    public: {
+        enabled: true,
+    },
+    system: {
+        enabled: true,
+    },
+    module_lib: {
+        enabled: true,
+    },
+
     api_dir: "api/public",
     api_only: true,
-    droiddoc_options: [
-        // Emit nullability annotations from the source to the stub files.
-        "--include-annotations",
-    ],
+
+    // Emit nullability annotations from the source to the stub files.
+    annotations_enabled: true,
 
     java_version: "1.9",
 
@@ -451,7 +460,6 @@
     droiddoc_options: [
         "--hide-annotation libcore.api.Hide",
         "--show-single-annotation libcore.api.IntraCoreApi",
-        "--skip-annotation-instance-methods=false",
     ],
 
     sdk_version: "none",
@@ -525,7 +533,12 @@
         ":conscrypt-unbundled_generated_constants",
     ],
     javacflags: ["-XDignore.symbol.file"],
-    java_version: "1.7",
+    java_version: "1.8",
+    target: {
+        windows: {
+            enabled: true,
+        },
+    },
 }
 
 // Static unbundled Conscrypt crypto JNI library
@@ -636,7 +649,7 @@
             enabled: false,
         },
     },
-    java_version: "1.7",
+    java_version: "1.8",
 }
 
 // Device SDK exposed by the Conscrypt module.
diff --git a/OWNERS b/OWNERS
index 6a33b41..73617ea 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,7 +1,2 @@
 # Bug component: 684135
-dauletz@google.com
-kroot@google.com
-mingaleev@google.com
-narayan@google.com
-ngeoffray@google.com
-prb@google.com
+include platform/libcore:/OWNERS
diff --git a/android-stub/build.gradle b/android-stub/build.gradle
index f1a911f..874f599 100644
--- a/android-stub/build.gradle
+++ b/android-stub/build.gradle
@@ -1,9 +1,5 @@
 description = 'Conscrypt: Android-Stub'
 
-// Needs to be binary-compatible with Android minSdkVersion.
-sourceCompatibility = androidMinJavaVersion
-targetCompatibility = androidMinJavaVersion
-
 dependencies {
     compileOnly project(':conscrypt-libcore-stub')
 }
diff --git a/apex/Android.bp b/apex/Android.bp
index 2a7d225..f20ea90 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -24,7 +24,6 @@
 
 apex_defaults {
     name: "com.android.conscrypt-defaults",
-    updatable: true,
     androidManifest: ":com.android.conscrypt-androidManifest",
     compile_multilib: "both",
     bootclasspath_fragments: ["com.android.conscrypt-bootclasspath-fragment"],
@@ -37,15 +36,17 @@
     },
     key: "apex.conscrypt.key",
     certificate: ":com.android.conscrypt.certificate",
+    // Indicates that pre-installed version of this apex can be compressed.
+    // Whether it actually will be compressed is controlled on per-device basis.
+    compressible: true,
 
-    // IMPORTANT: For the APEX to be installed on Android 10,
-    // min_sdk_version should be 29. This enables the build system to make
+    // IMPORTANT: q-launched-apex-module enables the build system to make
     // sure the package compatible to Android 10 in two ways:
     // - build the APEX package compatible to Android 10
     //   so that the package can be installed.
     // - build artifacts (lib/javalib/bin) against Android 10 SDK
     //   so that the artifacts can run.
-    min_sdk_version: "29",
+    defaults: ["q-launched-apex-module"],
 }
 
 filegroup {
@@ -111,5 +112,19 @@
     // from the api stub libraries.
     hidden_api: {
         max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+
+        // This module does not contain any split packages.
+        split_packages: [],
+
+        // The following packages and all their subpackages currently only
+        // contain classes from this bootclasspath_fragment. Listing a package
+        // here won't prevent other bootclasspath modules from adding classes in
+        // any of those packages but it will prevent them from adding those
+        // classes into an API surface, e.g. public, system, etc.. Doing so will
+        // result in a build failure due to inconsistent flags.
+        package_prefixes: [
+            "android.net.ssl",
+            "com.android.org.conscrypt",
+        ],
     },
 }
diff --git a/apex/apex_manifest.json b/apex/apex_manifest.json
index e59fbcf..9584d95 100644
--- a/apex/apex_manifest.json
+++ b/apex/apex_manifest.json
@@ -1,4 +1,4 @@
 {
   "name": "com.android.conscrypt",
-  "version": 319999900
+  "version": 339990000
 }
diff --git a/api/public/module-lib-current.txt b/api/public/module-lib-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/public/module-lib-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/public/module-lib-removed.txt b/api/public/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/public/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/public/system-current.txt b/api/public/system-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/public/system-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/public/system-removed.txt b/api/public/system-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/public/system-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/build.gradle b/build.gradle
index 162c491..08b9ebd 100644
--- a/build.gradle
+++ b/build.gradle
@@ -165,8 +165,8 @@
     }
 
     if (!androidProject) {
-        sourceCompatibility = JavaVersion.VERSION_1_7
-        targetCompatibility = JavaVersion.VERSION_1_7
+        sourceCompatibility = JavaVersion.VERSION_1_8
+        targetCompatibility = JavaVersion.VERSION_1_8
 
         [tasks.named("compileJava"), tasks.named("compileTestJava")].forEach { t ->
             t.configure {
diff --git a/common/src/jni/main/cpp/conscrypt/jniutil.cc b/common/src/jni/main/cpp/conscrypt/jniutil.cc
index c30adf1..f5c6b92 100644
--- a/common/src/jni/main/cpp/conscrypt/jniutil.cc
+++ b/common/src/jni/main/cpp/conscrypt/jniutil.cc
@@ -38,9 +38,12 @@
 jclass outputStreamClass;
 jclass stringClass;
 jclass byteBufferClass;
-jclass bufferClass;
+static jclass bufferClass;
+static jclass fileDescriptorClass;
+static jclass sslHandshakeCallbacksClass;
 
 jfieldID nativeRef_address;
+static jfieldID fileDescriptor_fd;
 
 jmethodID calendar_setMethod;
 jmethodID inputStream_readMethod;
@@ -50,6 +53,19 @@
 jmethodID outputStream_flushMethod;
 jmethodID buffer_positionMethod;
 jmethodID buffer_limitMethod;
+jmethodID buffer_isDirectMethod;
+jmethodID cryptoUpcallsClass_rawSignMethod;
+jmethodID cryptoUpcallsClass_rsaSignMethod;
+jmethodID cryptoUpcallsClass_rsaDecryptMethod;
+jmethodID sslHandshakeCallbacks_verifyCertificateChain;
+jmethodID sslHandshakeCallbacks_onSSLStateChange;
+jmethodID sslHandshakeCallbacks_clientCertificateRequested;
+jmethodID sslHandshakeCallbacks_serverCertificateRequested;
+jmethodID sslHandshakeCallbacks_clientPSKKeyRequested;
+jmethodID sslHandshakeCallbacks_serverPSKKeyRequested;
+jmethodID sslHandshakeCallbacks_onNewSessionEstablished;
+jmethodID sslHandshakeCallbacks_selectApplicationProtocol;
+jmethodID sslHandshakeCallbacks_serverSessionRequested;
 
 void init(JavaVM* vm, JNIEnv* env) {
     gJavaVM = vm;
@@ -64,6 +80,7 @@
     stringClass = findClass(env, "java/lang/String");
     byteBufferClass = findClass(env, "java/nio/ByteBuffer");
     bufferClass = findClass(env, "java/nio/Buffer");
+    fileDescriptorClass = findClass(env, "java/io/FileDescriptor");
 
     cryptoUpcallsClass = getGlobalRefToClass(
             env, TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/CryptoUpcalls");
@@ -71,8 +88,15 @@
             env, TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/NativeRef");
     openSslInputStreamClass = getGlobalRefToClass(
             env, TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLBIOInputStream");
+    sslHandshakeCallbacksClass = getGlobalRefToClass(
+            env, TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/NativeCrypto$SSLHandshakeCallbacks");
 
     nativeRef_address = getFieldRef(env, nativeRefClass, "address", "J");
+#if defined(ANDROID) && !defined(CONSCRYPT_OPENJDK)
+    fileDescriptor_fd = getFieldRef(env, fileDescriptorClass, "descriptor", "I");
+#else /* !ANDROID || CONSCRYPT_OPENJDK */
+    fileDescriptor_fd = getFieldRef(env, fileDescriptorClass, "fd", "I");
+#endif
 
     calendar_setMethod = getMethodRef(env, calendarClass, "set", "(IIIIII)V");
     inputStream_readMethod = getMethodRef(env, inputStreamClass, "read", "([B)I");
@@ -84,6 +108,41 @@
     outputStream_flushMethod = getMethodRef(env, outputStreamClass, "flush", "()V");
     buffer_positionMethod = getMethodRef(env, bufferClass, "position", "()I");
     buffer_limitMethod = getMethodRef(env, bufferClass, "limit", "()I");
+    buffer_isDirectMethod = getMethodRef(env, bufferClass, "isDirect", "()Z");
+    sslHandshakeCallbacks_verifyCertificateChain = getMethodRef(
+            env, sslHandshakeCallbacksClass, "verifyCertificateChain", "([[BLjava/lang/String;)V");
+    sslHandshakeCallbacks_onSSLStateChange =
+            getMethodRef(env, sslHandshakeCallbacksClass, "onSSLStateChange", "(II)V");
+    sslHandshakeCallbacks_clientCertificateRequested = getMethodRef(
+            env, sslHandshakeCallbacksClass, "clientCertificateRequested", "([B[I[[B)V");
+    sslHandshakeCallbacks_serverCertificateRequested =
+            getMethodRef(env, sslHandshakeCallbacksClass, "serverCertificateRequested", "()V");
+    sslHandshakeCallbacks_clientPSKKeyRequested = getMethodRef(
+            env, sslHandshakeCallbacksClass, "clientPSKKeyRequested", "(Ljava/lang/String;[B[B)I");
+    sslHandshakeCallbacks_serverPSKKeyRequested =
+            getMethodRef(env, sslHandshakeCallbacksClass, "serverPSKKeyRequested",
+                         "(Ljava/lang/String;Ljava/lang/String;[B)I");
+    sslHandshakeCallbacks_onNewSessionEstablished =
+            getMethodRef(env, sslHandshakeCallbacksClass, "onNewSessionEstablished", "(J)V");
+    sslHandshakeCallbacks_serverSessionRequested =
+            getMethodRef(env, sslHandshakeCallbacksClass, "serverSessionRequested", "([B)J");
+    sslHandshakeCallbacks_selectApplicationProtocol =
+            getMethodRef(env, sslHandshakeCallbacksClass, "selectApplicationProtocol", "([B)I");
+    cryptoUpcallsClass_rawSignMethod = env->GetStaticMethodID(
+            cryptoUpcallsClass, "ecSignDigestWithPrivateKey", "(Ljava/security/PrivateKey;[B)[B");
+    if (cryptoUpcallsClass_rawSignMethod == nullptr) {
+        env->FatalError("Could not find ecSignDigestWithPrivateKey");
+    }
+    cryptoUpcallsClass_rsaSignMethod = env->GetStaticMethodID(
+            cryptoUpcallsClass, "rsaSignDigestWithPrivateKey", "(Ljava/security/PrivateKey;I[B)[B");
+    if (cryptoUpcallsClass_rsaSignMethod == nullptr) {
+        env->FatalError("Could not find rsaSignDigestWithPrivateKey");
+    }
+    cryptoUpcallsClass_rsaDecryptMethod = env->GetStaticMethodID(
+            cryptoUpcallsClass, "rsaDecryptWithPrivateKey", "(Ljava/security/PrivateKey;I[B)[B");
+    if (cryptoUpcallsClass_rsaDecryptMethod == nullptr) {
+        env->FatalError("Could not find rsaDecryptWithPrivateKey");
+    }
 }
 
 void jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods,
@@ -106,19 +165,25 @@
 }
 
 int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor) {
-    ScopedLocalRef<jclass> localClass(env, env->FindClass("java/io/FileDescriptor"));
-#if defined(ANDROID) && !defined(CONSCRYPT_OPENJDK)
-    static jfieldID fid = env->GetFieldID(localClass.get(), "descriptor", "I");
-#else /* !ANDROID || CONSCRYPT_OPENJDK */
-    static jfieldID fid = env->GetFieldID(localClass.get(), "fd", "I");
-#endif
     if (fileDescriptor != nullptr) {
-        return env->GetIntField(fileDescriptor, fid);
+        return env->GetIntField(fileDescriptor, fileDescriptor_fd);
     } else {
         return -1;
     }
 }
 
+extern bool isDirectByteBufferInstance(JNIEnv* env, jobject buffer) {
+    // Some versions of ART do not check the buffer validity when handling GetDirectBufferAddress()
+    // and GetDirectBufferCapacity().
+    if (buffer == nullptr) {
+        return false;
+    }
+    if (!env->IsInstanceOf(buffer, conscrypt::jniutil::byteBufferClass)) {
+        return false;
+    }
+    return env->CallBooleanMethod(buffer, conscrypt::jniutil::buffer_isDirectMethod) == JNI_TRUE;
+}
+
 bool isGetByteArrayElementsLikelyToReturnACopy(size_t size) {
 #if defined(ANDROID) && !defined(CONSCRYPT_OPENJDK)
     // ART's GetByteArrayElements creates copies only for arrays smaller than 12 kB.
@@ -153,9 +218,11 @@
     return conscrypt::jniutil::throwException(env, "java/lang/RuntimeException", msg);
 }
 
+#ifdef CONSCRYPT_CHECK_ERROR_QUEUE
 int throwAssertionError(JNIEnv* env, const char* msg) {
     return conscrypt::jniutil::throwException(env, "java/lang/AssertionError", msg);
 }
+#endif
 
 int throwNullPointerException(JNIEnv* env, const char* msg) {
     return conscrypt::jniutil::throwException(env, "java/lang/NullPointerException", msg);
diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc
index 5b0acf0..94a61fd 100644
--- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc
+++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc
@@ -52,6 +52,7 @@
 #include <openssl/x509v3.h>
 
 #include <limits>
+#include <type_traits>
 #include <vector>
 
 using conscrypt::AppData;
@@ -283,10 +284,19 @@
 /**
  * Converts various OpenSSL ASN.1 types to a jbyteArray with DER-encoded data
  * inside. The "i2d_func" function pointer is a function of the "i2d_<TYPE>"
- * from the OpenSSL ASN.1 API.
+ * from the OpenSSL ASN.1 API. Note i2d_func may take a const parameter, so we
+ * use a separate type parameter.
+ *
+ * TODO(https://crbug.com/boringssl/407): When all BoringSSL i2d functions are
+ * const, switch back to a single template parameter.
  */
-template <typename T>
-jbyteArray ASN1ToByteArray(JNIEnv* env, T* obj, int (*i2d_func)(T*, unsigned char**)) {
+template <typename T, typename U>
+jbyteArray ASN1ToByteArray(JNIEnv* env, T* obj, int (*i2d_func)(U*, unsigned char**)) {
+    // T and U should be the same type, but may differ in const.
+    static_assert(std::is_same<typename std::remove_const<T>::type,
+                               typename std::remove_const<U>::type>::value,
+                  "obj and i2d_func have incompatible types");
+
     if (obj == nullptr) {
         conscrypt::jniutil::throwNullPointerException(env, "ASN1 input == null");
         JNI_TRACE("ASN1ToByteArray(%p) => null input", obj);
@@ -559,16 +569,9 @@
         memcpy(messageBytes.get(), message, message_len);
     }
 
-    jmethodID rawSignMethod = env->GetStaticMethodID(conscrypt::jniutil::cryptoUpcallsClass,
-                                                     "ecSignDigestWithPrivateKey",
-                                                     "(Ljava/security/PrivateKey;[B)[B");
-    if (rawSignMethod == nullptr) {
-        CONSCRYPT_LOG_ERROR("Could not find ecSignDigestWithPrivateKey");
-        return nullptr;
-    }
-
     return reinterpret_cast<jbyteArray>(env->CallStaticObjectMethod(
-            conscrypt::jniutil::cryptoUpcallsClass, rawSignMethod, privateKey, messageArray.get()));
+            conscrypt::jniutil::cryptoUpcallsClass,
+            conscrypt::jniutil::cryptoUpcallsClass_rawSignMethod, privateKey, messageArray.get()));
 }
 
 static jbyteArray rsaSignDigestWithPrivateKey(JNIEnv* env, jobject privateKey, jint padding,
@@ -594,16 +597,9 @@
         memcpy(messageBytes.get(), message, message_len);
     }
 
-    jmethodID rsaSignMethod = env->GetStaticMethodID(conscrypt::jniutil::cryptoUpcallsClass,
-                                                     "rsaSignDigestWithPrivateKey",
-                                                     "(Ljava/security/PrivateKey;I[B)[B");
-    if (rsaSignMethod == nullptr) {
-        CONSCRYPT_LOG_ERROR("Could not find rsaSignDigestWithPrivateKey");
-        return nullptr;
-    }
-
     return reinterpret_cast<jbyteArray>(
-            env->CallStaticObjectMethod(conscrypt::jniutil::cryptoUpcallsClass, rsaSignMethod,
+            env->CallStaticObjectMethod(conscrypt::jniutil::cryptoUpcallsClass,
+                                        conscrypt::jniutil::cryptoUpcallsClass_rsaSignMethod,
                                         privateKey, padding, messageArray.get()));
 }
 
@@ -634,16 +630,9 @@
         memcpy(ciphertextBytes.get(), ciphertext, ciphertext_len);
     }
 
-    jmethodID rsaDecryptMethod =
-            env->GetStaticMethodID(conscrypt::jniutil::cryptoUpcallsClass,
-                                   "rsaDecryptWithPrivateKey", "(Ljava/security/PrivateKey;I[B)[B");
-    if (rsaDecryptMethod == nullptr) {
-        CONSCRYPT_LOG_ERROR("Could not find rsaDecryptWithPrivateKey");
-        return nullptr;
-    }
-
     return reinterpret_cast<jbyteArray>(
-            env->CallStaticObjectMethod(conscrypt::jniutil::cryptoUpcallsClass, rsaDecryptMethod,
+            env->CallStaticObjectMethod(conscrypt::jniutil::cryptoUpcallsClass,
+                                        conscrypt::jniutil::cryptoUpcallsClass_rsaDecryptMethod,
                                         privateKey, padding, ciphertextArray.get()));
 }
 
@@ -2204,7 +2193,9 @@
     const EC_GROUP* group = fromContextObject<EC_GROUP>(env, groupRef);
     JNI_TRACE("EC_KEY_marshal_curve_name(%p)", group);
     if (group == nullptr) {
+        env->ExceptionClear();
         conscrypt::jniutil::throwIOException(env, "Invalid group pointer");
+        JNI_TRACE("group=%p EC_KEY_marshal_curve_name => Invalid group pointer", group);
         return nullptr;
     }
 
@@ -2231,8 +2222,9 @@
 
     ScopedByteArrayRO bytes(env, curveNameBytes);
     if (bytes.get() == nullptr) {
-        conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding");
-        JNI_TRACE("bytes=%p EC_KEY_parse_curve_name => threw exception", curveNameBytes);
+        env->ExceptionClear();
+        conscrypt::jniutil::throwIOException(env, "Null EC curve name");
+        JNI_TRACE("bytes=%p EC_KEY_parse_curve_name => curveNameBytes == null ", curveNameBytes);
         return 0;
     }
 
@@ -3208,6 +3200,12 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     JNI_TRACE("EVP_get_cipherbyname(%p)", algorithm);
 
+    if (algorithm == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "algorithm == null");
+        JNI_TRACE("EVP_get_cipherbyname(%p) => algorithm == null", algorithm);
+        return -1;
+    }
+
     ScopedUtfChars scoped_alg(env, algorithm);
     const char* alg = scoped_alg.c_str();
     const EVP_CIPHER* cipher;
@@ -3245,7 +3243,7 @@
     } else if (strcasecmp(alg, "aes-256-gcm") == 0) {
         cipher = EVP_aes_256_gcm();
     } else {
-        JNI_TRACE("NativeCrypto_EVP_get_digestbyname(%s) => error", alg);
+        JNI_TRACE("NativeCrypto_EVP_get_cipherbyname(%s) => error", alg);
         return 0;
     }
 
@@ -3684,20 +3682,22 @@
     JNI_TRACE("evp_aead_ctx_op(%p, %p, %d, %p, %p, %p, %p)", evpAead, keyArray, tagLen,
               outBuffer, nonceArray, inBuffer, aadArray);
 
-    if (env->IsInstanceOf(inBuffer, conscrypt::jniutil::byteBufferClass) != JNI_TRUE ||
-                        env->IsInstanceOf(outBuffer, conscrypt::jniutil::byteBufferClass) != JNI_TRUE  ) {
-        conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", "ByteBuffer Class Error");
+    if (!conscrypt::jniutil::isDirectByteBufferInstance(env, inBuffer)) {
+        conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException",
+                                           "inBuffer is not a direct ByteBuffer");
+        return 0;
+    }
+
+    if (!conscrypt::jniutil::isDirectByteBufferInstance(env, outBuffer)) {
+        conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException",
+                                           "outBuffer is not a direct ByteBuffer");
         return 0;
     }
 
     uint8_t* inBuf;
     jint in_limit;
     jint in_position;
-    jint inCapacity = env->GetDirectBufferCapacity(inBuffer);
-    if (inCapacity == -1) {
-        conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", "Non Direct ByteBuffer  Error");
-        return 0;
-    }
+
     inBuf = (uint8_t*)(env->GetDirectBufferAddress(inBuffer));
      // limit is the index of the first element that should not be read or written
     in_limit = env->CallIntMethod(inBuffer,conscrypt::jniutil::buffer_limitMethod);
@@ -3707,11 +3707,6 @@
     uint8_t* outBuf;
     jint out_limit;
     jint out_position;
-    jint outCapacity = env->GetDirectBufferCapacity(outBuffer);
-    if (outCapacity == -1) {
-        conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", "Non Direct ByteBuffer  Error");
-        return 0;
-    }
     outBuf = (uint8_t*)(env->GetDirectBufferAddress(outBuffer));
     // limit is the index of the first element that should not be read or written
     out_limit = env->CallIntMethod(outBuffer,conscrypt::jniutil::buffer_limitMethod);
@@ -4176,17 +4171,24 @@
         case GEN_EMAIL:
         case GEN_DNS:
         case GEN_URI: {
-            // This must not be a T61String and must not contain NULs.
-            const char* data = reinterpret_cast<const char*>(ASN1_STRING_data(gen->d.ia5));
+            // This must be a valid IA5String and must not contain NULs.
+            // BoringSSL does not currently enforce the former (see
+            // https://crbug.com/boringssl/427). The latter was historically an
+            // issue for parsers that truncate at NUL.
+            const uint8_t* data = ASN1_STRING_get0_data(gen->d.ia5);
             ssize_t len = ASN1_STRING_length(gen->d.ia5);
-            if ((len == static_cast<ssize_t>(strlen(data))) &&
-                (ASN1_PRINTABLE_type(ASN1_STRING_data(gen->d.ia5), len) != V_ASN1_T61STRING)) {
-                JNI_TRACE("GENERAL_NAME_to_jobject(%p) => Email/DNS/URI \"%s\"", gen, data);
-                return env->NewStringUTF(data);
-            } else {
-                JNI_TRACE("GENERAL_NAME_to_jobject(%p) => Email/DNS/URI invalid", gen);
-                return nullptr;
+            std::vector<jchar> jchars;
+            jchars.reserve(len);
+            for (ssize_t i = 0; i < len; i++) {
+                if (data[i] == 0 || data[i] > 127) {
+                    JNI_TRACE("GENERAL_NAME_to_jobject(%p) => Email/DNS/URI invalid", gen);
+                    return nullptr;
+                }
+                // Converting ASCII to UTF-16 is the identity function.
+                jchars.push_back(data[i]);
             }
+            JNI_TRACE("GENERAL_NAME_to_jobject(%p)=> Email/DNS/URI \"%.*s\"", gen, (int) len, data);
+            return env->NewString(jchars.data(), jchars.size());
         }
         case GEN_DIRNAME:
             /* Write in RFC 2253 format */
@@ -4378,6 +4380,12 @@
     X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
     JNI_TRACE("X509_get_version(%p)", x509);
 
+    if (x509 == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "x509 == null");
+        JNI_TRACE("X509_get_version(%p) => x509 == null", x509);
+        return 0;
+    }
+
     // NOLINTNEXTLINE(runtime/int)
     long version = X509_get_version(x509);
     JNI_TRACE("X509_get_version(%p) => %ld", x509, version);
@@ -4417,6 +4425,12 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
     JNI_TRACE("X509_get_serialNumber(%p)", x509);
+
+    if (x509 == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "x509 == null");
+        JNI_TRACE("X509_get_serialNumber(%p) => x509 == null", x509);
+        return nullptr;
+    }
     return get_X509Type_serialNumber<X509>(env, x509, X509_get0_serialNumber);
 }
 
@@ -4425,6 +4439,12 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509_REVOKED* revoked = reinterpret_cast<X509_REVOKED*>(static_cast<uintptr_t>(x509RevokedRef));
     JNI_TRACE("X509_REVOKED_get_serialNumber(%p)", revoked);
+
+    if (revoked == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "revoked == null");
+        JNI_TRACE("X509_REVOKED_get_serialNumber(%p) => revoked == null", revoked);
+        return 0;
+    }
     return get_X509Type_serialNumber<X509_REVOKED>(env, revoked, X509_REVOKED_get0_serialNumber);
 }
 
@@ -4546,6 +4566,16 @@
     X509* x509_2 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref2));
     JNI_TRACE("X509_check_issued(%p, %p)", x509_1, x509_2);
 
+    if (x509_1 == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "x509Ref1 == null");
+        JNI_TRACE("X509_check_issued(%p, %p) => x509_1 == null", x509_1, x509_2);
+        return 0;
+    }
+    if (x509_2 == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "x509Ref2 == null");
+        JNI_TRACE("X509_check_issued(%p, %p) => x509_2 == null", x509_1, x509_2);
+        return 0;
+    }
     int ret = X509_check_issued(x509_1, x509_2);
     JNI_TRACE("X509_check_issued(%p, %p) => %d", x509_1, x509_2, ret);
     return ret;
@@ -4601,6 +4631,12 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
     JNI_TRACE("get_X509_signature(%p)", x509);
+
+    if (x509 == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "x509 == null");
+        JNI_TRACE("get_X509_signature(%p) => x509 == null", x509);
+        return nullptr;
+    }
     return get_X509Type_signature<X509>(env, x509, get_X509_signature);
 }
 
@@ -4609,6 +4645,12 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
     JNI_TRACE("get_X509_CRL_signature(%p)", crl);
+
+    if (crl == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "crl == null");
+        JNI_TRACE("X509_CRL_signature(%p) => crl == null", crl);
+        return nullptr;
+    }
     return get_X509Type_signature<X509_CRL>(env, crl, get_X509_CRL_signature);
 }
 
@@ -4694,6 +4736,7 @@
 
     if (crl == nullptr) {
         conscrypt::jniutil::throwNullPointerException(env, "crl == null");
+        JNI_TRACE("X509_CRL_get_REVOKED(%p) => crl == null", crl);
         return nullptr;
     }
 
@@ -4721,6 +4764,12 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
     JNI_TRACE("i2d_X509_CRL(%p)", crl);
+
+    if (crl == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "crl == null");
+        JNI_TRACE("i2d_X509_CRL(%p) => crl == null", crl);
+        return nullptr;
+    }
     return ASN1ToByteArray<X509_CRL>(env, crl, i2d_X509_CRL);
 }
 
@@ -4827,6 +4876,12 @@
                                                         CONSCRYPT_UNUSED jobject holder) {
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
+
+    if (crl == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "crl == null");
+        JNI_TRACE("X509_CRL_get_issuer_name(%p) => crl == null", crl);
+        return nullptr;
+    }
     JNI_TRACE("X509_CRL_get_issuer_name(%p)", crl);
     return ASN1ToByteArray<X509_NAME>(env, X509_CRL_get_issuer(crl), i2d_X509_NAME);
 }
@@ -4838,6 +4893,11 @@
     X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
     JNI_TRACE("X509_CRL_get_version(%p)", crl);
 
+    if (crl == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "crl == null");
+        JNI_TRACE("X509_CRL_get_version(%p) => crl == null", crl);
+        return 0;
+    }
     // NOLINTNEXTLINE(runtime/int)
     long version = X509_CRL_get_version(crl);
     JNI_TRACE("X509_CRL_get_version(%p) => %ld", crl, version);
@@ -4898,6 +4958,12 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
     JNI_TRACE("X509_CRL_get_ext(%p, %p)", crl, oid);
+
+    if (crl == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "crl == null");
+        JNI_TRACE("X509_CRL_get_ext(%p) => crl == null", crl);
+        return 0;
+    }
     X509_EXTENSION* ext =
             X509Type_get_ext<X509_CRL, X509_CRL_get_ext_by_OBJ, X509_CRL_get_ext>(env, crl, oid);
     JNI_TRACE("X509_CRL_get_ext(%p, %p) => %p", crl, oid, ext);
@@ -4991,6 +5057,12 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
     JNI_TRACE("get_X509_CRL_crl_enc(%p)", crl);
+
+    if (crl == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "crl == null");
+        JNI_TRACE("get_X509_CRL_crl_enc(%p) => crl == null", crl);
+        return nullptr;
+    }
     return ASN1ToByteArray<X509_CRL>(env, crl, i2d_X509_CRL_tbs);
 }
 
@@ -5528,6 +5600,12 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
     JNI_TRACE("i2d_X509(%p)", x509);
+
+    if (x509 == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "x509 == null");
+        JNI_TRACE("i2d_X509(%p) => x509 == null", x509);
+        return nullptr;
+    }
     return ASN1ToByteArray<X509>(env, x509, i2d_X509);
 }
 
@@ -5536,6 +5614,12 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
     JNI_TRACE("i2d_X509_PUBKEY(%p)", x509);
+
+    if (x509 == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "x509 == null");
+        JNI_TRACE("i2d_X509_PUBKEY(%p) => x509 == null", x509);
+        return nullptr;
+    }
     return ASN1ToByteArray<X509_PUBKEY>(env, X509_get_X509_PUBKEY(x509), i2d_X509_PUBKEY);
 }
 
@@ -5949,6 +6033,12 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
     JNI_TRACE("X509_get_issuer_name(%p)", x509);
+
+    if (x509 == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "x509 == null");
+        JNI_TRACE("X509_get_issuer_name(%p) => x509 == null", x509);
+        return nullptr;
+    }
     return ASN1ToByteArray<X509_NAME>(env, X509_get_issuer_name(x509), i2d_X509_NAME);
 }
 
@@ -5957,6 +6047,12 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
     JNI_TRACE("X509_get_subject_name(%p)", x509);
+
+    if (x509 == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "x509 == null");
+        JNI_TRACE("X509_get_subject_name(%p) => x509 == null", x509);
+        return nullptr;
+    }
     return ASN1ToByteArray<X509_NAME>(env, X509_get_subject_name(x509), i2d_X509_NAME);
 }
 
@@ -6199,6 +6295,12 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
     JNI_TRACE("X509_CRL_get_ext_oid(%p, %p)", crl, oidString);
+
+    if (crl == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "crl == null");
+        JNI_TRACE("X509_CRL_get_ext_oid(%p) => crl == null", crl);
+        return nullptr;
+    }
     return X509Type_get_ext_oid<X509_CRL, X509_CRL_get_ext_by_OBJ, X509_CRL_get_ext>(env, crl,
                                                                                      oidString);
 }
@@ -6208,6 +6310,12 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509_REVOKED* revoked = reinterpret_cast<X509_REVOKED*>(static_cast<uintptr_t>(x509RevokedRef));
     JNI_TRACE("X509_REVOKED_get_ext_oid(%p, %p)", revoked, oidString);
+
+    if (revoked == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "revoked == null");
+        JNI_TRACE("X509_REVOKED_get_ext_oid(%p) => revoked == null", revoked);
+        return nullptr;
+    }
     return X509Type_get_ext_oid<X509_REVOKED, X509_REVOKED_get_ext_by_OBJ, X509_REVOKED_get_ext>(
             env, revoked, oidString);
 }
@@ -6553,9 +6661,7 @@
     }
 
     jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks;
-    jclass cls = env->GetObjectClass(sslHandshakeCallbacks);
-    jmethodID methodID =
-            env->GetMethodID(cls, "verifyCertificateChain", "([[BLjava/lang/String;)V");
+    jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_verifyCertificateChain;
 
     const SSL_CIPHER* cipher = SSL_get_pending_cipher(ssl);
     const char* authMethod = SSL_CIPHER_get_kx_name(cipher);
@@ -6599,11 +6705,9 @@
 
     jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks;
 
-    jclass cls = env->GetObjectClass(sslHandshakeCallbacks);
-    jmethodID methodID = env->GetMethodID(cls, "onSSLStateChange", "(II)V");
-
     JNI_TRACE("ssl=%p info_callback calling onSSLStateChange", ssl);
-    env->CallVoidMethod(sslHandshakeCallbacks, methodID, type, value);
+    env->CallVoidMethod(sslHandshakeCallbacks,
+                        conscrypt::jniutil::sslHandshakeCallbacks_onSSLStateChange, type, value);
 
     if (env->ExceptionCheck()) {
         JNI_TRACE("ssl=%p info_callback exception", ssl);
@@ -6641,8 +6745,7 @@
     }
     jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks;
 
-    jclass cls = env->GetObjectClass(sslHandshakeCallbacks);
-    jmethodID methodID = env->GetMethodID(cls, "clientCertificateRequested", "([B[I[[B)V");
+    jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_clientCertificateRequested;
 
     // Call Java callback which can reconfigure the client certificate.
     const uint8_t* ctype = nullptr;
@@ -6716,8 +6819,7 @@
     }
 
     jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks;
-    jclass cls = env->GetObjectClass(sslHandshakeCallbacks);
-    jmethodID methodID = env->GetMethodID(cls, "serverCertificateRequested", "()V");
+    jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_serverCertificateRequested;
 
     JNI_TRACE("ssl=%p select_certificate_cb calling serverCertificateRequested", ssl);
     env->CallVoidMethod(sslHandshakeCallbacks, methodID);
@@ -6751,9 +6853,7 @@
     }
 
     jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks;
-    jclass cls = env->GetObjectClass(sslHandshakeCallbacks);
-    jmethodID methodID =
-            env->GetMethodID(cls, "clientPSKKeyRequested", "(Ljava/lang/String;[B[B)I");
+    jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_clientPSKKeyRequested;
     JNI_TRACE("ssl=%p psk_client_callback calling clientPSKKeyRequested", ssl);
     ScopedLocalRef<jstring> identityHintJava(env,
                                              (hint != nullptr) ? env->NewStringUTF(hint) : nullptr);
@@ -6819,9 +6919,7 @@
     }
 
     jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks;
-    jclass cls = env->GetObjectClass(sslHandshakeCallbacks);
-    jmethodID methodID = env->GetMethodID(cls, "serverPSKKeyRequested",
-                                          "(Ljava/lang/String;Ljava/lang/String;[B)I");
+    jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_serverPSKKeyRequested;
     JNI_TRACE("ssl=%p psk_server_callback calling serverPSKKeyRequested", ssl);
     const char* identityHint = SSL_get_psk_identity_hint(ssl);
     ScopedLocalRef<jstring> identityHintJava(
@@ -6873,8 +6971,7 @@
     }
 
     jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks;
-    jclass cls = env->GetObjectClass(sslHandshakeCallbacks);
-    jmethodID methodID = env->GetMethodID(cls, "onNewSessionEstablished", "(J)V");
+    jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_onNewSessionEstablished;
     JNI_TRACE("ssl=%p new_session_callback calling onNewSessionEstablished", ssl);
     env->CallVoidMethod(sslHandshakeCallbacks, methodID, reinterpret_cast<jlong>(session));
     if (env->ExceptionCheck()) {
@@ -6918,8 +7015,7 @@
                             reinterpret_cast<const jbyte*>(id));
 
     jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks;
-    jclass cls = env->GetObjectClass(sslHandshakeCallbacks);
-    jmethodID methodID = env->GetMethodID(cls, "serverSessionRequested", "([B)J");
+    jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_serverSessionRequested;
     JNI_TRACE("ssl=%p server_session_requested_callback calling serverSessionRequested", ssl);
     jlong ssl_session_address = env->CallLongMethod(sslHandshakeCallbacks, methodID, id_array);
     if (env->ExceptionCheck()) {
@@ -6956,7 +7052,8 @@
 
     // Packet preamble for text2pcap
     CONSCRYPT_LOG(LOG_INFO, LOG_TAG "-jni", "ssl=%p SSL_DATA: %c %ld.%06ld", ssl, direction,
-                  tv.tv_sec, static_cast<long>(tv.tv_usec));  // NOLINT(runtime/int)
+                  static_cast<long>(tv.tv_sec),
+                  static_cast<long>(tv.tv_usec));  // NOLINT(runtime/int)
 
     char out[kDataWidth * 3 + 1];
     for (size_t i = 0; i < len; i += kDataWidth) {
@@ -7145,8 +7242,7 @@
 }
 
 static void NativeCrypto_SSL_enable_tls_channel_id(JNIEnv* env, jclass, jlong ssl_address,
-                                                   CONSCRYPT_UNUSED CONSCRYPT_UNUSED jobject
-                                                           ssl_holder) {
+                                                   CONSCRYPT_UNUSED jobject ssl_holder) {
     CHECK_ERROR_QUEUE_ON_RETURN;
     SSL* ssl = to_SSL(env, ssl_address, true);
     JNI_TRACE("ssl=%p NativeCrypto_SSL_enable_tls_channel_id", ssl);
@@ -8041,8 +8137,7 @@
                             reinterpret_cast<const jbyte*>(in));
 
     // Invoke the selection method.
-    jclass cls = env->GetObjectClass(sslHandshakeCallbacks);
-    jmethodID methodID = env->GetMethodID(cls, "selectApplicationProtocol", "([B)I");
+    jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_selectApplicationProtocol;
     jint offset = env->CallIntMethod(sslHandshakeCallbacks, methodID, protocols.get());
 
     if (offset < 0) {
@@ -9339,11 +9434,12 @@
 
 static jstring NativeCrypto_SSL_CIPHER_get_kx_name(JNIEnv* env, jclass, jlong cipher_address) {
     CHECK_ERROR_QUEUE_ON_RETURN;
-    const SSL_CIPHER* cipher = to_SSL_CIPHER(env, cipher_address, true);
-    const char* kx_name = nullptr;
+    const SSL_CIPHER* cipher = to_SSL_CIPHER(env, cipher_address, /*throwIfNull=*/true);
+    if (cipher == nullptr) {
+        return nullptr;
+    }
 
-    kx_name = SSL_CIPHER_get_kx_name(cipher);
-
+    const char* kx_name = SSL_CIPHER_get_kx_name(cipher);
     return env->NewStringUTF(kx_name);
 }
 
@@ -9635,6 +9731,11 @@
 }
 
 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.
+    if (!conscrypt::jniutil::isDirectByteBufferInstance(env, buffer)) {
+        return 0;
+    }
     return reinterpret_cast<jlong>(env->GetDirectBufferAddress(buffer));
 }
 
diff --git a/common/src/jni/main/include/conscrypt/jniutil.h b/common/src/jni/main/include/conscrypt/jniutil.h
index 6f55608..dcde21e 100644
--- a/common/src/jni/main/include/conscrypt/jniutil.h
+++ b/common/src/jni/main/include/conscrypt/jniutil.h
@@ -41,7 +41,6 @@
 extern jclass outputStreamClass;
 extern jclass stringClass;
 extern jclass byteBufferClass;
-extern jclass bufferClass;
 
 extern jfieldID nativeRef_address;
 
@@ -53,6 +52,19 @@
 extern jmethodID outputStream_flushMethod;
 extern jmethodID buffer_positionMethod;
 extern jmethodID buffer_limitMethod;
+extern jmethodID buffer_isDirectMethod;
+extern jmethodID cryptoUpcallsClass_rawSignMethod;
+extern jmethodID cryptoUpcallsClass_rsaSignMethod;
+extern jmethodID cryptoUpcallsClass_rsaDecryptMethod;
+extern jmethodID sslHandshakeCallbacks_verifyCertificateChain;
+extern jmethodID sslHandshakeCallbacks_onSSLStateChange;
+extern jmethodID sslHandshakeCallbacks_clientCertificateRequested;
+extern jmethodID sslHandshakeCallbacks_serverCertificateRequested;
+extern jmethodID sslHandshakeCallbacks_clientPSKKeyRequested;
+extern jmethodID sslHandshakeCallbacks_serverPSKKeyRequested;
+extern jmethodID sslHandshakeCallbacks_onNewSessionEstablished;
+extern jmethodID sslHandshakeCallbacks_selectApplicationProtocol;
+extern jmethodID sslHandshakeCallbacks_serverSessionRequested;
 
 /**
  * Initializes the JNI constants from the environment.
@@ -135,6 +147,11 @@
 extern int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor);
 
 /**
+ * Returns true if buffer is a non-null direct ByteBuffer instance.
+ */
+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.
  */
@@ -159,10 +176,12 @@
  */
 extern int throwRuntimeException(JNIEnv* env, const char* msg);
 
+#ifdef CONSCRYPT_CHECK_ERROR_QUEUE
 /**
  * Throw a java.lang.AssertionError, with an optional message.
  */
 extern int throwAssertionError(JNIEnv* env, const char* msg);
+#endif
 
 /*
  * Throw a java.lang.NullPointerException, with an optional message.
diff --git a/common/src/test/java/org/conscrypt/ConscryptSuite.java b/common/src/test/java/org/conscrypt/ConscryptSuite.java
index 263afcd..cce41d5 100644
--- a/common/src/test/java/org/conscrypt/ConscryptSuite.java
+++ b/common/src/test/java/org/conscrypt/ConscryptSuite.java
@@ -35,6 +35,7 @@
 import org.conscrypt.java.security.KeyFactoryTestDSA;
 import org.conscrypt.java.security.KeyFactoryTestEC;
 import org.conscrypt.java.security.KeyFactoryTestRSA;
+import org.conscrypt.java.security.KeyFactoryTestRSACrt;
 import org.conscrypt.java.security.KeyPairGeneratorTest;
 import org.conscrypt.java.security.KeyPairGeneratorTestDH;
 import org.conscrypt.java.security.KeyPairGeneratorTestDSA;
@@ -80,8 +81,9 @@
         // org.conscrypt tests
         CertPinManagerTest.class,
         ChainStrengthAnalyzerTest.class,
-        TrustManagerImplTest.class,
         HostnameVerifierTest.class,
+        NativeCryptoArgTest.class,
+        TrustManagerImplTest.class,
         // org.conscrypt.ct tests
         CTVerifierTest.class,
         SerializationTest.class,
@@ -106,6 +108,7 @@
         KeyFactoryTestDSA.class,
         KeyFactoryTestEC.class,
         KeyFactoryTestRSA.class,
+        KeyFactoryTestRSACrt.class,
         KeyPairGeneratorTest.class,
         KeyPairGeneratorTestDH.class,
         KeyPairGeneratorTestDSA.class,
diff --git a/common/src/test/java/org/conscrypt/NativeCryptoArgTest.java b/common/src/test/java/org/conscrypt/NativeCryptoArgTest.java
new file mode 100644
index 0000000..76e8beb
--- /dev/null
+++ b/common/src/test/java/org/conscrypt/NativeCryptoArgTest.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.junit.AfterClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class NativeCryptoArgTest {
+    // Null value passed in for a long which represents a native address
+    private static final long NULL = 0L;
+    /*
+     * Non-null value passed in for a long which represents a native address. Shouldn't
+     * ever get de-referenced but we make it a multiple of 4 to avoid any alignment errors.
+     * Used in the case where there are multiple checks we want to test in a native method,
+     * so we can get past the first check and test the second one.
+     */
+    private static final long NOT_NULL = 4L;
+    private static final String CONSCRYPT_PACKAGE = NativeCryptoArgTest.class.getCanonicalName()
+            .substring(0, NativeCryptoArgTest.class.getCanonicalName().lastIndexOf('.') + 1);
+    private static final Set<String> testedMethods = new HashSet<>();
+    private final Map<String, Class<?>> classCache = new HashMap<>();
+    private final Map<String, Method> methodMap = buildMethodMap();
+
+    @AfterClass
+    public static void after() {
+        // TODO(prb): Temporary hacky check - remove
+        assertTrue(testedMethods.size() >= 190);
+    }
+
+    @Test
+    public void ecMethods() throws Throwable {
+        String[] illegalArgMethods = new String[] {
+                "EC_GROUP_new_arbitrary"
+        };
+        String[] ioExMethods = new String[] {
+                "EC_KEY_parse_curve_name",
+                "EC_KEY_marshal_curve_name"
+        };
+
+        // All of the EC_* methods apart from the exceptions below throw NPE if their
+        // first argument is null.
+        MethodFilter filter = MethodFilter.newBuilder("EC_ methods")
+                .hasPrefix("EC_")
+                .except(illegalArgMethods)
+                .except(ioExMethods)
+                .expectSize(16)
+                .build();
+        testMethods(filter, NullPointerException.class);
+
+        filter = MethodFilter.nameFilter("EC_ methods (IllegalArgument)", illegalArgMethods);
+        testMethods(filter, IllegalArgumentException.class);
+
+        filter = MethodFilter.nameFilter("EC_ methods (IOException)", ioExMethods);
+        testMethods(filter, IOException.class);
+    }
+
+    @Test
+    public void macMethods() throws Throwable {
+        // All of the non-void HMAC and CMAC methods throw NPE when passed a null pointer
+        MethodFilter filter = MethodFilter.newBuilder("HMAC methods")
+                .hasPrefix("HMAC_")
+                .takesArguments()
+                .expectSize(5)
+                .build();
+        testMethods(filter, NullPointerException.class);
+
+        filter = MethodFilter.newBuilder("CMAC methods")
+                .hasPrefix("CMAC_")
+                .takesArguments()
+                .expectSize(5)
+                .build();
+        testMethods(filter, NullPointerException.class);
+    }
+
+    @Test
+    public void sslMethods() throws Throwable {
+        // These methods don't throw on a null first arg as they can get called before the
+        // connection is fully initialised. However if the first arg is non-NULL, any subsequent
+        // null args should throw NPE.
+        String[] nonThrowingMethods = new String[] {
+                "SSL_interrupt",
+                "SSL_shutdown",
+                "ENGINE_SSL_shutdown",
+        };
+
+        // Most of the NativeSsl methods take a long holding a pointer to the native
+        // object followed by a {@code NativeSsl} holder object. However the second arg
+        // is unused(!) so we don't need to test it.
+        MethodFilter filter = MethodFilter.newBuilder("NativeSsl methods")
+                .hasArg(0, long.class)
+                .hasArg(1, conscryptClass("NativeSsl"))
+                .except(nonThrowingMethods)
+                .expectSize(60)
+                .build();
+
+        testMethods(filter, NullPointerException.class);
+
+        // Many of the SSL_* methods take a single long which points
+        // to a native object.
+        filter = MethodFilter.newBuilder("1-arg SSL methods")
+                .hasPrefix("SSL_")
+                .hasArgLength(1)
+                .hasArg(0, long.class)
+                .expectSize(10)
+                .build();
+
+        testMethods(filter, NullPointerException.class);
+
+        filter = MethodFilter.nameFilter("Non throwing NativeSsl methods", nonThrowingMethods);
+        testMethods(filter, null);
+
+        expectVoid("SSL_shutdown", NOT_NULL, null, null, null);
+        expectNPE("SSL_shutdown", NOT_NULL, null, new FileDescriptor(), null);
+        expectNPE("ENGINE_SSL_shutdown", NOT_NULL, null, null);
+        expectVoid("SSL_set_session", NOT_NULL, null, NULL);
+    }
+
+    @Test
+    public void evpMethods() throws Throwable {
+        String[] illegalArgMethods = new String[] {
+                "EVP_AEAD_CTX_open_buf",
+                "EVP_AEAD_CTX_seal_buf",
+                "EVP_PKEY_new_RSA"
+        };
+        String[] nonThrowingMethods = new String[] {
+                "EVP_MD_CTX_destroy",
+                "EVP_PKEY_CTX_free",
+                "EVP_PKEY_free",
+                "EVP_CIPHER_CTX_free"
+        };
+
+        // All of the non-void EVP_ methods apart from the above should throw on a null
+        // first argument.
+        MethodFilter filter = MethodFilter.newBuilder("EVP methods")
+                .hasPrefix("EVP_")
+                .takesArguments()
+                .except(illegalArgMethods)
+                .except(nonThrowingMethods)
+                .expectSize(45)
+                .build();
+
+        testMethods(filter, NullPointerException.class);
+
+        filter = MethodFilter.nameFilter("EVP methods (IllegalArgument)", illegalArgMethods);
+        testMethods(filter, IllegalArgumentException.class);
+
+        filter = MethodFilter.nameFilter("EVP methods (non-throwing)", nonThrowingMethods);
+        testMethods(filter, null);
+    }
+
+    @Test
+    public void x509Methods() throws Throwable {
+        // A number of X509 methods have a native pointer as arg 0 and an
+        // OpenSSLX509Certificate or OpenSSLX509CRL as arg 1.
+        MethodFilter filter = MethodFilter.newBuilder("X509 methods")
+                .hasArgLength(2)
+                .hasArg(0, long.class)
+                .hasArg(1, conscryptClass("OpenSSLX509Certificate"),
+                        conscryptClass("OpenSSLX509CRL"))
+                .expectSize(32)
+                .build();
+        // TODO(prb): test null second argument
+        testMethods(filter, NullPointerException.class);
+
+        // The rest of the X509 methods are somewhat ad hoc.
+        expectNPE("d2i_X509", (Object) null);
+
+        invokeAndExpect( conscryptThrowable("OpenSSLX509CertificateFactory$ParsingException"),
+                 "d2i_X509", new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0});
+
+        expectNPE("d2i_X509_bio", NULL);
+        expectNPE("PEM_read_bio_X509", NULL);
+        expectNPE("ASN1_seq_pack_X509", (Object) null);
+
+        // TODO(prb): Check what this should really throw
+        // expectNPE("ASN1_seq_pack_X509", (Object) new long[] { NULL });
+
+        expectNPE("ASN1_seq_unpack_X509_bio", NULL);
+
+        //
+        expectNPE("X509_cmp", NULL, null, NULL, null);
+        expectNPE("X509_cmp", NOT_NULL, null, NULL, null);
+        expectNPE("X509_cmp", NULL, null, NOT_NULL, null);
+
+        expectNPE("X509_print_ex", NULL, NULL, null, NULL, NULL);
+        expectNPE("X509_print_ex", NOT_NULL, NULL, null, NULL, NULL);
+        expectNPE("X509_print_ex", NULL, NOT_NULL, null, NULL, NULL);
+    }
+
+    private void testMethods(MethodFilter filter, Class<? extends Throwable> exceptionClass)
+            throws Throwable {
+        List<Method> methods = filter.filter(methodMap.values());
+
+        for (Method method : methods) {
+            List<Object[]> argsLists = permuteArgs(method);
+            for (Object[] args : argsLists) {
+                invokeAndExpect(exceptionClass, method, args);
+            }
+        }
+    }
+
+    private List<Object[]> permuteArgs(Method method) {
+        // For now just supply 0 for integral types and null for everything else
+        // TODO: allow user defined strategy, e.g. if two longs passed as native refs,
+        // generate {NULL,NULL}, {NULL,NOT_NULL}, {NOT_NULL,NULL} to test both null checks
+        List<Object[]> result = new ArrayList<>(1);
+
+        Class<?>[] argTypes = method.getParameterTypes();
+
+        int argCount = argTypes.length;
+        assertTrue(argCount > 0);
+        Object[] args = new Object[argCount];
+
+        for (int arg = 0; arg < argCount; arg++) {
+            if (argTypes[arg] == int.class) {
+                args[arg] = 0;
+            } else if (argTypes[arg] == long.class) {
+                args[arg] = NULL;
+            } else if (argTypes[arg] == boolean.class) {
+                args[arg] = false;
+            } else {
+                args[arg] = null;
+            }
+        }
+        result.add(args);
+        return result;
+    }
+
+    private void expectVoid(String methodName, Object... args) throws Throwable {
+        invokeAndExpect(null, methodName, args);
+    }
+
+    private void expectNPE(String methodName, Object... args) throws Throwable {
+        invokeAndExpect(NullPointerException.class, methodName, args);
+    }
+
+    private void invokeAndExpect(Class<? extends Throwable> expectedThrowable, String methodName,
+                                   Object... args) throws Throwable {
+        Method method = methodMap.get(methodName);
+        assertNotNull(method);
+        assertEquals(methodName, method.getName());
+        invokeAndExpect(expectedThrowable, method, args);
+    }
+
+    private void invokeAndExpect(Class<? extends Throwable> expectedThrowable, Method method,
+                                   Object... args) throws Throwable {
+        try {
+            method.invoke(null, args);
+            if (expectedThrowable != null) {
+                fail("No exception thrown by method " + method.getName());
+            }
+        } catch (IllegalAccessException e) {
+            throw new AssertionError("Illegal access", e);
+        } catch (InvocationTargetException e) {
+            Throwable cause = e.getCause();
+            if (expectedThrowable != null) {
+                assertEquals("Method: " + method.getName(), expectedThrowable, cause.getClass());
+            } else {
+                throw cause;
+            }
+        }
+        testedMethods.add(method.getName());
+    }
+
+    @SuppressWarnings("unchecked")
+    private Class<? extends Throwable> conscryptThrowable(String name) {
+        Class<?> klass = conscryptClass(name);
+        assertNotNull(klass);
+        assertTrue(Throwable.class.isAssignableFrom(klass));
+        return (Class<? extends Throwable>) klass;
+    }
+
+    private Class<?> conscryptClass(String className) {
+        return classCache.computeIfAbsent(className, s -> {
+            try {
+                return Class.forName(CONSCRYPT_PACKAGE + className);
+            } catch (ClassNotFoundException e) {
+                return null;
+            }
+        });
+    }
+
+    private Map<String, Method> buildMethodMap() {
+        Map<String, Method> classMap = new HashMap<>();
+        assertNotNull(classMap);
+        Class<?> nativeCryptoClass = conscryptClass("NativeCrypto");
+        assertNotNull(nativeCryptoClass);
+        for (Method method : nativeCryptoClass.getDeclaredMethods()) {
+            int modifiers = method.getModifiers();
+            if (!Modifier.isNative(modifiers)) {
+                continue;
+            }
+            method.setAccessible(true);
+            classMap.put(method.getName(), method);
+        }
+        return classMap;
+    }
+}
diff --git a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java
index ff9be81..571c1a7 100644
--- a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java
+++ b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java
@@ -42,7 +42,6 @@
 import java.util.Arrays;
 import java.util.List;
 import org.junit.ClassRule;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
@@ -69,7 +68,6 @@
     }
 
     @Test
-    @Ignore("b/209335673 Base image has fix for b/191150645 but release version of module does not")
     public void getEncodedFailsWhenCrtValuesMissing() throws Exception {
         PrivateKey privateKey = getPrivateKey();
         try {
@@ -121,7 +119,6 @@
     }
 
     @Test
-    @Ignore("b/209335673 Base image has fix for b/191150645 but release version of module does not")
     public void javaSerialization() throws Exception{
         PrivateKey privatekey = getPrivateKey();
 
@@ -136,13 +133,12 @@
         assertEquals(privatekey, copy);
     }
 
-    // b/209335673 Base image has fix for b/191150645 but release version of module does not,
-    // @Override
-    // protected List<KeyPair> getKeys() throws NoSuchAlgorithmException, InvalidKeySpecException {
-    //     return Arrays.asList(
-    //             new KeyPair(DefaultKeys.getPublicKey(algorithmName), getPrivateKey())
-    //     );
-    // }
+    @Override
+    protected List<KeyPair> getKeys() throws NoSuchAlgorithmException, InvalidKeySpecException {
+        return Arrays.asList(
+                new KeyPair(DefaultKeys.getPublicKey(algorithmName), getPrivateKey())
+        );
+    }
 
     // The private RSA key returned by DefaultKeys.getPrivateKey() is built from a PKCS#8
     // KeySpec and so will be an instance of RSAPrivateCrtKey, but we want to test RSAPrivateKey
diff --git a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSACrt.java b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSACrt.java
index 3afa73f..bddd898 100644
--- a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSACrt.java
+++ b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSACrt.java
@@ -30,7 +30,6 @@
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPublicKeySpec;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -60,8 +59,7 @@
   @Test
   public void testExtraBufferSpace_Private() throws Exception {
     PrivateKey privateKey = DefaultKeys.getPrivateKey("RSA");
-    // b/209335673 Base image has fix for b/191150645 but release version of module does not
-    // assertTrue(privateKey instanceof RSAPrivateCrtKey);
+    assertTrue(privateKey instanceof RSAPrivateCrtKey);
 
     byte[] encoded = privateKey.getEncoded();
     byte[] longBuffer = new byte[encoded.length + 147];
@@ -72,7 +70,6 @@
   }
 
   @Test
-  @Ignore("b/209335673 Base image has fix for b/191150645 but release version of module does not")
   public void javaSerialization() throws Exception{
     PrivateKey privateKey = DefaultKeys.getPrivateKey("RSA");
     assertTrue(privateKey instanceof RSAPrivateCrtKey);
diff --git a/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java b/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java
index 8f4f896..e77f492 100644
--- a/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java
+++ b/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java
@@ -148,6 +148,33 @@
              || algorithm.equals("AES/OFB/PKCS7PADDING"))) {
             return false;
         }
+
+        if (provider.equals("BC")) {
+            return isSupportedByBC(algorithm);
+        }
+
+        return true;
+    }
+
+    /**
+     * Checks for algorithms removed from BC in Android 12 and so not usable for these
+     * tests.
+     *
+     * TODO(prb): make this version aware, as this test runs against BC on older Android
+     * versions via MTS and should continue to test these algorithms there.
+     *
+     */
+    private static boolean isSupportedByBC(String algorithm) {
+        String[] removedBcPrefices = new String[]{
+            "AES/ECB",
+            "AES/CBC",
+            "AES/GCM"
+        };
+        for (String prefix : removedBcPrefices) {
+            if (algorithm.startsWith(prefix)) {
+                return false;
+            }
+        }
         return true;
     }
 
@@ -1080,7 +1107,9 @@
                             for (String padding : paddings) {
                                 final String algorithmName = algorithm + "/" + mode + "/" + padding;
                                 try {
-                                    test_Cipher_Algorithm(provider, algorithmName);
+                                    if (isSupported(algorithmName, provider.getName())) {
+                                        test_Cipher_Algorithm(provider, algorithmName);
+                                    }
                                 } catch (Throwable e) {
                                     out.append("Error encountered checking " + algorithmName
                                                + " with provider " + provider.getName() + "\n");
@@ -3475,6 +3504,9 @@
             if (provider.equals("SunJCE") && transformation.endsWith("/PKCS7PADDING")) {
                 return false;
             }
+            if (provider.equals("BC")) {
+                return isSupportedByBC(transformation);
+            }
             return true;
         }
     }
@@ -4102,6 +4134,9 @@
         final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();
         PrintStream out = new PrintStream(errBuffer);
         for (CipherTestParam p : CIPHER_TEST_PARAMS) {
+            if (!p.compatibleWith(provider)) {
+                continue;
+            }
             try {
                 checkCipher_ShortBlock_Failure(p, provider);
             } catch (Exception e) {
@@ -4301,32 +4336,6 @@
         }
     }
 
-    // Test that when reading GCM parameters encoded using ASN1, a value for the tag size
-    // not present indicates a value of 12.
-    // https://b/29876633
-    @Test
-    public void test_DefaultGCMTagSizeAlgorithmParameterSpec() throws Exception {
-        Assume.assumeNotNull(Security.getProvider("BC"));
-        final String AES = "AES";
-        final String AES_GCM = "AES/GCM/NoPadding";
-        byte[] input = new byte[16];
-        byte[] key = new byte[16];
-        Cipher cipher = Cipher.getInstance(AES_GCM, "BC");
-        AlgorithmParameters param = AlgorithmParameters.getInstance("GCM");
-        param.init(new byte[] {
-            (byte) 48,    // DER encoding : tag_Sequence
-            (byte) 14,    // DER encoding : total length
-            (byte) 4,     // DER encoding : tag_OctetString
-            (byte) 12,    // DER encoding : counter length
-            (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0,
-            (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0 });
-        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, AES), param);
-        byte[] ciphertext = cipher.update(input);
-        assertEquals(16, ciphertext.length);
-        byte[] tag = cipher.doFinal();
-        assertEquals(12, tag.length);
-    }
-
     @Test
     public void testAES_ECB_PKCS5Padding_ShortBuffer_Failure() throws Exception {
         for (String provider : AES_PROVIDERS) {
@@ -4389,7 +4398,11 @@
     }
 
     private void testAES_ECB_NoPadding_IncrementalUpdate_Success(String provider) throws Exception {
-        Cipher c = Cipher.getInstance("AES/ECB/NoPadding", provider);
+        String algorithm = "AES/ECB/NoPadding";
+        if (!isSupported(algorithm, provider)) {
+            return;
+        }
+        Cipher c = Cipher.getInstance(algorithm, provider);
         assertEquals(provider, c.getProvider().getName());
         c.init(Cipher.ENCRYPT_MODE, AES_128_KEY);
 
@@ -4423,7 +4436,11 @@
     }
 
     private void testAES_ECB_NoPadding_IvParameters_Failure(String provider) throws Exception {
-        Cipher c = Cipher.getInstance("AES/ECB/NoPadding", provider);
+        String algorithm = "AES/ECB/NoPadding";
+        if (!isSupported(algorithm, provider)) {
+            return;
+        }
+        Cipher c = Cipher.getInstance(algorithm, provider);
 
         AlgorithmParameterSpec spec = new IvParameterSpec(AES_IV_ZEROES);
         try {
diff --git a/common/src/test/java/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java b/common/src/test/java/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java
index 4cd0843..9f64315 100644
--- a/common/src/test/java/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java
+++ b/common/src/test/java/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java
@@ -85,6 +85,10 @@
     @Test
     public void test_XDHKeyAgreement() throws Exception {
         for (Provider p : Security.getProviders("KeyAgreement.XDH")) {
+            // Skip testing Android Keystore as it's covered by CTS tests.
+            if ("AndroidKeyStore".equals(p.getName())) {
+                continue;
+            }
             setupKeys(p);
 
             KeyAgreement ka = KeyAgreement.getInstance("XDH", p);
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/ClientSessionContext.java b/repackaged/common/src/main/java/com/android/org/conscrypt/ClientSessionContext.java
index 41bc8d2..c4fa3a7 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/ClientSessionContext.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/ClientSessionContext.java
@@ -52,10 +52,9 @@
      * Applications should not use this method. Instead use {@link
      * Conscrypt#setClientSessionCache(SSLContext, SSLClientSessionCache)}.
      */
-    @android.compat.annotation
-            .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
-            public void setPersistentCache(SSLClientSessionCache persistentCache) {
+    @android.compat.annotation.UnsupportedAppUsage
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    public void setPersistentCache(SSLClientSessionCache persistentCache) {
         this.persistentCache = persistentCache;
     }
 
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/FileClientSessionCache.java b/repackaged/common/src/main/java/com/android/org/conscrypt/FileClientSessionCache.java
index cdcae47..248eee1 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/FileClientSessionCache.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/FileClientSessionCache.java
@@ -331,10 +331,9 @@
      * @throws IOException if the file exists and is not a directory or if
      *  creating the directories fails
      */
-    @android.compat.annotation
-            .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
-            public static synchronized SSLClientSessionCache usingDirectory(File directory)
+    @android.compat.annotation.UnsupportedAppUsage
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    public static synchronized SSLClientSessionCache usingDirectory(File directory)
             throws IOException {
         FileClientSessionCache.Impl cache = caches.get(directory);
         if (cache == null) {
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLKey.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLKey.java
index 61216ab..bc09c81 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLKey.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLKey.java
@@ -65,9 +65,8 @@
     /**
      * Returns the EVP_PKEY context for use in JNI calls.
      */
-    @android.compat.annotation
-            .UnsupportedAppUsage
-            NativeRef.EVP_PKEY getNativeRef() {
+    @android.compat.annotation.UnsupportedAppUsage
+    NativeRef.EVP_PKEY getNativeRef() {
         return ctx;
     }
 
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLProvider.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLProvider.java
index 42a5d33..16241e3 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLProvider.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLProvider.java
@@ -32,8 +32,7 @@
  * </ul>
  * @hide This class is not part of the Android public SDK API
  */
-@libcore.
-api.IntraCoreApi
+@libcore.api.IntraCoreApi
 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
 @Internal
 public final class OpenSSLProvider extends Provider {
@@ -50,12 +49,10 @@
     private static final String STANDARD_RSA_PUBLIC_KEY_INTERFACE_CLASS_NAME =
             "java.security.interfaces.RSAPublicKey";
 
-    @android.compat.annotation
-            .UnsupportedAppUsage
-            @libcore.api
-            .IntraCoreApi
-            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
-            public OpenSSLProvider() {
+    @android.compat.annotation.UnsupportedAppUsage
+    @libcore.api.IntraCoreApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    public OpenSSLProvider() {
         this(Platform.getDefaultProviderName());
     }
 
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLRandom.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLRandom.java
index 57e2fe6..0c7b404 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLRandom.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLRandom.java
@@ -29,10 +29,9 @@
 public final class OpenSSLRandom extends SecureRandomSpi implements Serializable {
     private static final long serialVersionUID = 8506210602917522861L;
 
-    @android.compat.annotation
-            .UnsupportedAppUsage
-            @libcore.api.IntraCoreApi
-            public OpenSSLRandom() {}
+    @android.compat.annotation.UnsupportedAppUsage
+    @libcore.api.IntraCoreApi
+    public OpenSSLRandom() {}
 
     @Override
     protected void engineSetSeed(byte[] seed) {
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLSocketImpl.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLSocketImpl.java
index ca2542c..717f62d 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLSocketImpl.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLSocketImpl.java
@@ -67,13 +67,13 @@
         return super.getHostname();
     }
 
-    @android.compat.annotation
-            .UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
-                    publicAlternatives = "Use {@code javax.net.ssl.SSLParameters#setServerNames}.")
-            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
-            @Override
-            public void
-            setHostname(String hostname) {
+    @android.compat.annotation.
+    UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
+            publicAlternatives = "Use {@code javax.net.ssl.SSLParameters#setServerNames}.")
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    @Override
+    public void
+    setHostname(String hostname) {
         super.setHostname(hostname);
     }
 
@@ -88,11 +88,10 @@
         return super.getFileDescriptor$();
     }
 
-    @android.compat.annotation
-            .UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
-            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
-            @Override
-            public void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException {
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    @Override
+    public void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException {
         super.setSoWriteTimeout(writeTimeoutMilliseconds);
     }
 
@@ -102,26 +101,23 @@
         return super.getSoWriteTimeout();
     }
 
-    @android.compat.annotation
-            .UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
-            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
-            @Override
-            public void setHandshakeTimeout(int handshakeTimeoutMilliseconds)
-            throws SocketException {
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    @Override
+    public void setHandshakeTimeout(int handshakeTimeoutMilliseconds) throws SocketException {
         super.setHandshakeTimeout(handshakeTimeoutMilliseconds);
     }
 
     @Override
     public abstract SSLSession getHandshakeSession();
 
-    @android.compat.annotation
-            .UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
-                    publicAlternatives =
-                            "Use {@link android.net.ssl.SSLSockets#setUseSessionTickets}.")
-            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
-            @Override
-            public abstract void
-            setUseSessionTickets(boolean useSessionTickets);
+    @android.compat.annotation.
+    UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
+            publicAlternatives = "Use {@link android.net.ssl.SSLSockets#setUseSessionTickets}.")
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    @Override
+    public abstract void
+    setUseSessionTickets(boolean useSessionTickets);
 
     @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     @Override
@@ -131,8 +127,7 @@
     @Override
     public abstract byte[] getChannelId() throws SSLException;
 
-    @android.compat.
-    annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     @Override
     public abstract void setChannelIdPrivateKey(PrivateKey privateKey);
@@ -140,24 +135,22 @@
     /**
      * @deprecated NPN is not supported
      */
-    @android.compat.annotation
-            .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
-            @Override
-            @Deprecated
-            public final byte[] getNpnSelectedProtocol() {
+    @android.compat.annotation.UnsupportedAppUsage
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    @Override
+    @Deprecated
+    public final byte[] getNpnSelectedProtocol() {
         return super.getNpnSelectedProtocol();
     }
 
     /**
      * @deprecated NPN is not supported
      */
-    @android.compat.annotation
-            .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
-            @Override
-            @Deprecated
-            public final void setNpnProtocols(byte[] npnProtocols) {
+    @android.compat.annotation.UnsupportedAppUsage
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    @Override
+    @Deprecated
+    public final void setNpnProtocols(byte[] npnProtocols) {
         super.setNpnProtocols(npnProtocols);
     }
 
@@ -178,29 +171,28 @@
     /**
      * @deprecated use {@link #getApplicationProtocol()} instead.
      */
-    @android.compat.annotation
-            .UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
-                    publicAlternatives =
-                            "Use {@code javax.net.ssl.SSLSocket#getApplicationProtocol()}.")
-            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
-            @Override
-            @Deprecated
-            public final byte[] getAlpnSelectedProtocol() {
+    @android.compat.annotation.
+    UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
+            publicAlternatives = "Use {@code javax.net.ssl.SSLSocket#getApplicationProtocol()}.")
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    @Override
+    @Deprecated
+    public final byte[] getAlpnSelectedProtocol() {
         return SSLUtils.toProtocolBytes(getApplicationProtocol());
     }
 
     /**
      * @deprecated Use {@link #setAlpnProtocols(String[])} instead.
      */
-    @android.compat.annotation
-            .UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
-                    publicAlternatives =
-                            "Use {@code javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}.")
-            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
-            @Override
-            @Deprecated
-            public final void
-            setAlpnProtocols(byte[] protocols) {
+    @android.compat.annotation.
+    UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
+            publicAlternatives =
+                    "Use {@code javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}.")
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    @Override
+    @Deprecated
+    public final void
+    setAlpnProtocols(byte[] protocols) {
         setApplicationProtocols(SSLUtils.decodeProtocols(protocols == null ? EmptyArray.BYTE : protocols));
     }
 }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/TEST_MAPPING b/repackaged/common/src/main/java/com/android/org/conscrypt/TEST_MAPPING
index b284ce2..317e347 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/TEST_MAPPING
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/TEST_MAPPING
@@ -5,8 +5,11 @@
       "options": [
         {
           "include-filter": "com.android.org.conscrypt"
+        },
+        {
+          "include-filter": "libcore.java.security"
         }
       ]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/TrustManagerImpl.java b/repackaged/common/src/main/java/com/android/org/conscrypt/TrustManagerImpl.java
index 79945c4..8fc5c36 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/TrustManagerImpl.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/TrustManagerImpl.java
@@ -149,10 +149,9 @@
     /**
      * Creates X509TrustManager based on a keystore
      */
-    @android.compat.annotation
-            .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
-            public TrustManagerImpl(KeyStore keyStore) {
+    @android.compat.annotation.UnsupportedAppUsage
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    public TrustManagerImpl(KeyStore keyStore) {
         this(keyStore, null);
     }
 
@@ -326,11 +325,10 @@
     /**
      * For backward compatibility with older Android API that used String for the hostname only.
      */
-    @android.compat.annotation
-            .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
-            public List<X509Certificate> checkServerTrusted(X509Certificate[] chain,
-                    String authType, String hostname) throws CertificateException {
+    @android.compat.annotation.UnsupportedAppUsage
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    public List<X509Certificate> checkServerTrusted(
+            X509Certificate[] chain, String authType, String hostname) throws CertificateException {
         return checkTrusted(chain, null /* ocspData */, null /* tlsSctData */, authType, hostname,
                 false);
     }
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/NativeCryptoArgTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/NativeCryptoArgTest.java
new file mode 100644
index 0000000..db096a0
--- /dev/null
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/NativeCryptoArgTest.java
@@ -0,0 +1,335 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2020 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 com.android.org.conscrypt;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.junit.AfterClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+@RunWith(JUnit4.class)
+public class NativeCryptoArgTest {
+    // Null value passed in for a long which represents a native address
+    private static final long NULL = 0L;
+    /*
+     * Non-null value passed in for a long which represents a native address. Shouldn't
+     * ever get de-referenced but we make it a multiple of 4 to avoid any alignment errors.
+     * Used in the case where there are multiple checks we want to test in a native method,
+     * so we can get past the first check and test the second one.
+     */
+    private static final long NOT_NULL = 4L;
+    private static final String CONSCRYPT_PACKAGE = NativeCryptoArgTest.class.getCanonicalName()
+            .substring(0, NativeCryptoArgTest.class.getCanonicalName().lastIndexOf('.') + 1);
+    private static final Set<String> testedMethods = new HashSet<>();
+    private final Map<String, Class<?>> classCache = new HashMap<>();
+    private final Map<String, Method> methodMap = buildMethodMap();
+
+    @AfterClass
+    public static void after() {
+        // TODO(prb): Temporary hacky check - remove
+        assertTrue(testedMethods.size() >= 190);
+    }
+
+    @Test
+    public void ecMethods() throws Throwable {
+        String[] illegalArgMethods = new String[] {
+                "EC_GROUP_new_arbitrary"
+        };
+        String[] ioExMethods = new String[] {
+                "EC_KEY_parse_curve_name",
+                "EC_KEY_marshal_curve_name"
+        };
+
+        // All of the EC_* methods apart from the exceptions below throw NPE if their
+        // first argument is null.
+        MethodFilter filter = MethodFilter.newBuilder("EC_ methods")
+                .hasPrefix("EC_")
+                .except(illegalArgMethods)
+                .except(ioExMethods)
+                .expectSize(16)
+                .build();
+        testMethods(filter, NullPointerException.class);
+
+        filter = MethodFilter.nameFilter("EC_ methods (IllegalArgument)", illegalArgMethods);
+        testMethods(filter, IllegalArgumentException.class);
+
+        filter = MethodFilter.nameFilter("EC_ methods (IOException)", ioExMethods);
+        testMethods(filter, IOException.class);
+    }
+
+    @Test
+    public void macMethods() throws Throwable {
+        // All of the non-void HMAC and CMAC methods throw NPE when passed a null pointer
+        MethodFilter filter = MethodFilter.newBuilder("HMAC methods")
+                .hasPrefix("HMAC_")
+                .takesArguments()
+                .expectSize(5)
+                .build();
+        testMethods(filter, NullPointerException.class);
+
+        filter = MethodFilter.newBuilder("CMAC methods")
+                .hasPrefix("CMAC_")
+                .takesArguments()
+                .expectSize(5)
+                .build();
+        testMethods(filter, NullPointerException.class);
+    }
+
+    @Test
+    public void sslMethods() throws Throwable {
+        // These methods don't throw on a null first arg as they can get called before the
+        // connection is fully initialised. However if the first arg is non-NULL, any subsequent
+        // null args should throw NPE.
+        String[] nonThrowingMethods = new String[] {
+                "SSL_interrupt",
+                "SSL_shutdown",
+                "ENGINE_SSL_shutdown",
+        };
+
+        // Most of the NativeSsl methods take a long holding a pointer to the native
+        // object followed by a {@code NativeSsl} holder object. However the second arg
+        // is unused(!) so we don't need to test it.
+        MethodFilter filter = MethodFilter.newBuilder("NativeSsl methods")
+                .hasArg(0, long.class)
+                .hasArg(1, conscryptClass("NativeSsl"))
+                .except(nonThrowingMethods)
+                .expectSize(60)
+                .build();
+
+        testMethods(filter, NullPointerException.class);
+
+        // Many of the SSL_* methods take a single long which points
+        // to a native object.
+        filter = MethodFilter.newBuilder("1-arg SSL methods")
+                .hasPrefix("SSL_")
+                .hasArgLength(1)
+                .hasArg(0, long.class)
+                .expectSize(10)
+                .build();
+
+        testMethods(filter, NullPointerException.class);
+
+        filter = MethodFilter.nameFilter("Non throwing NativeSsl methods", nonThrowingMethods);
+        testMethods(filter, null);
+
+        expectVoid("SSL_shutdown", NOT_NULL, null, null, null);
+        expectNPE("SSL_shutdown", NOT_NULL, null, new FileDescriptor(), null);
+        expectNPE("ENGINE_SSL_shutdown", NOT_NULL, null, null);
+        expectVoid("SSL_set_session", NOT_NULL, null, NULL);
+    }
+
+    @Test
+    public void evpMethods() throws Throwable {
+        String[] illegalArgMethods = new String[] {
+                "EVP_AEAD_CTX_open_buf",
+                "EVP_AEAD_CTX_seal_buf",
+                "EVP_PKEY_new_RSA"
+        };
+        String[] nonThrowingMethods = new String[] {
+                "EVP_MD_CTX_destroy",
+                "EVP_PKEY_CTX_free",
+                "EVP_PKEY_free",
+                "EVP_CIPHER_CTX_free"
+        };
+
+        // All of the non-void EVP_ methods apart from the above should throw on a null
+        // first argument.
+        MethodFilter filter = MethodFilter.newBuilder("EVP methods")
+                .hasPrefix("EVP_")
+                .takesArguments()
+                .except(illegalArgMethods)
+                .except(nonThrowingMethods)
+                .expectSize(45)
+                .build();
+
+        testMethods(filter, NullPointerException.class);
+
+        filter = MethodFilter.nameFilter("EVP methods (IllegalArgument)", illegalArgMethods);
+        testMethods(filter, IllegalArgumentException.class);
+
+        filter = MethodFilter.nameFilter("EVP methods (non-throwing)", nonThrowingMethods);
+        testMethods(filter, null);
+    }
+
+    @Test
+    public void x509Methods() throws Throwable {
+        // A number of X509 methods have a native pointer as arg 0 and an
+        // OpenSSLX509Certificate or OpenSSLX509CRL as arg 1.
+        MethodFilter filter = MethodFilter.newBuilder("X509 methods")
+                .hasArgLength(2)
+                .hasArg(0, long.class)
+                .hasArg(1, conscryptClass("OpenSSLX509Certificate"),
+                        conscryptClass("OpenSSLX509CRL"))
+                .expectSize(32)
+                .build();
+        // TODO(prb): test null second argument
+        testMethods(filter, NullPointerException.class);
+
+        // The rest of the X509 methods are somewhat ad hoc.
+        expectNPE("d2i_X509", (Object) null);
+
+        invokeAndExpect( conscryptThrowable("OpenSSLX509CertificateFactory$ParsingException"),
+                 "d2i_X509", new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0});
+
+        expectNPE("d2i_X509_bio", NULL);
+        expectNPE("PEM_read_bio_X509", NULL);
+        expectNPE("ASN1_seq_pack_X509", (Object) null);
+
+        // TODO(prb): Check what this should really throw
+        // expectNPE("ASN1_seq_pack_X509", (Object) new long[] { NULL });
+
+        expectNPE("ASN1_seq_unpack_X509_bio", NULL);
+
+        //
+        expectNPE("X509_cmp", NULL, null, NULL, null);
+        expectNPE("X509_cmp", NOT_NULL, null, NULL, null);
+        expectNPE("X509_cmp", NULL, null, NOT_NULL, null);
+
+        expectNPE("X509_print_ex", NULL, NULL, null, NULL, NULL);
+        expectNPE("X509_print_ex", NOT_NULL, NULL, null, NULL, NULL);
+        expectNPE("X509_print_ex", NULL, NOT_NULL, null, NULL, NULL);
+    }
+
+    private void testMethods(MethodFilter filter, Class<? extends Throwable> exceptionClass)
+            throws Throwable {
+        List<Method> methods = filter.filter(methodMap.values());
+
+        for (Method method : methods) {
+            List<Object[]> argsLists = permuteArgs(method);
+            for (Object[] args : argsLists) {
+                invokeAndExpect(exceptionClass, method, args);
+            }
+        }
+    }
+
+    private List<Object[]> permuteArgs(Method method) {
+        // For now just supply 0 for integral types and null for everything else
+        // TODO: allow user defined strategy, e.g. if two longs passed as native refs,
+        // generate {NULL,NULL}, {NULL,NOT_NULL}, {NOT_NULL,NULL} to test both null checks
+        List<Object[]> result = new ArrayList<>(1);
+
+        Class<?>[] argTypes = method.getParameterTypes();
+
+        int argCount = argTypes.length;
+        assertTrue(argCount > 0);
+        Object[] args = new Object[argCount];
+
+        for (int arg = 0; arg < argCount; arg++) {
+            if (argTypes[arg] == int.class) {
+                args[arg] = 0;
+            } else if (argTypes[arg] == long.class) {
+                args[arg] = NULL;
+            } else if (argTypes[arg] == boolean.class) {
+                args[arg] = false;
+            } else {
+                args[arg] = null;
+            }
+        }
+        result.add(args);
+        return result;
+    }
+
+    private void expectVoid(String methodName, Object... args) throws Throwable {
+        invokeAndExpect(null, methodName, args);
+    }
+
+    private void expectNPE(String methodName, Object... args) throws Throwable {
+        invokeAndExpect(NullPointerException.class, methodName, args);
+    }
+
+    private void invokeAndExpect(Class<? extends Throwable> expectedThrowable, String methodName,
+                                   Object... args) throws Throwable {
+        Method method = methodMap.get(methodName);
+        assertNotNull(method);
+        assertEquals(methodName, method.getName());
+        invokeAndExpect(expectedThrowable, method, args);
+    }
+
+    private void invokeAndExpect(Class<? extends Throwable> expectedThrowable, Method method,
+                                   Object... args) throws Throwable {
+        try {
+            method.invoke(null, args);
+            if (expectedThrowable != null) {
+                fail("No exception thrown by method " + method.getName());
+            }
+        } catch (IllegalAccessException e) {
+            throw new AssertionError("Illegal access", e);
+        } catch (InvocationTargetException e) {
+            Throwable cause = e.getCause();
+            if (expectedThrowable != null) {
+                assertEquals("Method: " + method.getName(), expectedThrowable, cause.getClass());
+            } else {
+                throw cause;
+            }
+        }
+        testedMethods.add(method.getName());
+    }
+
+    @SuppressWarnings("unchecked")
+    private Class<? extends Throwable> conscryptThrowable(String name) {
+        Class<?> klass = conscryptClass(name);
+        assertNotNull(klass);
+        assertTrue(Throwable.class.isAssignableFrom(klass));
+        return (Class<? extends Throwable>) klass;
+    }
+
+    private Class<?> conscryptClass(String className) {
+        return classCache.computeIfAbsent(className, s -> {
+            try {
+                return Class.forName(CONSCRYPT_PACKAGE + className);
+            } catch (ClassNotFoundException e) {
+                return null;
+            }
+        });
+    }
+
+    private Map<String, Method> buildMethodMap() {
+        Map<String, Method> classMap = new HashMap<>();
+        assertNotNull(classMap);
+        Class<?> nativeCryptoClass = conscryptClass("NativeCrypto");
+        assertNotNull(nativeCryptoClass);
+        for (Method method : nativeCryptoClass.getDeclaredMethods()) {
+            int modifiers = method.getModifiers();
+            if (!Modifier.isNative(modifiers)) {
+                continue;
+            }
+            method.setAccessible(true);
+            classMap.put(method.getName(), method);
+        }
+        return classMap;
+    }
+}
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestRSA.java b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestRSA.java
index 1c51b59..b4287f9 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestRSA.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestRSA.java
@@ -43,7 +43,6 @@
 import java.util.List;
 import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule;
 import org.junit.ClassRule;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
@@ -72,7 +71,6 @@
     }
 
     @Test
-    @Ignore("b/209335673 Base image has fix for b/191150645 but release version of module does not")
     public void getEncodedFailsWhenCrtValuesMissing() throws Exception {
         PrivateKey privateKey = getPrivateKey();
         try {
@@ -124,7 +122,6 @@
     }
 
     @Test
-    @Ignore("b/209335673 Base image has fix for b/191150645 but release version of module does not")
     public void javaSerialization() throws Exception {
         PrivateKey privatekey = getPrivateKey();
 
@@ -139,13 +136,10 @@
         assertEquals(privatekey, copy);
     }
 
-    // b/209335673 Base image has fix for b/191150645 but release version of module does not,
-    // @Override
-    // protected List<KeyPair> getKeys() throws NoSuchAlgorithmException, InvalidKeySpecException {
-    //     return Arrays.asList(
-    //             new KeyPair(DefaultKeys.getPublicKey(algorithmName), getPrivateKey())
-    //     );
-    // }
+    @Override
+    protected List<KeyPair> getKeys() throws NoSuchAlgorithmException, InvalidKeySpecException {
+        return Arrays.asList(new KeyPair(DefaultKeys.getPublicKey(algorithmName), getPrivateKey()));
+    }
 
     // The private RSA key returned by DefaultKeys.getPrivateKey() is built from a PKCS#8
     // KeySpec and so will be an instance of RSAPrivateCrtKey, but we want to test RSAPrivateKey
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestRSACrt.java b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestRSACrt.java
index d8d8ebe..5e84e7c 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestRSACrt.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestRSACrt.java
@@ -31,7 +31,6 @@
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPublicKeySpec;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -64,8 +63,7 @@
   @Test
   public void testExtraBufferSpace_Private() throws Exception {
       PrivateKey privateKey = DefaultKeys.getPrivateKey("RSA");
-      // b/209335673 Base image has fix for b/191150645 but release version of module does not
-      // assertTrue(privateKey instanceof RSAPrivateCrtKey);
+      assertTrue(privateKey instanceof RSAPrivateCrtKey);
 
       byte[] encoded = privateKey.getEncoded();
       byte[] longBuffer = new byte[encoded.length + 147];
@@ -76,7 +74,6 @@
   }
 
   @Test
-  @Ignore("b/209335673 Base image has fix for b/191150645 but release version of module does not")
   public void javaSerialization() throws Exception {
       PrivateKey privateKey = DefaultKeys.getPrivateKey("RSA");
       assertTrue(privateKey instanceof RSAPrivateCrtKey);
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/CipherTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/CipherTest.java
index 0a762d2..3956c83 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/CipherTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/CipherTest.java
@@ -152,6 +152,29 @@
              || algorithm.equals("AES/OFB/PKCS7PADDING"))) {
             return false;
         }
+
+        if (provider.equals("BC")) {
+            return isSupportedByBC(algorithm);
+        }
+
+        return true;
+    }
+
+    /**
+     * Checks for algorithms removed from BC in Android 12 and so not usable for these
+     * tests.
+     *
+     * TODO(prb): make this version aware, as this test runs against BC on older Android
+     * versions via MTS and should continue to test these algorithms there.
+     *
+     */
+    private static boolean isSupportedByBC(String algorithm) {
+        String[] removedBcPrefices = new String[] {"AES/ECB", "AES/CBC", "AES/GCM"};
+        for (String prefix : removedBcPrefices) {
+            if (algorithm.startsWith(prefix)) {
+                return false;
+            }
+        }
         return true;
     }
 
@@ -1084,7 +1107,9 @@
                             for (String padding : paddings) {
                                 final String algorithmName = algorithm + "/" + mode + "/" + padding;
                                 try {
-                                    test_Cipher_Algorithm(provider, algorithmName);
+                                    if (isSupported(algorithmName, provider.getName())) {
+                                        test_Cipher_Algorithm(provider, algorithmName);
+                                    }
                                 } catch (Throwable e) {
                                     out.append("Error encountered checking " + algorithmName
                                                + " with provider " + provider.getName() + "\n");
@@ -3470,6 +3495,9 @@
             if (provider.equals("SunJCE") && transformation.endsWith("/PKCS7PADDING")) {
                 return false;
             }
+            if (provider.equals("BC")) {
+                return isSupportedByBC(transformation);
+            }
             return true;
         }
     }
@@ -4098,6 +4126,9 @@
         final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();
         PrintStream out = new PrintStream(errBuffer);
         for (CipherTestParam p : CIPHER_TEST_PARAMS) {
+            if (!p.compatibleWith(provider)) {
+                continue;
+            }
             try {
                 checkCipher_ShortBlock_Failure(p, provider);
             } catch (Exception e) {
@@ -4297,32 +4328,6 @@
         }
     }
 
-    // Test that when reading GCM parameters encoded using ASN1, a value for the tag size
-    // not present indicates a value of 12.
-    // https://b/29876633
-    @Test
-    public void test_DefaultGCMTagSizeAlgorithmParameterSpec() throws Exception {
-        Assume.assumeNotNull(Security.getProvider("BC"));
-        final String AES = "AES";
-        final String AES_GCM = "AES/GCM/NoPadding";
-        byte[] input = new byte[16];
-        byte[] key = new byte[16];
-        Cipher cipher = Cipher.getInstance(AES_GCM, "BC");
-        AlgorithmParameters param = AlgorithmParameters.getInstance("GCM");
-        param.init(new byte[] {
-            (byte) 48,    // DER encoding : tag_Sequence
-            (byte) 14,    // DER encoding : total length
-            (byte) 4,     // DER encoding : tag_OctetString
-            (byte) 12,    // DER encoding : counter length
-            (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0,
-            (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0, (byte) 0 });
-        cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, AES), param);
-        byte[] ciphertext = cipher.update(input);
-        assertEquals(16, ciphertext.length);
-        byte[] tag = cipher.doFinal();
-        assertEquals(12, tag.length);
-    }
-
     @Test
     public void testAES_ECB_PKCS5Padding_ShortBuffer_Failure() throws Exception {
         for (String provider : AES_PROVIDERS) {
@@ -4385,7 +4390,11 @@
     }
 
     private void testAES_ECB_NoPadding_IncrementalUpdate_Success(String provider) throws Exception {
-        Cipher c = Cipher.getInstance("AES/ECB/NoPadding", provider);
+        String algorithm = "AES/ECB/NoPadding";
+        if (!isSupported(algorithm, provider)) {
+            return;
+        }
+        Cipher c = Cipher.getInstance(algorithm, provider);
         assertEquals(provider, c.getProvider().getName());
         c.init(Cipher.ENCRYPT_MODE, AES_128_KEY);
 
@@ -4419,7 +4428,11 @@
     }
 
     private void testAES_ECB_NoPadding_IvParameters_Failure(String provider) throws Exception {
-        Cipher c = Cipher.getInstance("AES/ECB/NoPadding", provider);
+        String algorithm = "AES/ECB/NoPadding";
+        if (!isSupported(algorithm, provider)) {
+            return;
+        }
+        Cipher c = Cipher.getInstance(algorithm, provider);
 
         AlgorithmParameterSpec spec = new IvParameterSpec(AES_IV_ZEROES);
         try {
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java
index ade5447..7f44a34 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java
@@ -87,6 +87,10 @@
     @Test
     public void test_XDHKeyAgreement() throws Exception {
         for (Provider p : Security.getProviders("KeyAgreement.XDH")) {
+            // Skip testing Android Keystore as it's covered by CTS tests.
+            if ("AndroidKeyStore".equals(p.getName())) {
+                continue;
+            }
             setupKeys(p);
 
             KeyAgreement ka = KeyAgreement.getInstance("XDH", p);
diff --git a/repackaged/platform/src/main/java/com/android/org/conscrypt/TrustedCertificateStore.java b/repackaged/platform/src/main/java/com/android/org/conscrypt/TrustedCertificateStore.java
index 16128b9..3d71e47 100644
--- a/repackaged/platform/src/main/java/com/android/org/conscrypt/TrustedCertificateStore.java
+++ b/repackaged/platform/src/main/java/com/android/org/conscrypt/TrustedCertificateStore.java
@@ -127,10 +127,9 @@
     private final File addedDir;
     private final File deletedDir;
 
-    @android.compat.annotation
-            .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
-            public TrustedCertificateStore() {
+    @android.compat.annotation.UnsupportedAppUsage
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    public TrustedCertificateStore() {
         this(PreloadHolder.defaultCaCertsSystemDir, PreloadHolder.defaultCaCertsAddedDir,
                 PreloadHolder.defaultCaCertsDeletedDir);
     }
@@ -498,10 +497,9 @@
      * @throws CertificateException if there was a problem parsing the
      *             certificates
      */
-    @android.compat.annotation
-            .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
-            public List<X509Certificate> getCertificateChain(X509Certificate leaf)
+    @android.compat.annotation.UnsupportedAppUsage
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    public List<X509Certificate> getCertificateChain(X509Certificate leaf)
             throws CertificateException {
         final LinkedHashSet<OpenSSLX509Certificate> chain
                 = new LinkedHashSet<OpenSSLX509Certificate>();
diff --git a/repackaged/testing/src/main/java/com/android/org/conscrypt/MethodFilter.java b/repackaged/testing/src/main/java/com/android/org/conscrypt/MethodFilter.java
new file mode 100644
index 0000000..680abd7
--- /dev/null
+++ b/repackaged/testing/src/main/java/com/android/org/conscrypt/MethodFilter.java
@@ -0,0 +1,201 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.org.conscrypt;
+
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+
+
+/**
+ * Test support class for filtering collections of {@link Method}.  Each filter is a list of
+ * predicates which must all be true for a Method in order for it to be included it the output.
+ * @hide This class is not part of the Android public SDK API
+ */
+public class MethodFilter {
+    private final String name;
+    private final CompoundMethodPredicate predicates = new CompoundMethodPredicate();
+    private int expectedSize = 0;
+
+    public MethodFilter(String name) {
+        this.name = name;
+    }
+
+    public List<Method> filter(Iterable<Method> input) {
+        List<Method> result = new ArrayList<>();
+        for (Method method : input) {
+            if (predicates.test(method)) {
+                result.add(method);
+            }
+        }
+        if (expectedSize != 0) {
+            assertTrue(String.format("Filter %s only returned %d methods, expected at least %d",
+                    name, result.size(), expectedSize), result.size() >= expectedSize);
+        }
+        return result;
+    }
+
+    /** Returns a new {@link Builder} */
+    public static Builder newBuilder(String name) {
+        return new Builder(name);
+    }
+
+    /** Returns a filter which selects only methods named in {@code methodNames} */
+    public static MethodFilter nameFilter(String name, String... methodNames) {
+        return newBuilder(name)
+                .named(methodNames)
+                .expectSize(methodNames.length)
+                .build();
+    }
+
+    private void addPredicate(Predicate<Method> predicate) {
+        predicates.add(predicate);
+    }
+
+    /**
+     * @hide This class is not part of the Android public SDK API
+     */
+    public static class Builder {
+        private final MethodFilter filter;
+
+        private Builder(String name) {
+            filter = new MethodFilter(name);
+        }
+
+        /** Method's simple name must start with {@code prefix}.  */
+        public Builder hasPrefix(String prefix) {
+            filter.addPredicate(new MethodNamePrefixPredicate(prefix));
+            return this;
+        }
+
+        /** Argument at {@code position} must be one of the supplied {@code classes}. */
+        public Builder hasArg(int position, Class<?>... classes) {
+            filter.addPredicate(new MethodArgPredicate(position, classes));
+            return this;
+        }
+
+        /** Method must take exactly {@code length} args. */
+        public Builder hasArgLength(int length) {
+            filter.addPredicate(new MethodArgLengthPredicate(length));
+            return this;
+        }
+
+        /* Method must take one or more arguments, i.e. not void. */
+        public Builder takesArguments() {
+            filter.addPredicate(new MethodArgLengthPredicate(0).negate());
+            return this;
+        }
+
+        /** Method's simple name is in the list of {@code names} provided. */
+        public Builder named(String... names) {
+            filter.addPredicate(new MethodNamePredicate(names));
+            return this;
+        }
+
+        /** Method's simple name is NOT in the list of {@code names} provided. */
+        public Builder except(String... names) {
+            filter.addPredicate(new MethodNamePredicate(names).negate());
+            return this;
+        }
+
+        /** Expect at least {@code size} matching methods when filtering, otherwise filter()
+         * will throw {@code AssertionError} */
+        public Builder expectSize(int size) {
+            filter.expectedSize = size;
+            return this;
+        }
+
+        public MethodFilter build() {
+            return filter;
+        }
+    }
+
+    // Implements Builder.hasPrefix()
+    private static class MethodNamePrefixPredicate implements Predicate<Method> {
+        private final String prefix;
+
+        public MethodNamePrefixPredicate(String prefix) {
+            this.prefix = prefix;
+        }
+
+        @Override
+        public boolean test(Method method) {
+            return method.getName().startsWith(prefix);
+        }
+    }
+
+    // Implements Builder.named()
+    private static class MethodNamePredicate implements Predicate<Method> {
+        private final List<String> names;
+
+        public MethodNamePredicate(String... names) {
+            this.names = Arrays.asList(names);
+        }
+
+        @Override
+        public boolean test(Method method) {
+            return names.contains(method.getName());
+        }
+    }
+
+    // Implements Builder.hasArg()
+    private static class MethodArgPredicate implements Predicate<Method> {
+        private final int position;
+        private final List<Class<?>> allowedClasses;
+
+        public MethodArgPredicate(int position, Class<?>... classes) {
+            this.position = position;
+            allowedClasses = Arrays.asList(classes);
+        }
+
+        @Override
+        public boolean test(Method method) {
+            Class<?>[] argTypes = method.getParameterTypes();
+            if (argTypes.length > position) {
+                for (Class<?> c : allowedClasses) {
+                    if (argTypes[position] == c) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    // Implements Builder.hasArgLength()
+    private static class MethodArgLengthPredicate implements Predicate<Method> {
+        private final int length;
+
+        public MethodArgLengthPredicate(int length) {
+            this.length = length;
+        }
+
+        @Override
+        public boolean test(Method method) {
+            return method.getParameterCount() == length;
+        }
+    }
+
+    // A Predicate which contains a list of sub-Predicates, all of which must be true
+    // for this one to be true.
+    private static class CompoundMethodPredicate implements Predicate<Method> {
+        private final List<Predicate<Method>> predicates = new ArrayList<>();
+
+        @Override
+        public boolean test(Method method) {
+            for (Predicate<Method> p : predicates) {
+                if (!p.test(method)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public void add(Predicate<Method> predicate) {
+            predicates.add(predicate);
+        }
+    }
+}
\ No newline at end of file
diff --git a/testing/src/main/java/org/conscrypt/MethodFilter.java b/testing/src/main/java/org/conscrypt/MethodFilter.java
new file mode 100644
index 0000000..0939d4b
--- /dev/null
+++ b/testing/src/main/java/org/conscrypt/MethodFilter.java
@@ -0,0 +1,196 @@
+package org.conscrypt;
+
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+
+
+/**
+ * Test support class for filtering collections of {@link Method}.  Each filter is a list of
+ * predicates which must all be true for a Method in order for it to be included it the output.
+ */
+public class MethodFilter {
+    private final String name;
+    private final CompoundMethodPredicate predicates = new CompoundMethodPredicate();
+    private int expectedSize = 0;
+
+    public MethodFilter(String name) {
+        this.name = name;
+    }
+
+    public List<Method> filter(Iterable<Method> input) {
+        List<Method> result = new ArrayList<>();
+        for (Method method : input) {
+            if (predicates.test(method)) {
+                result.add(method);
+            }
+        }
+        if (expectedSize != 0) {
+            assertTrue(String.format("Filter %s only returned %d methods, expected at least %d",
+                    name, result.size(), expectedSize), result.size() >= expectedSize);
+        }
+        return result;
+    }
+
+    /** Returns a new {@link Builder} */
+    public static Builder newBuilder(String name) {
+        return new Builder(name);
+    }
+
+    /** Returns a filter which selects only methods named in {@code methodNames} */
+    public static MethodFilter nameFilter(String name, String... methodNames) {
+        return newBuilder(name)
+                .named(methodNames)
+                .expectSize(methodNames.length)
+                .build();
+    }
+
+    private void addPredicate(Predicate<Method> predicate) {
+        predicates.add(predicate);
+    }
+
+    public static class Builder {
+        private final MethodFilter filter;
+
+        private Builder(String name) {
+            filter = new MethodFilter(name);
+        }
+
+        /** Method's simple name must start with {@code prefix}.  */
+        public Builder hasPrefix(String prefix) {
+            filter.addPredicate(new MethodNamePrefixPredicate(prefix));
+            return this;
+        }
+
+        /** Argument at {@code position} must be one of the supplied {@code classes}. */
+        public Builder hasArg(int position, Class<?>... classes) {
+            filter.addPredicate(new MethodArgPredicate(position, classes));
+            return this;
+        }
+
+        /** Method must take exactly {@code length} args. */
+        public Builder hasArgLength(int length) {
+            filter.addPredicate(new MethodArgLengthPredicate(length));
+            return this;
+        }
+
+        /* Method must take one or more arguments, i.e. not void. */
+        public Builder takesArguments() {
+            filter.addPredicate(new MethodArgLengthPredicate(0).negate());
+            return this;
+        }
+
+        /** Method's simple name is in the list of {@code names} provided. */
+        public Builder named(String... names) {
+            filter.addPredicate(new MethodNamePredicate(names));
+            return this;
+        }
+
+        /** Method's simple name is NOT in the list of {@code names} provided. */
+        public Builder except(String... names) {
+            filter.addPredicate(new MethodNamePredicate(names).negate());
+            return this;
+        }
+
+        /** Expect at least {@code size} matching methods when filtering, otherwise filter()
+         * will throw {@code AssertionError} */
+        public Builder expectSize(int size) {
+            filter.expectedSize = size;
+            return this;
+        }
+
+        public MethodFilter build() {
+            return filter;
+        }
+    }
+
+    // Implements Builder.hasPrefix()
+    private static class MethodNamePrefixPredicate implements Predicate<Method> {
+        private final String prefix;
+
+        public MethodNamePrefixPredicate(String prefix) {
+            this.prefix = prefix;
+        }
+
+        @Override
+        public boolean test(Method method) {
+            return method.getName().startsWith(prefix);
+        }
+    }
+
+    // Implements Builder.named()
+    private static class MethodNamePredicate implements Predicate<Method> {
+        private final List<String> names;
+
+        public MethodNamePredicate(String... names) {
+            this.names = Arrays.asList(names);
+        }
+
+        @Override
+        public boolean test(Method method) {
+            return names.contains(method.getName());
+        }
+    }
+
+    // Implements Builder.hasArg()
+    private static class MethodArgPredicate implements Predicate<Method> {
+        private final int position;
+        private final List<Class<?>> allowedClasses;
+
+        public MethodArgPredicate(int position, Class<?>... classes) {
+            this.position = position;
+            allowedClasses = Arrays.asList(classes);
+        }
+
+        @Override
+        public boolean test(Method method) {
+            Class<?>[] argTypes = method.getParameterTypes();
+            if (argTypes.length > position) {
+                for (Class<?> c : allowedClasses) {
+                    if (argTypes[position] == c) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    // Implements Builder.hasArgLength()
+    private static class MethodArgLengthPredicate implements Predicate<Method> {
+        private final int length;
+
+        public MethodArgLengthPredicate(int length) {
+            this.length = length;
+        }
+
+        @Override
+        public boolean test(Method method) {
+            return method.getParameterCount() == length;
+        }
+    }
+
+    // A Predicate which contains a list of sub-Predicates, all of which must be true
+    // for this one to be true.
+    private static class CompoundMethodPredicate implements Predicate<Method> {
+        private final List<Predicate<Method>> predicates = new ArrayList<>();
+
+        @Override
+        public boolean test(Method method) {
+            for (Predicate<Method> p : predicates) {
+                if (!p.test(method)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public void add(Predicate<Method> predicate) {
+            predicates.add(predicate);
+        }
+    }
+}
\ No newline at end of file