Snap for 7783058 from 03484669b8d6331efcff6769e7ae052c02b6f23f to sc-v2-release

Change-Id: I9ae74fcc3bb897ffc8912547892d87fc19b55e68
diff --git a/Android.bp b/Android.bp
index ff3a903..af9e01e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -525,7 +525,7 @@
         ":conscrypt-unbundled_generated_constants",
     ],
     javacflags: ["-XDignore.symbol.file"],
-    java_version: "1.7",
+    java_version: "1.8",
 }
 
 // Static unbundled Conscrypt crypto JNI library
@@ -599,7 +599,7 @@
             enabled: false,
         },
     },
-    java_version: "1.7",
+    java_version: "1.8",
 }
 
 // Make the conscrypt-benchmarks library.
@@ -636,7 +636,7 @@
             enabled: false,
         },
     },
-    java_version: "1.7",
+    java_version: "1.8",
 }
 
 // Device SDK exposed by the Conscrypt module.
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/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/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc
index 5b0acf0..98d7dbc 100644
--- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc
+++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc
@@ -2204,7 +2204,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 +2233,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 +3211,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 +3254,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;
     }
 
@@ -4378,6 +4387,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 +4432,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 +4446,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 +4573,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 +4638,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 +4652,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 +4743,7 @@
 
     if (crl == nullptr) {
         conscrypt::jniutil::throwNullPointerException(env, "crl == null");
+        JNI_TRACE("X509_CRL_get_REVOKED(%p) => crl == null", crl);
         return nullptr;
     }
 
@@ -4721,6 +4771,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 +4883,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 +4900,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 +4965,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 +5064,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 +5607,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 +5621,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 +6040,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 +6054,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 +6302,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 +6317,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);
 }
@@ -7145,8 +7260,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);
diff --git a/common/src/main/java/org/conscrypt/OpenSSLKey.java b/common/src/main/java/org/conscrypt/OpenSSLKey.java
index 06fdd4e..6eb94f4 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLKey.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLKey.java
@@ -313,7 +313,7 @@
     PrivateKey getPrivateKey() throws NoSuchAlgorithmException {
         switch (NativeCrypto.EVP_PKEY_type(ctx)) {
             case NativeConstants.EVP_PKEY_RSA:
-                return new OpenSSLRSAPrivateKey(this);
+                return OpenSSLRSAPrivateKey.getInstance(this);
             case NativeConstants.EVP_PKEY_EC:
                 return new OpenSSLECPrivateKey(this);
             default:
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 6724535..571c1a7 100644
--- a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java
+++ b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java
@@ -15,16 +15,25 @@
  */
 package org.conscrypt.java.security;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.security.KeyFactory;
 import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.Provider;
 import java.security.PublicKey;
 import java.security.Security;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPrivateKey;
 import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPrivateKeySpec;
 import java.security.spec.RSAPublicKeySpec;
@@ -59,12 +68,15 @@
     }
 
     @Test
-    public void testExtraBufferSpace_Private() throws Exception {
-        PrivateKey privateKey = DefaultKeys.getPrivateKey("RSA");
-        byte[] encoded = privateKey.getEncoded();
-        byte[] longBuffer = new byte[encoded.length + 147];
-        System.arraycopy(encoded, 0, longBuffer, 0, encoded.length);
-        KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(longBuffer));
+    public void getEncodedFailsWhenCrtValuesMissing() throws Exception {
+        PrivateKey privateKey = getPrivateKey();
+        try {
+            // Key has only modulus and private exponent so can't be encoded as PKCS#8
+            privateKey.getEncoded();
+            fail();
+        } catch (RuntimeException e) {
+            // Expected
+        }
     }
 
     @Test
@@ -105,4 +117,40 @@
             // expected
         }
     }
+
+    @Test
+    public void javaSerialization() throws Exception{
+        PrivateKey privatekey = getPrivateKey();
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        ObjectOutputStream out = new ObjectOutputStream(bos);
+        out.writeObject(privatekey);
+
+        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
+        ObjectInputStream in = new ObjectInputStream(bis);
+        PrivateKey copy = (PrivateKey) in.readObject();
+
+        assertEquals(privatekey, copy);
+    }
+
+    @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
+    // in this unit test and so we extract the modulus and private exponent to build the
+    // correct private key subtype.
+    private PrivateKey getPrivateKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
+        RSAPrivateCrtKey crtKey = (RSAPrivateCrtKey) DefaultKeys.getPrivateKey(algorithmName);
+        RSAPrivateKeySpec spec =
+                new RSAPrivateKeySpec(crtKey.getModulus(), crtKey.getPrivateExponent());
+        PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(spec);
+        assertTrue(privateKey instanceof RSAPrivateKey);
+        assertFalse(privateKey instanceof RSAPrivateCrtKey);
+        return privateKey;
+    }
 }
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 957be7a..bddd898 100644
--- a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSACrt.java
+++ b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSACrt.java
@@ -15,9 +15,22 @@
  */
 package org.conscrypt.java.security;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.security.KeyFactory;
 import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPublicKeySpec;
