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;