+import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import tests.util.ServiceTester;
@@ -42,4 +55,35 @@
     // implmenetations.
     return tester.skipProvider("BC");
   }
+
+  @Test
+  public void testExtraBufferSpace_Private() throws Exception {
+    PrivateKey privateKey = DefaultKeys.getPrivateKey("RSA");
+    assertTrue(privateKey instanceof RSAPrivateCrtKey);
+
+    byte[] encoded = privateKey.getEncoded();
+    byte[] longBuffer = new byte[encoded.length + 147];
+    System.arraycopy(encoded, 0, longBuffer, 0, encoded.length);
+    PrivateKey copy =
+            KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(longBuffer));
+    assertEquals(privateKey, copy);
+  }
+
+  @Test
+  public void javaSerialization() throws Exception{
+    PrivateKey privateKey = DefaultKeys.getPrivateKey("RSA");
+    assertTrue(privateKey instanceof RSAPrivateCrtKey);
+
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    ObjectOutputStream out = new ObjectOutputStream(bos);
+    out.writeObject(privateKey);
+
+    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
+    ObjectInputStream in = new ObjectInputStream(bis);
+    PrivateKey copy = (PrivateKey) in.readObject();
+    assertTrue(copy instanceof RSAPrivateCrtKey);
+
+    assertEquals(privateKey.getFormat(), copy.getFormat());
+    assertArrayEquals(privateKey.getEncoded(), copy.getEncoded());
+  }
 }
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 439b554..ad5b52a 100644
--- a/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java
+++ b/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java
@@ -147,6 +147,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;
     }
 
@@ -1079,7 +1106,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");
@@ -3474,6 +3503,9 @@
             if (provider.equals("SunJCE") && transformation.endsWith("/PKCS7PADDING")) {
                 return false;
             }
+            if (provider.equals("BC")) {
+                return isSupportedByBC(transformation);
+            }
             return true;
         }
     }
@@ -4101,6 +4133,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) {
@@ -4300,32 +4335,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) {
@@ -4388,7 +4397,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);
 
@@ -4422,7 +4435,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/main/java/com/android/org/conscrypt/OpenSSLKey.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLKey.java
index f34b935..61216ab 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
@@ -319,7 +319,7 @@
     PrivateKey getPrivateKey() throws NoSuchAlgorithmException {
         switch (NativeCrypto.EVP_PKEY_type(ctx)) {
             case NativeConstants.EVP_PKEY_RSA:
-                return new OpenSSLRSAPrivateKey(this);
+                return OpenSSLRSAPrivateKey.getInstance(this);
             case NativeConstants.EVP_PKEY_EC:
                 return new OpenSSLECPrivateKey(this);
             default:
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 6b0567e..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
@@ -16,16 +16,25 @@
  */
 package com.android.org.conscrypt.java.security;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.security.KeyFactory;
 import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.Provider;
 import java.security.PublicKey;
 import java.security.Security;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.interfaces.RSAPrivateKey;
 import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPrivateKeySpec;
 import java.security.spec.RSAPublicKeySpec;
@@ -62,12 +71,15 @@
     }
 
     @Test
-    public void testExtraBufferSpace_Private() throws Exception {
-        PrivateKey privateKey = DefaultKeys.getPrivateKey("RSA");
-        byte[] encoded = privateKey.getEncoded();
-        byte[] longBuffer = new byte[encoded.length + 147];
-        System.arraycopy(encoded, 0, longBuffer, 0, encoded.length);
-        KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(longBuffer));
+    public void getEncodedFailsWhenCrtValuesMissing() throws Exception {
+        PrivateKey privateKey = getPrivateKey();
+        try {
+            // Key has only modulus and private exponent so can't be encoded as PKCS#8
+            privateKey.getEncoded();
+            fail();
+        } catch (RuntimeException e) {
+            // Expected
+        }
     }
 
     @Test
@@ -108,4 +120,38 @@
             // expected
         }
     }
+
+    @Test
+    public void javaSerialization() throws Exception {
+        PrivateKey privatekey = getPrivateKey();
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        ObjectOutputStream out = new ObjectOutputStream(bos);
+        out.writeObject(privatekey);
+
+        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
+        ObjectInputStream in = new ObjectInputStream(bis);
+        PrivateKey copy = (PrivateKey) in.readObject();
+
+        assertEquals(privatekey, copy);
+    }
+
+    @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
+    // in this unit test and so we extract the modulus and private exponent to build the
+    // correct private key subtype.
+    private PrivateKey getPrivateKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
+        RSAPrivateCrtKey crtKey = (RSAPrivateCrtKey) DefaultKeys.getPrivateKey(algorithmName);
+        RSAPrivateKeySpec spec =
+                new RSAPrivateKeySpec(crtKey.getModulus(), crtKey.getPrivateExponent());
+        PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(spec);
+        assertTrue(privateKey instanceof RSAPrivateKey);
+        assertFalse(privateKey instanceof RSAPrivateCrtKey);
+        return privateKey;
+    }
 }
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 15b1628..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
@@ -16,9 +16,22 @@
  */
 package com.android.org.conscrypt.java.security;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.security.KeyFactory;
 import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPublicKeySpec;
+import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import tests.util.ServiceTester;
@@ -46,4 +59,35 @@
     // implmenetations.
     return tester.skipProvider("BC");
   }
+
+  @Test
+  public void testExtraBufferSpace_Private() throws Exception {
+      PrivateKey privateKey = DefaultKeys.getPrivateKey("RSA");
+      assertTrue(privateKey instanceof RSAPrivateCrtKey);
+
+      byte[] encoded = privateKey.getEncoded();
+      byte[] longBuffer = new byte[encoded.length + 147];
+      System.arraycopy(encoded, 0, longBuffer, 0, encoded.length);
+      PrivateKey copy =
+              KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(longBuffer));
+      assertEquals(privateKey, copy);
+  }
+
+  @Test
+  public void javaSerialization() throws Exception {
+      PrivateKey privateKey = DefaultKeys.getPrivateKey("RSA");
+      assertTrue(privateKey instanceof RSAPrivateCrtKey);
+
+      ByteArrayOutputStream bos = new ByteArrayOutputStream();
+      ObjectOutputStream out = new ObjectOutputStream(bos);
+      out.writeObject(privateKey);
+
+      ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
+      ObjectInputStream in = new ObjectInputStream(bis);
+      PrivateKey copy = (PrivateKey) in.readObject();
+      assertTrue(copy instanceof RSAPrivateCrtKey);
+
+      assertEquals(privateKey.getFormat(), copy.getFormat());
+      assertArrayEquals(privateKey.getEncoded(), copy.getEncoded());
+  }
 }
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 a418d67..37c702b 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
@@ -151,6 +151,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;
     }
 
@@ -1083,7 +1106,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");
@@ -3469,6 +3494,9 @@
             if (provider.equals("SunJCE") && transformation.endsWith("/PKCS7PADDING")) {
                 return false;
             }
+            if (provider.equals("BC")) {
+                return isSupportedByBC(transformation);
+            }
             return true;
         }
     }
@@ -4097,6 +4125,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) {
@@ -4296,32 +4327,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) {
@@ -4384,7 +4389,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);
 
@@ -4418,7 +4427,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/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/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/AbstractKeyFactoryTest.java b/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/AbstractKeyFactoryTest.java
index 8a751aa..319b198 100644
--- a/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/AbstractKeyFactoryTest.java
+++ b/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/AbstractKeyFactoryTest.java
@@ -33,8 +33,7 @@
  * @hide This class is not part of the Android public SDK API
  */
 public abstract class AbstractKeyFactoryTest<PublicKeySpec extends KeySpec, PrivateKeySpec extends KeySpec> {
-
-    private final String algorithmName;
+    protected final String algorithmName;
     private final Class<PublicKeySpec> publicKeySpecClass;
     private final Class<PrivateKeySpec> privateKeySpecClass;
 
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
diff --git a/testing/src/main/java/org/conscrypt/java/security/AbstractKeyFactoryTest.java b/testing/src/main/java/org/conscrypt/java/security/AbstractKeyFactoryTest.java
index 3523e45..ae5c963 100644
--- a/testing/src/main/java/org/conscrypt/java/security/AbstractKeyFactoryTest.java
+++ b/testing/src/main/java/org/conscrypt/java/security/AbstractKeyFactoryTest.java
@@ -30,7 +30,7 @@
 
 public abstract class AbstractKeyFactoryTest<PublicKeySpec extends KeySpec, PrivateKeySpec extends KeySpec> {
 
-    private final String algorithmName;
+    protected final String algorithmName;
     private final Class<PublicKeySpec> publicKeySpecClass;
     private final Class<PrivateKeySpec> privateKeySpecClass;