[automerger skipped] Remove CT tests am: de68ba35b9 -s ours

am skip reason: Merged-In I24e87ebcd5033c0a7d78ee203a318c398c4d85c5 with SHA-1 d29e52b96c is already in history

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

Change-Id: I83032ad1bb3263a77a99b99afc2d81b2bb969a98
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/.gitignore b/.gitignore
index e065940..00eefa9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,5 @@
 # Gradle
 build
-gradle.properties
 .gradle
 local.properties
 
diff --git a/.travis.yml b/.travis.yml
index 20ee519..1910355 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,7 +23,6 @@
         - ANDROID_TOOLS_URL="https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip"
         - ANDROID_HOME="$HOME/android-sdk-linux"
         - JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
-        - JAVA7_HOME=/usr/lib/jvm/java-7-openjdk-amd64
         - JAVA11_HOME=/usr/lib/jvm/java-11-openjdk-amd64
         - CC=clang-5.0
         - CXX=clang++-5.0
@@ -42,7 +41,7 @@
         - $ANDROID_HOME/tools/bin/sdkmanager 'build-tools;28.0.3' | tr '\r' '\n' | uniq
         - $ANDROID_HOME/tools/bin/sdkmanager 'platforms;android-26' | tr '\r' '\n' | uniq
         - $ANDROID_HOME/tools/bin/sdkmanager 'extras;android;m2repository' | tr '\r' '\n' | uniq
-        - $ANDROID_HOME/tools/bin/sdkmanager ndk-bundle | tr '\r' '\n' | uniq
+        - $ANDROID_HOME/tools/bin/sdkmanager 'ndk;21.3.6528147' | tr '\r' '\n' | uniq
         - $ANDROID_HOME/tools/bin/sdkmanager 'cmake;3.10.2.4988404' | tr '\r' '\n' | uniq
         - gimme 1.13 # Needed for BoringSSL build
         - source ~/.gimme/envs/go1.13.env
@@ -62,7 +61,6 @@
             - libc6-dev:i386
             - linux-libc-dev
             - ninja-build
-            - openjdk-7-jre # for running tests with Java 7
             - openjdk-8-jdk # for building
             - openjdk-11-jre # for running tests with Java 11
 
@@ -109,15 +107,7 @@
 
   - ./gradlew build -PcheckErrorQueue
 
-  # Also test with Java 7 and 11 on linux
-  - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" != "false" ]];
-      then
-        ${JAVA7_HOME}/bin/java -version;
-      fi
-  - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" != "false" ]];
-    then
-      ./gradlew check -DjavaExecutable64=${JAVA7_HOME}/bin/java -PcheckErrorQueue;
-    fi
+  # Also test with Java 11 on linux
   - if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" != "false" ]];
     then
       ./gradlew check -DjavaExecutable64=${JAVA11_HOME}/bin/java -PcheckErrorQueue;
diff --git a/Android.bp b/Android.bp
index 6d4472f..af41a1a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -18,6 +18,40 @@
     default_visibility: [
         ":__subpackages__",
     ],
+    default_applicable_licenses: ["external_conscrypt_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+//
+// large-scale-change included anything that looked like it might be a license
+// text as a license_text. e.g. LICENSE, NOTICE, COPYING etc.
+//
+// Please consider removing redundant or irrelevant files from 'license_text:'.
+// See: http://go/android-license-faq
+license {
+    name: "external_conscrypt_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+        "legacy_unencumbered",
+    ],
+    license_text: [
+        "LICENSE",
+        "NOTICE",
+        "licenses/**/*",
+    ],
 }
 
 //
@@ -69,6 +103,8 @@
         "common/src/jni/main/cpp/conscrypt/trace.cc",
     ],
 
+    header_libs: ["jni_headers"],
+
     local_include_dirs: [
         "common/src/jni/main/include",
     ],
@@ -84,6 +120,8 @@
         "common/src/jni/unbundled/include",
     ],
 
+    header_libs: ["jni_headers"],
+
     shared_libs: [
         "liblog",
     ],
@@ -106,6 +144,11 @@
 
 cc_library_host_shared {
     name: "libconscrypt_openjdk_jni",
+    visibility: [
+        "//build/make/tools/signapk",
+        "//tools/apksig",
+        "//vendor:__subpackages__",
+    ],
     defaults: ["conscrypt_global"],
 
     cflags: [
@@ -189,9 +232,7 @@
 java_library {
     name: "conscrypt",
     visibility: [
-        "//art/build",
         "//device:__subpackages__",
-        "//external/robolectric-shadows",
         "//system/apex/tests",
         ":__subpackages__",
     ],
@@ -203,7 +244,9 @@
     min_sdk_version: "29",
 
     installable: true,
-    hostdex: true,
+    // Hostdex is only for ART testing on host: ART build file has its
+    // own hostdex support for conscrypt.
+    hostdex: false,
 
     srcs: [
         ":conscrypt_java_files",
@@ -218,18 +261,11 @@
     system_modules: "art-module-intra-core-api-stubs-system-modules",
     patch_module: "java.base",
 
-    // Workaround for b/124476339: libjavacrypto is required for both APEX and
-    // hostdex builds, but adding a top-level required property results in
-    // it being installed to /system on Android.
-    // TODO(b/124476339): move required back to a top level property
     target: {
         // boringssl_self_test needed in both /system/bin and /apex/com.android.conscrypt/bin
         android: {
             required: ["boringssl_self_test"],
         },
-        hostdex: {
-            required: ["libjavacrypto"],
-        },
     },
 
     permitted_packages: [
@@ -240,6 +276,20 @@
     plugins: ["java_api_finder"],
 }
 
+// Java library for use on host, e.g. by robolectric.
+java_library {
+    name: "conscrypt-for-host",
+    visibility: [
+        "//art/build",
+        "//external/robolectric-shadows",
+    ],
+    static_libs: [
+        "conscrypt",
+    ],
+    sdk_version: "none",
+    system_modules: "none",
+}
+
 // Referenced implicitly from conscrypt.module.platform.api.
 filegroup {
     name: "conscrypt.module.platform.api.api.public.latest",
@@ -256,6 +306,14 @@
     ],
 }
 
+// Referenced implicitly from conscrypt.module.platform.api.
+filegroup {
+    name: "conscrypt.module.platform.api-incompatibilities.api.public.latest",
+    srcs: [
+        "api/platform/last-incompatibilities.txt",
+    ],
+}
+
 // A library containing the core platform API stubs of the Conscrypt module.
 //
 // Core platform APIs are only intended for use of other parts of the platform, not the
@@ -264,71 +322,71 @@
 // The API specification .txt files managed by this only contain the additional
 // classes/members that are in the platform API but which are not in the public
 // API.
+//
+// Note that this entire API surface is considered stable in the sense described in
+// libcore/mmodules/core_platform_api/Android.bp.
 java_sdk_library {
     name: "conscrypt.module.platform.api",
     visibility: [
         "//external/wycheproof",
         "//libcore:__subpackages__",
+        // Visibility for prebuilt conscrypt-module-sdk from the prebuilt of
+        // this module.
+        // TODO(b/155921753): Restrict this when prebuilts are in their proper
+        // locations.
+        "//prebuilts:__subpackages__",
     ],
     srcs: [
         ":conscrypt_java_files",
     ],
     api_dir: "api/platform",
     api_only: true,
+    api_lint: {
+        enabled: true,
+    },
     droiddoc_options: [
         "--hide-annotation libcore.api.Hide",
-        "--show-single-annotation libcore.api.CorePlatformApi",
+        "--show-single-annotation libcore.api.CorePlatformApi\\(status=libcore.api.CorePlatformApi.Status.STABLE\\)",
         "--skip-annotation-instance-methods=false",
     ],
     hostdex: true,
 
     sdk_version: "none",
-    system_modules: "art-module-platform-api-stubs-system-modules",
-}
+    system_modules: "art-module-lib-api-stubs-system-modules",
 
-// A guaranteed unstripped version of conscrypt.
-// The build system may or may not strip the conscrypt jar, but this one will
-// not be stripped. See b/24535627.
-java_library {
-    name: "conscrypt-testdex",
-    installable: true,
-
-    static_libs: ["conscrypt"],
-    dex_preopt: {
-        enabled: false,
-    },
-
-    sdk_version: "core_platform",
-
-    required: ["libjavacrypto"],
-}
-
-// Referenced implicitly from conscrypt.module.public.api.
-filegroup {
-    name: "conscrypt.module.public.api.api.public.latest",
-    srcs: [
-        "api/public/last-api.txt",
-    ],
-}
-
-// Referenced implicitly from conscrypt.module.public.api.
-filegroup {
-    name: "conscrypt.module.public.api-removed.api.public.latest",
-    srcs: [
-        "api/public/last-removed.txt",
-    ],
+    dist_group: "android",
+    dist_stem: "conscrypt-coreplatform",
+    // TODO: remove this when Conscrypt's @CorePlatformApi has been migrated to @SystemApi
+    unsafe_ignore_missing_latest_api: true,
 }
 
 // A library containing the public API stubs of the Conscrypt module.
 java_sdk_library {
     name: "conscrypt.module.public.api",
     visibility: [
+        "//packages/modules/common/sdk",
         "//frameworks/base",
+        "//frameworks/base/api",
         "//libcore",
+        // TODO(b/165823103): Remove visiblity for IPsec once CorePlatformApi is available
+        "//packages/modules/IPsec",
+        // Visibility for prebuilt art-module-host-exports from the prebuilt of
+        // this module.
+        // TODO(b/155921753): Restrict this when prebuilts are in their proper
+        // locations.
+        "//prebuilts:__subpackages__",
     ],
     srcs: [
         ":conscrypt_public_api_files",
     ],
+
+    // The base name for the artifacts that are automatically published to the
+    // dist and which end up in one of the sub-directories of prebuilts/sdk.
+    // As long as this matches the name of the artifacts in prebuilts/sdk then
+    // the API will be checked for compatibility against the latest released
+    // version of the API.
+    dist_stem: "conscrypt",
+
     api_dir: "api/public",
     api_only: true,
     droiddoc_options: [
@@ -340,6 +398,7 @@
 
     sdk_version: "none",
     system_modules: "art-module-public-api-stubs-system-modules",
+    dist_group: "android",
 }
 
 // Referenced implicitly from conscrypt.module.intra.core.api.
@@ -358,6 +417,14 @@
     ],
 }
 
+// Referenced implicitly from conscrypt.module.intra.core.api.
+filegroup {
+    name: "conscrypt.module.intra.core.api-incompatibilities.api.public.latest",
+    srcs: [
+        "api/intra/last-incompatibilities.txt",
+    ],
+}
+
 // A library containing the intra-core API stubs of the Conscrypt module.
 //
 // Intra-core APIs are only intended for the use of other core library modules.
@@ -369,6 +436,11 @@
     visibility: [
         "//external/okhttp",
         "//libcore:__subpackages__",
+        // Visibility for prebuilt conscrypt-module-sdk from the prebuilt of
+        // this module.
+        // TODO(b/155921753): Restrict this when prebuilts are in their proper
+        // locations.
+        "//prebuilts:__subpackages__",
     ],
     srcs: [
         ":conscrypt_java_files",
@@ -384,6 +456,9 @@
 
     sdk_version: "none",
     system_modules: "art-module-intra-core-api-stubs-system-modules",
+
+    // Don't copy any output files to the dist.
+    no_dist: true,
 }
 
 // Platform conscrypt crypto JNI library
@@ -399,6 +474,7 @@
     ],
 
     srcs: ["common/src/jni/main/cpp/**/*.cc"],
+    header_libs: ["jni_headers"],
     local_include_dirs: ["common/src/jni/main/include"],
 }
 
@@ -500,6 +576,7 @@
         "core-test-rules",
         "junit",
         "mockito-target-minus-junit4",
+        "framework-statsd.stubs.module_lib",
     ],
 
     static_libs: [
@@ -513,8 +590,16 @@
         //"-Xlint:-serial,-deprecation,-unchecked",
     ],
 
-    required: ["libjavacrypto"],
-    java_version: "1.7",
+    target: {
+        host: {
+            required: ["libjavacrypto"],
+        },
+        darwin: {
+            // required module "libjavacrypto" is disabled on darwin
+            enabled: false,
+        },
+    },
+    java_version: "1.8",
 }
 
 // Make the conscrypt-benchmarks library.
@@ -542,13 +627,24 @@
         //"-Xlint:-serial,-deprecation,-unchecked",
     ],
 
-    required: ["libjavacrypto"],
+    target: {
+        host: {
+            required: ["libjavacrypto"],
+        },
+        darwin: {
+            // required module "libjavacrypto" is disabled on darwin
+            enabled: false,
+        },
+    },
     java_version: "1.7",
 }
 
 // Device SDK exposed by the Conscrypt module.
 sdk {
     name: "conscrypt-module-sdk",
+    bootclasspath_fragments: [
+        "com.android.conscrypt-bootclasspath-fragment",
+    ],
     java_sdk_libs: [
         "conscrypt.module.public.api",
         "conscrypt.module.intra.core.api",
@@ -567,19 +663,38 @@
     java_libs: [
         "conscrypt-unbundled",
     ],
+    native_shared_libs: [
+        "libconscrypt_openjdk_jni",
+    ],
 }
 
 // Test libraries exposed by the Conscrypt module.
 module_exports {
     name: "conscrypt-module-test-exports",
-    java_libs: [
-        // For use by robolectric.
-        "conscrypt",
-        // For use by art tests
-        "conscrypt-testdex",
-    ],
-    java_tests: [
-        // For use by CTS
-        "conscrypt-tests",
-    ],
+    host_supported: true,
+    target: {
+        android: {
+            java_libs: [
+                // For use by robolectric and ART tests.
+                "conscrypt-for-host",
+            ],
+            java_tests: [
+                // For use by CTS
+                "conscrypt-tests",
+            ],
+            // TODO: Remove this when we resolve b/151303681.
+            native_shared_libs: [
+                "libjavacrypto",
+            ],
+        },
+        darwin: {
+            enabled: false,
+        },
+        // For use by ART tests on host.
+        not_windows: {
+            native_shared_libs: [
+                "libjavacrypto",
+            ],
+        },
+    },
 }
diff --git a/CAPABILITIES.md b/CAPABILITIES.md
index 0be1621..b043f63 100644
--- a/CAPABILITIES.md
+++ b/CAPABILITIES.md
@@ -89,6 +89,7 @@
 * `AES/CTR/NoPadding`
 * `AES/ECB/NoPadding`
 * `AES/ECB/PKCS5Padding`
+* `AES/GCM-SIV/NoPadding`
 
 AES with 128, 192, or 256-bit keys.
 
@@ -101,11 +102,13 @@
 * `AES_128/ECB/NoPadding`
 * `AES_128/ECB/PKCS5Padding`
 * `AES_128/GCM/NoPadding`
+* `AES_128/GCM-SIV/NoPadding`
 * `AES_256/CBC/NoPadding`
 * `AES_256/CBC/PKCS5Padding`
 * `AES_256/ECB/NoPadding`
 * `AES_256/ECB/PKCS5Padding`
 * `AES_256/GCM/NoPadding`
+* `AES_256/GCM-SIV/NoPadding`
 
 Key-restricted versions of the AES ciphers.
 
diff --git a/IMPLEMENTATION_NOTES.md b/IMPLEMENTATION_NOTES.md
index 3c40dea..87c89aa 100644
--- a/IMPLEMENTATION_NOTES.md
+++ b/IMPLEMENTATION_NOTES.md
@@ -13,14 +13,20 @@
 
 ## Hostname Verification
 
-Conscrypt's hostname verification (enabled by
+Prior to version 2.5.0 Conscrypt's hostname verification (enabled by
 [`setEndpointIdentificationAlgorithm("HTTPS")`](https://docs.oracle.com/javase/9/docs/api/javax/net/ssl/SSLParameters.html#setEndpointIdentificationAlgorithm-java.lang.String-))
-defers entirely to the hostname verifier.  The default `HostnameVerifier` on
-OpenJDK always fails, so a `HostnameVerifier` or `ConscryptHostnameVerifier`
-must be set to use hostname verification on OpenJDK.  On Android, the default
+defers entirely to the underlying platform's `HttpsURLConnection` hostname verifier.
+
+The default `HostnameVerifier` on OpenJDK rejects all hostnames, and
+so a `HostnameVerifier` or `ConscryptHostnameVerifier`
+must be set in order to use hostname verification on OpenJDK.  On Android, the default
 `HostnameVerifier` performs [RFC 2818](https://tools.ietf.org/html/rfc2818)
 hostname validation, so it will work out of the box.
 
+As of version 2.5.0, Conscrypt ships with its own default `ConscryptHostnameVerifier`
+and this is used on both Android and OpenJDK. It performs RFC 2818 verification
+and is equivalent to the system `HostnameVerifier` on Android 10 and 11.
+
 ## AEAD Ciphers
 
 Conscrypt's AEAD ciphers do not support incremental processing (i.e. they will
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d97975c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+  license_type: NOTICE
+}
diff --git a/OWNERS b/OWNERS
index b2ed0be..6a33b41 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,7 +1,7 @@
 # Bug component: 684135
 dauletz@google.com
 kroot@google.com
+mingaleev@google.com
 narayan@google.com
-nfuller@google.com
+ngeoffray@google.com
 prb@google.com
-tobiast@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 84d310f..7d08c6f 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -2,3 +2,7 @@
 commit_msg_test_field = true
 clang_format = true
 bpfmt = true
+
+[Hook Scripts]
+
+hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/checksorted_sha.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT}
diff --git a/README.md b/README.md
index dbc2808..c63c5ed 100644
--- a/README.md
+++ b/README.md
@@ -68,7 +68,7 @@
 <dependency>
   <groupId>org.conscrypt</groupId>
   <artifactId>conscrypt-openjdk</artifactId>
-  <version>2.4.0</version>
+  <version>2.5.2</version>
   <classifier>${os.detected.classifier}</classifier>
 </dependency>
 ```
@@ -91,7 +91,7 @@
 apply plugin: "com.google.osdetector"
 
 dependencies {
-  compile 'org.conscrypt:conscrypt-openjdk:2.4.0:' + osdetector.classifier
+  compile 'org.conscrypt:conscrypt-openjdk:2.5.2:' + osdetector.classifier
 }
 ```
 
@@ -109,14 +109,14 @@
 <dependency>
   <groupId>org.conscrypt</groupId>
   <artifactId>conscrypt-openjdk-uber</artifactId>
-  <version>2.4.0</version>
+  <version>2.5.2</version>
 </dependency>
 ```
 
 ##### Gradle
 ```gradle
 dependencies {
-  compile 'org.conscrypt:conscrypt-openjdk-uber:2.4.0'
+  compile 'org.conscrypt:conscrypt-openjdk-uber:2.5.2'
 }
 ```
 
@@ -129,7 +129,7 @@
 
 ```gradle
 dependencies {
-  implementation 'org.conscrypt:conscrypt-android:2.4.0'
+  implementation 'org.conscrypt:conscrypt-android:2.5.2'
 }
 ```
 
diff --git a/android-stub/build.gradle b/android-stub/build.gradle
index 8355c94..f1a911f 100644
--- a/android-stub/build.gradle
+++ b/android-stub/build.gradle
@@ -9,4 +9,4 @@
 }
 
 // Disable the javadoc task.
-tasks.withType(Javadoc).all { enabled = false }
+tasks.withType(Javadoc).configureEach { enabled = false }
diff --git a/android-stub/src/main/java/android/util/StatsEvent.java b/android-stub/src/main/java/android/util/StatsEvent.java
new file mode 100644
index 0000000..c24c4d9
--- /dev/null
+++ b/android-stub/src/main/java/android/util/StatsEvent.java
@@ -0,0 +1,124 @@
+/*
+ * 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 android.util;
+
+@SuppressWarnings("unused")
+public final class StatsEvent {
+    private StatsEvent(int atomId, StatsEvent.Buffer buffer, byte[] payload, int numBytes) {
+        throw new RuntimeException("Stub!");
+    }
+
+    public static StatsEvent.Builder newBuilder() {
+        throw new RuntimeException("Stub!");
+    }
+
+    public int getAtomId() {
+        throw new RuntimeException("Stub!");
+    }
+
+    public byte[] getBytes() {
+        throw new RuntimeException("Stub!");
+    }
+
+    public int getNumBytes() {
+        throw new RuntimeException("Stub!");
+    }
+
+    public void release() {
+        throw new RuntimeException("Stub!");
+    }
+
+    private static final class Buffer {
+        private static StatsEvent.Buffer obtain() {
+            throw new RuntimeException("Stub!");
+        }
+
+        private Buffer() {
+            throw new RuntimeException("Stub!");
+        }
+    }
+
+    public static final class Builder {
+        private Builder(StatsEvent.Buffer buffer) {
+            throw new RuntimeException("Stub!");
+        }
+
+        public StatsEvent.Builder setAtomId(int atomId) {
+            throw new RuntimeException("Stub!");
+        }
+
+        public StatsEvent.Builder writeBoolean(boolean value) {
+            throw new RuntimeException("Stub!");
+        }
+
+        public StatsEvent.Builder writeInt(int value) {
+            throw new RuntimeException("Stub!");
+        }
+
+        public StatsEvent.Builder writeLong(long value) {
+            throw new RuntimeException("Stub!");
+        }
+
+        public StatsEvent.Builder writeFloat(float value) {
+            throw new RuntimeException("Stub!");
+        }
+
+        public StatsEvent.Builder writeString(String value) {
+            throw new RuntimeException("Stub!");
+        }
+
+        public StatsEvent.Builder writeByteArray(byte[] value) {
+            throw new RuntimeException("Stub!");
+        }
+
+        private void writeByteArray(byte[] value, byte typeId) {
+            throw new RuntimeException("Stub!");
+        }
+
+        public StatsEvent.Builder writeAttributionChain(int[] uids, String[] tags) {
+            throw new RuntimeException("Stub!");
+        }
+
+        public StatsEvent.Builder addBooleanAnnotation(byte annotationId, boolean value) {
+            throw new RuntimeException("Stub!");
+        }
+
+        public StatsEvent.Builder addIntAnnotation(byte annotationId, int value) {
+            throw new RuntimeException("Stub!");
+        }
+
+        public StatsEvent.Builder usePooledBuffer() {
+            throw new RuntimeException("Stub!");
+        }
+
+        public StatsEvent build() {
+            throw new RuntimeException("Stub!");
+        }
+
+        private void writeTypeId(byte typeId) {
+            throw new RuntimeException("Stub!");
+        }
+
+        private void writeAnnotationCount() {
+            throw new RuntimeException("Stub!");
+        }
+
+        private static byte[] stringToBytes(String value) {
+            throw new RuntimeException("Stub!");
+        }
+    }
+}
diff --git a/android-stub/src/main/java/javax/net/ssl/ExtendedSSLSession.java b/android-stub/src/main/java/javax/net/ssl/ExtendedSSLSession.java
index aa49734..c899170 100644
--- a/android-stub/src/main/java/javax/net/ssl/ExtendedSSLSession.java
+++ b/android-stub/src/main/java/javax/net/ssl/ExtendedSSLSession.java
@@ -23,7 +23,7 @@
  */
 public abstract class ExtendedSSLSession implements SSLSession {
 
-    public ExtendedSSLSession() {
+    protected ExtendedSSLSession() {
     }
 
     public abstract String[] getLocalSupportedSignatureAlgorithms();
diff --git a/android/build.gradle b/android/build.gradle
index 38ea56d..732e52c 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -21,6 +21,7 @@
     androidVersionName = "$version"
     androidMinSdkVersion = 9
     androidTargetSdkVersion = 26
+    androidNdkVersion = "21.3.6528147"
 }
 
 if (androidSdkInstalled) {
@@ -32,6 +33,7 @@
 
     android {
         compileSdkVersion androidTargetSdkVersion
+        ndkVersion androidNdkVersion
 
         compileOptions {
             sourceCompatibility androidMinJavaVersion
@@ -119,14 +121,15 @@
         compileOnly project(':conscrypt-constants')
     }
 
-    task configureJavadocs {
+    def configureJavaDocs = tasks.register("configureJavadocs") {
         dependsOn configurations.publicApiDocs
         doLast {
             javadocs.options.docletpath = configurations.publicApiDocs.files as List
         }
     }
 
-    task javadocs(type: Javadoc, dependsOn: [configureJavadocs]) {
+    def javadocs = tasks.register("javadocs", Javadoc) {
+        dependsOn configureJavadocs
         source = android.sourceSets.main.java.srcDirs
         classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) + project(':conscrypt-android-stub').sourceSets.main.output
         // TODO(nmittler): Fix the javadoc errors.
@@ -142,12 +145,15 @@
         }
     }
 
-    task javadocsJar(type: Jar, dependsOn: javadocs) {
+    def javadocsJar = tasks.register("javadocsJar", Jar) {
+        dependsOn javadocs
         classifier = 'javadoc'
-        from javadocs.destinationDir
+        from {
+            javadocs.get().destinationDir
+        }
     }
 
-    task sourcesJar(type: Jar) {
+    def sourcesJar = tasks.register("sourcesJar", Jar) {
         classifier = 'sources'
         from android.sourceSets.main.java.srcDirs
     }
@@ -155,14 +161,14 @@
     apply from: "$rootDir/gradle/publishing.gradle"
     publishing.publications.maven {
         from components.android
-        artifact sourcesJar
-        artifact javadocsJar
+        artifact sourcesJar.get()
+        artifact javadocsJar.get()
     }
 } else {
     logger.warn('Android SDK has not been detected. The Android module will not be built.')
 
     // Disable all tasks
-    tasks.collect {
+    tasks.configureEach {
         it.enabled = false
     }
 }
diff --git a/android/proguard-rules.pro b/android/proguard-rules.pro
index 3bc75b2..e966c43 100644
--- a/android/proguard-rules.pro
+++ b/android/proguard-rules.pro
@@ -17,6 +17,7 @@
 -dontnote org.apache.harmony.security.utils.AlgNameMapper
 -dontnote sun.security.x509.AlgorithmId
 
+-dontwarn android.util.StatsEvent
 -dontwarn dalvik.system.BlockGuard
 -dontwarn dalvik.system.BlockGuard$Policy
 -dontwarn dalvik.system.CloseGuard
diff --git a/android/src/main/java/org/conscrypt/Platform.java b/android/src/main/java/org/conscrypt/Platform.java
index a739a78..68a7409 100644
--- a/android/src/main/java/org/conscrypt/Platform.java
+++ b/android/src/main/java/org/conscrypt/Platform.java
@@ -19,6 +19,7 @@
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.os.Build;
+import android.os.SystemClock;
 import android.util.Log;
 import dalvik.system.BlockGuard;
 import dalvik.system.CloseGuard;
@@ -59,6 +60,9 @@
 import javax.net.ssl.X509TrustManager;
 import org.conscrypt.ct.CTLogStore;
 import org.conscrypt.ct.CTPolicy;
+import org.conscrypt.metrics.CipherSuite;
+import org.conscrypt.metrics.ConscryptStatsLog;
+import org.conscrypt.metrics.Protocol;
 
 /**
  * Platform-specific methods for unbundled Android.
@@ -72,6 +76,7 @@
             m_getCurveName = ECParameterSpec.class.getDeclaredMethod("getCurveName");
             m_getCurveName.setAccessible(true);
         } catch (Exception ignored) {
+            //Ignored
         }
     }
 
@@ -124,6 +129,7 @@
             Method setCurveName = spec.getClass().getDeclaredMethod("setCurveName", String.class);
             setCurveName.invoke(spec, curveName);
         } catch (Exception ignored) {
+            //Ignored
         }
     }
 
@@ -244,7 +250,9 @@
                 }
             }
         } catch (NoSuchMethodException ignored) {
+            //Ignored
         } catch (IllegalAccessException ignored) {
+            //Ignored
         } catch (InvocationTargetException e) {
             throw new RuntimeException(e.getCause());
         }
@@ -262,7 +270,9 @@
                 }
             }
         } catch (NoSuchMethodException ignored) {
+            //Ignored
         } catch (IllegalAccessException ignored) {
+            //Ignored
         } catch (InvocationTargetException e) {
             throw new RuntimeException(e.getCause());
         }
@@ -306,7 +316,9 @@
                 setParametersSniHostname(params, impl, socket);
             }
         } catch (NoSuchMethodException ignored) {
+            //Ignored
         } catch (IllegalAccessException ignored) {
+            //Ignored
         } catch (InvocationTargetException e) {
             throw new RuntimeException(e.getCause());
         }
@@ -333,7 +345,9 @@
                 setParametersSniHostname(params, impl, engine);
             }
         } catch (NoSuchMethodException ignored) {
+            //Ignored
         } catch (IllegalAccessException ignored) {
+            //Ignored
         } catch (InvocationTargetException e) {
             throw new RuntimeException(e.getCause());
         }
@@ -359,6 +373,7 @@
             try {
                 return Class.forName(klass);
             } catch (Exception ignored) {
+                //Ignored
             }
         }
         return null;
@@ -388,7 +403,9 @@
             method.invoke(tm, chain, authType, argumentInstance);
             return true;
         } catch (NoSuchMethodException ignored) {
+            //Ignored
         } catch (IllegalAccessException ignored) {
+            //Ignored
         } catch (InvocationTargetException e) {
             if (e.getCause() instanceof CertificateException) {
                 throw(CertificateException) e.getCause();
@@ -826,6 +843,7 @@
             }
             throw new RuntimeException(e);
         } catch (Exception ignored) {
+            //Ignored
         }
 
         // Newer OpenJDK style
@@ -847,6 +865,7 @@
             }
             throw new RuntimeException(e);
         } catch (Exception ignored) {
+            //Ignored
         }
 
         return oid;
@@ -884,7 +903,9 @@
             } catch (ClassNotFoundException ignore) {
                 // passthrough and return addr.getHostAddress()
             } catch (IllegalAccessException ignore) {
+                //Ignored
             } catch (NoSuchMethodException ignore) {
+                //Ignored
             }
         }
         return addr.getHostAddress();
@@ -902,6 +923,7 @@
             } catch (InvocationTargetException e) {
                 throw new RuntimeException(e);
             } catch (Exception ignored) {
+                //Ignored
             }
         }
         return null;
@@ -984,7 +1006,7 @@
         return null;
     }
 
-    static CertBlacklist newDefaultBlacklist() {
+    static CertBlocklist newDefaultBlocklist() {
         return null;
     }
 
@@ -1019,4 +1041,39 @@
         }
         return false;
     }
+
+    public static ConscryptHostnameVerifier getDefaultHostnameVerifier() {
+        return OkHostnameVerifier.strictInstance();
+    }
+
+    /**
+     * Returns milliseconds elapsed since boot, including time spent in sleep.
+     * @return long number of milliseconds elapsed since boot
+     */
+    static long getMillisSinceBoot() {
+        return SystemClock.elapsedRealtime();
+    }
+
+    static void countTlsHandshake(
+            boolean success, String protocol, String cipherSuite, long duration) {
+        // Statsd classes appeared in SDK 30 and aren't available in earlier versions
+
+        if (Build.VERSION.SDK_INT >= 30) {
+            Protocol proto = Protocol.forName(protocol);
+            CipherSuite suite = CipherSuite.forName(cipherSuite);
+            int dur = (int) duration;
+
+            writeStats(success, proto.getId(), suite.getId(), dur);
+        }
+    }
+
+    @TargetApi(30)
+    private static void writeStats(boolean success, int protocol, int cipherSuite, int duration) {
+        ConscryptStatsLog.write(
+                ConscryptStatsLog.TLS_HANDSHAKE_REPORTED, success, protocol, cipherSuite, duration);
+    }
+
+    public static boolean isJavaxCertificateSupported() {
+        return true;
+    }
 }
diff --git a/apex/Android.bp b/apex/Android.bp
index 7310f7a..2a7d225 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -13,13 +13,23 @@
 // limitations under the License.
 
 // Defaults shared between real and test versions of the APEX.
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_conscrypt_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["external_conscrypt_license"],
+}
+
 apex_defaults {
     name: "com.android.conscrypt-defaults",
     updatable: true,
     androidManifest: ":com.android.conscrypt-androidManifest",
     compile_multilib: "both",
-    java_libs: ["conscrypt"],
-    native_shared_libs: ["libjavacrypto"],
+    bootclasspath_fragments: ["com.android.conscrypt-bootclasspath-fragment"],
+    jni_libs: ["libjavacrypto"],
+    prebuilts: ["current_sdkinfo"],
     multilib: {
         both: {
             binaries: ["boringssl_self_test"],
@@ -72,3 +82,34 @@
     binaries: ["boringssl_self_test"],
     prebuilts: ["com.android.conscrypt.ld.config.txt"],
 }
+
+// Encapsulate the contributions made by the com.android.conscrypt to the bootclasspath.
+bootclasspath_fragment {
+    name: "com.android.conscrypt-bootclasspath-fragment",
+    contents: ["conscrypt"],
+    apex_available: ["com.android.conscrypt"],
+    // The bootclasspath_fragments that provide APIs on which this depends.
+    fragments: [
+        {
+            apex: "com.android.art",
+            module: "art-bootclasspath-fragment",
+        },
+    ],
+    // The APIs provided by this fragment.
+    api: {
+        stub_libs: [
+            "conscrypt.module.public.api",
+        ],
+    },
+    // The core platform APIs provided by this fragment.
+    core_platform_api: {
+        stub_libs: [
+            "conscrypt.module.platform.api",
+        ],
+    },
+    // Additional hidden API flags that override the default flags derived
+    // from the api stub libraries.
+    hidden_api: {
+        max_target_o_low_priority: ["hiddenapi/hiddenapi-max-target-o-low-priority.txt"],
+    },
+}
diff --git a/apex/apex_manifest.json b/apex/apex_manifest.json
index 14d81ea..e59fbcf 100644
--- a/apex/apex_manifest.json
+++ b/apex/apex_manifest.json
@@ -1,4 +1,4 @@
 {
   "name": "com.android.conscrypt",
-  "version": 300000000
+  "version": 319999900
 }
diff --git a/apex/hiddenapi/OWNERS b/apex/hiddenapi/OWNERS
new file mode 100644
index 0000000..ac8a2b6
--- /dev/null
+++ b/apex/hiddenapi/OWNERS
@@ -0,0 +1,5 @@
+# soong-team@ as the hiddenapi files are tightly coupled with Soong
+file:platform/build/soong:/OWNERS
+
+# compat-team@ for changes to hiddenapi files
+file:tools/platform-compat:/OWNERS
diff --git a/apex/hiddenapi/hiddenapi-max-target-o-low-priority.txt b/apex/hiddenapi/hiddenapi-max-target-o-low-priority.txt
new file mode 100644
index 0000000..bd3d12b
--- /dev/null
+++ b/apex/hiddenapi/hiddenapi-max-target-o-low-priority.txt
@@ -0,0 +1,277 @@
+Lcom/android/org/conscrypt/AbstractConscryptSocket;-><init>()V
+Lcom/android/org/conscrypt/AbstractConscryptSocket;-><init>(Ljava/lang/String;I)V
+Lcom/android/org/conscrypt/AbstractConscryptSocket;-><init>(Ljava/lang/String;ILjava/net/InetAddress;I)V
+Lcom/android/org/conscrypt/AbstractConscryptSocket;-><init>(Ljava/net/InetAddress;I)V
+Lcom/android/org/conscrypt/AbstractConscryptSocket;-><init>(Ljava/net/InetAddress;ILjava/net/InetAddress;I)V
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->getFileDescriptor$()Ljava/io/FileDescriptor;
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->getTlsUnique()[B
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->peerInfoProvider()Lcom/android/org/conscrypt/PeerInfoProvider;
+Lcom/android/org/conscrypt/AbstractConscryptSocket;->setApplicationProtocolSelector(Lcom/android/org/conscrypt/ApplicationProtocolSelector;)V
+Lcom/android/org/conscrypt/ApplicationProtocolSelector;-><init>()V
+Lcom/android/org/conscrypt/ApplicationProtocolSelector;->selectApplicationProtocol(Ljavax/net/ssl/SSLEngine;Ljava/util/List;)Ljava/lang/String;
+Lcom/android/org/conscrypt/ApplicationProtocolSelector;->selectApplicationProtocol(Ljavax/net/ssl/SSLSocket;Ljava/util/List;)Ljava/lang/String;
+Lcom/android/org/conscrypt/ApplicationProtocolSelectorAdapter;-><init>(Ljavax/net/ssl/SSLEngine;Lcom/android/org/conscrypt/ApplicationProtocolSelector;)V
+Lcom/android/org/conscrypt/ApplicationProtocolSelectorAdapter;-><init>(Ljavax/net/ssl/SSLSocket;Lcom/android/org/conscrypt/ApplicationProtocolSelector;)V
+Lcom/android/org/conscrypt/ApplicationProtocolSelectorAdapter;->engine:Ljavax/net/ssl/SSLEngine;
+Lcom/android/org/conscrypt/ApplicationProtocolSelectorAdapter;->NO_PROTOCOL_SELECTED:I
+Lcom/android/org/conscrypt/ApplicationProtocolSelectorAdapter;->selectApplicationProtocol([B)I
+Lcom/android/org/conscrypt/ApplicationProtocolSelectorAdapter;->selector:Lcom/android/org/conscrypt/ApplicationProtocolSelector;
+Lcom/android/org/conscrypt/ApplicationProtocolSelectorAdapter;->socket:Ljavax/net/ssl/SSLSocket;
+Lcom/android/org/conscrypt/CertBlacklist;-><init>(Ljava/util/Set;Ljava/util/Set;)V
+Lcom/android/org/conscrypt/CertBlacklist;->closeQuietly(Ljava/io/Closeable;)V
+Lcom/android/org/conscrypt/CertBlacklist;->getDefault()Lcom/android/org/conscrypt/CertBlacklist;
+Lcom/android/org/conscrypt/CertBlacklist;->HEX_TABLE:[B
+Lcom/android/org/conscrypt/CertBlacklist;->isHex(Ljava/lang/String;)Z
+Lcom/android/org/conscrypt/CertBlacklist;->isPubkeyHash(Ljava/lang/String;)Z
+Lcom/android/org/conscrypt/CertBlacklist;->isPublicKeyBlackListed(Ljava/security/PublicKey;)Z
+Lcom/android/org/conscrypt/CertBlacklist;->isSerialNumberBlackListed(Ljava/math/BigInteger;)Z
+Lcom/android/org/conscrypt/CertBlacklist;->logger:Ljava/util/logging/Logger;
+Lcom/android/org/conscrypt/CertBlacklist;->pubkeyBlacklist:Ljava/util/Set;
+Lcom/android/org/conscrypt/CertBlacklist;->readBlacklist(Ljava/lang/String;)Ljava/lang/String;
+Lcom/android/org/conscrypt/CertBlacklist;->readFileAsBytes(Ljava/lang/String;)Ljava/io/ByteArrayOutputStream;
+Lcom/android/org/conscrypt/CertBlacklist;->readFileAsString(Ljava/lang/String;)Ljava/lang/String;
+Lcom/android/org/conscrypt/CertBlacklist;->readPublicKeyBlackList(Ljava/lang/String;)Ljava/util/Set;
+Lcom/android/org/conscrypt/CertBlacklist;->readSerialBlackList(Ljava/lang/String;)Ljava/util/Set;
+Lcom/android/org/conscrypt/CertBlacklist;->serialBlacklist:Ljava/util/Set;
+Lcom/android/org/conscrypt/CertBlacklist;->toHex([B)[B
+Lcom/android/org/conscrypt/CertificatePriorityComparator;-><init>()V
+Lcom/android/org/conscrypt/CertificatePriorityComparator;->ALGORITHM_OID_PRIORITY_MAP:Ljava/util/Map;
+Lcom/android/org/conscrypt/CertificatePriorityComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
+Lcom/android/org/conscrypt/CertificatePriorityComparator;->compare(Ljava/security/cert/X509Certificate;Ljava/security/cert/X509Certificate;)I
+Lcom/android/org/conscrypt/CertificatePriorityComparator;->compareKeyAlgorithm(Ljava/security/PublicKey;Ljava/security/PublicKey;)I
+Lcom/android/org/conscrypt/CertificatePriorityComparator;->compareKeySize(Ljava/security/PublicKey;Ljava/security/PublicKey;)I
+Lcom/android/org/conscrypt/CertificatePriorityComparator;->compareSignatureAlgorithm(Ljava/security/cert/X509Certificate;Ljava/security/cert/X509Certificate;)I
+Lcom/android/org/conscrypt/CertificatePriorityComparator;->compareStrength(Ljava/security/cert/X509Certificate;Ljava/security/cert/X509Certificate;)I
+Lcom/android/org/conscrypt/CertificatePriorityComparator;->getKeySize(Ljava/security/PublicKey;)I
+Lcom/android/org/conscrypt/CertificatePriorityComparator;->PRIORITY_MD5:Ljava/lang/Integer;
+Lcom/android/org/conscrypt/CertificatePriorityComparator;->PRIORITY_SHA1:Ljava/lang/Integer;
+Lcom/android/org/conscrypt/CertificatePriorityComparator;->PRIORITY_SHA224:Ljava/lang/Integer;
+Lcom/android/org/conscrypt/CertificatePriorityComparator;->PRIORITY_SHA256:Ljava/lang/Integer;
+Lcom/android/org/conscrypt/CertificatePriorityComparator;->PRIORITY_SHA384:Ljava/lang/Integer;
+Lcom/android/org/conscrypt/CertificatePriorityComparator;->PRIORITY_SHA512:Ljava/lang/Integer;
+Lcom/android/org/conscrypt/CertificatePriorityComparator;->PRIORITY_UNKNOWN:Ljava/lang/Integer;
+Lcom/android/org/conscrypt/CertPinManager;->checkChainPinning(Ljava/lang/String;Ljava/util/List;)V
+Lcom/android/org/conscrypt/ConscryptSocketBase;-><init>()V
+Lcom/android/org/conscrypt/ConscryptSocketBase;-><init>(Ljava/lang/String;I)V
+Lcom/android/org/conscrypt/ConscryptSocketBase;-><init>(Ljava/lang/String;ILjava/net/InetAddress;I)V
+Lcom/android/org/conscrypt/ConscryptSocketBase;-><init>(Ljava/net/InetAddress;I)V
+Lcom/android/org/conscrypt/ConscryptSocketBase;-><init>(Ljava/net/InetAddress;ILjava/net/InetAddress;I)V
+Lcom/android/org/conscrypt/ConscryptSocketBase;-><init>(Ljava/net/Socket;Ljava/lang/String;IZ)V
+Lcom/android/org/conscrypt/ConscryptSocketBase;->autoClose:Z
+Lcom/android/org/conscrypt/ConscryptSocketBase;->checkOpen()V
+Lcom/android/org/conscrypt/ConscryptSocketBase;->getActiveSession()Ljavax/net/ssl/SSLSession;
+Lcom/android/org/conscrypt/ConscryptSocketBase;->getFileDescriptor$()Ljava/io/FileDescriptor;
+Lcom/android/org/conscrypt/ConscryptSocketBase;->isDelegating()Z
+Lcom/android/org/conscrypt/ConscryptSocketBase;->listeners:Ljava/util/List;
+Lcom/android/org/conscrypt/ConscryptSocketBase;->notifyHandshakeCompletedListeners()V
+Lcom/android/org/conscrypt/ConscryptSocketBase;->peerHostname:Ljava/lang/String;
+Lcom/android/org/conscrypt/ConscryptSocketBase;->peerInfoProvider()Lcom/android/org/conscrypt/PeerInfoProvider;
+Lcom/android/org/conscrypt/ConscryptSocketBase;->peerInfoProvider:Lcom/android/org/conscrypt/PeerInfoProvider;
+Lcom/android/org/conscrypt/ConscryptSocketBase;->peerPort:I
+Lcom/android/org/conscrypt/ConscryptSocketBase;->readTimeoutMilliseconds:I
+Lcom/android/org/conscrypt/ConscryptSocketBase;->setApplicationProtocolSelector(Lcom/android/org/conscrypt/ApplicationProtocolSelectorAdapter;)V
+Lcom/android/org/conscrypt/NativeRef$EC_GROUP;-><init>(J)V
+Lcom/android/org/conscrypt/NativeRef$EC_GROUP;->doFree(J)V
+Lcom/android/org/conscrypt/NativeRef$EC_POINT;-><init>(J)V
+Lcom/android/org/conscrypt/NativeRef$EC_POINT;->doFree(J)V
+Lcom/android/org/conscrypt/NativeRef$EVP_CIPHER_CTX;-><init>(J)V
+Lcom/android/org/conscrypt/NativeRef$EVP_CIPHER_CTX;->doFree(J)V
+Lcom/android/org/conscrypt/NativeRef$EVP_MD_CTX;-><init>(J)V
+Lcom/android/org/conscrypt/NativeRef$EVP_MD_CTX;->doFree(J)V
+Lcom/android/org/conscrypt/NativeRef$EVP_PKEY;-><init>(J)V
+Lcom/android/org/conscrypt/NativeRef$EVP_PKEY;->doFree(J)V
+Lcom/android/org/conscrypt/NativeRef$EVP_PKEY_CTX;-><init>(J)V
+Lcom/android/org/conscrypt/NativeRef$EVP_PKEY_CTX;->doFree(J)V
+Lcom/android/org/conscrypt/NativeRef$HMAC_CTX;-><init>(J)V
+Lcom/android/org/conscrypt/NativeRef$HMAC_CTX;->doFree(J)V
+Lcom/android/org/conscrypt/NativeRef$SSL_SESSION;-><init>(J)V
+Lcom/android/org/conscrypt/NativeRef$SSL_SESSION;->doFree(J)V
+Lcom/android/org/conscrypt/NativeRef;-><init>(J)V
+Lcom/android/org/conscrypt/NativeRef;->context:J
+Lcom/android/org/conscrypt/NativeRef;->doFree(J)V
+Lcom/android/org/conscrypt/OpenSSLKey;-><init>(JZ)V
+Lcom/android/org/conscrypt/OpenSSLKey;->ctx:Lcom/android/org/conscrypt/NativeRef$EVP_PKEY;
+Lcom/android/org/conscrypt/OpenSSLKey;->fromECPrivateKeyForTLSStackOnly(Ljava/security/PrivateKey;Ljava/security/spec/ECParameterSpec;)Lcom/android/org/conscrypt/OpenSSLKey;
+Lcom/android/org/conscrypt/OpenSSLKey;->fromKeyMaterial(Ljava/security/PrivateKey;)Lcom/android/org/conscrypt/OpenSSLKey;
+Lcom/android/org/conscrypt/OpenSSLKey;->fromPrivateKeyForTLSStackOnly(Ljava/security/PrivateKey;Ljava/security/PublicKey;)Lcom/android/org/conscrypt/OpenSSLKey;
+Lcom/android/org/conscrypt/OpenSSLKey;->fromPrivateKeyPemInputStream(Ljava/io/InputStream;)Lcom/android/org/conscrypt/OpenSSLKey;
+Lcom/android/org/conscrypt/OpenSSLKey;->fromPublicKey(Ljava/security/PublicKey;)Lcom/android/org/conscrypt/OpenSSLKey;
+Lcom/android/org/conscrypt/OpenSSLKey;->fromPublicKeyPemInputStream(Ljava/io/InputStream;)Lcom/android/org/conscrypt/OpenSSLKey;
+Lcom/android/org/conscrypt/OpenSSLKey;->getOpenSSLKey(Ljava/security/PrivateKey;)Lcom/android/org/conscrypt/OpenSSLKey;
+Lcom/android/org/conscrypt/OpenSSLKey;->getPrivateKey()Ljava/security/PrivateKey;
+Lcom/android/org/conscrypt/OpenSSLKey;->getPrivateKey(Ljava/security/spec/PKCS8EncodedKeySpec;I)Ljava/security/PrivateKey;
+Lcom/android/org/conscrypt/OpenSSLKey;->getPublicKey(Ljava/security/spec/X509EncodedKeySpec;I)Ljava/security/PublicKey;
+Lcom/android/org/conscrypt/OpenSSLKey;->isWrapped()Z
+Lcom/android/org/conscrypt/OpenSSLKey;->wrapJCAPrivateKeyForTLSStackOnly(Ljava/security/PrivateKey;Ljava/security/PublicKey;)Lcom/android/org/conscrypt/OpenSSLKey;
+Lcom/android/org/conscrypt/OpenSSLKey;->wrapped:Z
+Lcom/android/org/conscrypt/OpenSSLKey;->wrapPrivateKey(Ljava/security/PrivateKey;)Lcom/android/org/conscrypt/OpenSSLKey;
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;-><init>()V
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;-><init>(Ljava/lang/String;I)V
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;-><init>(Ljava/lang/String;ILjava/net/InetAddress;I)V
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;-><init>(Ljava/net/InetAddress;I)V
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;-><init>(Ljava/net/InetAddress;ILjava/net/InetAddress;I)V
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;-><init>(Ljava/net/Socket;Ljava/lang/String;IZ)V
+Lcom/android/org/conscrypt/OpenSSLSocketImpl;->getFileDescriptor$()Ljava/io/FileDescriptor;
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;-><init>(J)V
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;-><init>(JLjava/util/Date;Ljava/util/Date;)V
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;->alternativeNameArrayToList([[Ljava/lang/Object;)Ljava/util/Collection;
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;->fromCertificate(Ljava/security/cert/Certificate;)Lcom/android/org/conscrypt/OpenSSLX509Certificate;
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;->fromPkcs7DerInputStream(Ljava/io/InputStream;)Ljava/util/List;
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;->fromPkcs7PemInputStream(Ljava/io/InputStream;)Ljava/util/List;
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;->fromX509Der([B)Lcom/android/org/conscrypt/OpenSSLX509Certificate;
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;->fromX509DerInputStream(Ljava/io/InputStream;)Lcom/android/org/conscrypt/OpenSSLX509Certificate;
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;->getContext()J
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;->mHashCode:Ljava/lang/Integer;
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;->notAfter:Ljava/util/Date;
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;->notBefore:Ljava/util/Date;
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;->toDate(J)Ljava/util/Date;
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;->verifyInternal(Ljava/security/PublicKey;Ljava/lang/String;)V
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;->verifyOpenSSL(Lcom/android/org/conscrypt/OpenSSLKey;)V
+Lcom/android/org/conscrypt/OpenSSLX509Certificate;->withDeletedExtension(Ljava/lang/String;)Lcom/android/org/conscrypt/OpenSSLX509Certificate;
+Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory$Parser;-><init>()V
+Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory$Parser;->fromPkcs7DerInputStream(Ljava/io/InputStream;)Ljava/util/List;
+Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory$Parser;->fromPkcs7PemInputStream(Ljava/io/InputStream;)Ljava/util/List;
+Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory$Parser;->fromX509DerInputStream(Ljava/io/InputStream;)Ljava/lang/Object;
+Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory$Parser;->fromX509PemInputStream(Ljava/io/InputStream;)Ljava/lang/Object;
+Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory$Parser;->generateItem(Ljava/io/InputStream;)Ljava/lang/Object;
+Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory$Parser;->generateItems(Ljava/io/InputStream;)Ljava/util/Collection;
+Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory$ParsingException;-><init>(Ljava/lang/Exception;)V
+Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory$ParsingException;-><init>(Ljava/lang/String;)V
+Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory$ParsingException;-><init>(Ljava/lang/String;Ljava/lang/Exception;)V
+Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory;-><init>()V
+Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory;->certificateParser:Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory$Parser;
+Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory;->crlParser:Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory$Parser;
+Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory;->PKCS7_MARKER:[B
+Lcom/android/org/conscrypt/OpenSSLX509CertificateFactory;->PUSHBACK_SIZE:I
+Lcom/android/org/conscrypt/OpenSSLX509CRL;-><init>(J)V
+Lcom/android/org/conscrypt/OpenSSLX509CRL;->fromPkcs7DerInputStream(Ljava/io/InputStream;)Ljava/util/List;
+Lcom/android/org/conscrypt/OpenSSLX509CRL;->fromPkcs7PemInputStream(Ljava/io/InputStream;)Ljava/util/List;
+Lcom/android/org/conscrypt/OpenSSLX509CRL;->fromX509DerInputStream(Ljava/io/InputStream;)Lcom/android/org/conscrypt/OpenSSLX509CRL;
+Lcom/android/org/conscrypt/OpenSSLX509CRL;->fromX509PemInputStream(Ljava/io/InputStream;)Lcom/android/org/conscrypt/OpenSSLX509CRL;
+Lcom/android/org/conscrypt/OpenSSLX509CRL;->mContext:J
+Lcom/android/org/conscrypt/OpenSSLX509CRL;->nextUpdate:Ljava/util/Date;
+Lcom/android/org/conscrypt/OpenSSLX509CRL;->thisUpdate:Ljava/util/Date;
+Lcom/android/org/conscrypt/OpenSSLX509CRL;->toDate(J)Ljava/util/Date;
+Lcom/android/org/conscrypt/OpenSSLX509CRL;->verifyInternal(Ljava/security/PublicKey;Ljava/lang/String;)V
+Lcom/android/org/conscrypt/OpenSSLX509CRL;->verifyOpenSSL(Lcom/android/org/conscrypt/OpenSSLKey;)V
+Lcom/android/org/conscrypt/PeerInfoProvider;-><init>()V
+Lcom/android/org/conscrypt/PeerInfoProvider;->forHostAndPort(Ljava/lang/String;I)Lcom/android/org/conscrypt/PeerInfoProvider;
+Lcom/android/org/conscrypt/PeerInfoProvider;->getHostname()Ljava/lang/String;
+Lcom/android/org/conscrypt/PeerInfoProvider;->getHostnameOrIP()Ljava/lang/String;
+Lcom/android/org/conscrypt/PeerInfoProvider;->getPort()I
+Lcom/android/org/conscrypt/PeerInfoProvider;->nullProvider()Lcom/android/org/conscrypt/PeerInfoProvider;
+Lcom/android/org/conscrypt/PeerInfoProvider;->NULL_PEER_INFO_PROVIDER:Lcom/android/org/conscrypt/PeerInfoProvider;
+Lcom/android/org/conscrypt/SSLClientSessionCache;->getSessionData(Ljava/lang/String;I)[B
+Lcom/android/org/conscrypt/SSLClientSessionCache;->putSessionData(Ljavax/net/ssl/SSLSession;[B)V
+Lcom/android/org/conscrypt/TrustedCertificateIndex;-><init>()V
+Lcom/android/org/conscrypt/TrustedCertificateIndex;-><init>(Ljava/util/Set;)V
+Lcom/android/org/conscrypt/TrustedCertificateIndex;->findAllByIssuerAndSignature(Ljava/security/cert/X509Certificate;)Ljava/util/Set;
+Lcom/android/org/conscrypt/TrustedCertificateIndex;->findByIssuerAndSignature(Ljava/security/cert/X509Certificate;)Ljava/security/cert/TrustAnchor;
+Lcom/android/org/conscrypt/TrustedCertificateIndex;->findBySubjectAndPublicKey(Ljava/security/cert/X509Certificate;)Ljava/security/cert/TrustAnchor;
+Lcom/android/org/conscrypt/TrustedCertificateIndex;->findBySubjectAndPublicKey(Ljava/security/cert/X509Certificate;Ljava/util/Collection;)Ljava/security/cert/TrustAnchor;
+Lcom/android/org/conscrypt/TrustedCertificateIndex;->index(Ljava/security/cert/TrustAnchor;)V
+Lcom/android/org/conscrypt/TrustedCertificateIndex;->index(Ljava/security/cert/X509Certificate;)Ljava/security/cert/TrustAnchor;
+Lcom/android/org/conscrypt/TrustedCertificateIndex;->index(Ljava/util/Set;)V
+Lcom/android/org/conscrypt/TrustedCertificateIndex;->reset()V
+Lcom/android/org/conscrypt/TrustedCertificateIndex;->reset(Ljava/util/Set;)V
+Lcom/android/org/conscrypt/TrustedCertificateIndex;->subjectToTrustAnchors:Ljava/util/Map;
+Lcom/android/org/conscrypt/TrustedCertificateStore$CertSelector;->match(Ljava/security/cert/X509Certificate;)Z
+Lcom/android/org/conscrypt/TrustedCertificateStore$PreloadHolder;-><init>()V
+Lcom/android/org/conscrypt/TrustedCertificateStore$PreloadHolder;->defaultCaCertsAddedDir:Ljava/io/File;
+Lcom/android/org/conscrypt/TrustedCertificateStore$PreloadHolder;->defaultCaCertsDeletedDir:Ljava/io/File;
+Lcom/android/org/conscrypt/TrustedCertificateStore$PreloadHolder;->defaultCaCertsSystemDir:Ljava/io/File;
+Lcom/android/org/conscrypt/TrustedCertificateStore;-><init>(Ljava/io/File;Ljava/io/File;Ljava/io/File;)V
+Lcom/android/org/conscrypt/TrustedCertificateStore;->addAliases(Ljava/util/Set;Ljava/lang/String;Ljava/io/File;)V
+Lcom/android/org/conscrypt/TrustedCertificateStore;->addedDir:Ljava/io/File;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->aliases()Ljava/util/Set;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->allSystemAliases()Ljava/util/Set;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->CERT_FACTORY:Ljava/security/cert/CertificateFactory;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->containsAlias(Ljava/lang/String;)Z
+Lcom/android/org/conscrypt/TrustedCertificateStore;->containsAlias(Ljava/lang/String;Z)Z
+Lcom/android/org/conscrypt/TrustedCertificateStore;->convertToOpenSSLIfNeeded(Ljava/security/cert/X509Certificate;)Lcom/android/org/conscrypt/OpenSSLX509Certificate;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->deleteCertificateEntry(Ljava/lang/String;)V
+Lcom/android/org/conscrypt/TrustedCertificateStore;->deletedDir:Ljava/io/File;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->file(Ljava/io/File;Ljava/lang/String;I)Ljava/io/File;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->fileForAlias(Ljava/lang/String;)Ljava/io/File;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->findAllIssuers(Ljava/security/cert/X509Certificate;)Ljava/util/Set;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->findCert(Ljava/io/File;Ljavax/security/auth/x500/X500Principal;Lcom/android/org/conscrypt/TrustedCertificateStore$CertSelector;Ljava/lang/Class;)Ljava/lang/Object;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->findIssuer(Ljava/security/cert/X509Certificate;)Ljava/security/cert/X509Certificate;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->getCertificate(Ljava/lang/String;)Ljava/security/cert/Certificate;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->getCertificate(Ljava/lang/String;Z)Ljava/security/cert/Certificate;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->getCertificateAlias(Ljava/security/cert/Certificate;)Ljava/lang/String;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->getCertificateAlias(Ljava/security/cert/Certificate;Z)Ljava/lang/String;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->getCertificateFile(Ljava/io/File;Ljava/security/cert/X509Certificate;)Ljava/io/File;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->getCreationDate(Ljava/lang/String;)Ljava/util/Date;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->getTrustAnchor(Ljava/security/cert/X509Certificate;)Ljava/security/cert/X509Certificate;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->hash(Ljavax/security/auth/x500/X500Principal;)Ljava/lang/String;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->installCertificate(Ljava/security/cert/X509Certificate;)V
+Lcom/android/org/conscrypt/TrustedCertificateStore;->isDeletedSystemCertificate(Ljava/security/cert/X509Certificate;)Z
+Lcom/android/org/conscrypt/TrustedCertificateStore;->isSelfIssuedCertificate(Lcom/android/org/conscrypt/OpenSSLX509Certificate;)Z
+Lcom/android/org/conscrypt/TrustedCertificateStore;->isSystem(Ljava/lang/String;)Z
+Lcom/android/org/conscrypt/TrustedCertificateStore;->isTombstone(Ljava/io/File;)Z
+Lcom/android/org/conscrypt/TrustedCertificateStore;->isUser(Ljava/lang/String;)Z
+Lcom/android/org/conscrypt/TrustedCertificateStore;->isUserAddedCertificate(Ljava/security/cert/X509Certificate;)Z
+Lcom/android/org/conscrypt/TrustedCertificateStore;->PREFIX_SYSTEM:Ljava/lang/String;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->PREFIX_USER:Ljava/lang/String;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->readCertificate(Ljava/io/File;)Ljava/security/cert/X509Certificate;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->removeUnnecessaryTombstones(Ljava/lang/String;)V
+Lcom/android/org/conscrypt/TrustedCertificateStore;->setDefaultUserDirectory(Ljava/io/File;)V
+Lcom/android/org/conscrypt/TrustedCertificateStore;->systemDir:Ljava/io/File;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->userAliases()Ljava/util/Set;
+Lcom/android/org/conscrypt/TrustedCertificateStore;->writeCertificate(Ljava/io/File;Ljava/security/cert/X509Certificate;)V
+Lcom/android/org/conscrypt/TrustManagerImpl$ExtendedKeyUsagePKIXCertPathChecker;-><init>(ZLjava/security/cert/X509Certificate;)V
+Lcom/android/org/conscrypt/TrustManagerImpl$ExtendedKeyUsagePKIXCertPathChecker;->clientAuth:Z
+Lcom/android/org/conscrypt/TrustManagerImpl$ExtendedKeyUsagePKIXCertPathChecker;->EKU_anyExtendedKeyUsage:Ljava/lang/String;
+Lcom/android/org/conscrypt/TrustManagerImpl$ExtendedKeyUsagePKIXCertPathChecker;->EKU_clientAuth:Ljava/lang/String;
+Lcom/android/org/conscrypt/TrustManagerImpl$ExtendedKeyUsagePKIXCertPathChecker;->EKU_msSGC:Ljava/lang/String;
+Lcom/android/org/conscrypt/TrustManagerImpl$ExtendedKeyUsagePKIXCertPathChecker;->EKU_nsSGC:Ljava/lang/String;
+Lcom/android/org/conscrypt/TrustManagerImpl$ExtendedKeyUsagePKIXCertPathChecker;->EKU_OID:Ljava/lang/String;
+Lcom/android/org/conscrypt/TrustManagerImpl$ExtendedKeyUsagePKIXCertPathChecker;->EKU_serverAuth:Ljava/lang/String;
+Lcom/android/org/conscrypt/TrustManagerImpl$ExtendedKeyUsagePKIXCertPathChecker;->leaf:Ljava/security/cert/X509Certificate;
+Lcom/android/org/conscrypt/TrustManagerImpl$ExtendedKeyUsagePKIXCertPathChecker;->SUPPORTED_EXTENSIONS:Ljava/util/Set;
+Lcom/android/org/conscrypt/TrustManagerImpl$TrustAnchorComparator;-><init>()V
+Lcom/android/org/conscrypt/TrustManagerImpl$TrustAnchorComparator;->CERT_COMPARATOR:Lcom/android/org/conscrypt/CertificatePriorityComparator;
+Lcom/android/org/conscrypt/TrustManagerImpl$TrustAnchorComparator;->compare(Ljava/lang/Object;Ljava/lang/Object;)I
+Lcom/android/org/conscrypt/TrustManagerImpl$TrustAnchorComparator;->compare(Ljava/security/cert/TrustAnchor;Ljava/security/cert/TrustAnchor;)I
+Lcom/android/org/conscrypt/TrustManagerImpl;-><init>(Ljava/security/KeyStore;Lcom/android/org/conscrypt/CertPinManager;)V
+Lcom/android/org/conscrypt/TrustManagerImpl;-><init>(Ljava/security/KeyStore;Lcom/android/org/conscrypt/CertPinManager;Lcom/android/org/conscrypt/TrustedCertificateStore;)V
+Lcom/android/org/conscrypt/TrustManagerImpl;-><init>(Ljava/security/KeyStore;Lcom/android/org/conscrypt/CertPinManager;Lcom/android/org/conscrypt/TrustedCertificateStore;Lcom/android/org/conscrypt/CertBlacklist;)V
+Lcom/android/org/conscrypt/TrustManagerImpl;-><init>(Ljava/security/KeyStore;Lcom/android/org/conscrypt/CertPinManager;Lcom/android/org/conscrypt/TrustedCertificateStore;Lcom/android/org/conscrypt/CertBlacklist;Lcom/android/org/conscrypt/ct/CTLogStore;Lcom/android/org/conscrypt/ct/CTVerifier;Lcom/android/org/conscrypt/ct/CTPolicy;)V
+Lcom/android/org/conscrypt/TrustManagerImpl;->acceptedIssuers(Ljava/security/KeyStore;)[Ljava/security/cert/X509Certificate;
+Lcom/android/org/conscrypt/TrustManagerImpl;->acceptedIssuers:[Ljava/security/cert/X509Certificate;
+Lcom/android/org/conscrypt/TrustManagerImpl;->blacklist:Lcom/android/org/conscrypt/CertBlacklist;
+Lcom/android/org/conscrypt/TrustManagerImpl;->checkBlacklist(Ljava/security/cert/X509Certificate;)V
+Lcom/android/org/conscrypt/TrustManagerImpl;->checkClientTrusted([Ljava/security/cert/X509Certificate;Ljava/lang/String;Ljava/lang/String;)Ljava/util/List;
+Lcom/android/org/conscrypt/TrustManagerImpl;->checkCT(Ljava/lang/String;Ljava/util/List;[B[B)V
+Lcom/android/org/conscrypt/TrustManagerImpl;->checkServerTrusted([Ljava/security/cert/X509Certificate;Ljava/lang/String;Ljavax/net/ssl/SSLSession;)Ljava/util/List;
+Lcom/android/org/conscrypt/TrustManagerImpl;->checkTrusted([Ljava/security/cert/X509Certificate;Ljava/lang/String;Ljavax/net/ssl/SSLSession;Ljavax/net/ssl/SSLParameters;Z)Ljava/util/List;
+Lcom/android/org/conscrypt/TrustManagerImpl;->checkTrusted([Ljava/security/cert/X509Certificate;[B[BLjava/lang/String;Ljava/lang/String;Z)Ljava/util/List;
+Lcom/android/org/conscrypt/TrustManagerImpl;->checkTrustedRecursive([Ljava/security/cert/X509Certificate;[B[BLjava/lang/String;ZLjava/util/ArrayList;Ljava/util/ArrayList;Ljava/util/Set;)Ljava/util/List;
+Lcom/android/org/conscrypt/TrustManagerImpl;->ctEnabledOverride:Z
+Lcom/android/org/conscrypt/TrustManagerImpl;->ctPolicy:Lcom/android/org/conscrypt/ct/CTPolicy;
+Lcom/android/org/conscrypt/TrustManagerImpl;->ctVerifier:Lcom/android/org/conscrypt/ct/CTVerifier;
+Lcom/android/org/conscrypt/TrustManagerImpl;->err:Ljava/lang/Exception;
+Lcom/android/org/conscrypt/TrustManagerImpl;->factory:Ljava/security/cert/CertificateFactory;
+Lcom/android/org/conscrypt/TrustManagerImpl;->findAllTrustAnchorsByIssuerAndSignature(Ljava/security/cert/X509Certificate;)Ljava/util/Set;
+Lcom/android/org/conscrypt/TrustManagerImpl;->findTrustAnchorBySubjectAndPublicKey(Ljava/security/cert/X509Certificate;)Ljava/security/cert/TrustAnchor;
+Lcom/android/org/conscrypt/TrustManagerImpl;->getHandshakeSessionOrThrow(Ljavax/net/ssl/SSLSocket;)Ljavax/net/ssl/SSLSession;
+Lcom/android/org/conscrypt/TrustManagerImpl;->getOcspDataFromSession(Ljavax/net/ssl/SSLSession;)[B
+Lcom/android/org/conscrypt/TrustManagerImpl;->getTlsSctDataFromSession(Ljavax/net/ssl/SSLSession;)[B
+Lcom/android/org/conscrypt/TrustManagerImpl;->getTrustedChainForServer([Ljava/security/cert/X509Certificate;Ljava/lang/String;Ljava/net/Socket;)Ljava/util/List;
+Lcom/android/org/conscrypt/TrustManagerImpl;->getTrustedChainForServer([Ljava/security/cert/X509Certificate;Ljava/lang/String;Ljavax/net/ssl/SSLEngine;)Ljava/util/List;
+Lcom/android/org/conscrypt/TrustManagerImpl;->handleTrustStorageUpdate()V
+Lcom/android/org/conscrypt/TrustManagerImpl;->intermediateIndex:Lcom/android/org/conscrypt/TrustedCertificateIndex;
+Lcom/android/org/conscrypt/TrustManagerImpl;->isUserAddedCertificate(Ljava/security/cert/X509Certificate;)Z
+Lcom/android/org/conscrypt/TrustManagerImpl;->pinManager:Lcom/android/org/conscrypt/CertPinManager;
+Lcom/android/org/conscrypt/TrustManagerImpl;->rootKeyStore:Ljava/security/KeyStore;
+Lcom/android/org/conscrypt/TrustManagerImpl;->setCTEnabledOverride(Z)V
+Lcom/android/org/conscrypt/TrustManagerImpl;->setCTPolicy(Lcom/android/org/conscrypt/ct/CTPolicy;)V
+Lcom/android/org/conscrypt/TrustManagerImpl;->setCTVerifier(Lcom/android/org/conscrypt/ct/CTVerifier;)V
+Lcom/android/org/conscrypt/TrustManagerImpl;->setOcspResponses(Ljava/security/cert/PKIXParameters;Ljava/security/cert/X509Certificate;[B)V
+Lcom/android/org/conscrypt/TrustManagerImpl;->sortPotentialAnchors(Ljava/util/Set;)Ljava/util/Collection;
+Lcom/android/org/conscrypt/TrustManagerImpl;->trustAnchors([Ljava/security/cert/X509Certificate;)Ljava/util/Set;
+Lcom/android/org/conscrypt/TrustManagerImpl;->trustedCertificateIndex:Lcom/android/org/conscrypt/TrustedCertificateIndex;
+Lcom/android/org/conscrypt/TrustManagerImpl;->trustedCertificateStore:Lcom/android/org/conscrypt/TrustedCertificateStore;
+Lcom/android/org/conscrypt/TrustManagerImpl;->TRUST_ANCHOR_COMPARATOR:Lcom/android/org/conscrypt/TrustManagerImpl$TrustAnchorComparator;
+Lcom/android/org/conscrypt/TrustManagerImpl;->validator:Ljava/security/cert/CertPathValidator;
+Lcom/android/org/conscrypt/TrustManagerImpl;->verifyChain(Ljava/util/List;Ljava/util/List;Ljava/lang/String;Z[B[B)Ljava/util/List;
diff --git a/apex/testing/Android.bp b/apex/testing/Android.bp
index 6f99a0f..8a1c102 100644
--- a/apex/testing/Android.bp
+++ b/apex/testing/Android.bp
@@ -12,10 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_conscrypt_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["external_conscrypt_license"],
+}
+
 apex_test {
     name: "test_com.android.conscrypt",
     visibility: [
         "//system/apex/tests",
+        "//vendor:__subpackages__",
     ],
     defaults: ["com.android.conscrypt-defaults"],
     manifest: "test_apex_manifest.json",
diff --git a/apex/tests/Android.bp b/apex/tests/Android.bp
index 408d95f..e8c85cb 100644
--- a/apex/tests/Android.bp
+++ b/apex/tests/Android.bp
@@ -12,10 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_conscrypt_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["external_conscrypt_license"],
+}
+
 android_test {
     name: "MtsConscryptTestCases",
     platform_apis: true,
-    defaults: ["cts_defaults"],
     static_libs: [
         "conscrypt-support",
         "conscrypt-tests",
@@ -33,6 +41,32 @@
     // Tag this module as an mts test artifact
     test_suites: [
         "general-tests",
+        "mts-conscrypt",
+    ],
+
+}
+
+android_test {
+    name: "MtsConscryptFdSocketTestCases",
+    platform_apis: true,
+    static_libs: [
+        "conscrypt-support",
+        "conscrypt-tests",
+        "core-test-rules",
+        "ctstestrunner-axt",
+        "libcore-crypto-tests",
+        "junit",
+    ],
+
+    test_config: "FdSocket.xml",
+    min_sdk_version: "29",
+    libs: [
+        "android.test.base.stubs",
+    ],
+
+    // Tag this module as an mts test artifact
+    test_suites: [
+        "general-tests",
         "mts",
     ],
 
diff --git a/apex/tests/AndroidTest.xml b/apex/tests/AndroidTest.xml
index 9e38f83..7d03650 100644
--- a/apex/tests/AndroidTest.xml
+++ b/apex/tests/AndroidTest.xml
@@ -19,6 +19,7 @@
   <option name="config-descriptor:metadata" key="component" value="libcore" />
   <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
   <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+  <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.conscrypt.apex" />
   <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
     <option name="cleanup-apks" value="true" />
     <option name="test-file-name" value="MtsConscryptTestCases.apk" />
@@ -33,26 +34,6 @@
     <option name="device-listeners" value="org.conscrypt.ConscryptInstrumentationListener" />
     <option name="instrumentation-arg" key="conscrypt_sslsocket_implementation" value="engine" />
   </test>
-    <!-- Re-run a subset of tests using Conscrypt's file-descriptor based implementation to ensure
-         there are no regressions in this implementation before it is fully deprecated.
-
-	 Apart from the include filters and SSLSocket implementation this test suite is
-	 idential to the one above.
-    -->
-  <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-    <option name="package" value="com.android.conscrypt.mts" />
-    <option name="include-filter" value="libcore.javax.net.ssl" />
-    <option name="include-filter" value="com.android.org.conscrypt.javax.net.ssl" />
-    <option name="include-filter" value="org.apache.harmony.tests.javax.net.ssl" />
-    <!-- module-libs APIs are not accessible to app code by default intentionally. -->
-    <option name="hidden-api-checks" value="false" />
-    <option name="runtime-hint" value="1m" />
-    <!-- test-timeout unit is ms, value = 20 min -->
-    <option name="test-timeout" value="1200000" />
-    <option name="device-listeners" value="org.conscrypt.ConscryptInstrumentationListener" />
-    <option name="instrumentation-arg" key="conscrypt_sslsocket_implementation" value="fd" />
-  </test>
-
   <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
     <option name="mainline-module-package-name" value="com.google.android.conscrypt" />
   </object>
diff --git a/apex/tests/FdSocket.xml b/apex/tests/FdSocket.xml
new file mode 100644
index 0000000..58b080d
--- /dev/null
+++ b/apex/tests/FdSocket.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.
+  ~
+  ~ Re-runs a subset of MtsConscryptTestCases using Conscrypt's file-descriptor based
+  ~ implementation to ensure there are no regressions in this implementation before
+  ~ it is fully deprecated.
+  ~
+  ~ Apart from the include filters and SSLSocket implementation this test suite is
+  ~ identical to MtsConscryptTestCases.
+  -->
+<configuration description="Config for MTS tests for the Conscrypt mainline module">
+  <option name="test-suite-tag" value="mts" />
+  <option name="config-descriptor:metadata" key="component" value="libcore" />
+  <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+  <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+  <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.conscrypt.apex" />
+  <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+    <option name="cleanup-apks" value="true" />
+    <option name="test-file-name" value="MtsConscryptFdSocketTestCases.apk" />
+  </target_preparer>
+  <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+    <option name="package" value="com.android.conscrypt.mts" />
+    <option name="include-filter" value="libcore.javax.net.ssl" />
+    <option name="include-filter" value="com.android.org.conscrypt.javax.net.ssl" />
+    <option name="include-filter" value="org.apache.harmony.tests.javax.net.ssl" />
+    <!-- module-libs APIs are not accessible to app code by default intentionally. -->
+    <option name="hidden-api-checks" value="false" />
+    <option name="runtime-hint" value="1m" />
+    <!-- test-timeout unit is ms, value = 20 min -->
+    <option name="test-timeout" value="1200000" />
+    <option name="device-listeners" value="org.conscrypt.ConscryptInstrumentationListener" />
+    <option name="instrumentation-arg" key="conscrypt_sslsocket_implementation" value="fd" />
+  </test>
+
+  <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+    <option name="mainline-module-package-name" value="com.google.android.conscrypt" />
+  </object>
+</configuration>
diff --git a/apex/tests/TEST_MAPPING b/apex/tests/TEST_MAPPING
new file mode 100644
index 0000000..8b9c9fa
--- /dev/null
+++ b/apex/tests/TEST_MAPPING
@@ -0,0 +1,41 @@
+{
+  "mainline-presubmit": [
+    {
+      "name": "MtsConscryptTestCases[com.google.android.conscrypt.apex]",
+      "options": [
+        {
+          "exclude-filter": "org.apache.harmony.crypto.tests.javax.crypto.func.KeyAgreementFunctionalTest#test_KeyAgreement"
+        },
+        {
+          "exclude-filter": "com.android.org.conscrypt.java.security.AlgorithmParameterGeneratorTestDH#testAlgorithmParameterGenerator"
+        },
+        {
+          "exclude-filter": "org.apache.harmony.crypto.tests.javax.crypto.KeyAgreementTest#test_generateSecretLjava_lang_String"
+        },
+        {
+          "exclude-filter": "org.apache.harmony.tests.javax.net.ssl.TrustManagerFactory1Test#test_initLjavax_net_ssl_ManagerFactoryParameters"
+        },
+	{
+          "exclude-filter": "com.android.org.conscrypt.java.security.SignatureTest#test_getInstance"
+	}
+      ]
+    },
+    {
+      "name": "MtsConscryptFdSocketTestCases[com.google.android.conscrypt.apex]",
+      "options": [
+        {
+          "exclude-filter": "org.apache.harmony.crypto.tests.javax.crypto.func.KeyAgreementFunctionalTest#test_KeyAgreement"
+        },
+        {
+          "exclude-filter": "com.android.org.conscrypt.java.security.AlgorithmParameterGeneratorTestDH#testAlgorithmParameterGenerator"
+        },
+        {
+          "exclude-filter": "org.apache.harmony.crypto.tests.javax.crypto.KeyAgreementTest#test_generateSecretLjava_lang_String"
+        },
+        {
+          "exclude-filter": "org.apache.harmony.tests.javax.net.ssl.TrustManagerFactory1Test#test_initLjavax_net_ssl_ManagerFactoryParameters"
+        }
+      ]
+    }
+  ]
+}
diff --git a/api-doclet/build.gradle b/api-doclet/build.gradle
index 65ad223..229e9d1 100644
--- a/api-doclet/build.gradle
+++ b/api-doclet/build.gradle
@@ -1,6 +1,8 @@
 description = 'Conscrypt: API Doclet'
 
-dependencies {
-    // This should be removed when upgrading to Gradle 5.x
-    compile files(org.gradle.internal.jvm.Jvm.current().toolsJar)
+def toolsJar = org.gradle.internal.jvm.Jvm.current().toolsJar
+if (toolsJar != null) {
+    dependencies {
+        compile files(toolsJar)
+    }
 }
diff --git a/api/public/last-removed.txt b/api/intra/last-incompatibilities.txt
similarity index 100%
rename from api/public/last-removed.txt
rename to api/intra/last-incompatibilities.txt
diff --git a/api/public/last-removed.txt b/api/platform/last-incompatibilities.txt
similarity index 100%
copy from api/public/last-removed.txt
copy to api/platform/last-incompatibilities.txt
diff --git a/api/public/current.txt b/api/public/current.txt
index fb4a405..809495c 100644
--- a/api/public/current.txt
+++ b/api/public/current.txt
@@ -2,11 +2,13 @@
 package android.net.ssl {
 
   public class SSLEngines {
+    method @Nullable public static byte[] exportKeyingMaterial(@NonNull javax.net.ssl.SSLEngine, @NonNull String, @Nullable byte[], int) throws javax.net.ssl.SSLException;
     method public static boolean isSupportedEngine(@NonNull javax.net.ssl.SSLEngine);
     method public static void setUseSessionTickets(@NonNull javax.net.ssl.SSLEngine, boolean);
   }
 
   public class SSLSockets {
+    method @Nullable public static byte[] exportKeyingMaterial(@NonNull javax.net.ssl.SSLSocket, @NonNull String, @Nullable byte[], int) throws javax.net.ssl.SSLException;
     method public static boolean isSupportedSocket(@NonNull javax.net.ssl.SSLSocket);
     method public static void setUseSessionTickets(@NonNull javax.net.ssl.SSLSocket, boolean);
   }
diff --git a/api/public/last-api.txt b/api/public/last-api.txt
deleted file mode 100644
index fb4a405..0000000
--- a/api/public/last-api.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-// Signature format: 2.0
-package android.net.ssl {
-
-  public class SSLEngines {
-    method public static boolean isSupportedEngine(@NonNull javax.net.ssl.SSLEngine);
-    method public static void setUseSessionTickets(@NonNull javax.net.ssl.SSLEngine, boolean);
-  }
-
-  public class SSLSockets {
-    method public static boolean isSupportedSocket(@NonNull javax.net.ssl.SSLSocket);
-    method public static void setUseSessionTickets(@NonNull javax.net.ssl.SSLSocket, boolean);
-  }
-
-}
-
diff --git a/benchmark-android/build.gradle b/benchmark-android/build.gradle
index 6be26d0..9673586 100644
--- a/benchmark-android/build.gradle
+++ b/benchmark-android/build.gradle
@@ -69,7 +69,7 @@
                    libraries.bouncycastle_apis
 
         depsJarImplementation project(':conscrypt-benchmark-base'),
-                              project(':conscrypt-testing'),
+                              project(path: ":conscrypt-testing", configuration: "runtime"),
                               project(':conscrypt-libcore-stub')
 
         implementation 'com.google.caliper:caliper:1.0-beta-2'
@@ -83,7 +83,7 @@
     // the .aar and .jar contents before the actual archives are built. To do this we create a
     // configure task where the "from" contents is set inside a doLast stanza to ensure it is run
     // after the execution phase of the "assemble" task.
-    task configureDepsJar {
+    def configureDepsJar = tasks.register("configureDepsJar") {
         dependsOn assemble, \
                   configurations.depsJarApi.artifacts, \
                   configurations.depsJarImplementation.artifacts
@@ -124,11 +124,12 @@
         }
     }
 
-    task depsJar(type: Jar, dependsOn: configureDepsJar) {
+    def depsJar = tasks.register("depsJar", Jar) {
+        dependsOn configureDepsJar
         archiveName = 'bundled-deps.jar'
     }
 
-    task getAndroidDeviceAbi {
+    def getAndroidDeviceAbi = tasks.register("getAndroidDeviceAbi") {
         doLast {
             new ByteArrayOutputStream().withStream { os ->
                 def result = exec {
@@ -142,7 +143,7 @@
         }
     }
 
-    task configureExtractNativeLib {
+    def configureExtractNativeLib = tasks.register("configureExtractNativeLib") {
         dependsOn getAndroidDeviceAbi, depsJar
         doLast {
             extractNativeLib.from {
@@ -156,11 +157,12 @@
         }
     }
 
-    task extractNativeLib(type: Copy, dependsOn: configureExtractNativeLib) {
+    def extractNativeLib = tasks.register("extractNativeLib", Copy) {
+        dependsOn configureExtractNativeLib
         into "$buildDir/extracted-native-libs"
     }
 
-    task configurePushNativeLibrary {
+    def configurePushNativeLibrary = tasks.register("configurePushNativeLibrary") {
         dependsOn extractNativeLib
         doLast {
             project.ext.nativeLibPath = "/system/lib${androidDevice64Bit ? '64' : ''}/libconscrypt_jni.so"
@@ -168,11 +170,13 @@
         }
     }
 
-    task pushNativeLibrary(type: Exec, dependsOn: configurePushNativeLibrary) {
+    def pushNativeLibrary = tasks.register("pushNativeLibrary", Exec) {
+        dependsOn configurePushNativeLibrary
         pushNativeLibrary.executable android.adbExecutable
     }
 
-    task runBenchmarks(dependsOn: [depsJar, pushNativeLibrary]) {
+    def runBenchmarks = tasks.register("runBenchmarks") {
+        dependsOn depsJar, pushNativeLibrary
         doLast {
             // Execute the benchmarks
             exec {
@@ -202,7 +206,7 @@
     logger.warn('Android SDK has not been detected. The Android Benchmark module will not be built.')
 
     // Disable all tasks
-    tasks.collect {
+    tasks.configureEach {
         it.enabled = false
     }
 }
diff --git a/benchmark-base/build.gradle b/benchmark-base/build.gradle
index a47943a..a69fee7 100644
--- a/benchmark-base/build.gradle
+++ b/benchmark-base/build.gradle
@@ -5,6 +5,6 @@
 targetCompatibility = androidMinJavaVersion
 
 dependencies {
-    compile project(':conscrypt-testing'),
+    compile project(path: ":conscrypt-testing", configuration: "runtime"),
             libraries.junit
 }
diff --git a/benchmark-jmh/build.gradle b/benchmark-jmh/build.gradle
index 2d4ebce..35611fe 100644
--- a/benchmark-jmh/build.gradle
+++ b/benchmark-jmh/build.gradle
@@ -69,7 +69,7 @@
 }
 
 dependencies {
-    compile project(':conscrypt-openjdk'),
+    compile project(path: ":conscrypt-openjdk", configuration: "runtime"),
             project(':conscrypt-benchmark-base'),
             // Add the preferred native openjdk configuration for this platform.
             project(':conscrypt-openjdk').sourceSets["$preferredSourceSet"].output,
diff --git a/build.gradle b/build.gradle
index 858425d..162c491 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,8 +1,8 @@
 import org.gradle.util.VersionNumber
 
 buildscript {
-    ext.android_tools = 'com.android.tools.build:gradle:3.5.0'
-    ext.errorproneVersion = '2.3.3'
+    ext.android_tools = 'com.android.tools.build:gradle:4.1.0'
+    ext.errorproneVersion = '2.4.0'
     ext.errorproneJavacVersion = '9+181-r4173-1'
     repositories {
         google()
@@ -20,7 +20,7 @@
     // Add dependency for build script so we can access Git from our
     // build script.
     id 'org.ajoberstar.grgit' version '3.1.1'
-    id 'net.ltgt.errorprone' version '1.1.1'
+    id 'net.ltgt.errorprone' version '1.3.0'
 }
 
 subprojects {
@@ -51,7 +51,7 @@
 
     group = "org.conscrypt"
     description = 'Conscrypt is an alternate Java Security Provider that uses BoringSSL'
-    version = "2.5.0-SNAPSHOT"
+    version = "2.6-SNAPSHOT"
 
     ext {
         os = org.gradle.internal.os.OperatingSystem.current();
@@ -153,7 +153,7 @@
         errorproneJavac("com.google.errorprone:javac:$errorproneJavacVersion")
     }
 
-    task generateProperties(type: WriteProperties) {
+    tasks.register("generateProperties", WriteProperties) {
         ext {
             parsedVersion = VersionNumber.parse(version)
         }
@@ -168,26 +168,30 @@
         sourceCompatibility = JavaVersion.VERSION_1_7
         targetCompatibility = JavaVersion.VERSION_1_7
 
-        [compileJava, compileTestJava].each() {
-            it.options.compilerArgs += ["-Xlint:all", "-Xlint:-options", '-Xmaxwarns', '9999999']
-            it.options.encoding = "UTF-8"
-            if (rootProject.hasProperty('failOnWarnings') && rootProject.failOnWarnings.toBoolean()) {
-                it.options.compilerArgs += ["-Werror"]
+        [tasks.named("compileJava"), tasks.named("compileTestJava")].forEach { t ->
+            t.configure {
+                options.compilerArgs += ["-Xlint:all", "-Xlint:-options", '-Xmaxwarns', '9999999']
+                options.encoding = "UTF-8"
+                if (rootProject.hasProperty('failOnWarnings') && rootProject.failOnWarnings.toBoolean()) {
+                    options.compilerArgs += ["-Werror"]
+                }
             }
         }
 
-        compileTestJava {
+        tasks.named("compileTestJava").configure {
             // serialVersionUID is basically guaranteed to be useless in our tests
             options.compilerArgs += ["-Xlint:-serial"]
         }
 
-        jar.manifest {
-            attributes('Implementation-Title': name,
-                    'Implementation-Version': version,
-                    'Built-By': System.getProperty('user.name'),
-                    'Built-JDK': System.getProperty('java.version'),
-                    'Source-Compatibility': sourceCompatibility,
-                    'Target-Compatibility': targetCompatibility)
+        tasks.named("jar").configure {
+            manifest {
+                attributes('Implementation-Title': name,
+                        'Implementation-Version': version,
+                        'Built-By': System.getProperty('user.name'),
+                        'Built-JDK': System.getProperty('java.version'),
+                        'Source-Compatibility': sourceCompatibility,
+                        'Target-Compatibility': targetCompatibility)
+            }
         }
 
         javadoc.options {
@@ -204,12 +208,12 @@
             }
         }
 
-        task javadocJar(type: Jar) {
+        tasks.register("javadocJar", Jar) {
             classifier = 'javadoc'
             from javadoc
         }
 
-        task sourcesJar(type: Jar) {
+        tasks.register("sourcesJar", Jar) {
             classifier = 'sources'
             from sourceSets.main.allSource
         }
diff --git a/common/src/jni/main/cpp/conscrypt/jniutil.cc b/common/src/jni/main/cpp/conscrypt/jniutil.cc
index 384bcb8..c30adf1 100644
--- a/common/src/jni/main/cpp/conscrypt/jniutil.cc
+++ b/common/src/jni/main/cpp/conscrypt/jniutil.cc
@@ -37,6 +37,8 @@
 jclass inputStreamClass;
 jclass outputStreamClass;
 jclass stringClass;
+jclass byteBufferClass;
+jclass bufferClass;
 
 jfieldID nativeRef_address;
 
@@ -46,6 +48,8 @@
 jmethodID openSslInputStream_readLineMethod;
 jmethodID outputStream_writeMethod;
 jmethodID outputStream_flushMethod;
+jmethodID buffer_positionMethod;
+jmethodID buffer_limitMethod;
 
 void init(JavaVM* vm, JNIEnv* env) {
     gJavaVM = vm;
@@ -58,6 +62,8 @@
     objectArrayClass = findClass(env, "[Ljava/lang/Object;");
     outputStreamClass = findClass(env, "java/io/OutputStream");
     stringClass = findClass(env, "java/lang/String");
+    byteBufferClass = findClass(env, "java/nio/ByteBuffer");
+    bufferClass = findClass(env, "java/nio/Buffer");
 
     cryptoUpcallsClass = getGlobalRefToClass(
             env, TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/CryptoUpcalls");
@@ -76,6 +82,8 @@
             getMethodRef(env, openSslInputStreamClass, "gets", "([B)I");
     outputStream_writeMethod = getMethodRef(env, outputStreamClass, "write", "([B)V");
     outputStream_flushMethod = getMethodRef(env, outputStreamClass, "flush", "()V");
+    buffer_positionMethod = getMethodRef(env, bufferClass, "position", "()I");
+    buffer_limitMethod = getMethodRef(env, bufferClass, "limit", "()I");
 }
 
 void jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods,
diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc
index c739546..5b0acf0 100644
--- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc
+++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc
@@ -37,6 +37,9 @@
 #include <openssl/aead.h>
 #include <openssl/asn1.h>
 #include <openssl/chacha.h>
+#include <openssl/curve25519.h>
+#include <openssl/cmac.h>
+#include <openssl/crypto.h>
 #include <openssl/engine.h>
 #include <openssl/err.h>
 #include <openssl/evp.h>
@@ -90,9 +93,9 @@
     return ssl;
 }
 
-static BIO* to_SSL_BIO(JNIEnv* env, jlong bio_address, bool throwIfNull) {
+static BIO* to_BIO(JNIEnv* env, jlong bio_address) {
     BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bio_address));
-    if ((bio == nullptr) && throwIfNull) {
+    if (bio == nullptr) {
         JNI_TRACE("bio == null");
         conscrypt::jniutil::throwNullPointerException(env, "bio == null");
     }
@@ -427,8 +430,8 @@
 /**
  * Converts ASN.1 BIT STRING to a jbooleanArray.
  */
-jbooleanArray ASN1BitStringToBooleanArray(JNIEnv* env, ASN1_BIT_STRING* bitStr) {
-    int size = bitStr->length * 8;
+jbooleanArray ASN1BitStringToBooleanArray(JNIEnv* env, const ASN1_BIT_STRING* bitStr) {
+    int size = ASN1_STRING_length(bitStr) * 8;
     if (bitStr->flags & ASN1_STRING_FLAG_BITS_LEFT) {
         size -= bitStr->flags & 0x07;
     }
@@ -1078,7 +1081,7 @@
         return -1;
     }
 
-    int result = EVP_PKEY_type(pkey->type);
+    int result = EVP_PKEY_id(pkey);
     JNI_TRACE("EVP_PKEY_type(%p) => %d", pkey, result);
     return result;
 }
@@ -1529,13 +1532,13 @@
         return nullptr;
     }
 
-    jbyteArray n = bignumToArray(env, rsa->n, "n");
+    jbyteArray n = bignumToArray(env, RSA_get0_n(rsa.get()), "n");
     if (env->ExceptionCheck()) {
         return nullptr;
     }
     env->SetObjectArrayElement(joa, 0, n);
 
-    jbyteArray e = bignumToArray(env, rsa->e, "e");
+    jbyteArray e = bignumToArray(env, RSA_get0_e(rsa.get()), "e");
     if (env->ExceptionCheck()) {
         return nullptr;
     }
@@ -1567,62 +1570,62 @@
         return nullptr;
     }
 
-    jbyteArray n = bignumToArray(env, rsa->n, "n");
+    jbyteArray n = bignumToArray(env, RSA_get0_n(rsa.get()), "n");
     if (env->ExceptionCheck()) {
         return nullptr;
     }
     env->SetObjectArrayElement(joa, 0, n);
 
-    if (rsa->e != nullptr) {
-        jbyteArray e = bignumToArray(env, rsa->e, "e");
+    if (RSA_get0_e(rsa.get()) != nullptr) {
+        jbyteArray e = bignumToArray(env, RSA_get0_e(rsa.get()), "e");
         if (env->ExceptionCheck()) {
             return nullptr;
         }
         env->SetObjectArrayElement(joa, 1, e);
     }
 
-    if (rsa->d != nullptr) {
-        jbyteArray d = bignumToArray(env, rsa->d, "d");
+    if (RSA_get0_d(rsa.get()) != nullptr) {
+        jbyteArray d = bignumToArray(env, RSA_get0_d(rsa.get()), "d");
         if (env->ExceptionCheck()) {
             return nullptr;
         }
         env->SetObjectArrayElement(joa, 2, d);
     }
 
-    if (rsa->p != nullptr) {
-        jbyteArray p = bignumToArray(env, rsa->p, "p");
+    if (RSA_get0_p(rsa.get()) != nullptr) {
+        jbyteArray p = bignumToArray(env, RSA_get0_p(rsa.get()), "p");
         if (env->ExceptionCheck()) {
             return nullptr;
         }
         env->SetObjectArrayElement(joa, 3, p);
     }
 
-    if (rsa->q != nullptr) {
-        jbyteArray q = bignumToArray(env, rsa->q, "q");
+    if (RSA_get0_q(rsa.get()) != nullptr) {
+        jbyteArray q = bignumToArray(env, RSA_get0_q(rsa.get()), "q");
         if (env->ExceptionCheck()) {
             return nullptr;
         }
         env->SetObjectArrayElement(joa, 4, q);
     }
 
-    if (rsa->dmp1 != nullptr) {
-        jbyteArray dmp1 = bignumToArray(env, rsa->dmp1, "dmp1");
+    if (RSA_get0_dmp1(rsa.get()) != nullptr) {
+        jbyteArray dmp1 = bignumToArray(env, RSA_get0_dmp1(rsa.get()), "dmp1");
         if (env->ExceptionCheck()) {
             return nullptr;
         }
         env->SetObjectArrayElement(joa, 5, dmp1);
     }
 
-    if (rsa->dmq1 != nullptr) {
-        jbyteArray dmq1 = bignumToArray(env, rsa->dmq1, "dmq1");
+    if (RSA_get0_dmq1(rsa.get()) != nullptr) {
+        jbyteArray dmq1 = bignumToArray(env, RSA_get0_dmq1(rsa.get()), "dmq1");
         if (env->ExceptionCheck()) {
             return nullptr;
         }
         env->SetObjectArrayElement(joa, 6, dmq1);
     }
 
-    if (rsa->iqmp != nullptr) {
-        jbyteArray iqmp = bignumToArray(env, rsa->iqmp, "iqmp");
+    if (RSA_get0_iqmp(rsa.get()) != nullptr) {
+        jbyteArray iqmp = bignumToArray(env, RSA_get0_iqmp(rsa.get()), "iqmp");
         if (env->ExceptionCheck()) {
             return nullptr;
         }
@@ -2128,14 +2131,13 @@
         return 0;
     }
 
-    if (EVP_PKEY_type(pkey->type) != EVP_PKEY_EC) {
+    if (EVP_PKEY_id(pkey) != EVP_PKEY_EC) {
         conscrypt::jniutil::throwRuntimeException(env, "not EC key");
-        JNI_TRACE("EC_KEY_get1_group(%p) => not EC key (type == %d)", pkey,
-                  EVP_PKEY_type(pkey->type));
+        JNI_TRACE("EC_KEY_get1_group(%p) => not EC key (type == %d)", pkey, EVP_PKEY_id(pkey));
         return 0;
     }
 
-    EC_GROUP* group = EC_GROUP_dup(EC_KEY_get0_group(pkey->pkey.ec));
+    EC_GROUP* group = EC_GROUP_dup(EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(pkey)));
     JNI_TRACE("EC_KEY_get1_group(%p) => %p", pkey, group);
     return reinterpret_cast<uintptr_t>(group);
 }
@@ -2440,6 +2442,67 @@
     return static_cast<jint>(result);
 }
 
+static jboolean NativeCrypto_X25519(JNIEnv* env, jclass, jbyteArray outArray,
+                                          jbyteArray privkeyArray, jbyteArray pubkeyArray) {
+    CHECK_ERROR_QUEUE_ON_RETURN;
+    JNI_TRACE("X25519(%p, %p, %p)", outArray, privkeyArray, pubkeyArray);
+
+    ScopedByteArrayRW out(env, outArray);
+    if (out.get() == nullptr) {
+        JNI_TRACE("X25519(%p, %p, %p) can't get output buffer", outArray, privkeyArray, pubkeyArray);
+        return JNI_FALSE;
+    }
+
+    ScopedByteArrayRO privkey(env, privkeyArray);
+    if (privkey.get() == nullptr) {
+        JNI_TRACE("X25519(%p) => privkey == null", outArray);
+        return JNI_FALSE;
+    }
+
+    ScopedByteArrayRO pubkey(env, pubkeyArray);
+    if (pubkey.get() == nullptr) {
+        JNI_TRACE("X25519(%p) => pubkey == null", outArray);
+        return JNI_FALSE;
+    }
+
+    if (X25519(reinterpret_cast<uint8_t*>(out.get()),
+               reinterpret_cast<const uint8_t*>(privkey.get()),
+               reinterpret_cast<const uint8_t*>(pubkey.get())) != 1) {
+        JNI_TRACE("X25519(%p) => failure", outArray);
+        conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "X25519",
+                                                      conscrypt::jniutil::throwInvalidKeyException);
+        return JNI_FALSE;
+    }
+
+    JNI_TRACE("X25519(%p) => success", outArray);
+    return JNI_TRUE;
+}
+
+static void NativeCrypto_X25519_keypair(JNIEnv* env, jclass, jbyteArray outPublicArray, jbyteArray outPrivateArray) {
+    CHECK_ERROR_QUEUE_ON_RETURN;
+    JNI_TRACE("X25519_keypair(%p, %p)", outPublicArray, outPrivateArray);
+
+    ScopedByteArrayRW outPublic(env, outPublicArray);
+    if (outPublic.get() == nullptr) {
+        JNI_TRACE("X25519_keypair(%p, %p) can't get output public key buffer", outPublicArray, outPrivateArray);
+        return;
+    }
+
+    ScopedByteArrayRW outPrivate(env, outPrivateArray);
+    if (outPrivate.get() == nullptr) {
+        JNI_TRACE("X25519_keypair(%p, %p) can't get output private key buffer", outPublicArray, outPrivateArray);
+        return;
+    }
+
+    if (outPublic.size() != X25519_PUBLIC_VALUE_LEN || outPrivate.size() != X25519_PRIVATE_KEY_LEN) {
+        conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", "Output key array length != 32");
+        return;
+    }
+
+    X25519_keypair(reinterpret_cast<uint8_t*>(outPublic.get()), reinterpret_cast<uint8_t*>(outPrivate.get()));
+    JNI_TRACE("X25519_keypair(%p, %p) => success", outPublicArray, outPrivateArray);
+}
+
 static jlong NativeCrypto_EVP_MD_CTX_create(JNIEnv* env, jclass) {
     CHECK_ERROR_QUEUE_ON_RETURN;
     JNI_TRACE_MD("EVP_MD_CTX_create()");
@@ -3511,6 +3574,63 @@
                                     const uint8_t* in, size_t in_len, const uint8_t* ad,
                                     size_t ad_len);
 
+static jint evp_aead_ctx_op_common(JNIEnv* env, jlong evpAeadRef, jbyteArray keyArray, jint tagLen,
+                               uint8_t* outBuf, jbyteArray nonceArray,
+                               const uint8_t* inBuf, jbyteArray aadArray,
+                               evp_aead_ctx_op_func realFunc, jobject inBuffer, jobject outBuffer, jint outRange, jint inRange)  {
+    const EVP_AEAD* evpAead = reinterpret_cast<const EVP_AEAD*>(evpAeadRef);
+
+    ScopedByteArrayRO keyBytes(env, keyArray);
+    if (keyBytes.get() == nullptr) {
+        return 0;
+    }
+
+    std::unique_ptr<ScopedByteArrayRO> aad;
+    const uint8_t* aad_chars = nullptr;
+    size_t aad_chars_size = 0;
+    if (aadArray != nullptr) {
+        aad.reset(new ScopedByteArrayRO(env, aadArray));
+        aad_chars = reinterpret_cast<const uint8_t*>(aad->get());
+        if (aad_chars == nullptr) {
+            return 0;
+        }
+        aad_chars_size = aad->size();
+    }
+
+    ScopedByteArrayRO nonceBytes(env, nonceArray);
+    if (nonceBytes.get() == nullptr) {
+        return 0;
+    }
+
+    bssl::ScopedEVP_AEAD_CTX aeadCtx;
+    const uint8_t* keyTmp = reinterpret_cast<const uint8_t*>(keyBytes.get());
+    if (!EVP_AEAD_CTX_init(aeadCtx.get(), evpAead, keyTmp, keyBytes.size(),
+                           static_cast<size_t>(tagLen), nullptr)) {
+        conscrypt::jniutil::throwExceptionFromBoringSSLError(env,
+                                                             "failure initializing AEAD context");
+        JNI_TRACE(
+                "evp_aead_ctx_op(%p, %p, %d, %p, %p, %p, %p) => fail EVP_AEAD_CTX_init",
+                evpAead, keyArray, tagLen, outBuffer, nonceArray, inBuffer,
+                aadArray);
+        return 0;
+    }
+
+    const uint8_t* nonceTmp = reinterpret_cast<const uint8_t*>(nonceBytes.get());
+    size_t actualOutLength;
+
+    if (!realFunc(aeadCtx.get(), outBuf, &actualOutLength, outRange,
+                  nonceTmp, nonceBytes.size(), inBuf, static_cast<size_t>(inRange),
+                  aad_chars, aad_chars_size)) {
+        conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "evp_aead_ctx_op");
+        return 0;
+    }
+
+    JNI_TRACE("evp_aead_ctx_op(%p, %p, %d, %p, %p, %p, %p) => success outlength=%zd",
+              evpAead, keyArray, tagLen, outBuffer, nonceArray, inBuffer,
+              aadArray, actualOutLength);
+    return static_cast<jint>(actualOutLength);
+}
+
 static jint evp_aead_ctx_op(JNIEnv* env, jlong evpAeadRef, jbyteArray keyArray, jint tagLen,
                             jbyteArray outArray, jint outOffset, jbyteArray nonceArray,
                             jbyteArray inArray, jint inOffset, jint inLength, jbyteArray aadArray,
@@ -3519,10 +3639,6 @@
     JNI_TRACE("evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p)", evpAead, keyArray, tagLen,
               outArray, outOffset, nonceArray, inArray, inOffset, inLength, aadArray);
 
-    ScopedByteArrayRO keyBytes(env, keyArray);
-    if (keyBytes.get() == nullptr) {
-        return 0;
-    }
 
     ScopedByteArrayRW outBytes(env, outArray);
     if (outBytes.get() == nullptr) {
@@ -3552,51 +3668,76 @@
         return 0;
     }
 
-    std::unique_ptr<ScopedByteArrayRO> aad;
-    const uint8_t* aad_chars = nullptr;
-    size_t aad_chars_size = 0;
-    if (aadArray != nullptr) {
-        aad.reset(new ScopedByteArrayRO(env, aadArray));
-        aad_chars = reinterpret_cast<const uint8_t*>(aad->get());
-        if (aad_chars == nullptr) {
-            return 0;
-        }
-        aad_chars_size = aad->size();
-    }
-
-    ScopedByteArrayRO nonceBytes(env, nonceArray);
-    if (nonceBytes.get() == nullptr) {
-        return 0;
-    }
-
-    bssl::ScopedEVP_AEAD_CTX aeadCtx;
-    const uint8_t* keyTmp = reinterpret_cast<const uint8_t*>(keyBytes.get());
-    if (!EVP_AEAD_CTX_init(aeadCtx.get(), evpAead, keyTmp, keyBytes.size(),
-                           static_cast<size_t>(tagLen), nullptr)) {
-        conscrypt::jniutil::throwExceptionFromBoringSSLError(env,
-                                                             "failure initializing AEAD context");
-        JNI_TRACE(
-                "evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p) => fail EVP_AEAD_CTX_init",
-                evpAead, keyArray, tagLen, outArray, outOffset, nonceArray, inArray, inOffset,
-                inLength, aadArray);
-        return 0;
-    }
-
     uint8_t* outTmp = reinterpret_cast<uint8_t*>(outBytes.get());
     const uint8_t* inTmp = reinterpret_cast<const uint8_t*>(inBytes.get());
-    const uint8_t* nonceTmp = reinterpret_cast<const uint8_t*>(nonceBytes.get());
-    size_t actualOutLength;
-    if (!realFunc(aeadCtx.get(), outTmp + outOffset, &actualOutLength, outBytes.size() - outOffset,
-                  nonceTmp, nonceBytes.size(), inTmp + inOffset, static_cast<size_t>(inLength),
-                  aad_chars, aad_chars_size)) {
-        conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "evp_aead_ctx_op");
+
+    return evp_aead_ctx_op_common(env, evpAeadRef, keyArray, tagLen, outTmp + outOffset, nonceArray, inTmp + inOffset,
+                            aadArray, realFunc, inArray, outArray, outBytes.size() - outOffset, inLength);
+}
+
+static jint evp_aead_ctx_op_buf(JNIEnv* env, jlong evpAeadRef, jbyteArray keyArray, jint tagLen,
+                            jobject outBuffer, jbyteArray nonceArray,
+                            jobject inBuffer, jbyteArray aadArray,
+                            evp_aead_ctx_op_func realFunc) {
+
+    const EVP_AEAD* evpAead = reinterpret_cast<const EVP_AEAD*>(evpAeadRef);
+    JNI_TRACE("evp_aead_ctx_op(%p, %p, %d, %p, %p, %p, %p)", evpAead, keyArray, tagLen,
+              outBuffer, nonceArray, inBuffer, aadArray);
+
+    if (env->IsInstanceOf(inBuffer, conscrypt::jniutil::byteBufferClass) != JNI_TRUE ||
+                        env->IsInstanceOf(outBuffer, conscrypt::jniutil::byteBufferClass) != JNI_TRUE  ) {
+        conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", "ByteBuffer Class Error");
         return 0;
     }
 
-    JNI_TRACE("evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p) => success outlength=%zd",
-              evpAead, keyArray, tagLen, outArray, outOffset, nonceArray, inArray, inOffset,
-              inLength, aadArray, actualOutLength);
-    return static_cast<jint>(actualOutLength);
+    uint8_t* inBuf;
+    jint in_limit;
+    jint in_position;
+    jint inCapacity = env->GetDirectBufferCapacity(inBuffer);
+    if (inCapacity == -1) {
+        conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", "Non Direct ByteBuffer  Error");
+        return 0;
+    }
+    inBuf = (uint8_t*)(env->GetDirectBufferAddress(inBuffer));
+     // limit is the index of the first element that should not be read or written
+    in_limit = env->CallIntMethod(inBuffer,conscrypt::jniutil::buffer_limitMethod);
+    // position is the index of the next element to be read or written
+    in_position = env->CallIntMethod(inBuffer,conscrypt::jniutil::buffer_positionMethod);
+
+    uint8_t* outBuf;
+    jint out_limit;
+    jint out_position;
+    jint outCapacity = env->GetDirectBufferCapacity(outBuffer);
+    if (outCapacity == -1) {
+        conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", "Non Direct ByteBuffer  Error");
+        return 0;
+    }
+    outBuf = (uint8_t*)(env->GetDirectBufferAddress(outBuffer));
+    // limit is the index of the first element that should not be read or written
+    out_limit = env->CallIntMethod(outBuffer,conscrypt::jniutil::buffer_limitMethod);
+    // position is the index of the next element to be read or written
+    out_position = env->CallIntMethod(outBuffer,conscrypt::jniutil::buffer_positionMethod);
+
+    // Shifting over of ByteBuffer address to start at true position
+    inBuf += in_position;
+    outBuf += out_position;
+
+    size_t inSize = in_limit - in_position;
+    uint8_t* outBufEnd = outBuf + out_limit - out_position;
+    uint8_t* inBufEnd = inBuf + inSize;
+    std::unique_ptr<uint8_t[]> inCopy;
+    if (outBufEnd >= inBuf && inBufEnd >= outBuf) { // We have an overlap
+      inCopy.reset((new(std::nothrow) uint8_t[inSize]));
+      if (inCopy.get() == nullptr) {
+            conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate new buffer for overlap");
+            return 0;
+        }
+        memcpy(inCopy.get(), inBuf, inSize);
+        inBuf = inCopy.get();
+    }
+
+    return evp_aead_ctx_op_common(env, evpAeadRef, keyArray, tagLen, outBuf, nonceArray, inBuf, aadArray, realFunc,
+                               inBuffer, outBuffer, out_limit-out_position, in_limit-in_position);
 }
 
 static jint NativeCrypto_EVP_AEAD_CTX_seal(JNIEnv* env, jclass, jlong evpAeadRef,
@@ -3619,6 +3760,166 @@
                            inArray, inOffset, inLength, aadArray, EVP_AEAD_CTX_open);
 }
 
+static jint NativeCrypto_EVP_AEAD_CTX_seal_buf(JNIEnv* env, jclass, jlong evpAeadRef,
+                                           jbyteArray keyArray, jint tagLen, jobject outBuffer,
+                                           jbyteArray nonceArray, jobject inBuffer, jbyteArray aadArray) {
+    CHECK_ERROR_QUEUE_ON_RETURN;
+    return evp_aead_ctx_op_buf(env, evpAeadRef, keyArray, tagLen, outBuffer, nonceArray,
+                           inBuffer, aadArray, EVP_AEAD_CTX_seal);
+}
+
+static jint NativeCrypto_EVP_AEAD_CTX_open_buf(JNIEnv* env, jclass, jlong evpAeadRef,
+                                           jbyteArray keyArray, jint tagLen, jobject outBuffer,
+                                           jbyteArray nonceArray, jobject inBuffer, jbyteArray aadArray) {
+    CHECK_ERROR_QUEUE_ON_RETURN;
+    return evp_aead_ctx_op_buf(env, evpAeadRef, keyArray, tagLen, outBuffer, nonceArray,
+                           inBuffer, aadArray, EVP_AEAD_CTX_open);
+}
+
+static jlong NativeCrypto_CMAC_CTX_new(JNIEnv* env, jclass) {
+    CHECK_ERROR_QUEUE_ON_RETURN;
+    JNI_TRACE("CMAC_CTX_new");
+    auto cmacCtx = CMAC_CTX_new();
+    if (cmacCtx == nullptr) {
+        conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate CMAC_CTX");
+        return 0;
+    }
+
+    return reinterpret_cast<jlong>(cmacCtx);
+}
+
+static void NativeCrypto_CMAC_CTX_free(JNIEnv* env, jclass, jlong cmacCtxRef) {
+    CHECK_ERROR_QUEUE_ON_RETURN;
+    CMAC_CTX* cmacCtx = reinterpret_cast<CMAC_CTX*>(cmacCtxRef);
+    JNI_TRACE("CMAC_CTX_free(%p)", cmacCtx);
+    if (cmacCtx == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "cmacCtx == null");
+        return;
+    }
+    CMAC_CTX_free(cmacCtx);
+}
+
+static void NativeCrypto_CMAC_Init(JNIEnv* env, jclass, jobject cmacCtxRef, jbyteArray keyArray) {
+    CHECK_ERROR_QUEUE_ON_RETURN;
+    CMAC_CTX* cmacCtx = fromContextObject<CMAC_CTX>(env, cmacCtxRef);
+    JNI_TRACE("CMAC_Init(%p, %p)", cmacCtx, keyArray);
+    if (cmacCtx == nullptr) {
+        return;
+    }
+    ScopedByteArrayRO keyBytes(env, keyArray);
+    if (keyBytes.get() == nullptr) {
+        return;
+    }
+
+    const uint8_t* keyPtr = reinterpret_cast<const uint8_t*>(keyBytes.get());
+
+    const EVP_CIPHER *cipher;
+    switch(keyBytes.size()) {
+      case 16:
+          cipher = EVP_aes_128_cbc();
+          break;
+      case 24:
+          cipher = EVP_aes_192_cbc();
+          break;
+      case 32:
+          cipher = EVP_aes_256_cbc();
+          break;
+      default:
+          conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException",
+                                           "CMAC_Init: Unsupported key length");
+          return;
+    }
+
+    if (!CMAC_Init(cmacCtx, keyPtr, keyBytes.size(), cipher, nullptr)) {
+        conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CMAC_Init");
+        JNI_TRACE("CMAC_Init(%p, %p) => fail CMAC_Init_ex", cmacCtx, keyArray);
+        return;
+    }
+}
+
+static void NativeCrypto_CMAC_UpdateDirect(JNIEnv* env, jclass, jobject cmacCtxRef, jlong inPtr,
+                                           int inLength) {
+    CHECK_ERROR_QUEUE_ON_RETURN;
+    CMAC_CTX* cmacCtx = fromContextObject<CMAC_CTX>(env, cmacCtxRef);
+    const uint8_t* p = reinterpret_cast<const uint8_t*>(inPtr);
+    JNI_TRACE("CMAC_UpdateDirect(%p, %p, %d)", cmacCtx, p, inLength);
+
+    if (cmacCtx == nullptr) {
+        return;
+    }
+
+    if (p == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, nullptr);
+        return;
+    }
+
+    if (!CMAC_Update(cmacCtx, p, static_cast<size_t>(inLength))) {
+        JNI_TRACE("CMAC_UpdateDirect(%p, %p, %d) => threw exception", cmacCtx, p, inLength);
+        conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CMAC_UpdateDirect");
+        return;
+    }
+}
+
+static void NativeCrypto_CMAC_Update(JNIEnv* env, jclass, jobject cmacCtxRef, jbyteArray inArray,
+                                     jint inOffset, jint inLength) {
+    CHECK_ERROR_QUEUE_ON_RETURN;
+    CMAC_CTX* cmacCtx = fromContextObject<CMAC_CTX>(env, cmacCtxRef);
+    JNI_TRACE("CMAC_Update(%p, %p, %d, %d)", cmacCtx, inArray, inOffset, inLength);
+
+    if (cmacCtx == nullptr) {
+        return;
+    }
+
+    ScopedByteArrayRO inBytes(env, inArray);
+    if (inBytes.get() == nullptr) {
+        return;
+    }
+
+    if (ARRAY_OFFSET_LENGTH_INVALID(inBytes, inOffset, inLength)) {
+        conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException",
+                                           "inBytes");
+        return;
+    }
+
+    const uint8_t* inPtr = reinterpret_cast<const uint8_t*>(inBytes.get());
+
+    if (!CMAC_Update(cmacCtx, inPtr + inOffset, static_cast<size_t>(inLength))) {
+        JNI_TRACE("CMAC_Update(%p, %p, %d, %d) => threw exception", cmacCtx, inArray, inOffset,
+                  inLength);
+        conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CMAC_Update");
+        return;
+    }
+}
+
+static jbyteArray NativeCrypto_CMAC_Final(JNIEnv* env, jclass, jobject cmacCtxRef) {
+    CHECK_ERROR_QUEUE_ON_RETURN;
+    CMAC_CTX* cmacCtx = fromContextObject<CMAC_CTX>(env, cmacCtxRef);
+    JNI_TRACE("CMAC_Final(%p)", cmacCtx);
+
+    if (cmacCtx == nullptr) {
+        return nullptr;
+    }
+
+    uint8_t result[EVP_MAX_MD_SIZE];
+    size_t len;
+    if (!CMAC_Final(cmacCtx, result, &len)) {
+        JNI_TRACE("CMAC_Final(%p) => threw exception", cmacCtx);
+        conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CMAC_Final");
+        return nullptr;
+    }
+
+    ScopedLocalRef<jbyteArray> resultArray(env, env->NewByteArray(static_cast<jsize>(len)));
+    if (resultArray.get() == nullptr) {
+        return nullptr;
+    }
+    ScopedByteArrayRW resultBytes(env, resultArray.get());
+    if (resultBytes.get() == nullptr) {
+        return nullptr;
+    }
+    memcpy(resultBytes.get(), result, len);
+    return resultArray.release();
+}
+
 static jlong NativeCrypto_HMAC_CTX_new(JNIEnv* env, jclass) {
     CHECK_ERROR_QUEUE_ON_RETURN;
     JNI_TRACE("HMAC_CTX_new");
@@ -3637,6 +3938,7 @@
     HMAC_CTX* hmacCtx = reinterpret_cast<HMAC_CTX*>(hmacCtxRef);
     JNI_TRACE("HMAC_CTX_free(%p)", hmacCtx);
     if (hmacCtx == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "hmacCtx == null");
         return;
     }
     HMAC_CTX_cleanup(hmacCtx);
@@ -3689,7 +3991,7 @@
 }
 
 static void NativeCrypto_HMAC_Update(JNIEnv* env, jclass, jobject hmacCtxRef, jbyteArray inArray,
-                                     jint inOffset, int inLength) {
+                                     jint inOffset, jint inLength) {
     CHECK_ERROR_QUEUE_ON_RETURN;
     HMAC_CTX* hmacCtx = fromContextObject<HMAC_CTX>(env, hmacCtxRef);
     JNI_TRACE("HMAC_Update(%p, %p, %d, %d)", hmacCtx, inArray, inOffset, inLength);
@@ -3830,11 +4132,10 @@
 
 static void NativeCrypto_BIO_free_all(JNIEnv* env, jclass, jlong bioRef) {
     CHECK_ERROR_QUEUE_ON_RETURN;
-    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    BIO* bio = to_BIO(env, bioRef);
     JNI_TRACE("BIO_free_all(%p)", bio);
 
     if (bio == nullptr) {
-        conscrypt::jniutil::throwNullPointerException(env, "bio == null");
         return;
     }
 
@@ -3948,22 +4249,26 @@
         return nullptr;
     }
 
-    X509_check_ca(x509);
-
-    STACK_OF(GENERAL_NAME) * gn_stack;
-    bssl::UniquePtr<STACK_OF(GENERAL_NAME)> stackHolder;
+    bssl::UniquePtr<STACK_OF(GENERAL_NAME)> gn_stack;
     if (type == GN_STACK_SUBJECT_ALT_NAME) {
-        gn_stack = x509->altname;
+        gn_stack.reset(static_cast<STACK_OF(GENERAL_NAME)*>(
+                X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr)));
     } else if (type == GN_STACK_ISSUER_ALT_NAME) {
-        stackHolder.reset(static_cast<STACK_OF(GENERAL_NAME)*>(
+        gn_stack.reset(static_cast<STACK_OF(GENERAL_NAME)*>(
                 X509_get_ext_d2i(x509, NID_issuer_alt_name, nullptr, nullptr)));
-        gn_stack = stackHolder.get();
     } else {
         JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => unknown type", x509, type);
         return nullptr;
     }
+    // TODO(https://github.com/google/conscrypt/issues/916): Handle errors, remove
+    // |ERR_clear_error|, and throw CertificateParsingException.
+    if (gn_stack == nullptr) {
+        JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => null (no extension or error)", x509, type);
+        ERR_clear_error();
+        return nullptr;
+    }
 
-    int count = static_cast<int>(sk_GENERAL_NAME_num(gn_stack));
+    int count = static_cast<int>(sk_GENERAL_NAME_num(gn_stack.get()));
     if (count <= 0) {
         JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => null (no entries)", x509, type);
         return nullptr;
@@ -3978,7 +4283,7 @@
     ScopedLocalRef<jobjectArray> joa(
             env, env->NewObjectArray(count, conscrypt::jniutil::objectArrayClass, nullptr));
     for (int i = 0, j = 0; i < origCount; i++, j++) {
-        GENERAL_NAME* gen = sk_GENERAL_NAME_value(gn_stack, static_cast<size_t>(i));
+        GENERAL_NAME* gen = sk_GENERAL_NAME_value(gn_stack.get(), static_cast<size_t>(i));
         ScopedLocalRef<jobject> val(env, GENERAL_NAME_to_jobject(env, gen));
         if (env->ExceptionCheck()) {
             JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => threw exception parsing gen name",
@@ -4080,8 +4385,8 @@
 }
 
 template <typename T>
-static jbyteArray get_X509Type_serialNumber(JNIEnv* env, T* x509Type,
-                                            ASN1_INTEGER* (*get_serial_func)(T*)) {
+static jbyteArray get_X509Type_serialNumber(JNIEnv* env, const T* x509Type,
+                                            const ASN1_INTEGER* (*get_serial_func)(const T*)) {
     JNI_TRACE("get_X509Type_serialNumber(%p)", x509Type);
 
     if (x509Type == nullptr) {
@@ -4090,7 +4395,7 @@
         return nullptr;
     }
 
-    ASN1_INTEGER* serialNumber = get_serial_func(x509Type);
+    const ASN1_INTEGER* serialNumber = get_serial_func(x509Type);
     bssl::UniquePtr<BIGNUM> serialBn(ASN1_INTEGER_to_BN(serialNumber, nullptr));
     if (serialBn.get() == nullptr) {
         JNI_TRACE("X509_get_serialNumber(%p) => threw exception", x509Type);
@@ -4107,19 +4412,12 @@
     return serialArray.release();
 }
 
-/* OpenSSL includes set_serialNumber but not get. */
-#if !defined(X509_REVOKED_get_serialNumber)
-static ASN1_INTEGER* X509_REVOKED_get_serialNumber(X509_REVOKED* x) {
-    return x->serialNumber;
-}
-#endif
-
 static jbyteArray NativeCrypto_X509_get_serialNumber(JNIEnv* env, jclass, jlong x509Ref,
                                                      CONSCRYPT_UNUSED jobject holder) {
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
     JNI_TRACE("X509_get_serialNumber(%p)", x509);
-    return get_X509Type_serialNumber<X509>(env, x509, X509_get_serialNumber);
+    return get_X509Type_serialNumber<X509>(env, x509, X509_get0_serialNumber);
 }
 
 static jbyteArray NativeCrypto_X509_REVOKED_get_serialNumber(JNIEnv* env, jclass,
@@ -4127,7 +4425,7 @@
     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);
-    return get_X509Type_serialNumber<X509_REVOKED>(env, revoked, X509_REVOKED_get_serialNumber);
+    return get_X509Type_serialNumber<X509_REVOKED>(env, revoked, X509_REVOKED_get0_serialNumber);
 }
 
 static void NativeCrypto_X509_verify(JNIEnv* env, jclass, jlong x509Ref,
@@ -4148,13 +4446,6 @@
         return;
     }
 
-    if (X509_ALGOR_cmp(x509->sig_alg, X509_CINF_get_signature(X509_get_cert_info(x509)))) {
-        conscrypt::jniutil::throwCertificateException(env,
-                "Certificate signature algorithms do not match");
-        JNI_TRACE("X509_verify(%p, %p) => signature alg mismatch", x509, pkey);
-        return;
-    }
-
     if (X509_verify(x509, pkey) != 1) {
         conscrypt::jniutil::throwExceptionFromBoringSSLError(
                 env, "X509_verify", conscrypt::jniutil::throwCertificateException);
@@ -4164,12 +4455,63 @@
     JNI_TRACE("X509_verify(%p, %p) => verify success", x509, pkey);
 }
 
-static jbyteArray NativeCrypto_get_X509_cert_info_enc(JNIEnv* env, jclass, jlong x509Ref,
-                                                      CONSCRYPT_UNUSED jobject holder) {
+static jbyteArray NativeCrypto_get_X509_tbs_cert(JNIEnv* env, jclass, jlong x509Ref,
+                                                 CONSCRYPT_UNUSED jobject holder) {
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
-    JNI_TRACE("get_X509_cert_info_enc(%p)", x509);
-    return ASN1ToByteArray<X509_CINF>(env, x509->cert_info, i2d_X509_CINF);
+    JNI_TRACE("get_X509_tbs_cert(%p)", x509);
+    // Note |i2d_X509_tbs| preserves the original encoding of the TBSCertificate.
+    return ASN1ToByteArray<X509>(env, x509, i2d_X509_tbs);
+}
+
+static jbyteArray NativeCrypto_get_X509_tbs_cert_without_ext(JNIEnv* env, jclass, jlong x509Ref,
+                                                             CONSCRYPT_UNUSED jobject holder,
+                                                             jstring oidString) {
+    CHECK_ERROR_QUEUE_ON_RETURN;
+    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
+    JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %p)", x509, oidString);
+
+    if (x509 == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "x509 == null");
+        JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %p) => x509 == null", x509, oidString);
+        return nullptr;
+    }
+
+    bssl::UniquePtr<X509> copy(X509_dup(x509));
+    if (copy == nullptr) {
+        conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "X509_dup");
+        JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %p) => threw error", x509, oidString);
+        return nullptr;
+    }
+
+    ScopedUtfChars oid(env, oidString);
+    if (oid.c_str() == nullptr) {
+        JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %p) => oidString == null", x509, oidString);
+        return nullptr;
+    }
+
+    bssl::UniquePtr<ASN1_OBJECT> obj(OBJ_txt2obj(oid.c_str(), 1 /* allow numerical form only */));
+    if (obj.get() == nullptr) {
+        JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %s) => oid conversion failed", x509,
+                  oid.c_str());
+        conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException",
+                                           "Invalid OID.");
+        ERR_clear_error();
+        return nullptr;
+    }
+
+    int extIndex = X509_get_ext_by_OBJ(copy.get(), obj.get(), -1);
+    if (extIndex == -1) {
+        JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %s) => ext not found", x509, oid.c_str());
+        conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException",
+                                           "Extension not found.");
+        return nullptr;
+    }
+
+    // Remove the extension and re-encode the TBSCertificate. Note |i2d_re_X509_tbs| ignores the
+    // cached encoding.
+    X509_EXTENSION_free(X509_delete_ext(copy.get(), extIndex));
+    return ASN1ToByteArray<X509>(env, copy.get(), i2d_re_X509_tbs);
 }
 
 static jint NativeCrypto_get_X509_ex_flags(JNIEnv* env, jclass, jlong x509Ref,
@@ -4184,14 +4526,21 @@
         return 0;
     }
 
-    X509_check_ca(x509);
-
-    return static_cast<jint>(x509->ex_flags);
+    uint32_t flags = X509_get_extension_flags(x509);
+    // X509_get_extension_flags sometimes leaves values in the error queue. See
+    // https://crbug.com/boringssl/382.
+    //
+    // TODO(https://github.com/google/conscrypt/issues/916): This function is used to check
+    // EXFLAG_CA, but does not check EXFLAG_INVALID. Fold the two JNI calls in getBasicConstraints()
+    // together and handle errors. (See also NativeCrypto_get_X509_ex_pathlen.) From there, limit
+    // this JNI call to EXFLAG_CRITICAL.
+    ERR_clear_error();
+    return flags;
 }
 
-static jboolean NativeCrypto_X509_check_issued(JNIEnv* env, jclass, jlong x509Ref1,
-                                               CONSCRYPT_UNUSED jobject holder, jlong x509Ref2,
-                                               CONSCRYPT_UNUSED jobject holder2) {
+static jint NativeCrypto_X509_check_issued(JNIEnv* env, jclass, jlong x509Ref1,
+                                           CONSCRYPT_UNUSED jobject holder, jlong x509Ref2,
+                                           CONSCRYPT_UNUSED jobject holder2) {
     CHECK_ERROR_QUEUE_ON_RETURN;
     X509* x509_1 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref1));
     X509* x509_2 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref2));
@@ -4199,20 +4548,24 @@
 
     int ret = X509_check_issued(x509_1, x509_2);
     JNI_TRACE("X509_check_issued(%p, %p) => %d", x509_1, x509_2, ret);
-    return static_cast<jboolean>(ret);
+    return ret;
 }
 
-static void get_X509_signature(X509* x509, ASN1_BIT_STRING** signature) {
-    *signature = x509->signature;
+static const ASN1_BIT_STRING* get_X509_signature(X509* x509) {
+    const ASN1_BIT_STRING* signature;
+    X509_get0_signature(&signature, nullptr, x509);
+    return signature;
 }
 
-static void get_X509_CRL_signature(X509_CRL* crl, ASN1_BIT_STRING** signature) {
-    *signature = crl->signature;
+static const ASN1_BIT_STRING* get_X509_CRL_signature(X509_CRL* crl) {
+    const ASN1_BIT_STRING* signature;
+    X509_CRL_get0_signature(crl, &signature, nullptr);
+    return signature;
 }
 
 template <typename T>
 static jbyteArray get_X509Type_signature(JNIEnv* env, T* x509Type,
-                                         void (*get_signature_func)(T*, ASN1_BIT_STRING**)) {
+                                         const ASN1_BIT_STRING* (*get_signature_func)(T*)) {
     JNI_TRACE("get_X509Type_signature(%p)", x509Type);
 
     if (x509Type == nullptr) {
@@ -4221,10 +4574,10 @@
         return nullptr;
     }
 
-    ASN1_BIT_STRING* signature;
-    get_signature_func(x509Type, &signature);
+    const ASN1_BIT_STRING* signature = get_signature_func(x509Type);
 
-    ScopedLocalRef<jbyteArray> signatureArray(env, env->NewByteArray(signature->length));
+    ScopedLocalRef<jbyteArray> signatureArray(env,
+                                              env->NewByteArray(ASN1_STRING_length(signature)));
     if (env->ExceptionCheck()) {
         JNI_TRACE("get_X509Type_signature(%p) => threw exception", x509Type);
         return nullptr;
@@ -4236,10 +4589,10 @@
         return nullptr;
     }
 
-    memcpy(signatureBytes.get(), signature->data, signature->length);
+    memcpy(signatureBytes.get(), ASN1_STRING_get0_data(signature), ASN1_STRING_length(signature));
 
     JNI_TRACE("get_X509Type_signature(%p) => %p (%d bytes)", x509Type, signatureArray.get(),
-              signature->length);
+              ASN1_STRING_length(signature));
     return signatureArray.release();
 }
 
@@ -4419,13 +4772,38 @@
     X509_CRL* crl = reinterpret_cast<X509_CRL*>(static_cast<uintptr_t>(x509CrlRef));
     JNI_TRACE("get_X509_CRL_sig_alg_oid(%p)", crl);
 
-    if (crl == nullptr || crl->sig_alg == nullptr) {
-        conscrypt::jniutil::throwNullPointerException(env, "crl == null || crl->sig_alg == null");
+    if (crl == nullptr) {
+        conscrypt::jniutil::throwNullPointerException(env, "crl == null");
         JNI_TRACE("get_X509_CRL_sig_alg_oid(%p) => crl == null", crl);
         return nullptr;
     }
 
-    return ASN1_OBJECT_to_OID_string(env, crl->sig_alg->algorithm);
+    const X509_ALGOR *sig_alg;
+    X509_CRL_get0_signature(crl, nullptr, &sig_alg);
+    const ASN1_OBJECT *oid;
+    X509_ALGOR_get0(&oid, nullptr, nullptr, sig_alg);
+    return ASN1_OBJECT_to_OID_string(env, oid);
+}
+
+static jbyteArray get_X509_ALGOR_parameter(JNIEnv* env, const X509_ALGOR *algor) {
+    int param_type;
+    const void* param_value;
+    X509_ALGOR_get0(nullptr, &param_type, &param_value, algor);
+
+    if (param_type == V_ASN1_UNDEF) {
+        JNI_TRACE("get_X509_ALGOR_parameter(%p) => no parameters", algor);
+        return nullptr;
+    }
+
+    // The OpenSSL 1.1.x API lacks a function to get the ASN1_TYPE out of X509_ALGOR directly, so
+    // recreate it from the returned components.
+    bssl::UniquePtr<ASN1_TYPE> param(ASN1_TYPE_new());
+    if (!param || !ASN1_TYPE_set1(param.get(), param_type, param_value)) {
+        conscrypt::jniutil::throwOutOfMemory(env, "Unable to serialize parameter");
+        return nullptr;
+    }
+
+    return ASN1ToByteArray<ASN1_TYPE>(env, param.get(), i2d_ASN1_TYPE);
 }
 
 static jbyteArray NativeCrypto_get_X509_CRL_sig_alg_parameter(JNIEnv* env, jclass, jlong x509CrlRef,
@@ -4440,12 +4818,9 @@
         return nullptr;
     }
 
-    if (crl->sig_alg->parameter == nullptr) {
-        JNI_TRACE("get_X509_CRL_sig_alg_parameter(%p) => null", crl);
-        return nullptr;
-    }
-
-    return ASN1ToByteArray<ASN1_TYPE>(env, crl->sig_alg->parameter, i2d_ASN1_TYPE);
+    const X509_ALGOR *sig_alg;
+    X509_CRL_get0_signature(crl, nullptr, &sig_alg);
+    return get_X509_ALGOR_parameter(env, sig_alg);
 }
 
 static jbyteArray NativeCrypto_X509_CRL_get_issuer_name(JNIEnv* env, jclass, jlong x509CrlRef,
@@ -4469,9 +4844,9 @@
     return version;
 }
 
-template <typename T, int (*get_ext_by_OBJ_func)(T*, ASN1_OBJECT*, int),
-          X509_EXTENSION* (*get_ext_func)(T*, int)>
-static X509_EXTENSION* X509Type_get_ext(JNIEnv* env, T* x509Type, jstring oidString) {
+template <typename T, int (*get_ext_by_OBJ_func)(const T*, const ASN1_OBJECT*, int),
+          X509_EXTENSION* (*get_ext_func)(const T*, int)>
+static X509_EXTENSION* X509Type_get_ext(JNIEnv* env, const T* x509Type, jstring oidString) {
     JNI_TRACE("X509Type_get_ext(%p)", x509Type);
 
     if (x509Type == nullptr) {
@@ -4502,9 +4877,9 @@
     return ext;
 }
 
-template <typename T, int (*get_ext_by_OBJ_func)(T*, ASN1_OBJECT*, int),
-          X509_EXTENSION* (*get_ext_func)(T*, int)>
-static jbyteArray X509Type_get_ext_oid(JNIEnv* env, T* x509Type, jstring oidString) {
+template <typename T, int (*get_ext_by_OBJ_func)(const T*, const ASN1_OBJECT*, int),
+          X509_EXTENSION* (*get_ext_func)(const T*, int)>
+static jbyteArray X509Type_get_ext_oid(JNIEnv* env, const T* x509Type, jstring oidString) {
     X509_EXTENSION* ext =
             X509Type_get_ext<T, get_ext_by_OBJ_func, get_ext_func>(env, x509Type, oidString);
     if (ext == nullptr) {
@@ -4512,8 +4887,10 @@
         return nullptr;
     }
 
-    JNI_TRACE("X509Type_get_ext_oid(%p, %p) => %p", x509Type, oidString, ext->value);
-    return ASN1ToByteArray<ASN1_OCTET_STRING>(env, ext->value, i2d_ASN1_OCTET_STRING);
+    JNI_TRACE("X509Type_get_ext_oid(%p, %p) => %p", x509Type, oidString,
+              X509_EXTENSION_get_data(ext));
+    return ASN1ToByteArray<ASN1_OCTET_STRING>(env, X509_EXTENSION_get_data(ext),
+                                              i2d_ASN1_OCTET_STRING);
 }
 
 static jlong NativeCrypto_X509_CRL_get_ext(JNIEnv* env, jclass, jlong x509CrlRef,
@@ -4567,8 +4944,9 @@
         return 0;
     }
 
-    JNI_TRACE("get_X509_REVOKED_revocationDate(%p) => %p", revoked, revoked->revocationDate);
-    return reinterpret_cast<uintptr_t>(revoked->revocationDate);
+    JNI_TRACE("get_X509_REVOKED_revocationDate(%p) => %p", revoked,
+              X509_REVOKED_get0_revocationDate(revoked));
+    return reinterpret_cast<uintptr_t>(X509_REVOKED_get0_revocationDate(revoked));
 }
 
 #ifdef __GNUC__
@@ -4595,11 +4973,14 @@
     }
 
     BIO_printf(bio, "Serial Number: ");
-    i2a_ASN1_INTEGER(bio, revoked->serialNumber);
+    i2a_ASN1_INTEGER(bio, X509_REVOKED_get0_serialNumber(revoked));
     BIO_printf(bio, "\nRevocation Date: ");
-    ASN1_TIME_print(bio, revoked->revocationDate);
+    ASN1_TIME_print(bio, X509_REVOKED_get0_revocationDate(revoked));
     BIO_printf(bio, "\n");
-    X509V3_extensions_print(bio, "CRL entry extensions", revoked->extensions, 0, 0);
+    // TODO(davidben): Should the flags parameter be |X509V3_EXT_DUMP_UNKNOWN| so we don't error on
+    // unknown extensions. Alternatively, maybe we can use a simpler toString() implementation.
+    X509V3_extensions_print(bio, "CRL entry extensions", X509_REVOKED_get0_extensions(revoked), 0,
+                            0);
 }
 #ifndef _WIN32
 #pragma GCC diagnostic pop
@@ -4610,7 +4991,7 @@
     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);
-    return ASN1ToByteArray<X509_CRL_INFO>(env, crl->crl, i2d_X509_CRL_INFO);
+    return ASN1ToByteArray<X509_CRL>(env, crl, i2d_X509_CRL_tbs);
 }
 
 static void NativeCrypto_X509_CRL_verify(JNIEnv* env, jclass, jlong x509CrlRef,
@@ -4694,12 +5075,17 @@
     return X509_supported_extension(ext);
 }
 
-static inline void get_ASN1_TIME_data(char** data, int* output, size_t len) {
-    char c = **data;
-    **data = '\0';
-    *data -= len;
-    *output = atoi(*data);
-    *(*data + len) = c;
+static inline bool decimal_to_integer(const char* data, size_t len, int* out) {
+    int ret = 0;
+    for (size_t i = 0; i < len; i++) {
+        ret *= 10;
+        if (data[i] < '0' || data[i] > '9') {
+            return false;
+        }
+        ret += data[i] - '0';
+    }
+    *out = ret;
+    return true;
 }
 
 static void NativeCrypto_ASN1_TIME_to_Calendar(JNIEnv* env, jclass, jlong asn1TimeRef,
@@ -4725,21 +5111,22 @@
         return;
     }
 
-    if (gen->length < 14 || gen->data == nullptr) {
+    if (ASN1_STRING_length(gen.get()) < 14 || ASN1_STRING_get0_data(gen.get()) == nullptr) {
         conscrypt::jniutil::throwNullPointerException(env, "gen->length < 14 || gen->data == null");
         return;
     }
 
-    int sec, min, hour, mday, mon, year;
-
-    char* p = reinterpret_cast<char*>(&gen->data[14]);
-
-    get_ASN1_TIME_data(&p, &sec, 2);
-    get_ASN1_TIME_data(&p, &min, 2);
-    get_ASN1_TIME_data(&p, &hour, 2);
-    get_ASN1_TIME_data(&p, &mday, 2);
-    get_ASN1_TIME_data(&p, &mon, 2);
-    get_ASN1_TIME_data(&p, &year, 4);
+    int year, mon, mday, hour, min, sec;
+    const char* data = reinterpret_cast<const char*>(ASN1_STRING_get0_data(gen.get()));
+    if (!decimal_to_integer(data, 4, &year) ||
+        !decimal_to_integer(data + 4, 2, &mon) ||
+        !decimal_to_integer(data + 6, 2, &mday) ||
+        !decimal_to_integer(data + 8, 2, &hour) ||
+        !decimal_to_integer(data + 10, 2, &min) ||
+        !decimal_to_integer(data + 12, 2, &sec)) {
+        conscrypt::jniutil::throwParsingException(env, "Invalid date format");
+        return;
+    }
 
     env->CallVoidMethod(calendar, conscrypt::jniutil::calendar_setMethod, year, mon - 1, mday, hour,
                         min, sec);
@@ -5091,11 +5478,10 @@
 
 template <typename T, T* (*d2i_func)(BIO*, T**)>
 static jlong d2i_ASN1Object_to_jlong(JNIEnv* env, jlong bioRef) {
-    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    BIO* bio = to_BIO(env, bioRef);
     JNI_TRACE("d2i_ASN1Object_to_jlong(%p)", bio);
 
     if (bio == nullptr) {
-        conscrypt::jniutil::throwNullPointerException(env, "bio == null");
         return 0;
     }
 
@@ -5155,11 +5541,10 @@
 
 template <typename T, T* (*PEM_read_func)(BIO*, T**, pem_password_cb*, void*)>
 static jlong PEM_to_jlong(JNIEnv* env, jlong bioRef) {
-    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    BIO* bio = to_BIO(env, bioRef);
     JNI_TRACE("PEM_to_jlong(%p)", bio);
 
     if (bio == nullptr) {
-        conscrypt::jniutil::throwNullPointerException(env, "bio == null");
         JNI_TRACE("PEM_to_jlong(%p) => bio == null", bio);
         return 0;
     }
@@ -5203,22 +5588,41 @@
     return PEM_to_jlong<EVP_PKEY, PEM_read_bio_PrivateKey>(env, bioRef);
 }
 
-template <typename T, typename T_stack>
-static jlongArray PKCS7_to_ItemArray(JNIEnv* env, T_stack* stack, T* (*dup_func)(T*)) {
-    if (stack == nullptr) {
+static jlongArray X509s_to_ItemArray(JNIEnv* env, STACK_OF(X509) *certs) {
+    if (certs == nullptr) {
         return nullptr;
     }
 
     ScopedLocalRef<jlongArray> ref_array(env, nullptr);
-    size_t size = sk_num(reinterpret_cast<_STACK*>(stack));
+    size_t size = sk_X509_num(certs);
     ref_array.reset(env->NewLongArray(size));
     ScopedLongArrayRW items(env, ref_array.get());
     for (size_t i = 0; i < size; i++) {
-        T* item = reinterpret_cast<T*>(sk_value(reinterpret_cast<_STACK*>(stack), i));
-        items[i] = reinterpret_cast<uintptr_t>(dup_func(item));
+        X509* cert = sk_X509_value(certs, i);
+        X509_up_ref(cert);
+        items[i] = reinterpret_cast<uintptr_t>(cert);
     }
 
-    JNI_TRACE("PKCS7_to_ItemArray(%p) => %p [size=%zd]", stack, ref_array.get(), size);
+    JNI_TRACE("X509s_to_ItemArray(%p) => %p [size=%zd]", certs, ref_array.get(), size);
+    return ref_array.release();
+}
+
+static jlongArray X509_CRLs_to_ItemArray(JNIEnv* env, STACK_OF(X509_CRL) *crls) {
+    if (crls == nullptr) {
+        return nullptr;
+    }
+
+    ScopedLocalRef<jlongArray> ref_array(env, nullptr);
+    size_t size = sk_X509_CRL_num(crls);
+    ref_array.reset(env->NewLongArray(size));
+    ScopedLongArrayRW items(env, ref_array.get());
+    for (size_t i = 0; i < size; i++) {
+        X509_CRL* crl = sk_X509_CRL_value(crls, i);
+        X509_CRL_up_ref(crl);
+        items[i] = reinterpret_cast<uintptr_t>(crl);
+    }
+
+    JNI_TRACE("X509_CRLs_to_ItemArray(%p) => %p [size=%zd]", crls, ref_array.get(), size);
     return ref_array.release();
 }
 
@@ -5254,11 +5658,10 @@
 
 static jlongArray NativeCrypto_PEM_read_bio_PKCS7(JNIEnv* env, jclass, jlong bioRef, jint which) {
     CHECK_ERROR_QUEUE_ON_RETURN;
-    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    BIO* bio = to_BIO(env, bioRef);
     JNI_TRACE("PEM_read_bio_PKCS7_CRLs(%p)", bio);
 
     if (bio == nullptr) {
-        conscrypt::jniutil::throwNullPointerException(env, "bio == null");
         JNI_TRACE("PEM_read_bio_PKCS7_CRLs(%p) => bio == null", bio);
         return nullptr;
     }
@@ -5269,14 +5672,14 @@
             conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "PKCS7_get_PEM_certificates");
             return nullptr;
         }
-        return PKCS7_to_ItemArray<X509, STACK_OF(X509)>(env, outCerts.get(), X509_dup);
+        return X509s_to_ItemArray(env, outCerts.get());
     } else if (which == PKCS7_CRLS) {
         bssl::UniquePtr<STACK_OF(X509_CRL)> outCRLs(sk_X509_CRL_new_null());
         if (!PKCS7_get_PEM_CRLs(outCRLs.get(), bio)) {
             conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "PKCS7_get_PEM_CRLs");
             return nullptr;
         }
-        return PKCS7_to_ItemArray<X509_CRL, STACK_OF(X509_CRL)>(env, outCRLs.get(), X509_CRL_dup);
+        return X509_CRLs_to_ItemArray(env, outCRLs.get());
     } else {
         conscrypt::jniutil::throwRuntimeException(env, "unknown PKCS7 field");
         return nullptr;
@@ -5285,11 +5688,10 @@
 
 static jlongArray NativeCrypto_d2i_PKCS7_bio(JNIEnv* env, jclass, jlong bioRef, jint which) {
     CHECK_ERROR_QUEUE_ON_RETURN;
-    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    BIO* bio = to_BIO(env, bioRef);
     JNI_TRACE("d2i_PKCS7_bio(%p, %d)", bio, which);
 
     if (bio == nullptr) {
-        conscrypt::jniutil::throwNullPointerException(env, "bio == null");
         JNI_TRACE("d2i_PKCS7_bio(%p, %d) => bio == null", bio, which);
         return nullptr;
     }
@@ -5316,7 +5718,7 @@
             return nullptr;
         }
         JNI_TRACE("d2i_PKCS7_bio(%p, %d) => success certs", bio, which);
-        return PKCS7_to_ItemArray<X509, STACK_OF(X509)>(env, outCerts.get(), X509_dup);
+        return X509s_to_ItemArray(env, outCerts.get());
     } else if (which == PKCS7_CRLS) {
         bssl::UniquePtr<STACK_OF(X509_CRL)> outCRLs(sk_X509_CRL_new_null());
         if (!PKCS7_get_CRLs(outCRLs.get(), &cbs)) {
@@ -5326,7 +5728,7 @@
             return nullptr;
         }
         JNI_TRACE("d2i_PKCS7_bio(%p, %d) => success CRLs", bio, which);
-        return PKCS7_to_ItemArray<X509_CRL, STACK_OF(X509_CRL)>(env, outCRLs.get(), X509_CRL_dup);
+        return X509_CRLs_to_ItemArray(env, outCRLs.get());
     } else {
         conscrypt::jniutil::throwRuntimeException(env, "unknown PKCS7 field");
         return nullptr;
@@ -5335,9 +5737,14 @@
 
 static jlongArray NativeCrypto_ASN1_seq_unpack_X509_bio(JNIEnv* env, jclass, jlong bioRef) {
     CHECK_ERROR_QUEUE_ON_RETURN;
-    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    BIO* bio = to_BIO(env, bioRef);
     JNI_TRACE("ASN1_seq_unpack_X509_bio(%p)", bio);
 
+    if (bio == nullptr) {
+        JNI_TRACE("ASN1_seq_unpack_X509_bio(%p) => bio == null", bio);
+        return nullptr;
+    }
+
     uint8_t* data;
     size_t len;
     if (!BIO_read_asn1(bio, &data, &len, 256 * 1024 * 1024 /* max length, 256MB for sanity */)) {
@@ -5447,21 +5854,6 @@
     X509_free(x509);
 }
 
-static jlong NativeCrypto_X509_dup(JNIEnv* env, jclass, jlong x509Ref,
-                                   CONSCRYPT_UNUSED jobject holder) {
-    CHECK_ERROR_QUEUE_ON_RETURN;
-    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
-    JNI_TRACE("X509_dup(%p)", x509);
-
-    if (x509 == nullptr) {
-        conscrypt::jniutil::throwNullPointerException(env, "x509 == null");
-        JNI_TRACE("X509_dup(%p) => x509 == null", x509);
-        return 0;
-    }
-
-    return reinterpret_cast<uintptr_t>(X509_dup(x509));
-}
-
 static jint NativeCrypto_X509_cmp(JNIEnv* env, jclass, jlong x509Ref1,
                                   CONSCRYPT_UNUSED jobject holder, jlong x509Ref2,
                                   CONSCRYPT_UNUSED jobject holder2) {
@@ -5487,53 +5879,11 @@
     return ret;
 }
 
-static void NativeCrypto_X509_delete_ext(JNIEnv* env, jclass, jlong x509Ref,
-                                         CONSCRYPT_UNUSED jobject holder, jstring oidString) {
-    CHECK_ERROR_QUEUE_ON_RETURN;
-    X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
-    JNI_TRACE("X509_delete_ext(%p, %p)", x509, oidString);
-
-    if (x509 == nullptr) {
-        conscrypt::jniutil::throwNullPointerException(env, "x509 == null");
-        JNI_TRACE("X509_delete_ext(%p, %p) => x509 == null", x509, oidString);
-        return;
-    }
-
-    ScopedUtfChars oid(env, oidString);
-    if (oid.c_str() == nullptr) {
-        JNI_TRACE("X509_delete_ext(%p, %p) => oidString == null", x509, oidString);
-        return;
-    }
-
-    bssl::UniquePtr<ASN1_OBJECT> obj(OBJ_txt2obj(oid.c_str(), 1 /* allow numerical form only */));
-    if (obj.get() == nullptr) {
-        JNI_TRACE("X509_delete_ext(%p, %s) => oid conversion failed", x509, oid.c_str());
-        conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException",
-                                           "Invalid OID.");
-        ERR_clear_error();
-        return;
-    }
-
-    int extIndex = X509_get_ext_by_OBJ(x509, obj.get(), -1);
-    if (extIndex == -1) {
-        JNI_TRACE("X509_delete_ext(%p, %s) => ext not found", x509, oid.c_str());
-        return;
-    }
-
-    X509_EXTENSION* ext = X509_delete_ext(x509, extIndex);
-    if (ext != nullptr) {
-        X509_EXTENSION_free(ext);
-
-        // Invalidate the cached encoding
-        X509_CINF_set_modified(X509_get_cert_info(x509));
-    }
-}
-
 static void NativeCrypto_X509_print_ex(JNIEnv* env, jclass, jlong bioRef, jlong x509Ref,
                                        CONSCRYPT_UNUSED jobject holder, jlong nmflagJava,
                                        jlong certflagJava) {
     CHECK_ERROR_QUEUE_ON_RETURN;
-    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    BIO* bio = to_BIO(env, bioRef);
     X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
     // NOLINTNEXTLINE(runtime/int)
     unsigned long nmflag = static_cast<unsigned long>(nmflagJava);
@@ -5542,7 +5892,6 @@
     JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld)", bio, x509, nmflag, certflag);
 
     if (bio == nullptr) {
-        conscrypt::jniutil::throwNullPointerException(env, "bio == null");
         JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld) => bio == null", bio, x509, nmflag, certflag);
         return;
     }
@@ -5624,7 +5973,9 @@
     }
 
     X509_PUBKEY* pubkey = X509_get_X509_PUBKEY(x509);
-    return ASN1_OBJECT_to_OID_string(env, pubkey->algor->algorithm);
+    ASN1_OBJECT* algorithm;
+    X509_PUBKEY_get0_param(&algorithm, nullptr, nullptr, nullptr, pubkey);
+    return ASN1_OBJECT_to_OID_string(env, algorithm);
 }
 
 static jstring NativeCrypto_get_X509_sig_alg_oid(JNIEnv* env, jclass, jlong x509Ref,
@@ -5633,13 +5984,17 @@
     X509* x509 = reinterpret_cast<X509*>(static_cast<uintptr_t>(x509Ref));
     JNI_TRACE("get_X509_sig_alg_oid(%p)", x509);
 
-    if (x509 == nullptr || x509->sig_alg == nullptr) {
+    if (x509 == nullptr) {
         conscrypt::jniutil::throwNullPointerException(env, "x509 == null || x509->sig_alg == null");
         JNI_TRACE("get_X509_sig_alg_oid(%p) => x509 == null", x509);
         return nullptr;
     }
 
-    return ASN1_OBJECT_to_OID_string(env, x509->sig_alg->algorithm);
+    const X509_ALGOR *sig_alg;
+    X509_get0_signature(nullptr, &sig_alg, x509);
+    const ASN1_OBJECT *oid;
+    X509_ALGOR_get0(&oid, nullptr, nullptr, sig_alg);
+    return ASN1_OBJECT_to_OID_string(env, oid);
 }
 
 static jbyteArray NativeCrypto_get_X509_sig_alg_parameter(JNIEnv* env, jclass, jlong x509Ref,
@@ -5654,12 +6009,9 @@
         return nullptr;
     }
 
-    if (x509->sig_alg->parameter == nullptr) {
-        JNI_TRACE("get_X509_sig_alg_parameter(%p) => null", x509);
-        return nullptr;
-    }
-
-    return ASN1ToByteArray<ASN1_TYPE>(env, x509->sig_alg->parameter, i2d_ASN1_TYPE);
+    const X509_ALGOR *sig_alg;
+    X509_get0_signature(nullptr, &sig_alg, x509);
+    return get_X509_ALGOR_parameter(env, sig_alg);
 }
 
 static jbooleanArray NativeCrypto_get_X509_issuerUID(JNIEnv* env, jclass, jlong x509Ref,
@@ -5674,12 +6026,14 @@
         return nullptr;
     }
 
-    if (x509->cert_info->issuerUID == nullptr) {
+    const ASN1_BIT_STRING *issuer_uid;
+    X509_get0_uids(x509, &issuer_uid, /*out_subject_uid=*/nullptr);
+    if (issuer_uid == nullptr) {
         JNI_TRACE("get_X509_issuerUID(%p) => null", x509);
         return nullptr;
     }
 
-    return ASN1BitStringToBooleanArray(env, x509->cert_info->issuerUID);
+    return ASN1BitStringToBooleanArray(env, issuer_uid);
 }
 
 static jbooleanArray NativeCrypto_get_X509_subjectUID(JNIEnv* env, jclass, jlong x509Ref,
@@ -5694,12 +6048,14 @@
         return nullptr;
     }
 
-    if (x509->cert_info->subjectUID == nullptr) {
+    const ASN1_BIT_STRING *subject_uid;
+    X509_get0_uids(x509, /*out_issuer_uid=*/nullptr, &subject_uid);
+    if (subject_uid == nullptr) {
         JNI_TRACE("get_X509_subjectUID(%p) => null", x509);
         return nullptr;
     }
 
-    return ASN1BitStringToBooleanArray(env, x509->cert_info->subjectUID);
+    return ASN1BitStringToBooleanArray(env, subject_uid);
 }
 
 static jbooleanArray NativeCrypto_get_X509_ex_kusage(JNIEnv* env, jclass, jlong x509Ref,
@@ -5714,10 +6070,14 @@
         return nullptr;
     }
 
+    // TODO(https://github.com/google/conscrypt/issues/916): Handle errors and remove
+    // |ERR_clear_error|. Note X509Certificate.getKeyUsage() cannot throw
+    // CertificateParsingException, so this needs to be checked earlier, e.g. in the constructor.
     bssl::UniquePtr<ASN1_BIT_STRING> bitStr(
             static_cast<ASN1_BIT_STRING*>(X509_get_ext_d2i(x509, NID_key_usage, nullptr, nullptr)));
     if (bitStr.get() == nullptr) {
         JNI_TRACE("get_X509_ex_kusage(%p) => null", x509);
+        ERR_clear_error();
         return nullptr;
     }
 
@@ -5736,10 +6096,13 @@
         return nullptr;
     }
 
+    // TODO(https://github.com/google/conscrypt/issues/916): Handle errors, remove
+    // |ERR_clear_error|, and throw CertificateParsingException.
     bssl::UniquePtr<STACK_OF(ASN1_OBJECT)> objArray(static_cast<STACK_OF(ASN1_OBJECT)*>(
             X509_get_ext_d2i(x509, NID_ext_key_usage, nullptr, nullptr)));
     if (objArray.get() == nullptr) {
         JNI_TRACE("get_X509_ex_xkusage(%p) => null", x509);
+        ERR_clear_error();
         return nullptr;
     }
 
@@ -5773,11 +6136,52 @@
         return 0;
     }
 
-    /* Just need to do this to cache the ex_* values. */
-    X509_check_ca(x509);
+    // Use |X509_get_ext_d2i| instead of |X509_get_pathlen| because the latter treats
+    // |EXFLAG_INVALID| as the error case. |EXFLAG_INVALID| is set if any built-in extension is
+    // invalid. For now, we preserve Conscrypt's historical behavior in accepting certificates in
+    // the constructor even if |EXFLAG_INVALID| is set.
+    //
+    // TODO(https://github.com/google/conscrypt/issues/916): Handle errors and remove
+    // |ERR_clear_error|. Note X509Certificate.getBasicConstraints() cannot throw
+    // CertificateParsingException, so this needs to be checked earlier, e.g. in the constructor.
+    bssl::UniquePtr<BASIC_CONSTRAINTS> basic_constraints(static_cast<BASIC_CONSTRAINTS*>(
+            X509_get_ext_d2i(x509, NID_basic_constraints, nullptr, nullptr)));
+    if (basic_constraints == nullptr) {
+        JNI_TRACE("get_X509_ex_path(%p) => -1 (no extension or error)", x509);
+        ERR_clear_error();
+        return -1;
+    }
 
-    JNI_TRACE("get_X509_ex_pathlen(%p) => %ld", x509, x509->ex_pathlen);
-    return x509->ex_pathlen;
+    if (basic_constraints->pathlen == nullptr) {
+        JNI_TRACE("get_X509_ex_path(%p) => -1 (no pathLenConstraint)", x509);
+        return -1;
+    }
+
+    if (!basic_constraints->ca) {
+        // Path length constraints are only valid for CA certificates.
+        // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an error condition.
+        JNI_TRACE("get_X509_ex_path(%p) => -1 (not a CA)", x509);
+        return -1;
+    }
+
+    if (basic_constraints->pathlen->type == V_ASN1_NEG_INTEGER) {
+        // Path length constraints may not be negative.
+        // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an error condition.
+        JNI_TRACE("get_X509_ex_path(%p) => -1 (negative)", x509);
+        return -1;
+    }
+
+    long pathlen = ASN1_INTEGER_get(basic_constraints->pathlen);
+    if (pathlen == -1 || pathlen > INT_MAX) {
+        // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an error condition.
+        // If the integer overflows, the certificate is effectively unconstrained. Reporting no
+        // constraint is plausible, but Chromium rejects all values above 255.
+        JNI_TRACE("get_X509_ex_path(%p) => -1 (overflow)", x509);
+        return -1;
+    }
+
+    JNI_TRACE("get_X509_ex_path(%p) => %ld", x509, pathlen);
+    return pathlen;
 }
 
 static jbyteArray NativeCrypto_X509_get_ext_oid(JNIEnv* env, jclass, jlong x509Ref,
@@ -5808,8 +6212,8 @@
             env, revoked, oidString);
 }
 
-template <typename T, typename C, C T::*member, int (*get_ext_by_critical_func)(T*, int, int),
-          X509_EXTENSION* (*get_ext_func)(T*, int)>
+template <typename T, int (*get_ext_by_critical_func)(const T*, int, int),
+          X509_EXTENSION* (*get_ext_func)(const T*, int)>
 static jobjectArray get_X509Type_ext_oids(JNIEnv* env, jlong x509Ref, jint critical) {
     T* x509 = reinterpret_cast<T*>(static_cast<uintptr_t>(x509Ref));
     JNI_TRACE("get_X509Type_ext_oids(%p, %d)", x509, critical);
@@ -5819,11 +6223,6 @@
         JNI_TRACE("get_X509Type_ext_oids(%p, %d) => x509 == null", x509, critical);
         return nullptr;
     }
-    if (member != nullptr && x509->*member == nullptr) {
-        conscrypt::jniutil::throwNullPointerException(env, "x509->*member == null");
-        JNI_TRACE("get_X509Type_ext_oids(%p, %d) => x509->*member == null", x509, critical);
-        return nullptr;
-    }
 
     int lastPos = -1;
     int count = 0;
@@ -5845,7 +6244,8 @@
     while ((lastPos = get_ext_by_critical_func(x509, critical, lastPos)) != -1) {
         X509_EXTENSION* ext = get_ext_func(x509, lastPos);
 
-        ScopedLocalRef<jstring> extOid(env, ASN1_OBJECT_to_OID_string(env, ext->object));
+        ScopedLocalRef<jstring> extOid(
+                env, ASN1_OBJECT_to_OID_string(env, X509_EXTENSION_get_object(ext)));
         if (extOid.get() == nullptr) {
             JNI_TRACE("get_X509Type_ext_oids(%p) => couldn't get OID", x509);
             return nullptr;
@@ -5863,8 +6263,8 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     // NOLINTNEXTLINE(runtime/int)
     JNI_TRACE("get_X509_ext_oids(0x%llx, %d)", (long long)x509Ref, critical);
-    return get_X509Type_ext_oids<X509, decltype(X509::cert_info), &X509::cert_info,
-            X509_get_ext_by_critical, X509_get_ext>(env, x509Ref, critical);
+    return get_X509Type_ext_oids<X509, X509_get_ext_by_critical, X509_get_ext>(env, x509Ref,
+                                                                               critical);
 }
 
 static jobjectArray NativeCrypto_get_X509_CRL_ext_oids(JNIEnv* env, jclass, jlong x509CrlRef,
@@ -5873,8 +6273,8 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     // NOLINTNEXTLINE(runtime/int)
     JNI_TRACE("get_X509_CRL_ext_oids(0x%llx, %d)", (long long)x509CrlRef, critical);
-    return get_X509Type_ext_oids<X509_CRL, decltype(X509_CRL::crl), &X509_CRL::crl,
-            X509_CRL_get_ext_by_critical, X509_CRL_get_ext>(env, x509CrlRef, critical);
+    return get_X509Type_ext_oids<X509_CRL, X509_CRL_get_ext_by_critical, X509_CRL_get_ext>(
+            env, x509CrlRef, critical);
 }
 
 static jobjectArray NativeCrypto_get_X509_REVOKED_ext_oids(JNIEnv* env, jclass,
@@ -5882,8 +6282,8 @@
     CHECK_ERROR_QUEUE_ON_RETURN;
     // NOLINTNEXTLINE(runtime/int)
     JNI_TRACE("get_X509_CRL_ext_oids(0x%llx, %d)", (long long)x509RevokedRef, critical);
-    return get_X509Type_ext_oids<X509_REVOKED, decltype(X509_REVOKED::extensions), nullptr,
-            X509_REVOKED_get_ext_by_critical, X509_REVOKED_get_ext>(env, x509RevokedRef, critical);
+    return get_X509Type_ext_oids<X509_REVOKED, X509_REVOKED_get_ext_by_critical,
+                                 X509_REVOKED_get_ext>(env, x509RevokedRef, critical);
 }
 
 /**
@@ -9029,7 +9429,7 @@
     bssl::UniquePtr<ASN1_INTEGER> serial_number(
             c2i_ASN1_INTEGER(nullptr, &p,
                              static_cast<long>(CBS_len(&serial))));  // NOLINT(runtime/int)
-    ASN1_INTEGER* expected_serial_number = X509_get_serialNumber(x509);
+    const ASN1_INTEGER* expected_serial_number = X509_get_serialNumber(x509);
     if (serial_number.get() == nullptr ||
         ASN1_INTEGER_cmp(expected_serial_number, serial_number.get()) != 0) {
         return false;
@@ -9043,15 +9443,16 @@
 
     // Hash the issuer's name and compare the hash with the one from the Cert ID
     uint8_t md[EVP_MAX_MD_SIZE];
-    X509_NAME* issuer_name = X509_get_subject_name(issuerX509);
+    const X509_NAME* issuer_name = X509_get_subject_name(issuerX509);
     if (!X509_NAME_digest(issuer_name, digest, md, nullptr) ||
         !CBS_mem_equal(&issuer_name_hash, md, EVP_MD_size(digest))) {
         return false;
     }
 
     // Same thing with the issuer's key
-    ASN1_BIT_STRING* issuer_key = X509_get0_pubkey_bitstr(issuerX509);
-    if (!EVP_Digest(issuer_key->data, static_cast<size_t>(issuer_key->length), md, nullptr, digest,
+    const ASN1_BIT_STRING* issuer_key = X509_get0_pubkey_bitstr(issuerX509);
+    if (!EVP_Digest(ASN1_STRING_get0_data(issuer_key),
+                    static_cast<size_t>(ASN1_STRING_length(issuer_key)), md, nullptr, digest,
                     nullptr) ||
         !CBS_mem_equal(&issuer_key_hash, md, EVP_MD_size(digest))) {
         return false;
@@ -9174,20 +9575,6 @@
 }
 
 /*
- * X509v3_get_ext_by_OBJ and X509v3_get_ext take const arguments, unlike the other *_get_ext
- * functions.
- * This means they cannot be used with X509Type_get_ext_oid, so these wrapper functions are used
- * instead.
- */
-static int _X509v3_get_ext_by_OBJ(X509_EXTENSIONS* exts, ASN1_OBJECT* obj, int lastpos) {
-    return X509v3_get_ext_by_OBJ(exts, obj, lastpos);
-}
-
-static X509_EXTENSION* _X509v3_get_ext(X509_EXTENSIONS* exts, int loc) {
-    return X509v3_get_ext(exts, loc);
-}
-
-/*
     public static native byte[] get_ocsp_single_extension(byte[] ocspData, String oid,
                                                           long x509Ref, long issuerX509Ref);
 */
@@ -9243,7 +9630,7 @@
         return nullptr;
     }
 
-    return X509Type_get_ext_oid<X509_EXTENSIONS, _X509v3_get_ext_by_OBJ, _X509v3_get_ext>(
+    return X509Type_get_ext_oid<X509_EXTENSIONS, X509v3_get_ext_by_OBJ, X509v3_get_ext>(
             env, x509_exts.get(), oid);
 }
 
@@ -9277,7 +9664,7 @@
 
 static jint NativeCrypto_SSL_pending_written_bytes_in_BIO(JNIEnv* env, jclass, jlong bio_address) {
     CHECK_ERROR_QUEUE_ON_RETURN;
-    BIO* bio = to_SSL_BIO(env, bio_address, true);
+    BIO* bio = to_BIO(env, bio_address);
     if (bio == nullptr) {
         return 0;
     }
@@ -9581,7 +9968,7 @@
                 ssl);
         return -1;
     }
-    BIO* bio = to_SSL_BIO(env, bioRef, true);
+    BIO* bio = to_BIO(env, bioRef);
     if (bio == nullptr) {
         return -1;
     }
@@ -9619,73 +10006,6 @@
     return result;
 }
 
-static int NativeCrypto_ENGINE_SSL_write_BIO_heap(JNIEnv* env, jclass, jlong ssl_address,
-                                                  CONSCRYPT_UNUSED jobject ssl_holder, jlong bioRef,
-                                                  jbyteArray sourceJava, jint sourceOffset,
-                                                  jint sourceLength, jobject shc) {
-    CHECK_ERROR_QUEUE_ON_RETURN;
-    SSL* ssl = to_SSL(env, ssl_address, true);
-    if (ssl == nullptr) {
-        return -1;
-    }
-    if (shc == nullptr) {
-        conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null");
-        JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_write_BIO_heap => sslHandshakeCallbacks == null",
-                  ssl);
-        return -1;
-    }
-    BIO* bio = to_SSL_BIO(env, bioRef, true);
-    if (bio == nullptr) {
-        return -1;
-    }
-    if (sourceLength < 0 || BIO_ctrl_get_write_guarantee(bio) < static_cast<size_t>(sourceLength)) {
-        // The network BIO couldn't handle the entire write. Don't write anything, so that we
-        // only process one packet at a time.
-        return 0;
-    }
-    ScopedByteArrayRO source(env, sourceJava);
-    if (source.get() == nullptr) {
-        JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_write_BIO_heap => threw exception", ssl);
-        return -1;
-    }
-    if (ARRAY_OFFSET_LENGTH_INVALID(source, sourceOffset, sourceLength)) {
-        JNI_TRACE(
-                "ssl=%p NativeCrypto_ENGINE_SSL_write_BIO_heap => sourceOffset=%d, "
-                "sourceLength=%d, size=%zd",
-                ssl, sourceOffset, sourceLength, source.size());
-        conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException",
-                                           nullptr);
-        return -1;
-    }
-
-    AppData* appData = toAppData(ssl);
-    if (appData == nullptr) {
-        conscrypt::jniutil::throwSSLExceptionStr(env, "Unable to retrieve application data");
-        ERR_clear_error();
-        JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_write_BIO_heap appData => null", ssl);
-        return -1;
-    }
-    if (!appData->setCallbackState(env, shc, nullptr)) {
-        conscrypt::jniutil::throwSSLExceptionStr(env, "Unable to set appdata callback");
-        ERR_clear_error();
-        JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_write_BIO_heap => exception", ssl);
-        return -1;
-    }
-
-    errno = 0;
-
-    int result = BIO_write(bio, reinterpret_cast<const char*>(source.get()) + sourceOffset,
-                           sourceLength);
-    appData->clearCallbackState();
-    JNI_TRACE(
-            "ssl=%p NativeCrypto_ENGINE_SSL_write_BIO_heap bio=%p source=%p sourceOffset=%d "
-            "sourceLength=%d shc=%p => ret=%d",
-            ssl, bio, source.get(), sourceOffset, sourceLength, shc, result);
-    JNI_TRACE_PACKET_DATA(ssl, 'O', reinterpret_cast<const char*>(source.get()) + sourceOffset,
-                          static_cast<size_t>(result));
-    return result;
-}
-
 static int NativeCrypto_ENGINE_SSL_read_BIO_direct(JNIEnv* env, jclass, jlong ssl_address,
                                                    CONSCRYPT_UNUSED jobject ssl_holder,
                                                    jlong bioRef, jlong address, jint outputSize,
@@ -9701,7 +10021,7 @@
                   ssl);
         return -1;
     }
-    BIO* bio = to_SSL_BIO(env, bioRef, true);
+    BIO* bio = to_BIO(env, bioRef);
     if (bio == nullptr) {
         return -1;
     }
@@ -9737,67 +10057,6 @@
     return result;
 }
 
-static int NativeCrypto_ENGINE_SSL_read_BIO_heap(JNIEnv* env, jclass, jlong ssl_address,
-                                                 CONSCRYPT_UNUSED jobject ssl_holder, jlong bioRef,
-                                                 jbyteArray destJava, jint destOffset,
-                                                 jint destLength, jobject shc) {
-    CHECK_ERROR_QUEUE_ON_RETURN;
-    SSL* ssl = to_SSL(env, ssl_address, true);
-    if (ssl == nullptr) {
-        return -1;
-    }
-    if (shc == nullptr) {
-        conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null");
-        JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_read_BIO_heap => sslHandshakeCallbacks == null",
-                  ssl);
-        return -1;
-    }
-    BIO* bio = to_SSL_BIO(env, bioRef, true);
-    if (bio == nullptr) {
-        return -1;
-    }
-    ScopedByteArrayRW dest(env, destJava);
-    if (dest.get() == nullptr) {
-        JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_read_BIO_heap => threw exception", ssl);
-        return -1;
-    }
-    if (ARRAY_OFFSET_LENGTH_INVALID(dest, destOffset, destLength)) {
-        JNI_TRACE(
-                "ssl=%p NativeCrypto_ENGINE_SSL_read_BIO_heap => destOffset=%d, destLength=%d, "
-                "size=%zd",
-                ssl, destOffset, destLength, dest.size());
-        conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException",
-                                           nullptr);
-        return -1;
-    }
-
-    AppData* appData = toAppData(ssl);
-    if (appData == nullptr) {
-        conscrypt::jniutil::throwSSLExceptionStr(env, "Unable to retrieve application data");
-        ERR_clear_error();
-        JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_read_BIO_heap appData => null", ssl);
-        return -1;
-    }
-    if (!appData->setCallbackState(env, shc, nullptr)) {
-        conscrypt::jniutil::throwSSLExceptionStr(env, "Unable to set appdata callback");
-        ERR_clear_error();
-        JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_read_BIO_heap => exception", ssl);
-        return -1;
-    }
-
-    errno = 0;
-
-    int result = BIO_read(bio, reinterpret_cast<char*>(dest.get()) + destOffset, destLength);
-    appData->clearCallbackState();
-    JNI_TRACE(
-            "ssl=%p NativeCrypto_ENGINE_SSL_read_BIO_heap bio=%p dest=%p destOffset=%d "
-            "destLength=%d shc=%p => ret=%d",
-            ssl, bio, dest.get(), destOffset, destLength, shc, result);
-    JNI_TRACE_PACKET_DATA(ssl, 'I', reinterpret_cast<char*>(dest.get()) + destOffset,
-                          static_cast<size_t>(result));
-    return result;
-}
-
 static void NativeCrypto_ENGINE_SSL_force_read(JNIEnv* env, jclass, jlong ssl_address,
                                                CONSCRYPT_UNUSED jobject ssl_holder, jobject shc) {
     CHECK_ERROR_QUEUE_ON_RETURN;
@@ -9921,13 +10180,25 @@
     return result;
 }
 
+/**
+ * public static native bool BoringSSL_FIPS_mode();
+ */
+static jboolean NativeCrypto_usesBoringSSL_FIPS_mode() {
+    return FIPS_mode();
+}
+
 // TESTING METHODS BEGIN
 
 static int NativeCrypto_BIO_read(JNIEnv* env, jclass, jlong bioRef, jbyteArray outputJavaBytes) {
     CHECK_ERROR_QUEUE_ON_RETURN;
-    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    BIO* bio = to_BIO(env, bioRef);
     JNI_TRACE("BIO_read(%p, %p)", bio, outputJavaBytes);
 
+    if (bio == nullptr) {
+        JNI_TRACE("BIO_read(%p, %p) => bio == null", bio, outputJavaBytes);
+        return 0;
+    }
+
     if (outputJavaBytes == nullptr) {
         conscrypt::jniutil::throwNullPointerException(env, "output == null");
         JNI_TRACE("BIO_read(%p, %p) => output == null", bio, outputJavaBytes);
@@ -9958,9 +10229,13 @@
 static void NativeCrypto_BIO_write(JNIEnv* env, jclass, jlong bioRef, jbyteArray inputJavaBytes,
                                    jint offset, jint length) {
     CHECK_ERROR_QUEUE_ON_RETURN;
-    BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
+    BIO* bio = to_BIO(env, bioRef);
     JNI_TRACE("BIO_write(%p, %p, %d, %d)", bio, inputJavaBytes, offset, length);
 
+    if (bio == nullptr) {
+        return;
+    }
+
     if (inputJavaBytes == nullptr) {
         conscrypt::jniutil::throwNullPointerException(env, "input == null");
         return;
@@ -10073,6 +10348,7 @@
 #define REF_EVP_PKEY "L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/NativeRef$EVP_PKEY;"
 #define REF_EVP_PKEY_CTX "L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/NativeRef$EVP_PKEY_CTX;"
 #define REF_HMAC_CTX "L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/NativeRef$HMAC_CTX;"
+#define REF_CMAC_CTX "L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/NativeRef$CMAC_CTX;"
 #define REF_BIO_IN_STREAM "L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLBIOInputStream;"
 #define REF_X509 "L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLX509Certificate;"
 #define REF_X509_CRL "L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLX509CRL;"
@@ -10080,6 +10356,12 @@
 #define REF_SSL_CTX "L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/AbstractSessionContext;"
 static JNINativeMethod sNativeCryptoMethods[] = {
         CONSCRYPT_NATIVE_METHOD(clinit, "()V"),
+        CONSCRYPT_NATIVE_METHOD(CMAC_CTX_new, "()J"),
+        CONSCRYPT_NATIVE_METHOD(CMAC_CTX_free, "(J)V"),
+        CONSCRYPT_NATIVE_METHOD(CMAC_Init, "(" REF_CMAC_CTX "[B)V"),
+        CONSCRYPT_NATIVE_METHOD(CMAC_Update, "(" REF_CMAC_CTX "[BII)V"),
+        CONSCRYPT_NATIVE_METHOD(CMAC_UpdateDirect, "(" REF_CMAC_CTX "JI)V"),
+        CONSCRYPT_NATIVE_METHOD(CMAC_Final, "(" REF_CMAC_CTX ")[B"),
         CONSCRYPT_NATIVE_METHOD(EVP_PKEY_new_RSA, "([B[B[B[B[B[B[B[B)J"),
         CONSCRYPT_NATIVE_METHOD(EVP_PKEY_new_EC_KEY, "(" REF_EC_GROUP REF_EC_POINT "[B)J"),
         CONSCRYPT_NATIVE_METHOD(EVP_PKEY_type, "(" REF_EVP_PKEY ")I"),
@@ -10130,6 +10412,8 @@
         CONSCRYPT_NATIVE_METHOD(ECDSA_size, "(" REF_EVP_PKEY ")I"),
         CONSCRYPT_NATIVE_METHOD(ECDSA_sign, "([B[B" REF_EVP_PKEY ")I"),
         CONSCRYPT_NATIVE_METHOD(ECDSA_verify, "([B[B" REF_EVP_PKEY ")I"),
+        CONSCRYPT_NATIVE_METHOD(X25519, "([B[B[B)Z"),
+        CONSCRYPT_NATIVE_METHOD(X25519_keypair, "([B[B)V"),
         CONSCRYPT_NATIVE_METHOD(EVP_MD_CTX_create, "()J"),
         CONSCRYPT_NATIVE_METHOD(EVP_MD_CTX_cleanup, "(" REF_EVP_MD_CTX ")V"),
         CONSCRYPT_NATIVE_METHOD(EVP_MD_CTX_destroy, "(J)V"),
@@ -10179,6 +10463,8 @@
         CONSCRYPT_NATIVE_METHOD(EVP_AEAD_nonce_length, "(J)I"),
         CONSCRYPT_NATIVE_METHOD(EVP_AEAD_CTX_seal, "(J[BI[BI[B[BII[B)I"),
         CONSCRYPT_NATIVE_METHOD(EVP_AEAD_CTX_open, "(J[BI[BI[B[BII[B)I"),
+        CONSCRYPT_NATIVE_METHOD(EVP_AEAD_CTX_seal_buf, "(J[BILjava/nio/ByteBuffer;[BLjava/nio/ByteBuffer;[B)I"),
+        CONSCRYPT_NATIVE_METHOD(EVP_AEAD_CTX_open_buf, "(J[BILjava/nio/ByteBuffer;[BLjava/nio/ByteBuffer;[B)I"),
         CONSCRYPT_NATIVE_METHOD(HMAC_CTX_new, "()J"),
         CONSCRYPT_NATIVE_METHOD(HMAC_CTX_free, "(J)V"),
         CONSCRYPT_NATIVE_METHOD(HMAC_Init_ex, "(" REF_HMAC_CTX "[BJ)V"),
@@ -10200,7 +10486,6 @@
         CONSCRYPT_NATIVE_METHOD(ASN1_seq_unpack_X509_bio, "(J)[J"),
         CONSCRYPT_NATIVE_METHOD(ASN1_seq_pack_X509, "([J)[B"),
         CONSCRYPT_NATIVE_METHOD(X509_free, "(J" REF_X509 ")V"),
-        CONSCRYPT_NATIVE_METHOD(X509_dup, "(J" REF_X509 ")J"),
         CONSCRYPT_NATIVE_METHOD(X509_cmp, "(J" REF_X509 "J" REF_X509 ")I"),
         CONSCRYPT_NATIVE_METHOD(X509_print_ex, "(JJ" REF_X509 "JJ)V"),
         CONSCRYPT_NATIVE_METHOD(X509_get_pubkey, "(J" REF_X509 ")J"),
@@ -10216,7 +10501,6 @@
         CONSCRYPT_NATIVE_METHOD(get_X509_ex_pathlen, "(J" REF_X509 ")I"),
         CONSCRYPT_NATIVE_METHOD(X509_get_ext_oid, "(J" REF_X509 "Ljava/lang/String;)[B"),
         CONSCRYPT_NATIVE_METHOD(X509_CRL_get_ext_oid, "(J" REF_X509_CRL "Ljava/lang/String;)[B"),
-        CONSCRYPT_NATIVE_METHOD(X509_delete_ext, "(J" REF_X509 "Ljava/lang/String;)V"),
         CONSCRYPT_NATIVE_METHOD(get_X509_CRL_crl_enc, "(J" REF_X509_CRL ")[B"),
         CONSCRYPT_NATIVE_METHOD(X509_CRL_verify, "(J" REF_X509_CRL REF_EVP_PKEY ")V"),
         CONSCRYPT_NATIVE_METHOD(X509_CRL_get_lastUpdate, "(J" REF_X509_CRL ")J"),
@@ -10235,7 +10519,8 @@
         CONSCRYPT_NATIVE_METHOD(X509_get_version, "(J" REF_X509 ")J"),
         CONSCRYPT_NATIVE_METHOD(X509_get_serialNumber, "(J" REF_X509 ")[B"),
         CONSCRYPT_NATIVE_METHOD(X509_verify, "(J" REF_X509 REF_EVP_PKEY ")V"),
-        CONSCRYPT_NATIVE_METHOD(get_X509_cert_info_enc, "(J" REF_X509 ")[B"),
+        CONSCRYPT_NATIVE_METHOD(get_X509_tbs_cert, "(J" REF_X509 ")[B"),
+        CONSCRYPT_NATIVE_METHOD(get_X509_tbs_cert_without_ext, "(J" REF_X509 "Ljava/lang/String;)[B"),
         CONSCRYPT_NATIVE_METHOD(get_X509_signature, "(J" REF_X509 ")[B"),
         CONSCRYPT_NATIVE_METHOD(get_X509_CRL_signature, "(J" REF_X509_CRL ")[B"),
         CONSCRYPT_NATIVE_METHOD(get_X509_ex_flags, "(J" REF_X509 ")I"),
@@ -10360,10 +10645,9 @@
         CONSCRYPT_NATIVE_METHOD(ENGINE_SSL_write_direct, "(J" REF_SSL "JI" SSL_CALLBACKS ")I"),
         CONSCRYPT_NATIVE_METHOD(ENGINE_SSL_write_BIO_direct, "(J" REF_SSL "JJI" SSL_CALLBACKS ")I"),
         CONSCRYPT_NATIVE_METHOD(ENGINE_SSL_read_BIO_direct, "(J" REF_SSL "JJI" SSL_CALLBACKS ")I"),
-        CONSCRYPT_NATIVE_METHOD(ENGINE_SSL_write_BIO_heap, "(J" REF_SSL "J[BII" SSL_CALLBACKS ")I"),
-        CONSCRYPT_NATIVE_METHOD(ENGINE_SSL_read_BIO_heap, "(J" REF_SSL "J[BII" SSL_CALLBACKS ")I"),
         CONSCRYPT_NATIVE_METHOD(ENGINE_SSL_force_read, "(J" REF_SSL SSL_CALLBACKS ")V"),
         CONSCRYPT_NATIVE_METHOD(ENGINE_SSL_shutdown, "(J" REF_SSL SSL_CALLBACKS ")V"),
+        CONSCRYPT_NATIVE_METHOD(usesBoringSSL_FIPS_mode, "()Z"),
 
         // Used for testing only.
         CONSCRYPT_NATIVE_METHOD(BIO_read, "(J[B)I"),
diff --git a/common/src/jni/main/include/conscrypt/jniutil.h b/common/src/jni/main/include/conscrypt/jniutil.h
index 8465c05..6f55608 100644
--- a/common/src/jni/main/include/conscrypt/jniutil.h
+++ b/common/src/jni/main/include/conscrypt/jniutil.h
@@ -40,6 +40,8 @@
 extern jclass inputStreamClass;
 extern jclass outputStreamClass;
 extern jclass stringClass;
+extern jclass byteBufferClass;
+extern jclass bufferClass;
 
 extern jfieldID nativeRef_address;
 
@@ -49,6 +51,8 @@
 extern jmethodID openSslInputStream_readLineMethod;
 extern jmethodID outputStream_writeMethod;
 extern jmethodID outputStream_flushMethod;
+extern jmethodID buffer_positionMethod;
+extern jmethodID buffer_limitMethod;
 
 /**
  * Initializes the JNI constants from the environment.
diff --git a/common/src/main/java/org/conscrypt/AbstractSessionContext.java b/common/src/main/java/org/conscrypt/AbstractSessionContext.java
index 70df88f..c527b58 100644
--- a/common/src/main/java/org/conscrypt/AbstractSessionContext.java
+++ b/common/src/main/java/org/conscrypt/AbstractSessionContext.java
@@ -41,7 +41,6 @@
 
     final long sslCtxNativePointer = NativeCrypto.SSL_CTX_new();
 
-    @SuppressWarnings("serial")
     private final Map<ByteArray, NativeSslSession> sessions =
             new LinkedHashMap<ByteArray, NativeSslSession>() {
                 @Override
@@ -76,7 +75,7 @@
         // Make a copy of the IDs.
         final Iterator<NativeSslSession> iter;
         synchronized (sessions) {
-            iter = Arrays.asList(sessions.values().toArray(new NativeSslSession[sessions.size()]))
+            iter = Arrays.asList(sessions.values().toArray(new NativeSslSession[0]))
                     .iterator();
         }
         return new Enumeration<byte[]>() {
@@ -188,6 +187,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     protected void finalize() throws Throwable {
         try {
             NativeCrypto.SSL_CTX_free(sslCtxNativePointer, this);
diff --git a/common/src/main/java/org/conscrypt/ActiveSession.java b/common/src/main/java/org/conscrypt/ActiveSession.java
index b00a3fa..4e3a4e8 100644
--- a/common/src/main/java/org/conscrypt/ActiveSession.java
+++ b/common/src/main/java/org/conscrypt/ActiveSession.java
@@ -33,7 +33,7 @@
  */
 final class ActiveSession implements ConscryptSession {
     private final NativeSsl ssl;
-    private AbstractSessionContext sessionContext;
+    private final AbstractSessionContext sessionContext;
     private byte[] id;
     private long creationTime;
     private String protocol;
@@ -41,6 +41,7 @@
     private String peerHost;
     private int peerPort = -1;
     private long lastAccessedTime = 0;
+    @SuppressWarnings("deprecation")
     private volatile javax.security.cert.X509Certificate[] peerCertificateChain;
     private X509Certificate[] localCertificates;
     private X509Certificate[] peerCertificates;
@@ -108,7 +109,7 @@
     @Override
     public List<byte[]> getStatusResponses() {
         if (peerCertificateOcspData == null) {
-            return Collections.<byte[]>emptyList();
+            return Collections.emptyList();
         }
 
         return Collections.singletonList(peerCertificateOcspData.clone());
@@ -205,8 +206,13 @@
      *         be verified.
      */
     @Override
+    @SuppressWarnings("deprecation") // Public API
     public javax.security.cert.X509Certificate[] getPeerCertificateChain()
             throws SSLPeerUnverifiedException {
+        if (!Platform.isJavaxCertificateSupported()) {
+            throw new UnsupportedOperationException("Use getPeerCertificates() instead");
+        }
+
         checkPeerCertificatesPresent();
         // TODO(nathanmittler): Should we clone?
         javax.security.cert.X509Certificate[] result = peerCertificateChain;
diff --git a/common/src/main/java/org/conscrypt/BufferUtils.java b/common/src/main/java/org/conscrypt/BufferUtils.java
new file mode 100644
index 0000000..e42506d
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/BufferUtils.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2021 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 java.lang.Math.min;
+import static org.conscrypt.Preconditions.checkArgument;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Utility methods for dealing with arrays of ByteBuffers.
+ *
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class BufferUtils {
+    private BufferUtils() {}
+
+    /**
+     * Throws {@link IllegalArgumentException} if any of the buffers in the array are null.
+     */
+    public static void checkNotNull(ByteBuffer[] buffers) {
+        for (ByteBuffer buffer : buffers) {
+            if (buffer == null) {
+                throw new IllegalArgumentException("Null buffer in array");
+            }
+        }
+    }
+
+    /**
+     * Returns the total number of bytes remaining in the buffer array.
+     */
+    public static long remaining(ByteBuffer[] buffers) {
+        long size = 0;
+        for (ByteBuffer buffer : buffers) {
+            size += buffer.remaining();
+        }
+        return size;
+    }
+
+    /**
+     * Marks {@code toConsume} bytes of data as consumed from the buffer array.
+     *
+     * @throws IllegalArgumentException if there are fewer than {@code toConsume} bytes remaining
+     */
+    public static void consume(ByteBuffer[] sourceBuffers, int toConsume) {
+        for (ByteBuffer sourceBuffer : sourceBuffers) {
+            int amount = min(sourceBuffer.remaining(), toConsume);
+            if (amount > 0) {
+                sourceBuffer.position(sourceBuffer.position() + amount);
+                toConsume -= amount;
+                if (toConsume == 0) {
+                    break;
+                }
+            }
+        }
+        if (toConsume > 0) {
+            throw new IllegalArgumentException("toConsume > data size");
+        }
+    }
+
+    /**
+     * Looks for a buffer in the buffer array which EITHER is larger than {@code minSize} AND
+     * has no preceding non-empty buffers OR is the only non-empty buffer in the array.
+     */
+    public static ByteBuffer getBufferLargerThan(ByteBuffer[] buffers, int minSize) {
+        int length = buffers.length;
+        for (int i = 0; i < length; i++) {
+            ByteBuffer buffer = buffers[i];
+            int remaining = buffer.remaining();
+            if (remaining > 0) {
+                if (remaining >= minSize) {
+                    return buffer;
+                }
+                for (int j = i + 1; j < length; j++) {
+                    if (buffers[j].remaining() > 0) {
+                        return null;
+                    }
+                }
+                return buffer;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Copies up to {@code maxAmount} bytes from a buffer array to {@code destination}.
+     * The copied data is <b>not</b> marked as consumed from the source buffers, on the
+     * assumption the copy will be passed to some method which will consume between 0 and
+     * {@code maxAmount} bytes which can then be reflected in the source array using the
+     * {@code consume()} method.
+     *
+     */
+    public static ByteBuffer copyNoConsume(ByteBuffer[] buffers, ByteBuffer destination, int maxAmount) {
+        checkArgument(destination.remaining() >= maxAmount, "Destination buffer too small");
+        int needed = maxAmount;
+        for (ByteBuffer buffer : buffers) {
+            int remaining = buffer.remaining();
+            if (remaining > 0) {
+                // If this buffer can fit completely then copy it all, otherwise temporarily
+                // adjust its limit to fill so as to the output buffer completely
+                int oldPosition = buffer.position();
+                if (remaining <= needed) {
+                    destination.put(buffer);
+                    needed -= remaining;
+                } else {
+                    int oldLimit = buffer.limit();
+                    buffer.limit(buffer.position() + needed);
+                    destination.put(buffer);
+                    buffer.limit(oldLimit);
+                    needed = 0;
+                }
+                // Restore the buffer's position, the data won't get marked as consumed until
+                // outputBuffer has been successfully consumed.
+                buffer.position(oldPosition);
+                if (needed == 0) {
+                    break;
+                }
+            }
+        }
+        destination.flip();
+        return destination;
+    }
+}
diff --git a/common/src/main/java/org/conscrypt/ByteArray.java b/common/src/main/java/org/conscrypt/ByteArray.java
index afdab14..bfc544f 100644
--- a/common/src/main/java/org/conscrypt/ByteArray.java
+++ b/common/src/main/java/org/conscrypt/ByteArray.java
@@ -41,6 +41,9 @@
             return false;
         }
         ByteArray lhs = (ByteArray) o;
+        if (hashCode != lhs.hashCode) {
+            return false;
+        }
         return Arrays.equals(bytes, lhs.bytes);
     }
 }
diff --git a/common/src/main/java/org/conscrypt/CertBlacklist.java b/common/src/main/java/org/conscrypt/CertBlocklist.java
similarity index 86%
rename from common/src/main/java/org/conscrypt/CertBlacklist.java
rename to common/src/main/java/org/conscrypt/CertBlocklist.java
index 6ef7e81..024947b 100644
--- a/common/src/main/java/org/conscrypt/CertBlacklist.java
+++ b/common/src/main/java/org/conscrypt/CertBlocklist.java
@@ -22,15 +22,15 @@
 /**
  * A set of certificates that are blacklisted from trust.
  */
-public interface CertBlacklist {
+public interface CertBlocklist {
 
     /**
      * Returns whether the given public key is in the blacklist.
      */
-    boolean isPublicKeyBlackListed(PublicKey publicKey);
+    boolean isPublicKeyBlockListed(PublicKey publicKey);
 
     /**
      * Returns whether the given serial number is in the blacklist.
      */
-    boolean isSerialNumberBlackListed(BigInteger serial);
+    boolean isSerialNumberBlockListed(BigInteger serial);
 }
diff --git a/common/src/main/java/org/conscrypt/CertificatePriorityComparator.java b/common/src/main/java/org/conscrypt/CertificatePriorityComparator.java
index c364da5..eb62fca 100644
--- a/common/src/main/java/org/conscrypt/CertificatePriorityComparator.java
+++ b/common/src/main/java/org/conscrypt/CertificatePriorityComparator.java
@@ -75,6 +75,7 @@
     }
 
     @Override
+    @SuppressWarnings("JdkObsolete")  // Certificate uses Date
     public int compare(X509Certificate lhs, X509Certificate rhs) {
         int result;
         boolean lhsSelfSigned = lhs.getSubjectDN().equals(lhs.getIssuerDN());
diff --git a/common/src/main/java/org/conscrypt/Conscrypt.java b/common/src/main/java/org/conscrypt/Conscrypt.java
index 42440fd..8b2f362 100644
--- a/common/src/main/java/org/conscrypt/Conscrypt.java
+++ b/common/src/main/java/org/conscrypt/Conscrypt.java
@@ -21,7 +21,9 @@
 import java.security.KeyManagementException;
 import java.security.PrivateKey;
 import java.security.Provider;
+import java.security.cert.X509Certificate;
 import java.util.Properties;
+import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLContextSpi;
@@ -29,6 +31,7 @@
 import javax.net.ssl.SSLEngineResult;
 import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSessionContext;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
@@ -55,6 +58,17 @@
         }
     }
 
+    /**
+     * Return {@code true} if BoringSSL has been built in FIPS mode.
+     */
+    public static boolean isBoringSSLFIPSBuild() {
+        try {
+            return NativeCrypto.usesBoringSSL_FIPS_mode();
+        } catch (Throwable e) {
+            return false;
+        }
+    }
+
     public static class Version {
         private final int major;
         private final int minor;
@@ -88,6 +102,7 @@
                 patch = Integer.parseInt(props.getProperty("org.conscrypt.version.patch", "-1"));
             }
         } catch (IOException e) {
+            // TODO(prb): This should probably be fatal or have some fallback behaviour
         } finally {
             IoUtils.closeQuietly(stream);
         }
@@ -781,5 +796,15 @@
         return toConscrypt(trustManager).getHostnameVerifier();
     }
 
-
+    /**
+     * Wraps the HttpsURLConnection.HostnameVerifier into a ConscryptHostnameVerifier
+     */
+    public static ConscryptHostnameVerifier wrapHostnameVerifier(final HostnameVerifier verifier) {
+        return  new ConscryptHostnameVerifier() {
+            @Override
+            public boolean verify(X509Certificate[] certificates, String hostname, SSLSession session) {
+                return verifier.verify(hostname, session);
+            }
+        };
+    }
 }
diff --git a/common/src/main/java/org/conscrypt/ConscryptEngine.java b/common/src/main/java/org/conscrypt/ConscryptEngine.java
index 5de5396..2296b90 100644
--- a/common/src/main/java/org/conscrypt/ConscryptEngine.java
+++ b/common/src/main/java/org/conscrypt/ConscryptEngine.java
@@ -76,6 +76,7 @@
 import java.security.cert.X509Certificate;
 import java.security.interfaces.ECKey;
 import java.security.spec.ECParameterSpec;
+import java.util.Arrays;
 import javax.crypto.SecretKey;
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLEngineResult;
@@ -89,7 +90,6 @@
 import javax.net.ssl.X509KeyManager;
 import javax.net.ssl.X509TrustManager;
 import javax.security.auth.x500.X500Principal;
-import org.conscrypt.ExternalSession.Provider;
 import org.conscrypt.NativeRef.SSL_SESSION;
 import org.conscrypt.NativeSsl.BioWrapper;
 import org.conscrypt.SSLParametersImpl.AliasChooser;
@@ -100,6 +100,7 @@
 final class ConscryptEngine extends AbstractConscryptEngine implements NativeCrypto.SSLHandshakeCallbacks,
                                                          SSLParametersImpl.AliasChooser,
                                                          SSLParametersImpl.PSKCallbacks {
+
     private static final SSLEngineResult NEED_UNWRAP_OK =
             new SSLEngineResult(OK, NEED_UNWRAP, 0, 0);
     private static final SSLEngineResult NEED_UNWRAP_CLOSED =
@@ -156,7 +157,7 @@
      * The session object exposed externally from this class.
      */
     private final SSLSession externalSession =
-        Platform.wrapSSLSession(new ExternalSession(new Provider() {
+        Platform.wrapSSLSession(new ExternalSession(new ExternalSession.Provider() {
             @Override
             public ConscryptSession provideSession() {
                 return ConscryptEngine.this.provideSession();
@@ -586,7 +587,7 @@
     SSLSession handshakeSession() {
         synchronized (ssl) {
             if (state == STATE_HANDSHAKE_STARTED) {
-                return Platform.wrapSSLSession(new ExternalSession(new Provider() {
+                return Platform.wrapSSLSession(new ExternalSession(new ExternalSession.Provider() {
                     @Override
                     public ConscryptSession provideSession() {
                         return ConscryptEngine.this.provideHandshakeSession();
@@ -1391,6 +1392,11 @@
             throw new ReadOnlyBufferException();
         }
 
+        if ((srcsOffset != 0) || (srcsLength != srcs.length)) {
+            srcs = Arrays.copyOfRange(srcs, srcsOffset, srcsOffset + srcsLength);
+        }
+        BufferUtils.checkNotNull(srcs);
+
         synchronized (ssl) {
             switch (state) {
                 case STATE_MODE_SET:
@@ -1430,118 +1436,111 @@
                 // NEED_WRAP - just fall through to perform the wrap.
             }
 
-            int srcsLen = 0;
-            final int endOffset = srcsOffset + srcsLength;
-            for (int i = srcsOffset; i < endOffset; ++i) {
-                final ByteBuffer src = srcs[i];
-                if (src == null) {
-                    throw new IllegalArgumentException("srcs[" + i + "] is null");
-                }
-                if (srcsLen == SSL3_RT_MAX_PLAIN_LENGTH) {
-                    continue;
-                }
-
-                srcsLen += src.remaining();
-                if (srcsLen > SSL3_RT_MAX_PLAIN_LENGTH || srcsLen < 0) {
-                    // If srcLen > MAX_PLAINTEXT_LENGTH or secLen < 0 just set it to
-                    // MAX_PLAINTEXT_LENGTH.
-                    // This also help us to guard against overflow.
-                    // We not break out here as we still need to check for null entries in srcs[].
-                    srcsLen = SSL3_RT_MAX_PLAIN_LENGTH;
-                }
-            }
-
-            if (dst.remaining() < calculateOutNetBufSize(srcsLen)) {
+            int dataLength = (int) min(BufferUtils.remaining(srcs), SSL3_RT_MAX_PLAIN_LENGTH);
+            if (dst.remaining() < calculateOutNetBufSize(dataLength)) {
                 return new SSLEngineResult(
                     Status.BUFFER_OVERFLOW, getHandshakeStatusInternal(), 0, 0);
             }
 
             int bytesProduced = 0;
             int bytesConsumed = 0;
-        loop:
-            for (int i = srcsOffset; i < endOffset; ++i) {
-                final ByteBuffer src = srcs[i];
-                checkArgument(src != null, "srcs[%d] is null", i);
-                while (src.hasRemaining()) {
-                    final SSLEngineResult pendingNetResult;
-                    // Write plaintext application data to the SSL engine
-                    int result = writePlaintextData(
-                        src, min(src.remaining(), SSL3_RT_MAX_PLAIN_LENGTH - bytesConsumed));
-                    if (result > 0) {
-                        bytesConsumed += result;
+            if (dataLength > 0) {
+                // Try and find a single buffer to send, e.g. the first non-empty buffer has
+                // more than enough data remaining to fill a TLS record. Otherwise copy as much
+                // data as possible from the source buffers to fill a record. Note the we can't
+                // mark the data as consumed until we see how much the TLS layer actually consumes.
+                boolean isCopy = false;
+                ByteBuffer outputBuffer
+                        = BufferUtils.getBufferLargerThan(srcs, SSL3_RT_MAX_PLAIN_LENGTH);
+                if (outputBuffer == null) {
+                    // The buffer by getOrCreateLazyDirectBuffer() is also used by
+                    // writePlainTextDataHeap(), but by filling it here the write path will go via
+                    // writePlainTextDataDirect() and the cost will be approximately the same,
+                    // especially if compacting multiple non-direct buffers into a single
+                    // direct one.
+                    // TODO(): use bufferAllocator if set.
+                    // https://github.com/google/conscrypt/issues/974
+                    outputBuffer = BufferUtils.copyNoConsume(
+                            srcs, getOrCreateLazyDirectBuffer(), SSL3_RT_MAX_PLAIN_LENGTH);
+                    isCopy = true;
+                }
+                final SSLEngineResult pendingNetResult;
+                // Write plaintext application data to the SSL engine
+                int result = writePlaintextData(outputBuffer,
+                        min(SSL3_RT_MAX_PLAIN_LENGTH, outputBuffer.remaining()));
+                if (result > 0) {
+                    bytesConsumed = result;
+                    if (isCopy) {
+                        // Data was a copy, so mark it as consumed in the original buffers.
+                        BufferUtils.consume(srcs, bytesConsumed);
+                    }
 
-                        pendingNetResult = readPendingBytesFromBIO(
+                    pendingNetResult = readPendingBytesFromBIO(
                             dst, bytesConsumed, bytesProduced, handshakeStatus);
-                        if (pendingNetResult != null) {
-                            if (pendingNetResult.getStatus() != OK) {
-                                return pendingNetResult;
-                            }
-                            bytesProduced = pendingNetResult.bytesProduced();
+                    if (pendingNetResult != null) {
+                        if (pendingNetResult.getStatus() != OK) {
+                            return pendingNetResult;
                         }
-                        if (bytesConsumed == SSL3_RT_MAX_PLAIN_LENGTH) {
-                            // If we consumed the maximum amount of bytes for the plaintext length
-                            // break out of the loop and start to fill the dst buffer.
-                            break loop;
-                        }
-                    } else {
-                        int sslError = ssl.getError(result);
-                        switch (sslError) {
-                            case SSL_ERROR_ZERO_RETURN:
-                                // This means the connection was shutdown correctly, close inbound
-                                // and outbound
-                                closeAll();
-                                pendingNetResult = readPendingBytesFromBIO(
-                                        dst, bytesConsumed, bytesProduced, handshakeStatus);
-                                return pendingNetResult != null ? pendingNetResult
-                                                                : CLOSED_NOT_HANDSHAKING;
-                            case SSL_ERROR_WANT_READ:
-                                // If there is no pending data to read from BIO we should go back to
-                                // event loop and try
-                                // to read more data [1]. It is also possible that event loop will
-                                // detect the socket
-                                // has been closed. [1]
-                                // https://www.openssl.org/docs/manmaster/man3/SSL_write.html
-                                pendingNetResult = readPendingBytesFromBIO(
-                                        dst, bytesConsumed, bytesProduced, handshakeStatus);
-                                return pendingNetResult != null
-                                        ? pendingNetResult
-                                        : new SSLEngineResult(getEngineStatus(), NEED_UNWRAP,
-                                                  bytesConsumed, bytesProduced);
-                            case SSL_ERROR_WANT_WRITE:
-                                // SSL_ERROR_WANT_WRITE typically means that the underlying
-                                // transport is not writable
-                                // and we should set the "want write" flag on the selector and try
-                                // again when the
-                                // underlying transport is writable [1]. However we are not directly
-                                // writing to the
-                                // underlying transport and instead writing to a BIO buffer. The
-                                // OpenSsl documentation
-                                // says we should do the following [1]:
-                                //
-                                // "When using a buffering BIO, like a BIO pair, data must be
-                                // written into or retrieved
-                                // out of the BIO before being able to continue."
-                                //
-                                // So we attempt to drain the BIO buffer below, but if there is no
-                                // data this condition
-                                // is undefined and we assume their is a fatal error with the
-                                // openssl engine and close.
-                                // [1] https://www.openssl.org/docs/manmaster/man3/SSL_write.html
-                                pendingNetResult = readPendingBytesFromBIO(
-                                        dst, bytesConsumed, bytesProduced, handshakeStatus);
-                                return pendingNetResult != null ? pendingNetResult
-                                                                : NEED_WRAP_CLOSED;
-                            default:
-                                // Everything else is considered as error
-                                closeAll();
-                                throw newSslExceptionWithMessage("SSL_write");
-                        }
+                        bytesProduced = pendingNetResult.bytesProduced();
+                    }
+                } else {
+                    int sslError = ssl.getError(result);
+                    switch (sslError) {
+                        case SSL_ERROR_ZERO_RETURN:
+                            // This means the connection was shutdown correctly, close inbound
+                            // and outbound
+                            closeAll();
+                            pendingNetResult = readPendingBytesFromBIO(
+                                    dst, bytesConsumed, bytesProduced, handshakeStatus);
+                            return pendingNetResult != null ? pendingNetResult
+                                    : CLOSED_NOT_HANDSHAKING;
+                        case SSL_ERROR_WANT_READ:
+                            // If there is no pending data to read from BIO we should go back to
+                            // event loop and try
+                            // to read more data [1]. It is also possible that event loop will
+                            // detect the socket
+                            // has been closed. [1]
+                            // https://www.openssl.org/docs/manmaster/man3/SSL_write.html
+                            pendingNetResult = readPendingBytesFromBIO(
+                                    dst, bytesConsumed, bytesProduced, handshakeStatus);
+                            return pendingNetResult != null
+                                    ? pendingNetResult
+                                    : new SSLEngineResult(getEngineStatus(), NEED_UNWRAP,
+                                    bytesConsumed, bytesProduced);
+                        case SSL_ERROR_WANT_WRITE:
+                            // SSL_ERROR_WANT_WRITE typically means that the underlying
+                            // transport is not writable
+                            // and we should set the "want write" flag on the selector and try
+                            // again when the
+                            // underlying transport is writable [1]. However we are not directly
+                            // writing to the
+                            // underlying transport and instead writing to a BIO buffer. The
+                            // OpenSsl documentation
+                            // says we should do the following [1]:
+                            //
+                            // "When using a buffering BIO, like a BIO pair, data must be
+                            // written into or retrieved
+                            // out of the BIO before being able to continue."
+                            //
+                            // So we attempt to drain the BIO buffer below, but if there is no
+                            // data this condition
+                            // is undefined and we assume their is a fatal error with the
+                            // openssl engine and close.
+                            // [1] https://www.openssl.org/docs/manmaster/man3/SSL_write.html
+                            pendingNetResult = readPendingBytesFromBIO(
+                                    dst, bytesConsumed, bytesProduced, handshakeStatus);
+                            return pendingNetResult != null ? pendingNetResult
+                                    : NEED_WRAP_CLOSED;
+                        default:
+                            // Everything else is considered as error
+                            closeAll();
+                            throw newSslExceptionWithMessage("SSL_write: error " + sslError);
                     }
                 }
             }
-            // We need to check if pendingWrittenBytesInBIO was checked yet, as we may not checked
-            // if the srcs was
-            // empty, or only contained empty buffers.
+
+            // We need to check if pendingWrittenBytesInBIO was checked yet, as we may not have
+            // checked if the srcs was empty, or only contained empty buffers.
             if (bytesConsumed == 0) {
                 SSLEngineResult pendingNetResult =
                         readPendingBytesFromBIO(dst, 0, bytesProduced, handshakeStatus);
@@ -1549,9 +1548,6 @@
                     return pendingNetResult;
                 }
             }
-
-            // return new SSLEngineResult(OK, getHandshakeStatusInternal(), bytesConsumed,
-            // bytesProduced);
             return newResult(bytesConsumed, bytesProduced, handshakeStatus);
         }
     }
@@ -1673,16 +1669,19 @@
 
     private void closeAndFreeResources() {
         transitionTo(STATE_CLOSED);
-        if (!ssl.isClosed()) {
+        if (ssl != null) {
             ssl.close();
+        }
+        if (networkBio != null) {
             networkBio.close();
         }
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     protected void finalize() throws Throwable {
         try {
-            transitionTo(STATE_CLOSED);
+            closeAndFreeResources();
         } finally {
             super.finalize();
         }
diff --git a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
index 11e7cbb..48c6b3d 100644
--- a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
+++ b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
@@ -60,6 +60,8 @@
     private SSLOutputStream out;
     private SSLInputStream in;
 
+    private long handshakeStartedMillis;
+
     private BufferAllocator bufferAllocator = ConscryptEngine.getDefaultBufferAllocator();
 
     // @GuardedBy("stateLock");
@@ -193,6 +195,7 @@
                     // Initialize the handshake if we haven't already.
                     if (state == STATE_NEW) {
                         state = STATE_HANDSHAKE_STARTED;
+                        handshakeStartedMillis = Platform.getMillisSinceBoot();
                         engine.beginHandshake();
                         in = new SSLInputStream();
                         out = new SSLOutputStream();
@@ -247,6 +250,9 @@
                     case FINISHED: {
                         // Handshake is complete.
                         finished = true;
+                        Platform.countTlsHandshake(true, engine.getSession().getProtocol(),
+                                engine.getSession().getCipherSuite(),
+                                Platform.getMillisSinceBoot() - handshakeStartedMillis);
                         break;
                     }
                     default: {
@@ -257,6 +263,9 @@
             }
         } catch (SSLException e) {
             drainOutgoingQueue();
+            Platform.countTlsHandshake(false, engine.getSession().getProtocol(),
+                    engine.getSession().getCipherSuite(),
+                    Platform.getMillisSinceBoot() - handshakeStartedMillis);
             close();
             throw e;
         } catch (IOException e) {
diff --git a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
index 003037b..cf4c83d 100644
--- a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
+++ b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java
@@ -108,7 +108,7 @@
      * The session object exposed externally from this class.
      */
     private final SSLSession externalSession =
-        Platform.wrapSSLSession(new ExternalSession(new Provider() {
+        Platform.wrapSSLSession(new ExternalSession(new ExternalSession.Provider() {
             @Override
             public ConscryptSession provideSession() {
                 return ConscryptFileDescriptorSocket.this.provideSession();
@@ -118,6 +118,8 @@
     private int writeTimeoutMilliseconds = 0;
     private int handshakeTimeoutMilliseconds = -1; // -1 = same as timeout; 0 = infinite
 
+    private long handshakeStartedMillis;
+
     // The constructors should not be called except from the Platform class, because we may
     // want to construct a subclass instead.
     ConscryptFileDescriptorSocket(SSLParametersImpl sslParameters) throws IOException {
@@ -183,6 +185,7 @@
         checkOpen();
         synchronized (ssl) {
             if (state == STATE_NEW) {
+                handshakeStartedMillis = Platform.getMillisSinceBoot();
                 transitionTo(STATE_HANDSHAKE_STARTED);
             } else {
                 // We've either started the handshake already or have been closed.
@@ -228,6 +231,9 @@
                 // Update the session from the current state of the SSL object.
                 activeSession.onPeerCertificateAvailable(getHostnameOrIP(), getPort());
             } catch (CertificateException e) {
+                Platform.countTlsHandshake(false, activeSession.getProtocol(),
+                        activeSession.getCipherSuite(),
+                        Platform.getMillisSinceBoot() - handshakeStartedMillis);
                 SSLHandshakeException wrapper = new SSLHandshakeException(e.getMessage());
                 wrapper.initCause(e);
                 throw wrapper;
@@ -285,6 +291,10 @@
                 }
             }
         } catch (SSLProtocolException e) {
+            Platform.countTlsHandshake(false, activeSession.getProtocol(),
+                    activeSession.getCipherSuite(),
+                    Platform.getMillisSinceBoot() - handshakeStartedMillis);
+
             throw(SSLHandshakeException) new SSLHandshakeException("Handshake failed").initCause(e);
         } finally {
             // on exceptional exit, treat the socket as closed
@@ -338,6 +348,10 @@
 
         // The handshake has completed successfully ...
 
+        Platform.countTlsHandshake(true, activeSession.getProtocol(),
+                activeSession.getCipherSuite(),
+                Platform.getMillisSinceBoot() - handshakeStartedMillis);
+
         // First, update the state.
         synchronized (ssl) {
             if (state == STATE_CLOSED) {
@@ -716,7 +730,7 @@
     public final SSLSession getHandshakeSession() {
         synchronized (ssl) {
             if (state >= STATE_HANDSHAKE_STARTED && state < STATE_READY) {
-                return Platform.wrapSSLSession(new ExternalSession(new Provider() {
+                return Platform.wrapSSLSession(new ExternalSession(new ExternalSession.Provider() {
                     @Override
                     public ConscryptSession provideSession() {
                         return ConscryptFileDescriptorSocket.this.provideHandshakeSession();
@@ -952,7 +966,7 @@
      * Note write timeouts are not part of the javax.net.ssl.SSLSocket API
      */
     @Override
-    public final int getSoWriteTimeout() throws SocketException {
+    public final int getSoWriteTimeout() {
         return writeTimeoutMilliseconds;
     }
 
@@ -1064,6 +1078,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     protected final void finalize() throws Throwable {
         try {
             /*
diff --git a/common/src/main/java/org/conscrypt/ConscryptHostnameVerifier.java b/common/src/main/java/org/conscrypt/ConscryptHostnameVerifier.java
index e09ac1e..2772269 100644
--- a/common/src/main/java/org/conscrypt/ConscryptHostnameVerifier.java
+++ b/common/src/main/java/org/conscrypt/ConscryptHostnameVerifier.java
@@ -16,6 +16,7 @@
 
 package org.conscrypt;
 
+import java.security.cert.X509Certificate;
 import javax.net.ssl.SSLSession;
 
 /**
@@ -29,6 +30,6 @@
    * Returns whether the given hostname is allowable given the peer's authentication information
    * from the given session.
    */
-  boolean verify(String hostname, SSLSession session);
+  boolean verify(X509Certificate[] certs, String hostname, SSLSession session);
 
 }
diff --git a/common/src/main/java/org/conscrypt/ExternalSession.java b/common/src/main/java/org/conscrypt/ExternalSession.java
index 18c67a3..123d895 100644
--- a/common/src/main/java/org/conscrypt/ExternalSession.java
+++ b/common/src/main/java/org/conscrypt/ExternalSession.java
@@ -48,7 +48,7 @@
 final class ExternalSession implements ConscryptSession {
 
   // Use an initial capacity of 2 to keep it small in the average case.
-  private final HashMap<String, Object> values = new HashMap<String, Object>(2);
+  private final HashMap<String, Object> values = new HashMap<>(2);
   private final Provider provider;
 
   public ExternalSession(Provider provider) {
@@ -112,6 +112,7 @@
   }
 
   @Override
+  @SuppressWarnings("deprecation") // Public API
   public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
     return provider.provideSession().getPeerCertificateChain();
   }
@@ -171,7 +172,7 @@
 
   @Override
   public String[] getValueNames() {
-    return values.keySet().toArray(new String[values.size()]);
+    return values.keySet().toArray(new String[0]);
   }
 
   @Override
diff --git a/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java b/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java
index 10b1104..2cc25ac 100644
--- a/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java
+++ b/common/src/main/java/org/conscrypt/Java7ExtendedSSLSession.java
@@ -134,6 +134,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation") // Public API
     public final X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
         return delegate.getPeerCertificateChain();
     }
diff --git a/common/src/main/java/org/conscrypt/Java8ExtendedSSLSession.java b/common/src/main/java/org/conscrypt/Java8ExtendedSSLSession.java
index def85d2..6330d3b 100644
--- a/common/src/main/java/org/conscrypt/Java8ExtendedSSLSession.java
+++ b/common/src/main/java/org/conscrypt/Java8ExtendedSSLSession.java
@@ -36,7 +36,7 @@
   public final List<SNIServerName> getRequestedServerNames() {
       String requestedServerName = delegate.getRequestedServerName();
       if (requestedServerName == null) {
-        return null;
+        return Collections.emptyList();
       }
 
       return Collections.singletonList((SNIServerName) new SNIHostName(requestedServerName));
diff --git a/common/src/main/java/org/conscrypt/KeyManagerImpl.java b/common/src/main/java/org/conscrypt/KeyManagerImpl.java
index c23d20a..afd957d 100644
--- a/common/src/main/java/org/conscrypt/KeyManagerImpl.java
+++ b/common/src/main/java/org/conscrypt/KeyManagerImpl.java
@@ -55,36 +55,33 @@
     /**
      * Creates Key manager
      */
+    @SuppressWarnings("JdkObsolete")  // KeyStore#aliases is the only way of enumerating all entries
     KeyManagerImpl(KeyStore keyStore, char[] pwd) {
-        this.hash = new HashMap<String, PrivateKeyEntry>();
+        this.hash = new HashMap<>();
         final Enumeration<String> aliases;
         try {
             aliases = keyStore.aliases();
         } catch (KeyStoreException e) {
             return;
         }
-        for (; aliases.hasMoreElements();) {
+        while (aliases.hasMoreElements()) {
             final String alias = aliases.nextElement();
             try {
-                if (keyStore.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) {
-                    KeyStore.PrivateKeyEntry entry;
+                if (keyStore.entryInstanceOf(alias, PrivateKeyEntry.class)) {
+                    PrivateKeyEntry entry;
                     try {
-                        entry = (KeyStore.PrivateKeyEntry) keyStore
+                        entry = (PrivateKeyEntry) keyStore
                             .getEntry(alias, new KeyStore.PasswordProtection(pwd));
                     } catch (UnsupportedOperationException e) {
                         // If the KeyStore doesn't support getEntry(), as Android Keystore
                         // doesn't, fall back to reading the two values separately.
                         PrivateKey key = (PrivateKey) keyStore.getKey(alias, pwd);
                         Certificate[] certs = keyStore.getCertificateChain(alias);
-                        entry = new KeyStore.PrivateKeyEntry(key, certs);
+                        entry = new PrivateKeyEntry(key, certs);
                     }
                     hash.put(alias, entry);
                 }
-            } catch (KeyStoreException ignored) {
-                // Ignored.
-            } catch (UnrecoverableEntryException ignored) {
-                // Ignored.
-            } catch (NoSuchAlgorithmException ignored) {
+            } catch (KeyStoreException | UnrecoverableEntryException | NoSuchAlgorithmException ignored) {
                 // Ignored.
             }
         }
@@ -159,7 +156,7 @@
             return null;
         }
         List<Principal> issuersList = (issuers == null) ? null : Arrays.asList(issuers);
-        ArrayList<String> found = new ArrayList<String>();
+        ArrayList<String> found = new ArrayList<>();
         for (final Map.Entry<String, PrivateKeyEntry> entry : hash.entrySet()) {
             final String alias = entry.getKey();
             final Certificate[] chain = entry.getValue().getCertificateChain();
@@ -224,7 +221,7 @@
             }
         }
         if (!found.isEmpty()) {
-            return found.toArray(new String[found.size()]);
+            return found.toArray(new String[0]);
         }
         return null;
     }
diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java
index ba55df3..b5f80fb 100644
--- a/common/src/main/java/org/conscrypt/NativeCrypto.java
+++ b/common/src/main/java/org/conscrypt/NativeCrypto.java
@@ -21,6 +21,7 @@
 import java.io.OutputStream;
 import java.net.SocketTimeoutException;
 import java.nio.Buffer;
+import java.nio.ByteBuffer;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.MessageDigest;
@@ -202,6 +203,12 @@
 
     static native int ECDSA_verify(byte[] data, byte[] sig, NativeRef.EVP_PKEY pkey);
 
+    // --- Curve25519 --------------
+
+    static native boolean X25519(byte[] out, byte[] privateKey, byte[] publicKey) throws InvalidKeyException;
+
+    static native void X25519_keypair(byte[] outPublicKey, byte[] outPrivateKey);
+
     // --- Message digest functions --------------
 
     // These return const references
@@ -331,11 +338,33 @@
 
     static native int EVP_AEAD_CTX_seal(long evpAead, byte[] key, int tagLengthInBytes, byte[] out,
             int outOffset, byte[] nonce, byte[] in, int inOffset, int inLength, byte[] ad)
-            throws ShortBufferException, BadPaddingException, IndexOutOfBoundsException;
+            throws ShortBufferException, BadPaddingException;
+
+    static native int EVP_AEAD_CTX_seal_buf(long evpAead, byte[] key, int tagLengthInBytes, ByteBuffer out,
+                                            byte[] nonce, ByteBuffer input, byte[] ad)
+            throws ShortBufferException, BadPaddingException;
 
     static native int EVP_AEAD_CTX_open(long evpAead, byte[] key, int tagLengthInBytes, byte[] out,
             int outOffset, byte[] nonce, byte[] in, int inOffset, int inLength, byte[] ad)
-            throws ShortBufferException, BadPaddingException, IndexOutOfBoundsException;
+            throws ShortBufferException, BadPaddingException;
+
+    static native int EVP_AEAD_CTX_open_buf(long evpAead, byte[] key, int tagLengthInBytes, ByteBuffer out,
+                                            byte[] nonce, ByteBuffer input, byte[] ad)
+            throws ShortBufferException, BadPaddingException;
+
+    // --- CMAC functions ------------------------------------------------------
+
+    static native long CMAC_CTX_new();
+
+    static native void CMAC_CTX_free(long ctx);
+
+    static native void CMAC_Init(NativeRef.CMAC_CTX ctx, byte[] key);
+
+    static native void CMAC_Update(NativeRef.CMAC_CTX ctx, byte[] in, int inOffset, int inLength);
+
+    static native void CMAC_UpdateDirect(NativeRef.CMAC_CTX ctx, long inPtr, int inLength);
+
+    static native byte[] CMAC_Final(NativeRef.CMAC_CTX ctx);
 
     // --- HMAC functions ------------------------------------------------------
 
@@ -413,8 +442,6 @@
 
     static native void X509_free(long x509ctx, OpenSSLX509Certificate holder);
 
-    static native long X509_dup(long x509ctx, OpenSSLX509Certificate holder);
-
     static native int X509_cmp(long x509ctx1, OpenSSLX509Certificate holder, long x509ctx2, OpenSSLX509Certificate holder2);
 
     static native void X509_print_ex(long bioCtx, long x509ctx, OpenSSLX509Certificate holder, long nmflag, long certflag);
@@ -460,7 +487,9 @@
     static native void X509_verify(long x509ctx, OpenSSLX509Certificate holder, NativeRef.EVP_PKEY pkeyCtx)
             throws BadPaddingException;
 
-    static native byte[] get_X509_cert_info_enc(long x509ctx, OpenSSLX509Certificate holder);
+    static native byte[] get_X509_tbs_cert(long x509ctx, OpenSSLX509Certificate holder);
+
+    static native byte[] get_X509_tbs_cert_without_ext(long x509ctx, OpenSSLX509Certificate holder, String oid);
 
     static native byte[] get_X509_signature(long x509ctx, OpenSSLX509Certificate holder);
 
@@ -518,8 +547,6 @@
 
     static native byte[] X509_CRL_get_ext_oid(long x509CrlCtx, OpenSSLX509CRL holder, String oid);
 
-    static native void X509_delete_ext(long x509, OpenSSLX509Certificate holder, String oid);
-
     static native long X509_CRL_get_version(long x509CrlCtx, OpenSSLX509CRL holder);
 
     static native long X509_CRL_get_ext(long x509CrlCtx, OpenSSLX509CRL holder, String oid);
@@ -1398,26 +1425,12 @@
             SSLHandshakeCallbacks shc) throws IOException;
 
     /**
-     * Writes data from the given array to the BIO.
-     */
-    static native int ENGINE_SSL_write_BIO_heap(long ssl, NativeSsl ssl_holder, long bioRef, byte[] sourceJava,
-            int sourceOffset, int sourceLength, SSLHandshakeCallbacks shc)
-            throws IOException, IndexOutOfBoundsException;
-
-    /**
      * Reads data from the given BIO into a direct {@link java.nio.ByteBuffer}.
      */
     static native int ENGINE_SSL_read_BIO_direct(long ssl, NativeSsl ssl_holder, long bioRef, long address, int len,
             SSLHandshakeCallbacks shc) throws IOException;
 
     /**
-     * Reads data from the given BIO into an array.
-     */
-    static native int ENGINE_SSL_read_BIO_heap(long ssl, NativeSsl ssl_holder, long bioRef, byte[] destJava,
-            int destOffset, int destLength, SSLHandshakeCallbacks shc)
-            throws IOException, IndexOutOfBoundsException;
-
-    /**
      * Forces the SSL object to process any data pending in the BIO.
      */
     static native void ENGINE_SSL_force_read(long ssl, NativeSsl ssl_holder,
@@ -1431,6 +1444,11 @@
             throws IOException;
 
     /**
+     * Return {@code true} if BoringSSL has been built in FIPS mode.
+     */
+    static native boolean usesBoringSSL_FIPS_mode();
+
+    /**
      * Used for testing only.
      */
     static native int BIO_read(long bioRef, byte[] buffer) throws IOException;
diff --git a/common/src/main/java/org/conscrypt/NativeRef.java b/common/src/main/java/org/conscrypt/NativeRef.java
index db14f62..9d7de4b 100644
--- a/common/src/main/java/org/conscrypt/NativeRef.java
+++ b/common/src/main/java/org/conscrypt/NativeRef.java
@@ -46,6 +46,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     protected void finalize() throws Throwable {
         try {
             if (address != 0) {
@@ -58,6 +59,17 @@
 
     abstract void doFree(long context);
 
+    static final class CMAC_CTX extends NativeRef {
+        CMAC_CTX(long nativePointer) {
+            super(nativePointer);
+        }
+
+        @Override
+        void doFree(long context) {
+            NativeCrypto.CMAC_CTX_free(context);
+        }
+    }
+
     static final class EC_GROUP extends NativeRef {
         EC_GROUP(long ctx) {
             super(ctx);
diff --git a/common/src/main/java/org/conscrypt/NativeSsl.java b/common/src/main/java/org/conscrypt/NativeSsl.java
index 6a70bf0..b7b0263 100644
--- a/common/src/main/java/org/conscrypt/NativeSsl.java
+++ b/common/src/main/java/org/conscrypt/NativeSsl.java
@@ -30,6 +30,7 @@
 import java.io.UnsupportedEncodingException;
 import java.net.SocketException;
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.security.InvalidKeyException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
@@ -211,7 +212,7 @@
             byte[][] asn1DerEncodedPrincipals)
             throws SSLException, CertificateEncodingException {
         Set<String> keyTypesSet = SSLUtils.getSupportedClientKeyTypes(keyTypeBytes, signatureAlgs);
-        String[] keyTypes = keyTypesSet.toArray(new String[keyTypesSet.size()]);
+        String[] keyTypes = keyTypesSet.toArray(new String[0]);
 
         X500Principal[] issuers;
         if (asn1DerEncodedPrincipals == null) {
@@ -636,6 +637,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     protected final void finalize() throws Throwable {
         try {
             close();
@@ -655,27 +657,51 @@
         }
 
         int getPendingWrittenBytes() {
-            if (bio != 0) {
-                return NativeCrypto.SSL_pending_written_bytes_in_BIO(bio);
-            } else {
-                return 0;
+            lock.readLock().lock();
+            try {
+                return (bio == 0L) ? 0 : NativeCrypto.SSL_pending_written_bytes_in_BIO(bio);
+            } finally {
+                lock.readLock().unlock();
             }
         }
 
         int writeDirectByteBuffer(long address, int length) throws IOException {
-            return NativeCrypto.ENGINE_SSL_write_BIO_direct(
-                    ssl, NativeSsl.this, bio, address, length, handshakeCallbacks);
+            lock.readLock().lock();
+            try {
+                if (isClosed()) {
+                    throw new SSLException("Connection closed");
+                }
+                return NativeCrypto.ENGINE_SSL_write_BIO_direct(
+                        ssl, NativeSsl.this, bio, address, length, handshakeCallbacks);
+            } finally {
+                lock.readLock().unlock();
+            }
         }
 
         int readDirectByteBuffer(long destAddress, int destLength) throws IOException {
-            return NativeCrypto.ENGINE_SSL_read_BIO_direct(
-                    ssl, NativeSsl.this, bio, destAddress, destLength, handshakeCallbacks);
+            lock.readLock().lock();
+            try {
+                if (isClosed()) {
+                    throw new SSLException("Connection closed");
+                }
+                return NativeCrypto.ENGINE_SSL_read_BIO_direct(
+                        ssl, NativeSsl.this, bio, destAddress, destLength, handshakeCallbacks);
+            } finally {
+                lock.readLock().unlock();
+            }
         }
 
         void close() {
-            long toFree = bio;
-            bio = 0L;
-            NativeCrypto.BIO_free_all(toFree);
+            lock.writeLock().lock();
+            try {
+                long toFree = bio;
+                bio = 0L;
+                if (toFree != 0L) {
+                    NativeCrypto.BIO_free_all(toFree);
+                }
+            } finally {
+                lock.writeLock().unlock();
+            }
         }
     }
 }
diff --git a/common/src/main/java/org/conscrypt/NativeSslSession.java b/common/src/main/java/org/conscrypt/NativeSslSession.java
index 983c2ef..7c15c44 100644
--- a/common/src/main/java/org/conscrypt/NativeSslSession.java
+++ b/common/src/main/java/org/conscrypt/NativeSslSession.java
@@ -428,7 +428,7 @@
                 }
 
                 @Override
-                public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
+                public Certificate[] getPeerCertificates() {
                     throw new UnsupportedOperationException();
                 }
 
@@ -438,13 +438,13 @@
                 }
 
                 @Override
-                public X509Certificate[] getPeerCertificateChain()
-                        throws SSLPeerUnverifiedException {
+                @SuppressWarnings("deprecation")
+                public X509Certificate[] getPeerCertificateChain() {
                     throw new UnsupportedOperationException();
                 }
 
                 @Override
-                public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+                public Principal getPeerPrincipal() {
                     throw new UnsupportedOperationException();
                 }
 
diff --git a/common/src/main/java/org/conscrypt/OkHostnameVerifier.java b/common/src/main/java/org/conscrypt/OkHostnameVerifier.java
new file mode 100644
index 0000000..c14e371
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/OkHostnameVerifier.java
@@ -0,0 +1,286 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.conscrypt;
+
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+
+/**
+ * A HostnameVerifier consistent with <a
+ * href="http://www.ietf.org/rfc/rfc2818.txt">RFC 2818</a>.
+ */
+public final class OkHostnameVerifier implements ConscryptHostnameVerifier {
+    // Android-changed: Add a mode which disallows top-level domain wildcards. b/144694112
+    // public static final OkHostnameVerifier INSTANCE = new OkHostnameVerifier();
+    public static final OkHostnameVerifier INSTANCE = new OkHostnameVerifier(false);
+
+    /**
+     * Quick and dirty pattern to differentiate IP addresses from hostnames. This
+     * is an approximation of Android's private InetAddress#isNumeric API.
+     *
+     * <p>This matches IPv6 addresses as a hex string containing at least one
+     * colon, and possibly including dots after the first colon. It matches IPv4
+     * addresses as strings containing only decimal digits and dots. This pattern
+     * matches strings like "a:.23" and "54" that are neither IP addresses nor
+     * hostnames; they will be verified as IP addresses (which is a more strict
+     * verification).
+     */
+    private static final Pattern VERIFY_AS_IP_ADDRESS = Pattern.compile(
+            "([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)");
+
+    private static final int ALT_DNS_NAME = 2;
+    private static final int ALT_IPA_NAME = 7;
+
+    // BEGIN Android-changed: Add a mode which disallows top-level domain wildcards. b/144694112
+    // private OkHostnameVerifier() {
+    // }
+    private final boolean strictWildcardMode;
+
+    private OkHostnameVerifier(boolean strictWildcardMode) {
+        this.strictWildcardMode = strictWildcardMode;
+    }
+
+    public static OkHostnameVerifier strictInstance() {
+        return new OkHostnameVerifier(true);
+    }
+    // END Android-changed: Add a mode which disallows top-level domain wildcards. b/144694112
+
+    @Override
+    public boolean verify(X509Certificate[] certs, String host, SSLSession session) {
+        if (certs.length > 0) {
+            return verify(host, certs[0]);
+        } else {
+            try {
+                Certificate[] certificates = session.getPeerCertificates();
+                return verify(host, (X509Certificate) certificates[0]);
+            } catch (SSLException e) {
+                return false;
+            }
+        }
+    }
+
+    public boolean verify(String host, X509Certificate certificate) {
+        return verifyAsIpAddress(host)
+                ? verifyIpAddress(host, certificate)
+                : verifyHostName(host, certificate);
+    }
+
+    static boolean verifyAsIpAddress(String host) {
+        return VERIFY_AS_IP_ADDRESS.matcher(host).matches();
+    }
+
+    /**
+     * Returns true if {@code certificate} matches {@code ipAddress}.
+     */
+    private boolean verifyIpAddress(String ipAddress, X509Certificate certificate) {
+        List<String> altNames = getSubjectAltNames(certificate, ALT_IPA_NAME);
+        for (int i = 0, size = altNames.size(); i < size; i++) {
+            if (ipAddress.equalsIgnoreCase(altNames.get(i))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if {@code certificate} matches {@code hostName}.
+     */
+    @SuppressWarnings("UnusedVariable")
+    private boolean verifyHostName(String hostName, X509Certificate certificate) {
+        hostName = hostName.toLowerCase(Locale.US);
+        boolean hasDns = false;
+        List<String> altNames = getSubjectAltNames(certificate, ALT_DNS_NAME);
+        for (int i = 0, size = altNames.size(); i < size; i++) {
+            hasDns = true;
+            if (verifyHostName(hostName, altNames.get(i))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // BEGIN Android-removed: Ignore common name in hostname verification. http://b/70278814
+    /*
+    if (!hasDns) {
+      X500Principal principal = certificate.getSubjectX500Principal();
+      // RFC 2818 advises using the most specific name for matching.
+      String cn = new DistinguishedNameParser(principal).findMostSpecific("cn");
+      if (cn != null) {
+        return verifyHostName(hostName, cn);
+      }
+    }
+    */
+    // END Android-removed: Ignore common name in hostname verification. http://b/70278814
+
+    public static List<String> allSubjectAltNames(X509Certificate certificate) {
+        List<String> altIpaNames = getSubjectAltNames(certificate, ALT_IPA_NAME);
+        List<String> altDnsNames = getSubjectAltNames(certificate, ALT_DNS_NAME);
+        List<String> result = new ArrayList<>(altIpaNames.size() + altDnsNames.size());
+        result.addAll(altIpaNames);
+        result.addAll(altDnsNames);
+        return result;
+    }
+
+    @SuppressWarnings("MixedMutabilityReturnType")
+    private static List<String> getSubjectAltNames(X509Certificate certificate, int type) {
+        List<String> result = new ArrayList<>();
+        try {
+            Collection<?> subjectAltNames = certificate.getSubjectAlternativeNames();
+            if (subjectAltNames == null) {
+                return Collections.emptyList();
+            }
+            for (Object subjectAltName : subjectAltNames) {
+                List<?> entry = (List<?>) subjectAltName;
+                if (entry == null || entry.size() < 2) {
+                    continue;
+                }
+                Integer altNameType = (Integer) entry.get(0);
+                if (altNameType == null) {
+                    continue;
+                }
+                if (altNameType == type) {
+                    String altName = (String) entry.get(1);
+                    if (altName != null) {
+                        result.add(altName);
+                    }
+                }
+            }
+            return result;
+        } catch (CertificateParsingException e) {
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * Returns {@code true} iff {@code hostName} matches the domain name {@code pattern}.
+     *
+     * @param hostName lower-case host name.
+     * @param pattern  domain name pattern from certificate. May be a wildcard pattern such as
+     *                 {@code *.android.com}.
+     */
+    private boolean verifyHostName(String hostName, String pattern) {
+        // Basic sanity checks
+        // Check length == 0 instead of .isEmpty() to support Java 5.
+        if (hostName == null || hostName.length() == 0 || hostName.startsWith(".")
+                || hostName.endsWith("..")) {
+            // Invalid domain name
+            return false;
+        }
+        if (pattern == null || pattern.length() == 0 || pattern.startsWith(".")
+                || pattern.endsWith("..")) {
+            // Invalid pattern/domain name
+            return false;
+        }
+
+        // Normalize hostName and pattern by turning them into absolute domain names if they are not
+        // yet absolute. This is needed because server certificates do not normally contain absolute
+        // names or patterns, but they should be treated as absolute. At the same time, any hostName
+        // presented to this method should also be treated as absolute for the purposes of matching
+        // to the server certificate.
+        //   www.android.com  matches www.android.com
+        //   www.android.com  matches www.android.com.
+        //   www.android.com. matches www.android.com.
+        //   www.android.com. matches www.android.com
+        if (!hostName.endsWith(".")) {
+            hostName += '.';
+        }
+        if (!pattern.endsWith(".")) {
+            pattern += '.';
+        }
+        // hostName and pattern are now absolute domain names.
+
+        pattern = pattern.toLowerCase(Locale.US);
+        // hostName and pattern are now in lower case -- domain names are case-insensitive.
+
+        if (!pattern.contains("*")) {
+            // Not a wildcard pattern -- hostName and pattern must match exactly.
+            return hostName.equals(pattern);
+        }
+        // Wildcard pattern
+
+        // WILDCARD PATTERN RULES:
+        // 1. Asterisk (*) is only permitted in the left-most domain name label and must be the
+        //    only character in that label (i.e., must match the whole left-most label).
+        //    For example, *.example.com is permitted, while *a.example.com, a*.example.com,
+        //    a*b.example.com, a.*.example.com are not permitted.
+        // 2. Asterisk (*) cannot match across domain name labels.
+        //    For example, *.example.com matches test.example.com but does not match
+        //    sub.test.example.com.
+        // 3. Wildcard patterns for single-label domain names are not permitted.
+        // 4. Android-added: if strictWildcardMode is true then wildcards matching top-level domains,
+        //    e.g. *.com, are not permitted.
+
+        if (!pattern.startsWith("*.") || pattern.indexOf('*', 1) != -1) {
+            // Asterisk (*) is only permitted in the left-most domain name label and must be the only
+            // character in that label
+            return false;
+        }
+
+        // Optimization: check whether hostName is too short to match the pattern. hostName must be at
+        // least as long as the pattern because asterisk must match the whole left-most label and
+        // hostName starts with a non-empty label. Thus, asterisk has to match one or more characters.
+        if (hostName.length() < pattern.length()) {
+            // hostName too short to match the pattern.
+            return false;
+        }
+
+        if ("*.".equals(pattern)) {
+            // Wildcard pattern for single-label domain name -- not permitted.
+            return false;
+        }
+
+        // BEGIN Android-added: Disallow top-level wildcards in strict mode. http://b/144694112
+        if (strictWildcardMode) {
+            // By this point we know the pattern has been normalised and starts with a wildcard,
+            // i.e. "*.domainpart."
+            String domainPart = pattern.substring(2, pattern.length() - 1);
+            // If the domain part contains no dots then this pattern will match top level domains.
+            if (domainPart.indexOf('.') < 0) {
+                return false;
+            }
+        }
+        // END Android-added: Disallow top-level wildcards in strict mode. http://b/144694112
+
+        // hostName must end with the region of pattern following the asterisk.
+        String suffix = pattern.substring(1);
+        if (!hostName.endsWith(suffix)) {
+            // hostName does not end with the suffix
+            return false;
+        }
+
+        // Check that asterisk did not match across domain name labels.
+        int suffixStartIndexInHostName = hostName.length() - suffix.length();
+        if ((suffixStartIndexInHostName > 0)
+                && (hostName.lastIndexOf('.', suffixStartIndexInHostName - 1) != -1)) {
+            // Asterisk is matching across domain name labels -- not permitted.
+            return false;
+        }
+
+        // hostName matches pattern
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/common/src/main/java/org/conscrypt/OpenSSLAeadCipher.java b/common/src/main/java/org/conscrypt/OpenSSLAeadCipher.java
index 0d48ad7..1b201cc 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLAeadCipher.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLAeadCipher.java
@@ -33,6 +33,11 @@
 @Internal
 public abstract class OpenSSLAeadCipher extends OpenSSLCipher {
     /**
+     * Controls whether no-copy optimizations for direct ByteBuffers are enabled.
+     */
+    private static final boolean ENABLE_BYTEBUFFER_OPTIMIZATIONS = true;
+
+    /**
      * The default tag size when one is not specified. Default to
      * full-length tags (128-bits or 16 octets).
      */
@@ -84,7 +89,7 @@
      */
     int tagLengthInBytes;
 
-    public OpenSSLAeadCipher(Mode mode) {
+    protected OpenSSLAeadCipher(Mode mode) {
         super(mode, Padding.NOPADDING);
     }
 
@@ -221,6 +226,49 @@
     }
 
     @Override
+    protected int engineDoFinal(ByteBuffer input, ByteBuffer output) throws ShortBufferException,
+            IllegalBlockSizeException, BadPaddingException {
+        if (!ENABLE_BYTEBUFFER_OPTIMIZATIONS) {
+            return super.engineDoFinal(input, output);
+        }
+        if (input == null || output == null) {
+            throw new NullPointerException("Null ByteBuffer Error");
+        }
+        if (getOutputSizeForFinal(input.remaining()) > output.remaining()) {
+            throw new ShortBufferWithoutStackTraceException("Insufficient Bytes for Output Buffer");
+        }
+        if (output.isReadOnly()) {
+            throw new IllegalArgumentException("Cannot write to Read Only ByteBuffer");
+        }
+        if (bufCount != 0) {
+            return super.engineDoFinal(input, output);// traditional case
+        }
+        int bytesWritten;
+        if (!input.isDirect()) {
+            int incap = input.remaining();
+            ByteBuffer inputClone = ByteBuffer.allocateDirect(incap);
+            inputClone.mark();
+            inputClone.put(input);
+            inputClone.reset();
+            input = inputClone;
+        }
+        if (!output.isDirect()) {
+            ByteBuffer outputClone = ByteBuffer.allocateDirect(
+                    getOutputSizeForFinal(input.remaining()));
+            bytesWritten = doFinalInternal(input, outputClone);
+            output.put(outputClone);
+            input.position(input.limit()); // API reasons
+        }
+        else {
+            bytesWritten =  doFinalInternal(input, output);
+            output.position(output.position() + bytesWritten);
+            input.position(input.limit()); // API reasons
+        }
+
+        return bytesWritten;
+    }
+
+    @Override
     protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
             int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
         BadPaddingException {
@@ -281,6 +329,28 @@
         }
     }
 
+    int doFinalInternal(ByteBuffer input, ByteBuffer output)
+            throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
+        checkInitialization();
+        final int bytesWritten;
+        try {
+            if (isEncrypting()) {
+                bytesWritten = NativeCrypto.EVP_AEAD_CTX_seal_buf(
+                        evpAead, encodedKey, tagLengthInBytes, output, iv, input, aad);
+            } else {
+                bytesWritten = NativeCrypto.EVP_AEAD_CTX_open_buf(
+                        evpAead, encodedKey, tagLengthInBytes, output, iv, input, aad);
+            }
+        } catch (BadPaddingException e) {
+            throwAEADBadTagExceptionIfAvailable(e.getMessage(), e.getCause());
+            throw e;
+        }
+        if (isEncrypting()) {
+            mustInitialize = true;
+        }
+        return bytesWritten;
+    }
+
     @Override
     int doFinalInternal(byte[] output, int outputOffset, int maximumLen)
             throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
diff --git a/common/src/main/java/org/conscrypt/OpenSSLBIOSink.java b/common/src/main/java/org/conscrypt/OpenSSLBIOSink.java
deleted file mode 100644
index b385eb9..0000000
--- a/common/src/main/java/org/conscrypt/OpenSSLBIOSink.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.conscrypt;
-
-import java.io.ByteArrayOutputStream;
-
-/**
- * Wraps a BoringSSL BIO to act as a place to write out data.
- */
-final class OpenSSLBIOSink {
-    private final long ctx;
-    private final ByteArrayOutputStream buffer;
-    private int position;
-
-    static OpenSSLBIOSink create() {
-        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
-        return new OpenSSLBIOSink(buffer);
-    }
-
-    private OpenSSLBIOSink(ByteArrayOutputStream buffer) {
-        ctx = NativeCrypto.create_BIO_OutputStream(buffer);
-        this.buffer = buffer;
-    }
-
-    int available() {
-        return buffer.size() - position;
-    }
-
-    void reset() {
-        buffer.reset();
-        position = 0;
-    }
-
-    long skip(long byteCount) {
-        int maxLength = Math.min(available(), (int) byteCount);
-        position += maxLength;
-        if (position == buffer.size()) {
-            reset();
-        }
-        return maxLength;
-    }
-
-    long getContext() {
-        return ctx;
-    }
-
-    byte[] toByteArray() {
-        return buffer.toByteArray();
-    }
-
-    int position() {
-        return position;
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            NativeCrypto.BIO_free_all(ctx);
-        } finally {
-            super.finalize();
-        }
-    }
-}
diff --git a/common/src/main/java/org/conscrypt/OpenSSLBIOSource.java b/common/src/main/java/org/conscrypt/OpenSSLBIOSource.java
deleted file mode 100644
index fd3437d..0000000
--- a/common/src/main/java/org/conscrypt/OpenSSLBIOSource.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.conscrypt;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-
-/**
- * Wrapped by a BoringSSL BIO to act as a source of bytes.
- */
-final class OpenSSLBIOSource {
-    private OpenSSLBIOInputStream source;
-
-    static OpenSSLBIOSource wrap(ByteBuffer buffer) {
-        return new OpenSSLBIOSource(
-            new OpenSSLBIOInputStream(new ByteBufferInputStream(buffer), false));
-    }
-
-    private OpenSSLBIOSource(OpenSSLBIOInputStream source) {
-        this.source = source;
-    }
-
-    long getContext() {
-        return source.getBioContext();
-    }
-
-    private synchronized void release() {
-        if (source != null) {
-            NativeCrypto.BIO_free_all(source.getBioContext());
-            source = null;
-        }
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            release();
-        } finally {
-            super.finalize();
-        }
-    }
-
-    private static class ByteBufferInputStream extends InputStream {
-        private final ByteBuffer source;
-
-        ByteBufferInputStream(ByteBuffer source) {
-            this.source = source;
-        }
-
-        @Override
-        public int read() throws IOException {
-            if (source.remaining() > 0) {
-                return source.get();
-            } else {
-                return -1;
-            }
-        }
-
-        @Override
-        public int available() throws IOException {
-            return source.limit() - source.position();
-        }
-
-        @Override
-        public int read(byte[] buffer) throws IOException {
-            int originalPosition = source.position();
-            source.get(buffer);
-            return source.position() - originalPosition;
-        }
-
-        @Override
-        public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
-            int toRead = Math.min(source.remaining(), byteCount);
-            int originalPosition = source.position();
-            source.get(buffer, byteOffset, toRead);
-            return source.position() - originalPosition;
-        }
-
-        @Override
-        public void reset() throws IOException {
-            source.reset();
-        }
-
-        @Override
-        public long skip(long byteCount) throws IOException {
-            long originalPosition = source.position();
-            source.position((int) (originalPosition + byteCount));
-            return source.position() - originalPosition;
-        }
-    }
-}
diff --git a/common/src/main/java/org/conscrypt/OpenSSLBaseDHKeyAgreement.java b/common/src/main/java/org/conscrypt/OpenSSLBaseDHKeyAgreement.java
new file mode 100644
index 0000000..2aa2643
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/OpenSSLBaseDHKeyAgreement.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import javax.crypto.KeyAgreementSpi;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Generic base classe for Diffie-Hellman style key agreement backed by the OpenSSL engine.
+ */
+@Internal
+public abstract class OpenSSLBaseDHKeyAgreement<T> extends KeyAgreementSpi {
+
+    /** OpenSSL handle of the private key. Only available after the engine has been initialized. */
+    private T mPrivateKey;
+
+    /**
+     * Expected length (in bytes) of the agreed key ({@link #mResult}). Only available after the
+     * engine has been initialized.
+     */
+    private int mExpectedResultLength;
+
+    /** Agreed key. Only available after {@link #engineDoPhase(Key, boolean)} completes. */
+    private byte[] mResult;
+
+    protected OpenSSLBaseDHKeyAgreement() {}
+
+    @Override
+    public Key engineDoPhase(Key key, boolean lastPhase) throws InvalidKeyException {
+        if (mPrivateKey == null) {
+            throw new IllegalStateException("Not initialized");
+        }
+        if (!lastPhase) {
+            throw new IllegalStateException("DH only has one phase");
+        }
+
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        }
+        if (!(key instanceof PublicKey)) {
+            throw new InvalidKeyException("Not a public key: " + key.getClass());
+        }
+        T openSslPublicKey = convertPublicKey((PublicKey) key);
+
+        byte[] buffer = new byte[mExpectedResultLength];
+        int actualResultLength = computeKey(buffer, openSslPublicKey, mPrivateKey);
+        byte[] result;
+        if (actualResultLength == -1) {
+            throw new RuntimeException("Engine returned -1");
+        } else if (actualResultLength == mExpectedResultLength) {
+            // The output is as long as expected -- use the whole buffer
+            result = buffer;
+        } else if (actualResultLength < mExpectedResultLength) {
+            // The output is shorter than expected -- use only what's produced by the engine
+            result = new byte[actualResultLength];
+            System.arraycopy(buffer, 0, mResult, 0, mResult.length);
+        } else {
+            // The output is longer than expected
+            throw new RuntimeException("Engine produced a longer than expected result. Expected: "
+                + mExpectedResultLength + ", actual: " + actualResultLength);
+        }
+        mResult = result;
+
+        return null; // No intermediate key
+    }
+
+    protected abstract T convertPublicKey(PublicKey key) throws InvalidKeyException;
+
+    protected abstract T convertPrivateKey(PrivateKey key) throws InvalidKeyException;
+
+    /**
+     * Given the public key {@code theirPublicKey} and the private key {@code ourPrivateKey} write the shared secret
+     * to {@code buffer} and return the actual output size.
+     */
+    protected abstract int computeKey(byte[] buffer, T theirPublicKey, T ourPrivateKey) throws InvalidKeyException;
+
+    @Override
+    protected int engineGenerateSecret(byte[] sharedSecret, int offset)
+            throws ShortBufferException {
+        checkCompleted();
+        int available = sharedSecret.length - offset;
+        if (mResult.length > available) {
+            throw new ShortBufferWithoutStackTraceException(
+                    "Needed: " + mResult.length + ", available: " + available);
+        }
+
+        System.arraycopy(mResult, 0, sharedSecret, offset, mResult.length);
+        return mResult.length;
+    }
+
+    @Override
+    protected byte[] engineGenerateSecret() {
+        checkCompleted();
+        return mResult;
+    }
+
+    @Override
+    protected SecretKey engineGenerateSecret(String algorithm) {
+        checkCompleted();
+        return new SecretKeySpec(engineGenerateSecret(), algorithm);
+    }
+
+    @Override
+    protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException {
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        }
+        if (!(key instanceof PrivateKey)) {
+            throw new InvalidKeyException("Not a private key: " + key.getClass());
+        }
+
+        T privateKey = convertPrivateKey((PrivateKey) key);
+        mExpectedResultLength = getOutputSize(privateKey);
+        mPrivateKey = privateKey;
+    }
+
+    /** Returns the expected result length for the given {@code key}. */
+    protected abstract int getOutputSize(T key);
+
+    @Override
+    protected void engineInit(Key key, AlgorithmParameterSpec params,
+            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+        // ECDH doesn't need an AlgorithmParameterSpec
+        if (params != null) {
+          throw new InvalidAlgorithmParameterException("No algorithm parameters supported");
+        }
+        engineInit(key, random);
+    }
+
+    private void checkCompleted() {
+        if (mResult == null) {
+            throw new IllegalStateException("Key agreement not completed");
+        }
+    }
+}
diff --git a/common/src/main/java/org/conscrypt/OpenSSLCipherRSA.java b/common/src/main/java/org/conscrypt/OpenSSLCipherRSA.java
index 678b3bd..b9771cc 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLCipherRSA.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLCipherRSA.java
@@ -375,7 +375,7 @@
     }
 
     public abstract static class DirectRSA extends OpenSSLCipherRSA {
-        public DirectRSA(int padding) {
+        protected DirectRSA(int padding) {
             super(padding);
         }
 
diff --git a/common/src/main/java/org/conscrypt/OpenSSLECDHKeyAgreement.java b/common/src/main/java/org/conscrypt/OpenSSLECDHKeyAgreement.java
index 708fd95..614f20e 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLECDHKeyAgreement.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLECDHKeyAgreement.java
@@ -16,136 +16,41 @@
 
 package org.conscrypt;
 
-import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
-import java.security.Key;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import javax.crypto.KeyAgreementSpi;
-import javax.crypto.SecretKey;
-import javax.crypto.ShortBufferException;
-import javax.crypto.spec.SecretKeySpec;
 
 /**
  * Elliptic Curve Diffie-Hellman key agreement backed by the OpenSSL engine.
  */
 @Internal
-public final class OpenSSLECDHKeyAgreement extends KeyAgreementSpi {
-
-    /** OpenSSL handle of the private key. Only available after the engine has been initialized. */
-    private OpenSSLKey mOpenSslPrivateKey;
-
-    /**
-     * Expected length (in bytes) of the agreed key ({@link #mResult}). Only available after the
-     * engine has been initialized.
-     */
-    private int mExpectedResultLength;
-
-    /** Agreed key. Only available after {@link #engineDoPhase(Key, boolean)} completes. */
-    private byte[] mResult;
-
-    public OpenSSLECDHKeyAgreement() {}
+public final class OpenSSLECDHKeyAgreement extends OpenSSLBaseDHKeyAgreement<OpenSSLKey> {
+    public OpenSSLECDHKeyAgreement() {
+    }
 
     @Override
-    public Key engineDoPhase(Key key, boolean lastPhase) throws InvalidKeyException {
-        if (mOpenSslPrivateKey == null) {
-            throw new IllegalStateException("Not initialized");
-        }
-        if (!lastPhase) {
-            throw new IllegalStateException("ECDH only has one phase");
-        }
+    protected OpenSSLKey convertPublicKey(PublicKey key) throws InvalidKeyException {
+        return OpenSSLKey.fromPublicKey(key);
+    }
 
-        if (key == null) {
-            throw new InvalidKeyException("key == null");
-        }
-        if (!(key instanceof PublicKey)) {
-            throw new InvalidKeyException("Not a public key: " + key.getClass());
-        }
-        OpenSSLKey openSslPublicKey = OpenSSLKey.fromPublicKey((PublicKey) key);
+    @Override
+    protected OpenSSLKey convertPrivateKey(PrivateKey key) throws InvalidKeyException {
+        return OpenSSLKey.fromPrivateKey(key);
+    }
 
-        byte[] buffer = new byte[mExpectedResultLength];
-        int actualResultLength = NativeCrypto.ECDH_compute_key(
+    @Override
+    protected int computeKey(byte[] buffer, OpenSSLKey theirPublicKey, OpenSSLKey ourPrivateKey) throws InvalidKeyException {
+        return NativeCrypto.ECDH_compute_key(
                 buffer,
                 0,
-                openSslPublicKey.getNativeRef(),
-                mOpenSslPrivateKey.getNativeRef());
-        byte[] result;
-        if (actualResultLength == -1) {
-            throw new RuntimeException("Engine returned " + actualResultLength);
-        } else if (actualResultLength == mExpectedResultLength) {
-            // The output is as long as expected -- use the whole buffer
-            result = buffer;
-        } else if (actualResultLength < mExpectedResultLength) {
-            // The output is shorter than expected -- use only what's produced by the engine
-            result = new byte[actualResultLength];
-            System.arraycopy(buffer, 0, mResult, 0, mResult.length);
-        } else {
-            // The output is longer than expected
-            throw new RuntimeException("Engine produced a longer than expected result. Expected: "
-                + mExpectedResultLength + ", actual: " + actualResultLength);
-        }
-        mResult = result;
-
-        return null; // No intermediate key
+                theirPublicKey.getNativeRef(),
+                ourPrivateKey.getNativeRef());
     }
 
     @Override
-    protected int engineGenerateSecret(byte[] sharedSecret, int offset)
-            throws ShortBufferException {
-        checkCompleted();
-        int available = sharedSecret.length - offset;
-        if (mResult.length > available) {
-            throw new ShortBufferWithoutStackTraceException(
-                    "Needed: " + mResult.length + ", available: " + available);
-        }
-
-        System.arraycopy(mResult, 0, sharedSecret, offset, mResult.length);
-        return mResult.length;
-    }
-
-    @Override
-    protected byte[] engineGenerateSecret() {
-        checkCompleted();
-        return mResult;
-    }
-
-    @Override
-    protected SecretKey engineGenerateSecret(String algorithm) {
-        checkCompleted();
-        return new SecretKeySpec(engineGenerateSecret(), algorithm);
-    }
-
-    @Override
-    protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException {
-        if (key == null) {
-            throw new InvalidKeyException("key == null");
-        }
-        if (!(key instanceof PrivateKey)) {
-            throw new InvalidKeyException("Not a private key: " + key.getClass());
-        }
-
-        OpenSSLKey openSslKey = OpenSSLKey.fromPrivateKey((PrivateKey) key);
+    protected int getOutputSize(OpenSSLKey openSslKey) {
         int fieldSizeBits = NativeCrypto.EC_GROUP_get_degree(new NativeRef.EC_GROUP(
                 NativeCrypto.EC_KEY_get1_group(openSslKey.getNativeRef())));
-        mExpectedResultLength = (fieldSizeBits + 7) / 8;
-        mOpenSslPrivateKey = openSslKey;
-    }
-
-    @Override
-    protected void engineInit(Key key, AlgorithmParameterSpec params,
-            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
-        // ECDH doesn't need an AlgorithmParameterSpec
-        if (params != null) {
-          throw new InvalidAlgorithmParameterException("No algorithm parameters supported");
-        }
-        engineInit(key, random);
-    }
-
-    private void checkCompleted() {
-        if (mResult == null) {
-            throw new IllegalStateException("Key agreement not completed");
-        }
+        return (fieldSizeBits + 7) / 8;
     }
 }
diff --git a/common/src/main/java/org/conscrypt/OpenSSLECPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSSLECPrivateKey.java
index 63495f1..9af2b13 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLECPrivateKey.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLECPrivateKey.java
@@ -41,9 +41,9 @@
 
     private static final String ALGORITHM = "EC";
 
-    protected transient OpenSSLKey key;
+    private transient OpenSSLKey key;
 
-    protected transient OpenSSLECGroupContext group;
+    private transient OpenSSLECGroupContext group;
 
     OpenSSLECPrivateKey(OpenSSLECGroupContext group, OpenSSLKey key) {
         this.group = group;
diff --git a/common/src/main/java/org/conscrypt/OpenSSLECPublicKey.java b/common/src/main/java/org/conscrypt/OpenSSLECPublicKey.java
index 1891e24..d68c465 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLECPublicKey.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLECPublicKey.java
@@ -37,9 +37,9 @@
 
     private static final String ALGORITHM = "EC";
 
-    protected transient OpenSSLKey key;
+    private transient OpenSSLKey key;
 
-    protected transient OpenSSLECGroupContext group;
+    private transient OpenSSLECGroupContext group;
 
     OpenSSLECPublicKey(OpenSSLECGroupContext group, OpenSSLKey key) {
         this.group = group;
diff --git a/common/src/main/java/org/conscrypt/OpenSSLEvpCipher.java b/common/src/main/java/org/conscrypt/OpenSSLEvpCipher.java
index 7631c78..f527127 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLEvpCipher.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLEvpCipher.java
@@ -46,7 +46,7 @@
      */
     private int modeBlockSize;
 
-    public OpenSSLEvpCipher(Mode mode, Padding padding) {
+    protected OpenSSLEvpCipher(Mode mode, Padding padding) {
         super(mode, padding);
     }
 
diff --git a/common/src/main/java/org/conscrypt/OpenSSLEvpCipherAES.java b/common/src/main/java/org/conscrypt/OpenSSLEvpCipherAES.java
index b094410..b49468e 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLEvpCipherAES.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLEvpCipherAES.java
@@ -78,13 +78,13 @@
                 super(Mode.CBC, padding);
             }
 
-            public static class NoPadding extends CBC {
+            public static class NoPadding extends AES.CBC {
                 public NoPadding() {
                     super(Padding.NOPADDING);
                 }
             }
 
-            public static class PKCS5Padding extends CBC {
+            public static class PKCS5Padding extends AES.CBC {
                 public PKCS5Padding() {
                     super(Padding.PKCS5PADDING);
                 }
@@ -102,13 +102,13 @@
                 super(Mode.ECB, padding);
             }
 
-            public static class NoPadding extends ECB {
+            public static class NoPadding extends AES.ECB {
                 public NoPadding() {
                     super(Padding.NOPADDING);
                 }
             }
 
-            public static class PKCS5Padding extends ECB {
+            public static class PKCS5Padding extends AES.ECB {
                 public PKCS5Padding() {
                     super(Padding.PKCS5PADDING);
                 }
@@ -139,13 +139,13 @@
                 super(Mode.CBC, padding);
             }
 
-            public static class NoPadding extends CBC {
+            public static class NoPadding extends AES_128.CBC {
                 public NoPadding() {
                     super(Padding.NOPADDING);
                 }
             }
 
-            public static class PKCS5Padding extends CBC {
+            public static class PKCS5Padding extends AES_128.CBC {
                 public PKCS5Padding() {
                     super(Padding.PKCS5PADDING);
                 }
@@ -163,13 +163,13 @@
                 super(Mode.ECB, padding);
             }
 
-            public static class NoPadding extends ECB {
+            public static class NoPadding extends AES_128.ECB {
                 public NoPadding() {
                     super(Padding.NOPADDING);
                 }
             }
 
-            public static class PKCS5Padding extends ECB {
+            public static class PKCS5Padding extends AES_128.ECB {
                 public PKCS5Padding() {
                     super(Padding.PKCS5PADDING);
                 }
@@ -194,13 +194,13 @@
                 super(Mode.CBC, padding);
             }
 
-            public static class NoPadding extends CBC {
+            public static class NoPadding extends AES_256.CBC {
                 public NoPadding() {
                     super(Padding.NOPADDING);
                 }
             }
 
-            public static class PKCS5Padding extends CBC {
+            public static class PKCS5Padding extends AES_256.CBC {
                 public PKCS5Padding() {
                     super(Padding.PKCS5PADDING);
                 }
@@ -218,13 +218,13 @@
                 super(Mode.ECB, padding);
             }
 
-            public static class NoPadding extends ECB {
+            public static class NoPadding extends AES_256.ECB {
                 public NoPadding() {
                     super(Padding.NOPADDING);
                 }
             }
 
-            public static class PKCS5Padding extends ECB {
+            public static class PKCS5Padding extends AES_256.ECB {
                 public PKCS5Padding() {
                     super(Padding.PKCS5PADDING);
                 }
diff --git a/common/src/main/java/org/conscrypt/OpenSSLMac.java b/common/src/main/java/org/conscrypt/OpenSSLMac.java
index 13f1d75..5a5a068 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLMac.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLMac.java
@@ -30,18 +30,10 @@
  */
 @Internal
 public abstract class OpenSSLMac extends MacSpi {
-    private NativeRef.HMAC_CTX ctx;
-
-    /**
-     * Holds the EVP_MD for the hashing algorithm, e.g.
-     * EVP_get_digestbyname("sha1");
-     */
-    private final long evp_md;
-
     /**
      * The secret key used in this keyed MAC.
      */
-    private byte[] keyBytes;
+    protected byte[] keyBytes;
 
     /**
      * Holds the output size of the message digest.
@@ -53,11 +45,20 @@
      */
     private final byte[] singleByte = new byte[1];
 
-    private OpenSSLMac(long evp_md, int size) {
-        this.evp_md = evp_md;
+    private OpenSSLMac(int size) {
         this.size = size;
     }
 
+    /**
+     * Creates and initializes the relevant BoringSSL *MAC context.
+     */
+    protected abstract void resetContext();
+
+    /**
+     * Passes the contents of a direct ByteBuffer to the relevant BoringSSL *MAC function.
+     */
+    protected abstract void updateDirect(long ptr, int len);
+
     @Override
     protected int engineGetMacLength() {
         return size;
@@ -82,15 +83,6 @@
         resetContext();
     }
 
-    private final void resetContext() {
-        NativeRef.HMAC_CTX ctxLocal = new NativeRef.HMAC_CTX(NativeCrypto.HMAC_CTX_new());
-        if (keyBytes != null) {
-            NativeCrypto.HMAC_Init_ex(ctxLocal, keyBytes, evp_md);
-        }
-
-        this.ctx = ctxLocal;
-    }
-
     @Override
     protected void engineUpdate(byte input) {
         singleByte[0] = input;
@@ -98,17 +90,10 @@
     }
 
     @Override
-    protected void engineUpdate(byte[] input, int offset, int len) {
-        final NativeRef.HMAC_CTX ctxLocal = ctx;
-        NativeCrypto.HMAC_Update(ctxLocal, input, offset, len);
-    }
-
-    @Override
     protected void engineUpdate(ByteBuffer input) {
         // Optimization: Avoid copying/allocation for direct buffers because their contents are
         // stored as a contiguous region in memory and thus can be efficiently accessed from native
         // code.
-
         if (!input.hasRemaining()) {
             return;
         }
@@ -120,7 +105,7 @@
 
         long baseAddress = NativeCrypto.getDirectBufferAddress(input);
         if (baseAddress == 0) {
-            // Direct buffer's contents can't be accessed from JNI  -- superclass's implementation
+            // Direct buffers' contents can't be accessed from JNI  -- superclass's implementation
             // is good enough to handle this.
             super.engineUpdate(input);
             return;
@@ -137,57 +122,135 @@
             throw new RuntimeException("Negative remaining amount");
         }
 
-        final NativeRef.HMAC_CTX ctxLocal = ctx;
-        NativeCrypto.HMAC_UpdateDirect(ctxLocal, ptr, len);
+        updateDirect(ptr, len);
         input.position(position + len);
     }
 
     @Override
     protected byte[] engineDoFinal() {
-        final NativeRef.HMAC_CTX ctxLocal = ctx;
-        final byte[] output = NativeCrypto.HMAC_Final(ctxLocal);
+        final byte[] output = doFinal();
         resetContext();
         return output;
     }
 
+    protected abstract byte[] doFinal();
+
     @Override
     protected void engineReset() {
         resetContext();
     }
 
-    public static final class HmacMD5 extends OpenSSLMac {
+    public static class Hmac extends OpenSSLMac {
+        private NativeRef.HMAC_CTX ctx;
+
+        /**
+         * Holds the EVP_MD for the hashing algorithm, e.g.
+         * EVP_get_digestbyname("sha1");
+         */
+        private final long evp_md;
+
+        public Hmac(long evp_md, int size) {
+            super(size);
+            this.evp_md = evp_md;
+        }
+
+        @Override
+        protected void resetContext() {
+            NativeRef.HMAC_CTX ctxLocal = new NativeRef.HMAC_CTX(NativeCrypto.HMAC_CTX_new());
+            if (keyBytes != null) {
+                NativeCrypto.HMAC_Init_ex(ctxLocal, keyBytes, evp_md);
+            }
+            this.ctx = ctxLocal;
+        }
+
+        @Override
+        protected void engineUpdate(byte[] input, int offset, int len) {
+            final NativeRef.HMAC_CTX ctxLocal = ctx;
+            NativeCrypto.HMAC_Update(ctxLocal, input, offset, len);
+        }
+
+        @Override
+        protected void updateDirect(long ptr, int len) {
+            final NativeRef.HMAC_CTX ctxLocal = ctx;
+            NativeCrypto.HMAC_UpdateDirect(ctxLocal, ptr, len);
+        }
+
+        @Override
+        protected byte[] doFinal() {
+            final NativeRef.HMAC_CTX ctxLocal = ctx;
+            return NativeCrypto.HMAC_Final(ctxLocal);
+        }
+
+    }
+
+    public static final class HmacMD5 extends Hmac {
         public HmacMD5() {
             super(EvpMdRef.MD5.EVP_MD, EvpMdRef.MD5.SIZE_BYTES);
         }
     }
 
-    public static final class HmacSHA1 extends OpenSSLMac {
+    public static final class HmacSHA1 extends Hmac {
         public HmacSHA1() {
             super(EvpMdRef.SHA1.EVP_MD, EvpMdRef.SHA1.SIZE_BYTES);
         }
     }
 
-    public static final class HmacSHA224 extends OpenSSLMac {
+    public static final class HmacSHA224 extends Hmac {
         public HmacSHA224() throws NoSuchAlgorithmException {
             super(EvpMdRef.SHA224.EVP_MD, EvpMdRef.SHA224.SIZE_BYTES);
         }
     }
 
-    public static final class HmacSHA256 extends OpenSSLMac {
+    public static final class HmacSHA256 extends Hmac {
         public HmacSHA256() throws NoSuchAlgorithmException {
             super(EvpMdRef.SHA256.EVP_MD, EvpMdRef.SHA256.SIZE_BYTES);
         }
     }
 
-    public static final class HmacSHA384 extends OpenSSLMac {
+    public static final class HmacSHA384 extends Hmac {
         public HmacSHA384() throws NoSuchAlgorithmException {
             super(EvpMdRef.SHA384.EVP_MD, EvpMdRef.SHA384.SIZE_BYTES);
         }
     }
 
-    public static final class HmacSHA512 extends OpenSSLMac {
+    public static final class HmacSHA512 extends Hmac {
         public HmacSHA512() {
             super(EvpMdRef.SHA512.EVP_MD, EvpMdRef.SHA512.SIZE_BYTES);
         }
     }
+
+    public static final class AesCmac extends OpenSSLMac {
+        private NativeRef.CMAC_CTX ctx;
+
+        public AesCmac() {
+            super(16);
+        }
+
+        @Override
+        protected void resetContext() {
+            NativeRef.CMAC_CTX ctxLocal = new NativeRef.CMAC_CTX(NativeCrypto.CMAC_CTX_new());
+            if (keyBytes != null) {
+                NativeCrypto.CMAC_Init(ctxLocal, keyBytes);
+            }
+            this.ctx = ctxLocal;
+        }
+
+        @Override
+        protected void updateDirect(long ptr, int len) {
+            final NativeRef.CMAC_CTX ctxLocal = ctx;
+            NativeCrypto.CMAC_UpdateDirect(ctxLocal, ptr, len);
+        }
+
+        @Override
+        protected byte[] doFinal() {
+            final NativeRef.CMAC_CTX ctxLocal = ctx;
+            return NativeCrypto.CMAC_Final(ctxLocal);
+        }
+
+        @Override
+        protected void engineUpdate(byte[] input, int offset, int len) {
+            final NativeRef.CMAC_CTX ctxLocal = ctx;
+            NativeCrypto.CMAC_Update(ctxLocal, input, offset, len);
+        }
+    }
 }
diff --git a/common/src/main/java/org/conscrypt/OpenSSLProvider.java b/common/src/main/java/org/conscrypt/OpenSSLProvider.java
index 1e703ce..607ae1e 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLProvider.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLProvider.java
@@ -38,6 +38,8 @@
 
     private static final String STANDARD_EC_PRIVATE_KEY_INTERFACE_CLASS_NAME =
             "java.security.interfaces.ECPrivateKey";
+    private static final String STANDARD_XEC_PRIVATE_KEY_INTERFACE_CLASS_NAME =
+            "java.security.interfaces.XECPrivateKey";
     private static final String STANDARD_RSA_PRIVATE_KEY_INTERFACE_CLASS_NAME =
             "java.security.interfaces.RSAPrivateKey";
     private static final String STANDARD_RSA_PUBLIC_KEY_INTERFACE_CLASS_NAME =
@@ -190,6 +192,9 @@
         put("Alg.Alias.KeyPairGenerator.1.2.840.10045.2.1", "EC");
         put("Alg.Alias.KeyPairGenerator.1.3.133.16.840.63.0.2", "EC");
 
+        put("KeyPairGenerator.XDH", PREFIX + "OpenSSLXDHKeyPairGenerator");
+        put("Alg.Alias.KeyPairGenerator.1.3.101.110", "XDH");
+
         /* == KeyFactory == */
         put("KeyFactory.RSA", PREFIX + "OpenSSLRSAKeyFactory");
         put("Alg.Alias.KeyFactory.1.2.840.113549.1.1.1", "RSA");
@@ -200,12 +205,16 @@
         put("Alg.Alias.KeyFactory.1.2.840.10045.2.1", "EC");
         put("Alg.Alias.KeyFactory.1.3.133.16.840.63.0.2", "EC");
 
+        put("KeyFactory.XDH", PREFIX + "OpenSSLXDHKeyFactory");
+        put("Alg.Alias.KeyFactory.1.3.101.110", "XDH");
+
         /* == SecretKeyFactory == */
         put("SecretKeyFactory.DESEDE", PREFIX + "DESEDESecretKeyFactory");
         put("Alg.Alias.SecretKeyFactory.TDEA", "DESEDE");
 
         /* == KeyAgreement == */
         putECDHKeyAgreementImplClass("OpenSSLECDHKeyAgreement");
+        putXDHKeyAgreementImplClass("OpenSSLXDHKeyAgreement");
 
         /* == Signatures == */
         putSignatureImplClass("MD5withRSA", "OpenSSLSignature$MD5RSA");
@@ -494,6 +503,8 @@
         put("Alg.Alias.Mac.HMAC/SHA512", "HmacSHA512");
         put("Alg.Alias.Mac.PBEWITHHMACSHA512", "HmacSHA512");
 
+        putMacImplClass("AESCMAC", "OpenSSLMac$AesCmac");
+
         /* === Certificate === */
 
         put("CertificateFactory.X509", PREFIX + "OpenSSLX509CertificateFactory");
@@ -592,6 +603,22 @@
                 supportedKeyFormats);
     }
 
+    private void putXDHKeyAgreementImplClass(String className) {
+        // Accept only keys for which any of the following is true:
+        // * the key is from this provider (subclass of OpenSSLKeyHolder),
+        // * the key provides its key material in "PKCS#8" encoding via Key.getEncoded.
+        // * the key is a transparent XEC private key (subclass of XECPrivateKey).
+        String supportedKeyClasses = PREFIX + "OpenSSLKeyHolder"
+                + "|" + STANDARD_XEC_PRIVATE_KEY_INTERFACE_CLASS_NAME
+                + "|" + PREFIX + "OpenSSLX25519PrivateKey";
+        String supportedKeyFormats = "PKCS#8";
+        putImplClassWithKeyConstraints(
+                "KeyAgreement.XDH",
+                PREFIX + className,
+                supportedKeyClasses,
+                supportedKeyFormats);
+    }
+
     private void putImplClassWithKeyConstraints(String typeAndAlgName,
             String fullyQualifiedClassName,
             String supportedKeyClasses,
diff --git a/common/src/main/java/org/conscrypt/OpenSSLX25519Key.java b/common/src/main/java/org/conscrypt/OpenSSLX25519Key.java
new file mode 100644
index 0000000..8845999
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/OpenSSLX25519Key.java
@@ -0,0 +1,7 @@
+package org.conscrypt;
+
+public interface OpenSSLX25519Key {
+    int X25519_KEY_SIZE_BYTES = 32;
+
+    byte[] getU();
+}
diff --git a/common/src/main/java/org/conscrypt/OpenSSLX25519PrivateKey.java b/common/src/main/java/org/conscrypt/OpenSSLX25519PrivateKey.java
new file mode 100644
index 0000000..a3c7e88
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/OpenSSLX25519PrivateKey.java
@@ -0,0 +1,106 @@
+package org.conscrypt;
+
+import java.security.PrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Arrays;
+
+public class OpenSSLX25519PrivateKey implements OpenSSLX25519Key, PrivateKey {
+    private static final byte[] PKCS8_PREAMBLE = new byte[]{
+            0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x04, 0x22, 0x04, 0x20,
+    };
+
+    private static final byte[] PKCS8_PREAMBLE_WITH_NULL = new byte[] {
+            0x30, 0x30, 0x02, 0x01, 0x00, 0x30, 0x07, 0x06, 0x03, 0x2B, 0x65, 0x6E, 0x05, 0x00, 0x04, 0x22, 0x04, 0x20,
+    };
+
+    private byte[] uCoordinate;
+
+    public OpenSSLX25519PrivateKey(PKCS8EncodedKeySpec keySpec) throws InvalidKeySpecException {
+        byte[] encoded = keySpec.getEncoded();
+        if (encoded == null || !"PKCS#8".equals(keySpec.getFormat())) {
+            throw new InvalidKeySpecException("Key must be encoded in PKCS#8 format");
+        }
+
+        int preambleLength = matchesPreamble(PKCS8_PREAMBLE, encoded) | matchesPreamble(PKCS8_PREAMBLE_WITH_NULL, encoded);
+        if (preambleLength == 0) {
+            throw new InvalidKeySpecException("Key size is not correct size");
+        }
+
+        uCoordinate = Arrays.copyOfRange(encoded, PKCS8_PREAMBLE.length, encoded.length);
+    }
+
+    private static int matchesPreamble(byte[] preamble, byte[] encoded) {
+        if (encoded.length != (preamble.length + X25519_KEY_SIZE_BYTES)) {
+            return 0;
+        }
+        int cmp = 0;
+        for (int i = 0; i < preamble.length; i++) {
+            cmp |= encoded[i] ^ preamble[i];
+        }
+        if (cmp != 0) {
+            return 0;
+        }
+        return preamble.length;
+    }
+
+    public OpenSSLX25519PrivateKey(byte[] coordinateBytes) {
+        uCoordinate = coordinateBytes.clone();
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return "XDH";
+    }
+
+    @Override
+    public String getFormat() {
+        return "PKCS#8";
+    }
+
+    @Override
+    public byte[] getEncoded() {
+        if (uCoordinate == null) {
+            throw new IllegalStateException("key is destroyed");
+        }
+
+        byte[] encoded = Arrays.copyOf(PKCS8_PREAMBLE, PKCS8_PREAMBLE.length + uCoordinate.length);
+        System.arraycopy(uCoordinate, 0, encoded, PKCS8_PREAMBLE.length, uCoordinate.length);
+        return encoded;
+    }
+
+    @Override
+    public byte[] getU() {
+        if (uCoordinate == null) {
+            throw new IllegalStateException("key is destroyed");
+        }
+
+        return uCoordinate.clone();
+    }
+
+    @Override
+    public void destroy() {
+        if (uCoordinate != null) {
+            Arrays.fill(uCoordinate, (byte) 0);
+            uCoordinate = null;
+        }
+    }
+
+    @Override
+    public boolean isDestroyed() {
+        return uCoordinate == null;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof OpenSSLX25519PrivateKey)) return false;
+        OpenSSLX25519PrivateKey that = (OpenSSLX25519PrivateKey) o;
+        return Arrays.equals(uCoordinate, that.uCoordinate);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(uCoordinate);
+    }
+}
\ No newline at end of file
diff --git a/common/src/main/java/org/conscrypt/OpenSSLX25519PublicKey.java b/common/src/main/java/org/conscrypt/OpenSSLX25519PublicKey.java
new file mode 100644
index 0000000..abe8dc0
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/OpenSSLX25519PublicKey.java
@@ -0,0 +1,101 @@
+package org.conscrypt;
+
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+
+public class OpenSSLX25519PublicKey implements OpenSSLX25519Key, PublicKey {
+    private static final byte[] X509_PREAMBLE = new byte[] {
+            0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00,
+    };
+
+    private static final byte[] X509_PREAMBLE_WITH_NULL = new byte[] {
+            0x30, 0x2C, 0x30, 0x07, 0x06, 0x03, 0x2B, 0x65, 0x6E, 0x05, 0x00, 0x03, 0x21, 0x00,
+    };
+
+    private byte[] uCoordinate;
+
+    public OpenSSLX25519PublicKey(X509EncodedKeySpec keySpec) throws InvalidKeySpecException {
+        byte[] encoded = keySpec.getEncoded();
+        if (encoded == null || !"X.509".equals(keySpec.getFormat())) {
+            throw new InvalidKeySpecException("Encoding must be in X.509 format");
+        }
+
+        int preambleLength = matchesPreamble(X509_PREAMBLE, encoded) | matchesPreamble(X509_PREAMBLE_WITH_NULL, encoded);
+        if (preambleLength == 0) {
+            throw new InvalidKeySpecException("Key size is not correct size");
+        }
+
+        uCoordinate = Arrays.copyOfRange(encoded, preambleLength, encoded.length);
+    }
+
+    private static int matchesPreamble(byte[] preamble, byte[] encoded) {
+        if (encoded.length != (preamble.length + X25519_KEY_SIZE_BYTES)) {
+            return 0;
+        }
+        int cmp = 0;
+        for (int i = 0; i < preamble.length; i++) {
+            cmp |= encoded[i] ^ preamble[i];
+        }
+        if (cmp != 0) {
+            return 0;
+        }
+        return preamble.length;
+    }
+
+    public OpenSSLX25519PublicKey(byte[] coordinateBytes) {
+        uCoordinate = coordinateBytes.clone();
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return "XDH";
+    }
+
+    @Override
+    public String getFormat() {
+        return "X.509";
+    }
+
+    @Override
+    public byte[] getEncoded() {
+        if (uCoordinate == null) {
+            throw new IllegalStateException("key is destroyed");
+        }
+
+        byte[] encoded = Arrays.copyOf(X509_PREAMBLE, X509_PREAMBLE.length + X25519_KEY_SIZE_BYTES);
+        System.arraycopy(uCoordinate, 0, encoded, X509_PREAMBLE.length, uCoordinate.length);
+        return encoded;
+    }
+
+    @Override
+    public byte[] getU() {
+        if (uCoordinate == null) {
+            throw new IllegalStateException("key is destroyed");
+        }
+
+        return uCoordinate.clone();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (uCoordinate == null) {
+            throw new IllegalStateException("key is destroyed");
+        }
+
+        if (this == o) return true;
+        if (!(o instanceof OpenSSLX25519PublicKey)) return false;
+        OpenSSLX25519PublicKey that = (OpenSSLX25519PublicKey) o;
+        return Arrays.equals(uCoordinate, that.uCoordinate);
+    }
+
+    @Override
+    public int hashCode() {
+        if (uCoordinate == null) {
+            throw new IllegalStateException("key is destroyed");
+        }
+
+        return Arrays.hashCode(uCoordinate);
+    }
+}
diff --git a/common/src/main/java/org/conscrypt/OpenSSLX509CRL.java b/common/src/main/java/org/conscrypt/OpenSSLX509CRL.java
index abe5932..13a944b 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLX509CRL.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLX509CRL.java
@@ -98,12 +98,12 @@
             bis.release();
         }
 
-        final List<OpenSSLX509CRL> certs = new ArrayList<OpenSSLX509CRL>(certRefs.length);
-        for (int i = 0; i < certRefs.length; i++) {
-            if (certRefs[i] == 0) {
+        final List<OpenSSLX509CRL> certs = new ArrayList<>(certRefs.length);
+        for (long certRef : certRefs) {
+            if (certRef == 0) {
                 continue;
             }
-            certs.add(new OpenSSLX509CRL(certRefs[i]));
+            certs.add(new OpenSSLX509CRL(certRef));
         }
         return certs;
     }
@@ -138,12 +138,12 @@
             bis.release();
         }
 
-        final List<OpenSSLX509CRL> certs = new ArrayList<OpenSSLX509CRL>(certRefs.length);
-        for (int i = 0; i < certRefs.length; i++) {
-            if (certRefs[i] == 0) {
+        final List<OpenSSLX509CRL> certs = new ArrayList<>(certRefs.length);
+        for (long certRef : certRefs) {
+            if (certRef == 0) {
                 continue;
             }
-            certs.add(new OpenSSLX509CRL(certRefs[i]));
+            certs.add(new OpenSSLX509CRL(certRef));
         }
         return certs;
     }
@@ -164,7 +164,7 @@
             return null;
         }
 
-        return new HashSet<String>(Arrays.asList(critOids));
+        return new HashSet<>(Arrays.asList(critOids));
     }
 
     @Override
@@ -189,7 +189,7 @@
             return null;
         }
 
-        return new HashSet<String>(Arrays.asList(nonCritOids));
+        return new HashSet<>(Arrays.asList(nonCritOids));
     }
 
     @Override
@@ -220,9 +220,8 @@
         }
     }
 
-    private void verifyInternal(PublicKey key, String sigProvider) throws CRLException,
-            NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
-            SignatureException {
+    private void verifyInternal(PublicKey key, String sigProvider) throws NoSuchAlgorithmException,
+            InvalidKeyException, NoSuchProviderException, SignatureException {
         String sigAlg = getSigAlgName();
         if (sigAlg == null) {
             sigAlg = getSigAlgOID();
@@ -329,7 +328,7 @@
             return null;
         }
 
-        final Set<OpenSSLX509CRLEntry> crlSet = new HashSet<OpenSSLX509CRLEntry>();
+        final Set<OpenSSLX509CRLEntry> crlSet = new HashSet<>();
         for (long entryRef : entryRefs) {
             try {
                 crlSet.add(new OpenSSLX509CRLEntry(entryRef));
@@ -342,7 +341,7 @@
     }
 
     @Override
-    public byte[] getTBSCertList() throws CRLException {
+    public byte[] getTBSCertList() {
         return NativeCrypto.get_X509_CRL_crl_enc(mContext, this);
     }
 
@@ -412,6 +411,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     protected void finalize() throws Throwable {
         try {
             if (mContext != 0) {
diff --git a/common/src/main/java/org/conscrypt/OpenSSLX509CertPath.java b/common/src/main/java/org/conscrypt/OpenSSLX509CertPath.java
index 50c2c30..3a5cb04 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLX509CertPath.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLX509CertPath.java
@@ -157,6 +157,7 @@
                 try {
                     inStream.reset();
                 } catch (IOException ignored) {
+                    // Ignored
                 }
             }
             throw new CertificateException(e);
@@ -220,6 +221,7 @@
                 try {
                     inStream.reset();
                 } catch (IOException ignored) {
+                    // Ignored
                 }
             }
             throw new CertificateException(e);
diff --git a/common/src/main/java/org/conscrypt/OpenSSLX509Certificate.java b/common/src/main/java/org/conscrypt/OpenSSLX509Certificate.java
index 2c86579..8245956 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLX509Certificate.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLX509Certificate.java
@@ -130,16 +130,16 @@
         if (certRefs == null) {
             // To avoid returning a immutable list in only one path, we create an
             // empty list here instead of using Collections.emptyList()
-            return new ArrayList<OpenSSLX509Certificate>();
+            return new ArrayList<>();
         }
 
-        final List<OpenSSLX509Certificate> certs = new ArrayList<OpenSSLX509Certificate>(
+        final List<OpenSSLX509Certificate> certs = new ArrayList<>(
                 certRefs.length);
-        for (int i = 0; i < certRefs.length; i++) {
-            if (certRefs[i] == 0) {
+        for (long certRef : certRefs) {
+            if (certRef == 0) {
                 continue;
             }
-            certs.add(new OpenSSLX509Certificate(certRefs[i]));
+            certs.add(new OpenSSLX509Certificate(certRef));
         }
         return certs;
     }
@@ -177,13 +177,13 @@
             bis.release();
         }
 
-        final List<OpenSSLX509Certificate> certs = new ArrayList<OpenSSLX509Certificate>(
+        final List<OpenSSLX509Certificate> certs = new ArrayList<>(
                 certRefs.length);
-        for (int i = 0; i < certRefs.length; i++) {
-            if (certRefs[i] == 0) {
+        for (long certRef : certRefs) {
+            if (certRef == 0) {
                 continue;
             }
-            certs.add(new OpenSSLX509Certificate(certRefs[i]));
+            certs.add(new OpenSSLX509Certificate(certRef));
         }
         return certs;
     }
@@ -215,7 +215,7 @@
             return null;
         }
 
-        return new HashSet<String>(Arrays.asList(critOids));
+        return new HashSet<>(Arrays.asList(critOids));
     }
 
     @Override
@@ -239,7 +239,7 @@
             return null;
         }
 
-        return new HashSet<String>(Arrays.asList(nonCritOids));
+        return new HashSet<>(Arrays.asList(nonCritOids));
     }
 
     @Override
@@ -248,12 +248,14 @@
     }
 
     @Override
+    @SuppressWarnings("JdkObsolete")  // Needed for API compatibility
     public void checkValidity() throws CertificateExpiredException,
             CertificateNotYetValidException {
         checkValidity(new Date());
     }
 
     @Override
+    @SuppressWarnings("JdkObsolete")  // Needed for API compatibility
     public void checkValidity(Date date) throws CertificateExpiredException,
             CertificateNotYetValidException {
         if (getNotBefore().compareTo(date) > 0) {
@@ -299,7 +301,7 @@
 
     @Override
     public byte[] getTBSCertificate() throws CertificateEncodingException {
-        return NativeCrypto.get_X509_cert_info_enc(mContext, this);
+        return NativeCrypto.get_X509_tbs_cert(mContext, this);
     }
 
     @Override
@@ -376,9 +378,7 @@
         return NativeCrypto.i2d_X509(mContext, this);
     }
 
-    private void verifyOpenSSL(OpenSSLKey pkey) throws CertificateException,
-                                                       NoSuchAlgorithmException,
-                                                       InvalidKeyException, SignatureException {
+    private void verifyOpenSSL(OpenSSLKey pkey) throws CertificateException, SignatureException {
         try {
             NativeCrypto.X509_verify(mContext, this, pkey.getNativeRef());
         } catch (RuntimeException e) {
@@ -388,9 +388,9 @@
         }
     }
 
-    private void verifyInternal(PublicKey key, String sigProvider) throws CertificateException,
+    private void verifyInternal(PublicKey key, String sigProvider) throws
             NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
-            SignatureException {
+            SignatureException, CertificateEncodingException {
         final Signature sig;
         if (sigProvider == null) {
             sig = Signature.getInstance(getSigAlgName());
@@ -414,7 +414,7 @@
             return;
         }
 
-        verifyInternal(key, (String) null);
+        verifyInternal(key, null);
     }
 
     @Override
@@ -468,8 +468,8 @@
         try {
             OpenSSLKey pkey = new OpenSSLKey(NativeCrypto.X509_get_pubkey(mContext, this));
             return pkey.getPublicKey();
-        } catch (NoSuchAlgorithmException ignored) {
-        } catch (InvalidKeyException ignored) {
+        } catch (NoSuchAlgorithmException | InvalidKeyException ignored) {
+            // Ignored
         }
 
         /* Try generating the key using other Java providers. */
@@ -478,8 +478,8 @@
         try {
             KeyFactory kf = KeyFactory.getInstance(oid);
             return kf.generatePublic(new X509EncodedKeySpec(encoded));
-        } catch (NoSuchAlgorithmException ignored) {
-        } catch (InvalidKeySpecException ignored) {
+        } catch (NoSuchAlgorithmException | InvalidKeySpecException ignored) {
+            // Ignored
         }
 
         /*
@@ -502,7 +502,7 @@
     }
 
     @Override
-    public List<String> getExtendedKeyUsage() throws CertificateParsingException {
+    public List<String> getExtendedKeyUsage() {
         String[] extUsage = NativeCrypto.get_X509_ex_xkusage(mContext, this);
         if (extUsage == null) {
             return null;
@@ -516,9 +516,9 @@
             return null;
         }
 
-        Collection<List<?>> coll = new ArrayList<List<?>>(altNameArray.length);
-        for (int i = 0; i < altNameArray.length; i++) {
-            coll.add(Collections.unmodifiableList(Arrays.asList(altNameArray[i])));
+        Collection<List<?>> coll = new ArrayList<>(altNameArray.length);
+        for (Object[] objects : altNameArray) {
+            coll.add(Collections.unmodifiableList(Arrays.asList(objects)));
         }
 
         return Collections.unmodifiableCollection(coll);
@@ -567,19 +567,14 @@
     }
 
     /**
-     * Delete an extension.
-     *
-     * A modified copy of the certificate is returned. The original object
-     * is unchanged.
-     * If the extension is not present, an unmodified copy is returned.
+     * Returns a re-encoded TBSCertificate with the extension identified by oid removed.
      */
-    public OpenSSLX509Certificate withDeletedExtension(String oid) {
-        OpenSSLX509Certificate copy = new OpenSSLX509Certificate(NativeCrypto.X509_dup(mContext, this), notBefore, notAfter);
-        NativeCrypto.X509_delete_ext(copy.getContext(), copy, oid);
-        return copy;
+    public byte[] getTBSCertificateWithoutExtension(String oid) {
+        return NativeCrypto.get_X509_tbs_cert_without_ext(mContext, this, oid);
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     protected void finalize() throws Throwable {
         try {
             if (mContext != 0) {
diff --git a/common/src/main/java/org/conscrypt/OpenSSLX509CertificateFactory.java b/common/src/main/java/org/conscrypt/OpenSSLX509CertificateFactory.java
index c2c49e8..02d9f27 100644
--- a/common/src/main/java/org/conscrypt/OpenSSLX509CertificateFactory.java
+++ b/common/src/main/java/org/conscrypt/OpenSSLX509CertificateFactory.java
@@ -60,6 +60,40 @@
         }
     }
 
+    private static boolean isMaybePkcs7(byte[] header) {
+        // The outer tag must be SEQUENCE.
+        if (header.length < 2 || header[0] != 0x30) {
+            return false;
+        }
+
+        // Bytes are signed in Java.
+        int lengthByte = header[1] & 0xff;
+
+        // Skip the length prefix to find the tag of the first child of SEQUENCE. This function is
+        // intentionally lax and does not attempt to parse the length itself. It is only necessary
+        // to return true on PKCS#7 inputs and false on X.509 inputs. Other structures can go either
+        // way.
+        int idx = 2;
+        if (lengthByte <= 0x80) {
+            // Short-form or indefinite length.
+        } else if (lengthByte == 0x81) {
+            idx += 1;
+        } else if (lengthByte == 0x82) {
+            idx += 2;
+        } else if (lengthByte == 0x83) {
+            idx += 3;
+        } else if (lengthByte == 0x84) {
+            idx += 4;
+        } else {
+            // BoringSSL stops at 4-byte lengths. A 5-byte length would require a 4GiB input.
+            return false;
+        }
+
+        // The first element of a PKCS#7 structure is OBJECT IDENTIFIER, which has tag 6. The first
+        // element of an X.509 structure is never OBJECT IDENTIFIER.
+        return idx < header.length && header[idx] == 0x06;
+    }
+
     /**
      * The code for X509 Certificates and CRL is pretty much the same. We use
      * this abstract class to share the code between them. This makes it ugly,
@@ -88,19 +122,10 @@
                 pbis.unread(buffer, 0, len);
 
                 if (buffer[0] == '-') {
-                    if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) {
-                        List<? extends T> items = fromPkcs7PemInputStream(pbis);
-                        if (items.size() == 0) {
-                            return null;
-                        }
-                        items.get(0);
-                    } else {
-                        return fromX509PemInputStream(pbis);
-                    }
+                    return fromX509PemInputStream(pbis);
                 }
 
-                /* PKCS#7 bags have a byte 0x06 at position 4 in the stream. */
-                if (buffer[4] == 0x06) {
+                if (isMaybePkcs7(buffer)) {
                     List<? extends T> certs = fromPkcs7DerInputStream(pbis);
                     if (certs.size() == 0) {
                         return null;
@@ -114,6 +139,7 @@
                     try {
                         inStream.reset();
                     } catch (IOException ignored) {
+                        // If resetting the stream fails, there's not much we can do
                     }
                 }
                 throw new ParsingException(e);
@@ -156,8 +182,7 @@
                     return fromPkcs7PemInputStream(pbis);
                 }
 
-                /* PKCS#7 bags have a byte 0x06 at position 4 in the stream. */
-                if (buffer[4] == 0x06) {
+                if (isMaybePkcs7(buffer)) {
                     return fromPkcs7DerInputStream(pbis);
                 }
             } catch (Exception e) {
@@ -165,6 +190,7 @@
                     try {
                         inStream.reset();
                     } catch (IOException ignored) {
+                        // If resetting the stream fails, there's not much we can do
                     }
                 }
                 throw new ParsingException(e);
@@ -197,6 +223,7 @@
                         try {
                             inStream.reset();
                         } catch (IOException ignored) {
+                            // If resetting the stream fails, there's not much we can do
                         }
                     }
 
diff --git a/common/src/main/java/org/conscrypt/OpenSSLXDHKeyAgreement.java b/common/src/main/java/org/conscrypt/OpenSSLXDHKeyAgreement.java
new file mode 100644
index 0000000..660c5f1
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/OpenSSLXDHKeyAgreement.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt;
+
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+/**
+ * Elliptic Curve Diffie-Hellman key agreement backed by the OpenSSL engine.
+ */
+@Internal
+public final class OpenSSLXDHKeyAgreement extends OpenSSLBaseDHKeyAgreement<byte[]> {
+    public OpenSSLXDHKeyAgreement() {
+    }
+
+    @Override
+    protected byte[] convertPublicKey(PublicKey key) throws InvalidKeyException {
+        if (!(key instanceof OpenSSLX25519PublicKey)) {
+            throw new InvalidKeyException("Only OpenSSLX25519PublicKey accepted");
+        }
+
+        return ((OpenSSLX25519PublicKey) key).getU();
+    }
+
+    @Override
+    protected byte[] convertPrivateKey(PrivateKey key) throws InvalidKeyException {
+        if (!(key instanceof OpenSSLX25519PrivateKey)) {
+            throw new InvalidKeyException("Only OpenSSLX25519PublicKey accepted");
+        }
+
+        return ((OpenSSLX25519PrivateKey) key).getU();
+    }
+
+    @Override
+    protected int computeKey(byte[] buffer, byte[] theirPublicKey, byte[] ourPrivateKey) throws InvalidKeyException {
+        if (!NativeCrypto.X25519(
+                buffer,
+                ourPrivateKey,
+                theirPublicKey)) {
+            throw new InvalidKeyException("Error running X25519");
+        }
+
+        return OpenSSLX25519Key.X25519_KEY_SIZE_BYTES;
+    }
+
+    @Override
+    protected int getOutputSize(byte[] key) {
+        // We only support X25519 which is 32-byte (256-bit)
+        return OpenSSLX25519Key.X25519_KEY_SIZE_BYTES;
+    }
+}
diff --git a/common/src/main/java/org/conscrypt/OpenSSLXDHKeyFactory.java b/common/src/main/java/org/conscrypt/OpenSSLXDHKeyFactory.java
new file mode 100644
index 0000000..e4af802
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/OpenSSLXDHKeyFactory.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactorySpi;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+/**
+ * An implementation of a {@link KeyFactorySpi} for EC keys based on BoringSSL.
+ */
+@Internal
+public final class OpenSSLXDHKeyFactory extends KeyFactorySpi {
+
+    public OpenSSLXDHKeyFactory() {}
+
+    @Override
+    protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException {
+        if (keySpec == null) {
+            throw new InvalidKeySpecException("keySpec == null");
+        }
+
+        if (keySpec instanceof X509EncodedKeySpec) {
+            return new OpenSSLX25519PublicKey((X509EncodedKeySpec) keySpec);
+        }
+        throw new InvalidKeySpecException("Must use ECPublicKeySpec or X509EncodedKeySpec; was "
+                + keySpec.getClass().getName());
+    }
+
+    @Override
+    protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException {
+        if (keySpec == null) {
+            throw new InvalidKeySpecException("keySpec == null");
+        }
+
+        if (keySpec instanceof PKCS8EncodedKeySpec) {
+            return new OpenSSLX25519PrivateKey((PKCS8EncodedKeySpec) keySpec);
+        }
+        throw new InvalidKeySpecException("Must use ECPrivateKeySpec or PKCS8EncodedKeySpec; was "
+                + keySpec.getClass().getName());
+    }
+
+    @Override
+    protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
+            throws InvalidKeySpecException {
+        if (key == null) {
+            throw new InvalidKeySpecException("key == null");
+        }
+
+        if (keySpec == null) {
+            throw new InvalidKeySpecException("keySpec == null");
+        }
+
+        if (!"XDH".equals(key.getAlgorithm())) {
+            throw new InvalidKeySpecException("Key must be an XDH key");
+        }
+
+        Class<?> publicKeySpec = getJavaPublicKeySpec();
+        Class<?> privateKeySpec = getJavaPrivateKeySpec();
+
+        if (publicKeySpec != null && key instanceof PublicKey && publicKeySpec.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"X.509".equals(key.getFormat()) || encoded == null) {
+                throw new InvalidKeySpecException("Not a valid X.509 encoding");
+            }
+            OpenSSLX25519PublicKey publicKey = (OpenSSLX25519PublicKey) engineGeneratePublic(new X509EncodedKeySpec(encoded));
+            @SuppressWarnings("unchecked")
+            T result = (T) constructJavaPublicKeySpec(publicKeySpec, publicKey);
+            return result;
+        } else if (privateKeySpec != null && key instanceof PrivateKey && privateKeySpec.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"PKCS#8".equals(key.getFormat()) || encoded == null) {
+                throw new InvalidKeySpecException("Not a valid PKCS#8 encoding");
+            }
+            OpenSSLX25519PrivateKey privateKey = (OpenSSLX25519PrivateKey) engineGeneratePrivate(new PKCS8EncodedKeySpec(encoded));
+            @SuppressWarnings("unchecked")
+            T result = (T) constructJavaPrivateKeySpec(privateKeySpec, privateKey);
+            return result;
+        } else if (key instanceof PrivateKey && PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"PKCS#8".equals(key.getFormat())) {
+                throw new InvalidKeySpecException("Encoding type must be PKCS#8; was "
+                        + key.getFormat());
+            } else if (encoded == null) {
+                throw new InvalidKeySpecException("Key is not encodable");
+            }
+            @SuppressWarnings("unchecked") T result = (T) new PKCS8EncodedKeySpec(encoded);
+            return result;
+        } else if (key instanceof PublicKey && X509EncodedKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"X.509".equals(key.getFormat())) {
+                throw new InvalidKeySpecException("Encoding type must be X.509; was "
+                        + key.getFormat());
+            } else if (encoded == null) {
+                throw new InvalidKeySpecException("Key is not encodable");
+            }
+            @SuppressWarnings("unchecked") T result = (T) new X509EncodedKeySpec(encoded);
+            return result;
+        }
+
+        throw new InvalidKeySpecException("Unsupported key type and key spec combination; key="
+                + key.getClass().getName() + ", keySpec=" + keySpec.getName());
+    }
+
+    @Override
+    protected Key engineTranslateKey(Key key) throws InvalidKeyException {
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        }
+        if ((key instanceof OpenSSLX25519PublicKey) || (key instanceof OpenSSLX25519PrivateKey)) {
+            return key;
+        } else if ((key instanceof PrivateKey) && "PKCS#8".equals(key.getFormat())) {
+            byte[] encoded = key.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Key does not support encoding");
+            }
+            try {
+                return engineGeneratePrivate(new PKCS8EncodedKeySpec(encoded));
+            } catch (InvalidKeySpecException e) {
+                throw new InvalidKeyException(e);
+            }
+        } else if ((key instanceof PublicKey) && "X.509".equals(key.getFormat())) {
+            byte[] encoded = key.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Key does not support encoding");
+            }
+            try {
+                return engineGeneratePublic(new X509EncodedKeySpec(encoded));
+            } catch (InvalidKeySpecException e) {
+                throw new InvalidKeyException(e);
+            }
+        } else {
+            throw new InvalidKeyException("Key must be EC public or private key; was "
+                    + key.getClass().getName());
+        }
+    }
+
+    private static Class<?> getJavaPrivateKeySpec() {
+        try {
+            return Class.forName("java.security.spec.XECPrivateKeySpec");
+        } catch (ClassNotFoundException ignored) {
+            return null;
+        }
+    }
+
+    private static Class<?> getJavaPublicKeySpec() {
+        try {
+            return Class.forName("java.security.spec.XECPublicKeySpec");
+        } catch (ClassNotFoundException ignored) {
+            return null;
+        }
+    }
+
+    private KeySpec constructJavaPrivateKeySpec(Class<?> privateKeySpec, OpenSSLX25519PrivateKey privateKey) throws InvalidKeySpecException {
+        if (privateKeySpec == null) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPrivateKeySpec");
+        }
+
+        try {
+            Constructor<?> c = privateKeySpec.getConstructor(AlgorithmParameterSpec.class, byte[].class);
+            @SuppressWarnings("unchecked")
+            KeySpec result = (KeySpec) c.newInstance(new OpenSSLXECParameterSpec(OpenSSLXECParameterSpec.X25519), privateKey.getU());
+            return result;
+        } catch (NoSuchMethodException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPrivateKeySpec", e);
+        } catch (InstantiationException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPrivateKeySpec", e);
+        } catch (IllegalAccessException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPrivateKeySpec", e);
+        } catch (InvocationTargetException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPrivateKeySpec", e);
+        }
+    }
+
+    private KeySpec constructJavaPublicKeySpec(Class<?> publicKeySpec, OpenSSLX25519PublicKey publicKey) throws InvalidKeySpecException {
+        try {
+            Constructor<?> c = publicKeySpec.getConstructor(AlgorithmParameterSpec.class, BigInteger.class);
+            @SuppressWarnings("unchecked")
+            KeySpec result = (KeySpec) c.newInstance(new OpenSSLXECParameterSpec(OpenSSLXECParameterSpec.X25519), new BigInteger(1, publicKey.getU()));
+            return result;
+        } catch (NoSuchMethodException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPublicKeySpec", e);
+        } catch (InstantiationException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPublicKeySpec", e);
+        } catch (IllegalAccessException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPublicKeySpec", e);
+        } catch (InvocationTargetException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPublicKeySpec", e);
+        }
+    }
+}
diff --git a/common/src/main/java/org/conscrypt/OpenSSLXDHKeyPairGenerator.java b/common/src/main/java/org/conscrypt/OpenSSLXDHKeyPairGenerator.java
new file mode 100644
index 0000000..361dd23
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/OpenSSLXDHKeyPairGenerator.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * An implementation of {@link KeyPairGenerator} for XDH keys which uses BoringSSL to perform all the
+ * operations. This only supports X25519 keys.
+ */
+@Internal
+public final class OpenSSLXDHKeyPairGenerator extends KeyPairGenerator {
+    private static final String ALGORITHM = "XDH";
+
+    public OpenSSLXDHKeyPairGenerator() {
+        super(ALGORITHM);
+    }
+
+    @Override
+    public KeyPair generateKeyPair() {
+        byte[] publicKeyBytes = new byte[OpenSSLX25519Key.X25519_KEY_SIZE_BYTES];
+        byte[] privateKeyBytes = new byte[OpenSSLX25519Key.X25519_KEY_SIZE_BYTES];
+
+        NativeCrypto.X25519_keypair(publicKeyBytes, privateKeyBytes);
+
+        return new KeyPair(new OpenSSLX25519PublicKey(publicKeyBytes), new OpenSSLX25519PrivateKey(privateKeyBytes));
+    }
+
+    @Override
+    public void initialize(int keysize, SecureRandom random) {
+    }
+
+    @Override
+    public void initialize(AlgorithmParameterSpec param, SecureRandom random)
+            throws InvalidAlgorithmParameterException {
+        throw new InvalidAlgorithmParameterException(
+                "No AlgorithmParameterSpec classes are supported");
+    }
+}
diff --git a/common/src/main/java/org/conscrypt/OpenSSLXECParameterSpec.java b/common/src/main/java/org/conscrypt/OpenSSLXECParameterSpec.java
new file mode 100644
index 0000000..9350f29
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/OpenSSLXECParameterSpec.java
@@ -0,0 +1,21 @@
+package org.conscrypt;
+
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * Parameter markers to assist in future compatibility should other XEC curves be supported.
+ */
+@Internal
+class OpenSSLXECParameterSpec implements AlgorithmParameterSpec {
+    public static final String X25519 = "1.3.101.110";
+
+    private final String oid;
+
+    public OpenSSLXECParameterSpec(String oid) {
+        this.oid = oid;
+    }
+
+    public String getOid() {
+        return oid;
+    }
+}
diff --git a/common/src/main/java/org/conscrypt/PSKKeyManager.java b/common/src/main/java/org/conscrypt/PSKKeyManager.java
index 5f6222d..b998189 100644
--- a/common/src/main/java/org/conscrypt/PSKKeyManager.java
+++ b/common/src/main/java/org/conscrypt/PSKKeyManager.java
@@ -81,7 +81,7 @@
  * The following example illustrates how to create an {@code SSLContext} which enables the use of
  * TLS-PSK in {@code SSLSocket}, {@code SSLServerSocket} and {@code SSLEngine} instances obtained
  * from it.
- * <pre> {@code
+ * <pre>
  * PSKKeyManager myPskKeyManager = ...;
  *
  * SSLContext sslContext = SSLContext.getInstance("TLS");
@@ -92,7 +92,7 @@
  *         );
  *
  * SSLSocket sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(...);
- * }</pre>
+ * </pre>
  *
  * @deprecated This abstraction is deprecated because it does not work with TLS 1.3.
  */
diff --git a/common/src/main/java/org/conscrypt/SSLNullSession.java b/common/src/main/java/org/conscrypt/SSLNullSession.java
index b266843..69d6337 100644
--- a/common/src/main/java/org/conscrypt/SSLNullSession.java
+++ b/common/src/main/java/org/conscrypt/SSLNullSession.java
@@ -116,6 +116,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation") // Public API
     public javax.security.cert.X509Certificate[] getPeerCertificateChain()
             throws SSLPeerUnverifiedException {
         throw new SSLPeerUnverifiedException("No peer certificate");
diff --git a/common/src/main/java/org/conscrypt/SSLParametersImpl.java b/common/src/main/java/org/conscrypt/SSLParametersImpl.java
index 44aa635..1c7cf98 100644
--- a/common/src/main/java/org/conscrypt/SSLParametersImpl.java
+++ b/common/src/main/java/org/conscrypt/SSLParametersImpl.java
@@ -577,7 +577,9 @@
             } else if (km != null) {
                 try {
                     return DuckTypedPSKKeyManager.getInstance(km);
-                } catch (NoSuchMethodException ignored) {}
+                } catch (NoSuchMethodException ignored) {
+                    // This PSKKeyManager doesn't support the required methods, go to the next
+                }
             }
         }
         return null;
diff --git a/common/src/main/java/org/conscrypt/SSLUtils.java b/common/src/main/java/org/conscrypt/SSLUtils.java
index 5610150..e1881d1 100644
--- a/common/src/main/java/org/conscrypt/SSLUtils.java
+++ b/common/src/main/java/org/conscrypt/SSLUtils.java
@@ -275,7 +275,7 @@
      */
     static Set<String> getSupportedClientKeyTypes(byte[] clientCertificateTypes,
             int[] signatureAlgs) {
-        Set<String> fromClientCerts = new HashSet<String>(clientCertificateTypes.length);
+        Set<String> fromClientCerts = new HashSet<>(clientCertificateTypes.length);
         for (byte keyTypeCode : clientCertificateTypes) {
             String keyType = SSLUtils.getClientKeyType(keyTypeCode);
             if (keyType == null) {
@@ -285,7 +285,7 @@
             fromClientCerts.add(keyType);
         }
         // Signature algorithms are listed in preference order
-        Set<String> fromSigAlgs = new LinkedHashSet<String>(signatureAlgs.length);
+        Set<String> fromSigAlgs = new LinkedHashSet<>(signatureAlgs.length);
         for (int signatureAlg : signatureAlgs) {
             String keyType = SSLUtils.getClientKeyTypeFromSignatureAlg(signatureAlg);
             if (keyType == null) {
@@ -319,6 +319,7 @@
     /**
      * Converts the peer certificates into a cert chain.
      */
+    @SuppressWarnings("deprecation") // Used in public Conscrypt APIs
     static javax.security.cert.X509Certificate[] toCertificateChain(X509Certificate[] certificates)
             throws SSLPeerUnverifiedException {
         try {
@@ -332,11 +333,11 @@
             return chain;
         } catch (CertificateEncodingException e) {
             SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
-            exception.initCause(exception);
+            exception.initCause(e);
             throw exception;
         } catch (CertificateException e) {
             SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
-            exception.initCause(exception);
+            exception.initCause(e);
             throw exception;
         }
     }
diff --git a/common/src/main/java/org/conscrypt/SessionSnapshot.java b/common/src/main/java/org/conscrypt/SessionSnapshot.java
index 28e63b9..1fc708c 100644
--- a/common/src/main/java/org/conscrypt/SessionSnapshot.java
+++ b/common/src/main/java/org/conscrypt/SessionSnapshot.java
@@ -64,7 +64,7 @@
 
     @Override
     public List<byte[]> getStatusResponses() {
-        List<byte[]> ret = new ArrayList<byte[]>(statusResponses.size());
+        List<byte[]> ret = new ArrayList<>(statusResponses.size());
         for (byte[] resp : statusResponses) {
             ret.add(resp.clone());
         }
@@ -141,8 +141,13 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation") // Public API
     public javax.security.cert.X509Certificate[] getPeerCertificateChain()
         throws SSLPeerUnverifiedException {
+        if (!Platform.isJavaxCertificateSupported()) {
+            throw new UnsupportedOperationException("Use getPeerCertificates() instead");
+        }
+
         throw new SSLPeerUnverifiedException("No peer certificates");
     }
 
diff --git a/common/src/main/java/org/conscrypt/ShortBufferWithoutStackTraceException.java b/common/src/main/java/org/conscrypt/ShortBufferWithoutStackTraceException.java
index 4bbfbb9..13c38d4 100644
--- a/common/src/main/java/org/conscrypt/ShortBufferWithoutStackTraceException.java
+++ b/common/src/main/java/org/conscrypt/ShortBufferWithoutStackTraceException.java
@@ -35,7 +35,7 @@
         super(msg);
     }
 
-    @Override public Throwable fillInStackTrace() {
+    @Override public synchronized Throwable fillInStackTrace() {
         return this;
     }
 }
diff --git a/common/src/main/java/org/conscrypt/TrustManagerImpl.java b/common/src/main/java/org/conscrypt/TrustManagerImpl.java
index 3515fd5..ccec8e9 100644
--- a/common/src/main/java/org/conscrypt/TrustManagerImpl.java
+++ b/common/src/main/java/org/conscrypt/TrustManagerImpl.java
@@ -135,7 +135,7 @@
 
     private final Exception err;
     private final CertificateFactory factory;
-    private final CertBlacklist blacklist;
+    private final CertBlocklist blocklist;
     private CTVerifier ctVerifier;
     private CTPolicy ctPolicy;
 
@@ -146,8 +146,6 @@
 
     /**
      * Creates X509TrustManager based on a keystore
-     *
-     * @param keyStore
      */
     public TrustManagerImpl(KeyStore keyStore) {
         this(keyStore, null);
@@ -164,16 +162,16 @@
 
     public TrustManagerImpl(KeyStore keyStore, CertPinManager manager,
             ConscryptCertStore certStore,
-                            CertBlacklist blacklist) {
-        this(keyStore, manager, certStore, blacklist, null, null, null);
+                            CertBlocklist blocklist) {
+        this(keyStore, manager, certStore, blocklist, null, null, null);
     }
 
     /**
      * For testing only.
      */
     public TrustManagerImpl(KeyStore keyStore, CertPinManager manager,
-            ConscryptCertStore certStore, CertBlacklist blacklist, CTLogStore ctLogStore,
-            CTVerifier ctVerifier, CTPolicy ctPolicy) {
+                            ConscryptCertStore certStore, CertBlocklist blocklist, CTLogStore ctLogStore,
+                            CTVerifier ctVerifier, CTPolicy ctPolicy) {
         CertPathValidator validatorLocal = null;
         CertificateFactory factoryLocal = null;
         KeyStore rootKeyStoreLocal = null;
@@ -205,8 +203,8 @@
             errLocal = e;
         }
 
-        if (blacklist == null) {
-            blacklist = Platform.newDefaultBlacklist();
+        if (blocklist == null) {
+            blocklist = Platform.newDefaultBlocklist();
         }
         if (ctLogStore == null) {
             ctLogStore = Platform.newDefaultLogStore();
@@ -225,11 +223,12 @@
         this.intermediateIndex = new TrustedCertificateIndex();
         this.acceptedIssuers = acceptedIssuersLocal;
         this.err = errLocal;
-        this.blacklist = blacklist;
+        this.blocklist = blocklist;
         this.ctVerifier = new CTVerifier(ctLogStore);
         this.ctPolicy = ctPolicy;
     }
 
+    @SuppressWarnings("JdkObsolete")  // KeyStore#aliases is the only API available
     private static X509Certificate[] acceptedIssuers(KeyStore ks) {
         try {
             // Note that unlike the PKIXParameters code to create a Set of
@@ -404,7 +403,7 @@
             String identificationAlgorithm = parameters.getEndpointIdentificationAlgorithm();
             if ("HTTPS".equalsIgnoreCase(identificationAlgorithm)) {
                 ConscryptHostnameVerifier verifier = getHttpsVerifier();
-                if (!verifier.verify(hostname, session)) {
+                if (!verifier.verify(certs, hostname, session)) {
                     throw new CertificateException("No subjectAltNames on the certificate match");
                 }
             }
@@ -413,7 +412,7 @@
     }
 
     @SuppressWarnings("unchecked")
-    private byte[] getOcspDataFromSession(SSLSession session) {
+    private static byte[] getOcspDataFromSession(SSLSession session) {
         List<byte[]> ocspResponses = null;
         if (session instanceof ConscryptSession) {
             ConscryptSession opensslSession = (ConscryptSession) session;
@@ -427,10 +426,9 @@
                 if (rawResponses instanceof List) {
                     ocspResponses = (List<byte[]>) rawResponses;
                 }
-            } catch (NoSuchMethodException ignored) {
-            } catch (SecurityException ignored) {
-            } catch (IllegalAccessException ignored) {
-            } catch (IllegalArgumentException ignored) {
+            } catch (NoSuchMethodException | SecurityException
+                    | IllegalAccessException | IllegalArgumentException ignored) {
+                // Method not available, fall through and return null
             } catch (InvocationTargetException e) {
                 throw new RuntimeException(e.getCause());
             }
@@ -457,10 +455,9 @@
             if (rawData instanceof byte[]) {
                 data = (byte[]) rawData;
             }
-        } catch (NoSuchMethodException ignored) {
-        } catch (SecurityException ignored) {
-        } catch (IllegalAccessException ignored) {
-        } catch (IllegalArgumentException ignored) {
+        } catch (NoSuchMethodException | SecurityException
+                | IllegalAccessException | IllegalArgumentException ignored) {
+            // Method not available, fall through and return null
         } catch (InvocationTargetException e) {
             throw new RuntimeException(e.getCause());
         }
@@ -530,8 +527,8 @@
             current = trustAnchorChain.get(trustAnchorChain.size() - 1).getTrustedCert();
         }
 
-        // Check that the certificate isn't blacklisted.
-        checkBlacklist(current);
+        // Check that the certificate isn't blocklisted.
+        checkBlocklist(current);
 
         // 1. If the current certificate in the chain is self-signed verify the chain as is.
         if (current.getIssuerDN().equals(current.getSubjectDN())) {
@@ -671,9 +668,9 @@
             if (pinManager != null) {
                 pinManager.checkChainPinning(host, wholeChain);
             }
-            // Check whole chain against the blacklist
+            // Check whole chain against the blocklist
             for (X509Certificate cert : wholeChain) {
-                checkBlacklist(cert);
+                checkBlocklist(cert);
             }
 
             // Check CT (if required).
@@ -720,9 +717,9 @@
         }
     }
 
-    private void checkBlacklist(X509Certificate cert) throws CertificateException {
-        if (blacklist != null && blacklist.isPublicKeyBlackListed(cert.getPublicKey())) {
-            throw new CertificateException("Certificate blacklisted by public key: " + cert);
+    private void checkBlocklist(X509Certificate cert) throws CertificateException {
+        if (blocklist != null && blocklist.isPublicKeyBlockListed(cert.getPublicKey())) {
+            throw new CertificateException("Certificate blocklisted by public key: " + cert);
         }
     }
 
@@ -942,7 +939,7 @@
             return trustAnchor;
         }
         if (trustedCertificateStore == null) {
-            // not trusted and no TrustedCertificateStore to check
+            // not trusted and no TrustedCertificateStore to check.
             return null;
         }
         // probe KeyStore for a cert. AndroidCAStore stores its
@@ -1002,24 +999,11 @@
         return hostnameVerifier;
     }
 
-    private enum GlobalHostnameVerifierAdapter implements ConscryptHostnameVerifier {
-        INSTANCE;
-
-        @Override
-        public boolean verify(String hostname, SSLSession session) {
-            return HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session);
-        }
-    }
-
     private ConscryptHostnameVerifier getHttpsVerifier() {
         if (hostnameVerifier != null) {
             return hostnameVerifier;
         }
-        ConscryptHostnameVerifier defaultVerifier = getDefaultHostnameVerifier();
-        if (defaultVerifier != null) {
-            return defaultVerifier;
-        }
-        return GlobalHostnameVerifierAdapter.INSTANCE;
+        return Platform.getDefaultHostnameVerifier();
     }
 
     public void setCTEnabledOverride(boolean enabled) {
diff --git a/common/src/main/java/org/conscrypt/TrustedCertificateIndex.java b/common/src/main/java/org/conscrypt/TrustedCertificateIndex.java
index 07d1e00..7e2e69a 100644
--- a/common/src/main/java/org/conscrypt/TrustedCertificateIndex.java
+++ b/common/src/main/java/org/conscrypt/TrustedCertificateIndex.java
@@ -119,6 +119,7 @@
                     cert.verify(publicKey);
                     return anchor;
                 } catch (Exception ignored) {
+                    // Ignored
                 }
             }
         }
@@ -195,6 +196,7 @@
                     cert.verify(publicKey);
                     result.add(anchor);
                 } catch (Exception ignored) {
+                    // Ignored
                 }
             }
             return result;
diff --git a/common/src/main/java/org/conscrypt/ct/CTVerifier.java b/common/src/main/java/org/conscrypt/ct/CTVerifier.java
index e6136f2..2f1f79b 100644
--- a/common/src/main/java/org/conscrypt/ct/CTVerifier.java
+++ b/common/src/main/java/org/conscrypt/ct/CTVerifier.java
@@ -163,7 +163,7 @@
      * @param origin used to create the SignedCertificateTimestamp instances.
      */
     @SuppressWarnings("MixedMutabilityReturnType")
-    private List<SignedCertificateTimestamp> getSCTsFromSCTList(byte[] data,
+    private static List<SignedCertificateTimestamp> getSCTsFromSCTList(byte[] data,
             SignedCertificateTimestamp.Origin origin) {
         if (data == null) {
             return Collections.emptyList();
diff --git a/common/src/main/java/org/conscrypt/ct/CertificateEntry.java b/common/src/main/java/org/conscrypt/ct/CertificateEntry.java
index 48f9bcd..72ed530 100644
--- a/common/src/main/java/org/conscrypt/ct/CertificateEntry.java
+++ b/common/src/main/java/org/conscrypt/ct/CertificateEntry.java
@@ -72,6 +72,8 @@
     }
 
     /**
+     * Creates a CertificateEntry with type PRECERT_ENTRY
+     *
      * @throws IllegalArgumentException if issuerKeyHash isn't 32 bytes
      */
     public static CertificateEntry createForPrecertificate(byte[] tbsCertificate, byte[] issuerKeyHash) {
@@ -85,8 +87,7 @@
                 throw new CertificateException("Certificate does not contain embedded signed timestamps");
             }
 
-            OpenSSLX509Certificate preCert = leaf.withDeletedExtension(CTConstants.X509_SCT_LIST_OID);
-            byte[] tbs = preCert.getTBSCertificate();
+            byte[] tbs = leaf.getTBSCertificateWithoutExtension(CTConstants.X509_SCT_LIST_OID);
 
             byte[] issuerKey = issuer.getPublicKey().getEncoded();
             MessageDigest md = MessageDigest.getInstance("SHA-256");
diff --git a/common/src/main/java/org/conscrypt/io/IoUtils.java b/common/src/main/java/org/conscrypt/io/IoUtils.java
index aae3e3b..4f531fd 100644
--- a/common/src/main/java/org/conscrypt/io/IoUtils.java
+++ b/common/src/main/java/org/conscrypt/io/IoUtils.java
@@ -35,6 +35,7 @@
             } catch (RuntimeException rethrown) {
                 throw rethrown;
             } catch (Exception ignored) {
+                // Ignored
             }
         }
     }
@@ -47,6 +48,7 @@
             try {
                 socket.close();
             } catch (Exception ignored) {
+                // Ignored
             }
         }
     }
diff --git a/common/src/main/java/org/conscrypt/metrics/CipherSuite.java b/common/src/main/java/org/conscrypt/metrics/CipherSuite.java
new file mode 100644
index 0000000..ebdf6d2
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/metrics/CipherSuite.java
@@ -0,0 +1,81 @@
+/*
+ * 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.metrics;
+
+import org.conscrypt.Internal;
+
+/**
+ * Cipher suites to metric mapping for metrics instrumentation.
+ *
+ * Must be in sync with frameworks/base/cmds/statsd/src/atoms.proto
+ *
+ * Ids are based on IANA's database of SSL/TLS cipher suites
+ * @see https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4
+ */
+@Internal
+public enum CipherSuite {
+    UNKNOWN_CIPHER_SUITE(0x0000),
+
+    // Supported but not enabled
+    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(0xC00A),
+    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(0xC014),
+    TLS_RSA_WITH_AES_256_CBC_SHA(0x0035),
+    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(0xC009),
+    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xC013),
+    TLS_RSA_WITH_AES_128_CBC_SHA(0x002F),
+    TLS_RSA_WITH_3DES_EDE_CBC_SHA(0x000A),
+
+    // TLSv1.2 cipher suites
+    TLS_RSA_WITH_AES_128_GCM_SHA256(0x009C),
+    TLS_RSA_WITH_AES_256_GCM_SHA384(0x009D),
+    TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xC02F),
+    TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xC030),
+    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xC02B),
+    TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xC02C),
+    TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(0xCCA9),
+    TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256(0xCCA8),
+
+    // Pre-Shared Key (PSK) cipher suites
+    TLS_PSK_WITH_AES_128_CBC_SHA(0x008C),
+    TLS_PSK_WITH_AES_256_CBC_SHA(0x008D),
+    TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA(0xC035),
+    TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA(0xC036),
+    TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256(0xCCAC),
+
+    // TLS 1.3 cipher suites
+    TLS_AES_128_GCM_SHA256(0x1301),
+    TLS_AES_256_GCM_SHA384(0x1302),
+    TLS_CHACHA20_POLY1305_SHA256(0x1303),
+    ;
+
+    final short id;
+
+    public int getId() {
+        return this.id;
+    }
+
+    public static CipherSuite forName(String name) {
+        try {
+            return CipherSuite.valueOf(name);
+        } catch (IllegalArgumentException e) {
+            return CipherSuite.UNKNOWN_CIPHER_SUITE;
+        }
+    }
+
+    private CipherSuite(int id) {
+        this.id = (short) id;
+    }
+}
\ No newline at end of file
diff --git a/common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java b/common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java
new file mode 100644
index 0000000..40b45a9
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java
@@ -0,0 +1,45 @@
+/*
+ * 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.metrics;
+
+import org.conscrypt.Internal;
+
+/**
+ * Reimplement with reflection calls the logging class,
+ * generated by frameworks/statsd.
+ *
+ * In case atom is changed, generate new wrapper with stats-log-api-gen
+ * tool as shown below and add corresponding methods to ReflexiveStatsEvent's
+ * newEvent() method.
+ *
+ * $ stats-log-api-gen \
+ *   --java "common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java" \
+ *   --module conscrypt \
+ *   --javaPackage org.conscrypt.metrics \
+ *   --javaClass ConscryptStatsLog
+ **/
+@Internal
+public final class ConscryptStatsLog {
+    public static final int TLS_HANDSHAKE_REPORTED = 317;
+
+    public static void write(
+            int code, boolean success, int protocol, int cipherSuite, int duration) {
+        ReflexiveStatsEvent event =
+                ReflexiveStatsEvent.buildEvent(code, success, protocol, cipherSuite, duration);
+
+        ReflexiveStatsLog.write(event);
+    }
+}
diff --git a/common/src/main/java/org/conscrypt/metrics/OptionalMethod.java b/common/src/main/java/org/conscrypt/metrics/OptionalMethod.java
new file mode 100644
index 0000000..13c8366
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/metrics/OptionalMethod.java
@@ -0,0 +1,85 @@
+/*
+ * 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.metrics;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.conscrypt.Internal;
+
+/**
+ * Helper class to handle reflexive loading and invocation of methods which may be absent.
+ *
+ * @hide This class is not part of the Android public SDK API
+ */
+@Internal
+public final class OptionalMethod {
+    private final Method cachedMethod;
+
+    /**
+     * Instantiates a new OptionalMethod.
+     * <p>Does not throw any exceptions if the class or method can't be loaded, or if any parameter
+     * classes are {@code null} and instead behaves as a no-op, always returning {@code null}.
+     *
+     * @param clazz the Class to search for methods on
+     * @param methodName the name of the {@code Method} on {@code clazz}
+     * @param methodParams list of {@code Classes} of the {@code Method's} parameters
+     *
+     * @throws NullPointerException if the method name is {@code null}
+     */
+    public OptionalMethod(Class<?> clazz, String methodName, Class<?>... methodParams) {
+        this.cachedMethod = initializeMethod(clazz, methodName, methodParams);
+    }
+
+    private static Method initializeMethod(
+            Class<?> clazz, String methodName, Class<?>... methodParams) {
+        try {
+            for (Class<?> paramClass : methodParams) {
+                if (paramClass == null) {
+                    return null;
+                }
+            }
+            if (clazz != null) {
+                return clazz.getMethod(checkNotNull(methodName), methodParams);
+            }
+        } catch (NoSuchMethodException ignored) {
+            // Ignored
+        }
+        return null;
+    }
+
+    public Object invoke(Object target, Object... args) {
+        // no-op if failed to load method in constructor
+        if (cachedMethod == null) {
+            return null;
+        }
+        try {
+            return cachedMethod.invoke(target, args);
+        } catch (IllegalAccessException ignored) {
+            // Ignored
+        } catch (InvocationTargetException ignored) {
+            // Ignored
+        }
+        return null;
+    }
+
+    private static <T> T checkNotNull(T reference) {
+        if (reference == null) {
+            throw new NullPointerException();
+        }
+        return reference;
+    }
+}
diff --git a/common/src/main/java/org/conscrypt/metrics/Protocol.java b/common/src/main/java/org/conscrypt/metrics/Protocol.java
new file mode 100644
index 0000000..0571260
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/metrics/Protocol.java
@@ -0,0 +1,61 @@
+/*
+ * 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.metrics;
+
+import org.conscrypt.Internal;
+
+/**
+ * Protocols to metric mapping for metrics instrumentation.
+ *
+ * Must be in sync with frameworks/base/cmds/statsd/src/atoms.proto
+ */
+@Internal
+public enum Protocol {
+    UNKNOWN_PROTO(0),
+    SSLv3(1),
+    TLSv1(2),
+    TLSv1_1(3),
+    TLSv1_2(4),
+    TLSv1_3(5),
+    ;
+
+    final byte id;
+
+    public int getId() {
+        return this.id;
+    }
+
+    public static Protocol forName(String name) {
+        switch (name) {
+            case "SSLv3":
+                return SSLv3;
+            case "TLSv1":
+                return TLSv1;
+            case "TLSv1.1":
+                return TLSv1_1;
+            case "TLSv1.2":
+                return TLSv1_2;
+            case "TLSv1.3":
+                return TLSv1_3;
+            default:
+                return UNKNOWN_PROTO;
+        }
+    }
+
+    private Protocol(int id) {
+        this.id = (byte) id;
+    }
+}
\ No newline at end of file
diff --git a/common/src/main/java/org/conscrypt/metrics/ReflexiveStatsEvent.java b/common/src/main/java/org/conscrypt/metrics/ReflexiveStatsEvent.java
new file mode 100644
index 0000000..f144867
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/metrics/ReflexiveStatsEvent.java
@@ -0,0 +1,122 @@
+/*
+ * 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.metrics;
+
+import org.conscrypt.Internal;
+
+/**
+ * Reflection wrapper around android.util.StatsEvent.
+ */
+@Internal
+public class ReflexiveStatsEvent {
+    private static final OptionalMethod newBuilder;
+    private static final Class<?> c_statsEvent;
+
+    static {
+        c_statsEvent = initStatsEventClass();
+        newBuilder = new OptionalMethod(c_statsEvent, "newBuilder");
+    }
+
+    private static Class<?> initStatsEventClass() {
+        try {
+            return Class.forName("android.util.StatsEvent");
+        } catch (ClassNotFoundException ignored) {
+            return null;
+        }
+    }
+
+    private Object statsEvent;
+
+    private ReflexiveStatsEvent(Object statsEvent) {
+        this.statsEvent = statsEvent;
+    }
+
+    public Object getStatsEvent() {
+        return statsEvent;
+    }
+
+    public static ReflexiveStatsEvent.Builder newBuilder() {
+        return new ReflexiveStatsEvent.Builder();
+    }
+
+    public static ReflexiveStatsEvent buildEvent(
+            int atomId, boolean success, int protocol, int cipherSuite, int duration) {
+        ReflexiveStatsEvent.Builder builder = ReflexiveStatsEvent.newBuilder();
+        builder.setAtomId(atomId);
+        builder.writeBoolean(success);
+        builder.writeInt(protocol);
+        builder.writeInt(cipherSuite);
+        builder.writeInt(duration);
+        builder.usePooledBuffer();
+        return builder.build();
+    }
+
+    public static final class Builder {
+        private static final Class<?> c_statsEvent_Builder;
+        private static final OptionalMethod setAtomId;
+        private static final OptionalMethod writeBoolean;
+        private static final OptionalMethod writeInt;
+        private static final OptionalMethod build;
+        private static final OptionalMethod usePooledBuffer;
+
+        static {
+            c_statsEvent_Builder = initStatsEventBuilderClass();
+            setAtomId = new OptionalMethod(c_statsEvent_Builder, "setAtomId", int.class);
+            writeBoolean = new OptionalMethod(c_statsEvent_Builder, "writeBoolean", boolean.class);
+            writeInt = new OptionalMethod(c_statsEvent_Builder, "writeInt", int.class);
+            build = new OptionalMethod(c_statsEvent_Builder, "build");
+            usePooledBuffer = new OptionalMethod(c_statsEvent_Builder, "usePooledBuffer");
+        }
+
+        private static Class<?> initStatsEventBuilderClass() {
+            try {
+                return Class.forName("android.util.StatsEvent$Builder");
+            } catch (ClassNotFoundException ignored) {
+                return null;
+            }
+        }
+
+        private Object builder;
+
+        private Builder() {
+            this.builder = newBuilder.invoke(null);
+        }
+
+        public Builder setAtomId(final int atomId) {
+            setAtomId.invoke(this.builder, atomId);
+            return this;
+        }
+
+        public Builder writeBoolean(final boolean value) {
+            writeBoolean.invoke(this.builder, value);
+            return this;
+        }
+
+        public Builder writeInt(final int value) {
+            writeInt.invoke(this.builder, value);
+            return this;
+        }
+
+        public void usePooledBuffer() {
+            usePooledBuffer.invoke(this.builder);
+        }
+
+        public ReflexiveStatsEvent build() {
+            Object statsEvent = build.invoke(this.builder);
+            return new ReflexiveStatsEvent(statsEvent);
+        }
+    }
+}
\ No newline at end of file
diff --git a/common/src/main/java/org/conscrypt/metrics/ReflexiveStatsLog.java b/common/src/main/java/org/conscrypt/metrics/ReflexiveStatsLog.java
new file mode 100644
index 0000000..aed898d
--- /dev/null
+++ b/common/src/main/java/org/conscrypt/metrics/ReflexiveStatsLog.java
@@ -0,0 +1,56 @@
+/*
+ * 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.metrics;
+
+import org.conscrypt.Internal;
+
+/**
+ * Reflection wrapper around android.util.StatsLog.
+ */
+@Internal
+public class ReflexiveStatsLog {
+    private static final Class<?> c_statsLog;
+    private static final Class<?> c_statsEvent;
+    private static final OptionalMethod write;
+
+    static {
+        c_statsLog = initStatsLogClass();
+        c_statsEvent = initStatsEventClass();
+        write = new OptionalMethod(c_statsLog, "write", c_statsEvent);
+    }
+
+    private static Class<?> initStatsLogClass() {
+        try {
+            return Class.forName("android.util.StatsLog");
+        } catch (ClassNotFoundException ignored) {
+            return null;
+        }
+    }
+
+    private static Class<?> initStatsEventClass() {
+        try {
+            return Class.forName("android.util.StatsEvent");
+        } catch (ClassNotFoundException ignored) {
+            return null;
+        }
+    }
+
+    private ReflexiveStatsLog() {}
+
+    public static void write(ReflexiveStatsEvent event) {
+        write.invoke(null, event.getStatsEvent());
+    }
+}
diff --git a/common/src/test/java/org/conscrypt/BufferUtilsTest.java b/common/src/test/java/org/conscrypt/BufferUtilsTest.java
new file mode 100644
index 0000000..1cce07b
--- /dev/null
+++ b/common/src/test/java/org/conscrypt/BufferUtilsTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2021 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.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import java.nio.ByteBuffer;
+import org.conscrypt.TestUtils.BufferType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+
+@RunWith(Parameterized.class)
+public class BufferUtilsTest {
+    private static final int K64 = 64 * 1024;
+    private static final int K16 = 16 * 1024;
+
+    private static final int[][] TEST_SIZES = {
+            // All even numbers as several tests use size/2
+            { 0 },
+            { 0, 0, 0, 0 },
+            { 0, 0, 0, 2 },
+            { 2, 0, 0, 0 },
+            { 100, 200, 300 },
+            { 1000, 2000, 3000 },
+            { K16 },
+            { 0, 0, K16 },
+            { K16, 0, 0 },
+            { K64 },
+            { 0, 0, K64 },
+            { K64, 0, 0 },
+            { 100, 100, K64 },
+            { K64, 100, 100 },
+            { K64, K64, K64 },
+    };
+
+
+    @Parameters(name = "{0}")
+    public static BufferType[] data() {
+        return new BufferType[] { BufferType.HEAP, BufferType.DIRECT };
+    }
+
+    @Parameter
+    public BufferType bufferType;
+
+    @Test
+    public void checkNotNull() {
+        for (int[] sizes : TEST_SIZES) {
+            BufferUtils.checkNotNull(bufferType.newRandomBuffers(sizes));
+        }
+
+        ByteBuffer[] buffers = bufferType.newRandomBuffers(10, 10, 10, 10, 10);
+        buffers[2] = null;
+        try {
+            BufferUtils.checkNotNull(buffers);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void remaining() {
+        for (int[] sizes : TEST_SIZES) {
+            assertEquals(arraySum(sizes),
+                    BufferUtils.remaining(bufferType.newRandomBuffers(sizes)));
+        }
+    }
+
+    @Test
+    public void consume() {
+        for (int[] sizes : TEST_SIZES) {
+            ByteBuffer[] buffers = bufferType.newRandomBuffers(sizes);
+            int totalSize = arraySum(sizes);
+
+            BufferUtils.consume(buffers, 0);
+            assertEquals(totalSize, BufferUtils.remaining(buffers));
+
+            BufferUtils.consume(buffers,totalSize / 2);
+            assertEquals(totalSize / 2, BufferUtils.remaining(buffers));
+
+            BufferUtils.consume(buffers,totalSize / 2);
+            assertEquals(0, BufferUtils.remaining(buffers));
+
+            if (totalSize > 0) {
+                try {
+                    BufferUtils.consume(buffers, totalSize / 2);
+                    fail("Managed to consume past end of buffer array");
+                } catch (IllegalArgumentException e) {
+                    // Expected
+                }
+            }
+        }
+    }
+
+    @Test
+    public void copyNoConsume() {
+        for (BufferType destinationType : BufferType.values()) {
+            for (int[] sizes : TEST_SIZES) {
+                ByteBuffer[] buffers = bufferType.newRandomBuffers(sizes);
+                int totalSize = arraySum(sizes);
+
+                ByteBuffer destination = destinationType.newBuffer(totalSize);
+                BufferUtils.copyNoConsume(buffers, destination, totalSize);
+                assertEquals(totalSize, BufferUtils.remaining(buffers));
+
+                assertArrayEquals(toArray(buffers), toArray(destination));
+            }
+        }
+    }
+
+    private static byte[] toArray(ByteBuffer... buffers) {
+        byte[] bytes = new byte[(int) BufferUtils.remaining(buffers)];
+        int offset = 0;
+        for (ByteBuffer buffer : buffers) {
+            int length = buffer.remaining();
+            if (length > 0) {
+                buffer.get(bytes, offset, length);
+                offset += length;
+            }
+        }
+        return bytes;
+    }
+
+    @Test
+    public void getBufferLargerThan_allSmall() {
+        ByteBuffer[] buffers = bufferType.newRandomBuffers(100, 200, 300, 400);
+
+        assertNull(BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 300);
+        assertNull(BufferUtils.getBufferLargerThan(buffers, K16));
+        assertSame(buffers[2], BufferUtils.getBufferLargerThan(buffers, 100));
+
+        BufferUtils.consume(buffers, 300);
+        assertSame(buffers[3], BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 200);
+        assertSame(buffers[3], BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 200);
+        ByteBuffer buffer = BufferUtils.getBufferLargerThan(buffers, K16);
+        assertNull(buffer);
+    }
+
+    @Test
+    public void getBufferLargerThan_oneLarge() {
+        ByteBuffer[] buffers = bufferType.newRandomBuffers(100, K64, 300, 400);
+
+        assertNull(BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 100);
+        assertSame(buffers[1], BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 1024); // 63K remaining in buffers[1]
+        assertSame(buffers[1], BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 60 * 1024); // 3K remaining in buffers[1]
+        assertNull(BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 3 * 1024);
+        assertEquals(0, buffers[1].remaining());
+        assertNull(BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 300);
+        assertSame(buffers[3], BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 400);
+        ByteBuffer buffer = BufferUtils.getBufferLargerThan(buffers, K16);
+        assertNull(buffer);
+    }
+
+    @Test
+    public void getBufferLargerThan_onlyOneBuffer() {
+        ByteBuffer[] buffers = bufferType.newRandomBuffers(0, 0, 100, 0, 0);
+
+        assertSame(buffers[2], BufferUtils.getBufferLargerThan(buffers, K16));
+    }
+
+    private int arraySum(int[] sizes) {
+        int sum = 0;
+        for (int i : sizes) {
+            sum += i;
+        }
+        return sum;
+    }
+}
diff --git a/common/src/test/java/org/conscrypt/ConscryptSuite.java b/common/src/test/java/org/conscrypt/ConscryptSuite.java
index 8591a68..263afcd 100644
--- a/common/src/test/java/org/conscrypt/ConscryptSuite.java
+++ b/common/src/test/java/org/conscrypt/ConscryptSuite.java
@@ -39,6 +39,7 @@
 import org.conscrypt.java.security.KeyPairGeneratorTestDH;
 import org.conscrypt.java.security.KeyPairGeneratorTestDSA;
 import org.conscrypt.java.security.KeyPairGeneratorTestRSA;
+import org.conscrypt.java.security.KeyPairGeneratorTestXDH;
 import org.conscrypt.java.security.MessageDigestTest;
 import org.conscrypt.java.security.SignatureTest;
 import org.conscrypt.java.security.cert.CertificateFactoryTest;
@@ -49,6 +50,7 @@
 import org.conscrypt.javax.crypto.CipherTest;
 import org.conscrypt.javax.crypto.ECDHKeyAgreementTest;
 import org.conscrypt.javax.crypto.KeyGeneratorTest;
+import org.conscrypt.javax.crypto.XDHKeyAgreementTest;
 import org.conscrypt.javax.net.ssl.HttpsURLConnectionTest;
 import org.conscrypt.javax.net.ssl.KeyManagerFactoryTest;
 import org.conscrypt.javax.net.ssl.KeyStoreBuilderParametersTest;
@@ -66,6 +68,9 @@
 import org.conscrypt.javax.net.ssl.SSLSocketVersionCompatibilityTest;
 import org.conscrypt.javax.net.ssl.TrustManagerFactoryTest;
 import org.conscrypt.javax.net.ssl.X509KeyManagerTest;
+import org.conscrypt.metrics.CipherSuiteTest;
+import org.conscrypt.metrics.OptionalMethodTest;
+import org.conscrypt.metrics.ProtocolTest;
 import org.junit.BeforeClass;
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
@@ -76,6 +81,7 @@
         CertPinManagerTest.class,
         ChainStrengthAnalyzerTest.class,
         TrustManagerImplTest.class,
+        HostnameVerifierTest.class,
         // org.conscrypt.ct tests
         CTVerifierTest.class,
         SerializationTest.class,
@@ -94,6 +100,8 @@
         AlgorithmParametersTestEC.class,
         AlgorithmParametersTestGCM.class,
         AlgorithmParametersTestOAEP.class,
+        BufferUtilsTest.class,
+        CipherSuiteTest.class,
         KeyFactoryTestDH.class,
         KeyFactoryTestDSA.class,
         KeyFactoryTestEC.class,
@@ -102,18 +110,23 @@
         KeyPairGeneratorTestDH.class,
         KeyPairGeneratorTestDSA.class,
         KeyPairGeneratorTestRSA.class,
+        KeyPairGeneratorTestXDH.class,
         MessageDigestTest.class,
         SignatureTest.class,
         // javax.crypto tests
         AeadCipherTest.class,
         CipherBasicsTest.class,
         CipherTest.class,
+        MacTest.class,
         ECDHKeyAgreementTest.class,
         KeyGeneratorTest.class,
+        XDHKeyAgreementTest.class,
         // javax.net.ssl tests
         HttpsURLConnectionTest.class,
         KeyManagerFactoryTest.class,
         KeyStoreBuilderParametersTest.class,
+        OptionalMethodTest.class,
+        ProtocolTest.class,
         SNIHostNameTest.class,
         SSLContextTest.class,
         SSLEngineTest.class,
diff --git a/common/src/test/java/org/conscrypt/HostnameVerifierTest.java b/common/src/test/java/org/conscrypt/HostnameVerifierTest.java
new file mode 100644
index 0000000..253ca34
--- /dev/null
+++ b/common/src/test/java/org/conscrypt/HostnameVerifierTest.java
@@ -0,0 +1,655 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You 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.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.Charset;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.security.auth.x500.X500Principal;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests for our hostname verifier. Most of these tests are from AOSP, which
+ * itself includes tests from the Apache HTTP Client test suite.
+ */
+@RunWith(Parameterized.class)
+public final class HostnameVerifierTest {
+    public static final class FakeSSLSession extends org.conscrypt.javax.net.ssl.FakeSSLSession {
+
+        private final Certificate[] certificates;
+
+        public FakeSSLSession(Certificate... certificates) throws Exception {
+            super("FakeHost");
+            this.certificates = certificates;
+        }
+
+        @Override
+        public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
+            if (certificates.length == 0) {
+                throw new SSLPeerUnverifiedException("peer not authenticated");
+            }
+            return certificates;
+        }
+    }
+
+    private static final Charset UTF_8 = Charset.forName("UTF-8");
+    // BEGIN Android-changed: Run tests for both default and strict verifiers. http://b/144694112
+    // private HostnameVerifier verifier = OkHostnameVerifier.INSTANCE;
+    @Parameters()
+    public static Collection<Object[]> data() {
+        // Both verifiers should behave the same in all tests except for
+        // subjectAltNameWithToplevelWildcard(), and that test is not parameterized for clarity.
+        return Arrays.asList(new Object[][] {
+                { OkHostnameVerifier.INSTANCE },
+                { OkHostnameVerifier.strictInstance() }
+        });
+    }
+
+    @Parameter
+    public OkHostnameVerifier verifier;
+    // END Android-changed: Run tests for both default and strict verifiers. http://b/144694112
+
+    @Test public void verify() throws Exception {
+        FakeSSLSession session = new FakeSSLSession();
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs,"localhost", session));
+    }
+
+    @Test public void verifyCn() throws Exception {
+        // CN=foo.com
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIERjCCAy6gAwIBAgIJAIz+EYMBU6aQMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE1MzE0MVoXDTI4MTEwNTE1MzE0MVowgaQx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczEQMA4GA1UEAxMHZm9vLmNvbTElMCMGCSqGSIb3DQEJARYWanVs\n"
+                + "aXVzZGF2aWVzQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n"
+                + "ggEBAMhjr5aCPoyp0R1iroWAfnEyBMGYWoCidH96yGPFjYLowez5aYKY1IOKTY2B\n"
+                + "lYho4O84X244QrZTRl8kQbYtxnGh4gSCD+Z8gjZ/gMvLUlhqOb+WXPAUHMB39GRy\n"
+                + "zerA/ZtrlUqf+lKo0uWcocxeRc771KN8cPH3nHZ0rV0Hx4ZAZy6U4xxObe4rtSVY\n"
+                + "07hNKXAb2odnVqgzcYiDkLV8ilvEmoNWMWrp8UBqkTcpEhYhCYp3cTkgJwMSuqv8\n"
+                + "BqnGd87xQU3FVZI4tbtkB+KzjD9zz8QCDJAfDjZHR03KNQ5mxOgXwxwKw6lGMaiV\n"
+                + "JTxpTKqym93whYk93l3ocEe55c0CAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgB\n"
+                + "hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE\n"
+                + "FJ8Ud78/OrbKOIJCSBYs2tDLXofYMB8GA1UdIwQYMBaAFHua2o+QmU5S0qzbswNS\n"
+                + "yoemDT4NMA0GCSqGSIb3DQEBBQUAA4IBAQC3jRmEya6sQCkmieULcvx8zz1euCk9\n"
+                + "fSez7BEtki8+dmfMXe3K7sH0lI8f4jJR0rbSCjpmCQLYmzC3NxBKeJOW0RcjNBpO\n"
+                + "c2JlGO9auXv2GDP4IYiXElLJ6VSqc8WvDikv0JmCCWm0Zga+bZbR/EWN5DeEtFdF\n"
+                + "815CLpJZNcYwiYwGy/CVQ7w2TnXlG+mraZOz+owr+cL6J/ZesbdEWfjoS1+cUEhE\n"
+                + "HwlNrAu8jlZ2UqSgskSWlhYdMTAP9CPHiUv9N7FcT58Itv/I4fKREINQYjDpvQcx\n"
+                + "SaTYb9dr5sB4WLNglk7zxDtM80H518VvihTcP7FHL+Gn6g4j5fkI98+S\n"
+                + "-----END CERTIFICATE-----\n");
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("foo.com", session));
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "foo.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+        assertFalse(verifier.verify(certs, "bar.com", session));
+    }
+
+    @Test public void verifyNonAsciiCn() throws Exception {
+        // CN=&#x82b1;&#x5b50;.co.jp
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIESzCCAzOgAwIBAgIJAIz+EYMBU6aTMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE1NDIxNVoXDTI4MTEwNTE1NDIxNVowgakx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNYXJ5bGFuZDEUMBIGA1UEBwwLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoMDmh0dHBjb21wb25lbnRzMRowGAYDVQQLDBF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczEVMBMGA1UEAwwM6Iqx5a2QLmNvLmpwMSUwIwYJKoZIhvcNAQkB\n"
+                + "FhZqdWxpdXNkYXZpZXNAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n"
+                + "MIIBCgKCAQEAyGOvloI+jKnRHWKuhYB+cTIEwZhagKJ0f3rIY8WNgujB7PlpgpjU\n"
+                + "g4pNjYGViGjg7zhfbjhCtlNGXyRBti3GcaHiBIIP5nyCNn+Ay8tSWGo5v5Zc8BQc\n"
+                + "wHf0ZHLN6sD9m2uVSp/6UqjS5ZyhzF5FzvvUo3xw8fecdnStXQfHhkBnLpTjHE5t\n"
+                + "7iu1JVjTuE0pcBvah2dWqDNxiIOQtXyKW8Sag1YxaunxQGqRNykSFiEJindxOSAn\n"
+                + "AxK6q/wGqcZ3zvFBTcVVkji1u2QH4rOMP3PPxAIMkB8ONkdHTco1DmbE6BfDHArD\n"
+                + "qUYxqJUlPGlMqrKb3fCFiT3eXehwR7nlzQIDAQABo3sweTAJBgNVHRMEAjAAMCwG\n"
+                + "CWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV\n"
+                + "HQ4EFgQUnxR3vz86tso4gkJIFiza0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLS\n"
+                + "rNuzA1LKh6YNPg0wDQYJKoZIhvcNAQEFBQADggEBALJ27i3okV/KvlDp6KMID3gd\n"
+                + "ITl68PyItzzx+SquF8gahMh016NX73z/oVZoVUNdftla8wPUB1GwIkAnGkhQ9LHK\n"
+                + "spBdbRiCj0gMmLCsX8SrjFvr7cYb2cK6J/fJe92l1tg/7Y4o7V/s4JBe/cy9U9w8\n"
+                + "a0ctuDmEBCgC784JMDtT67klRfr/2LlqWhlOEq7pUFxRLbhpquaAHSOjmIcWnVpw\n"
+                + "9BsO7qe46hidgn39hKh1WjKK2VcL/3YRsC4wUi0PBtFW6ScMCuMhgIRXSPU55Rae\n"
+                + "UIlOdPjjr1SUNWGId1rD7W16Scpwnknn310FNxFMHVI0GTGFkNdkilNCFJcIoRA=\n"
+                + "-----END CERTIFICATE-----\n");
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("\u82b1\u5b50.co.jp", session));
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "\u82b1\u5b50.co.jp", session));
+        assertFalse(verifier.verify(certs, "a.\u82b1\u5b50.co.jp", session));
+    }
+
+    @Test public void verifySubjectAlt() throws Exception {
+        // CN=foo.com, subjectAlt=bar.com
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIEXDCCA0SgAwIBAgIJAIz+EYMBU6aRMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE1MzYyOVoXDTI4MTEwNTE1MzYyOVowgaQx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczEQMA4GA1UEAxMHZm9vLmNvbTElMCMGCSqGSIb3DQEJARYWanVs\n"
+                + "aXVzZGF2aWVzQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n"
+                + "ggEBAMhjr5aCPoyp0R1iroWAfnEyBMGYWoCidH96yGPFjYLowez5aYKY1IOKTY2B\n"
+                + "lYho4O84X244QrZTRl8kQbYtxnGh4gSCD+Z8gjZ/gMvLUlhqOb+WXPAUHMB39GRy\n"
+                + "zerA/ZtrlUqf+lKo0uWcocxeRc771KN8cPH3nHZ0rV0Hx4ZAZy6U4xxObe4rtSVY\n"
+                + "07hNKXAb2odnVqgzcYiDkLV8ilvEmoNWMWrp8UBqkTcpEhYhCYp3cTkgJwMSuqv8\n"
+                + "BqnGd87xQU3FVZI4tbtkB+KzjD9zz8QCDJAfDjZHR03KNQ5mxOgXwxwKw6lGMaiV\n"
+                + "JTxpTKqym93whYk93l3ocEe55c0CAwEAAaOBkDCBjTAJBgNVHRMEAjAAMCwGCWCG\n"
+                + "SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4E\n"
+                + "FgQUnxR3vz86tso4gkJIFiza0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLSrNuz\n"
+                + "A1LKh6YNPg0wEgYDVR0RBAswCYIHYmFyLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEA\n"
+                + "dQyprNZBmVnvuVWjV42sey/PTfkYShJwy1j0/jcFZR/ypZUovpiHGDO1DgL3Y3IP\n"
+                + "zVQ26uhUsSw6G0gGRiaBDe/0LUclXZoJzXX1qpS55OadxW73brziS0sxRgGrZE/d\n"
+                + "3g5kkio6IED47OP6wYnlmZ7EKP9cqjWwlnvHnnUcZ2SscoLNYs9rN9ccp8tuq2by\n"
+                + "88OyhKwGjJfhOudqfTNZcDzRHx4Fzm7UsVaycVw4uDmhEHJrAsmMPpj/+XRK9/42\n"
+                + "2xq+8bc6HojdtbCyug/fvBZvZqQXSmU8m8IVcMmWMz0ZQO8ee3QkBHMZfCy7P/kr\n"
+                + "VbWx/uETImUu+NZg22ewEw==\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "foo.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+        assertTrue(verifier.verify(certs, "bar.com", session));
+        assertFalse(verifier.verify(certs, "a.bar.com", session));
+    }
+
+    /**
+     * Ignored due to incompatibilities between Android and Java on how non-ASCII
+     * subject alt names are parsed. Android fails to parse these, which means we
+     * fall back to the CN. The RI does parse them, so the CN is unused.
+     */
+    @Test @Ignore public void verifyNonAsciiSubjectAlt() throws Exception {
+        // CN=foo.com, subjectAlt=bar.com, subjectAlt=&#x82b1;&#x5b50;.co.jp
+        // (hanako.co.jp in kanji)
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIEajCCA1KgAwIBAgIJAIz+EYMBU6aSMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE1MzgxM1oXDTI4MTEwNTE1MzgxM1owgaQx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczEQMA4GA1UEAxMHZm9vLmNvbTElMCMGCSqGSIb3DQEJARYWanVs\n"
+                + "aXVzZGF2aWVzQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n"
+                + "ggEBAMhjr5aCPoyp0R1iroWAfnEyBMGYWoCidH96yGPFjYLowez5aYKY1IOKTY2B\n"
+                + "lYho4O84X244QrZTRl8kQbYtxnGh4gSCD+Z8gjZ/gMvLUlhqOb+WXPAUHMB39GRy\n"
+                + "zerA/ZtrlUqf+lKo0uWcocxeRc771KN8cPH3nHZ0rV0Hx4ZAZy6U4xxObe4rtSVY\n"
+                + "07hNKXAb2odnVqgzcYiDkLV8ilvEmoNWMWrp8UBqkTcpEhYhCYp3cTkgJwMSuqv8\n"
+                + "BqnGd87xQU3FVZI4tbtkB+KzjD9zz8QCDJAfDjZHR03KNQ5mxOgXwxwKw6lGMaiV\n"
+                + "JTxpTKqym93whYk93l3ocEe55c0CAwEAAaOBnjCBmzAJBgNVHRMEAjAAMCwGCWCG\n"
+                + "SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4E\n"
+                + "FgQUnxR3vz86tso4gkJIFiza0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLSrNuz\n"
+                + "A1LKh6YNPg0wIAYDVR0RBBkwF4IHYmFyLmNvbYIM6Iqx5a2QLmNvLmpwMA0GCSqG\n"
+                + "SIb3DQEBBQUAA4IBAQBeZs7ZIYyKtdnVxVvdLgwySEPOE4pBSXii7XYv0Q9QUvG/\n"
+                + "++gFGQh89HhABzA1mVUjH5dJTQqSLFvRfqTHqLpxSxSWqMHnvRM4cPBkIRp/XlMK\n"
+                + "PlXadYtJLPTgpbgvulA1ickC9EwlNYWnowZ4uxnfsMghW4HskBqaV+PnQ8Zvy3L0\n"
+                + "12c7Cg4mKKS5pb1HdRuiD2opZ+Hc77gRQLvtWNS8jQvd/iTbh6fuvTKfAOFoXw22\n"
+                + "sWIKHYrmhCIRshUNohGXv50m2o+1w9oWmQ6Dkq7lCjfXfUB4wIbggJjpyEtbNqBt\n"
+                + "j4MC2x5rfsLKKqToKmNE7pFEgqwe8//Aar1b+Qj+\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        assertTrue(verifier.verify(certs, "foo.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+        // these checks test alternative subjects. The test data contains an
+        // alternative subject starting with a japanese kanji character. This is
+        // not supported by Android because the underlying implementation from
+        // harmony follows the definition from rfc 1034 page 10 for alternative
+        // subject names. This causes the code to drop all alternative subjects.
+        // assertTrue(verifier.verify("bar.com", session));
+        // assertFalse(verifier.verify("a.bar.com", session));
+        // assertFalse(verifier.verify("a.\u82b1\u5b50.co.jp", session));
+    }
+
+    @Test public void verifySubjectAltOnly() throws Exception {
+        // subjectAlt=foo.com
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIESjCCAzKgAwIBAgIJAIz+EYMBU6aYMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE2MjYxMFoXDTI4MTEwNTE2MjYxMFowgZIx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNYXJ5bGFuZDEUMBIGA1UEBwwLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoMDmh0dHBjb21wb25lbnRzMRowGAYDVQQLDBF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczElMCMGCSqGSIb3DQEJARYWanVsaXVzZGF2aWVzQGdtYWlsLmNv\n"
+                + "bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMhjr5aCPoyp0R1iroWA\n"
+                + "fnEyBMGYWoCidH96yGPFjYLowez5aYKY1IOKTY2BlYho4O84X244QrZTRl8kQbYt\n"
+                + "xnGh4gSCD+Z8gjZ/gMvLUlhqOb+WXPAUHMB39GRyzerA/ZtrlUqf+lKo0uWcocxe\n"
+                + "Rc771KN8cPH3nHZ0rV0Hx4ZAZy6U4xxObe4rtSVY07hNKXAb2odnVqgzcYiDkLV8\n"
+                + "ilvEmoNWMWrp8UBqkTcpEhYhCYp3cTkgJwMSuqv8BqnGd87xQU3FVZI4tbtkB+Kz\n"
+                + "jD9zz8QCDJAfDjZHR03KNQ5mxOgXwxwKw6lGMaiVJTxpTKqym93whYk93l3ocEe5\n"
+                + "5c0CAwEAAaOBkDCBjTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NM\n"
+                + "IEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUnxR3vz86tso4gkJIFiza\n"
+                + "0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLSrNuzA1LKh6YNPg0wEgYDVR0RBAsw\n"
+                + "CYIHZm9vLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAjl78oMjzFdsMy6F1sGg/IkO8\n"
+                + "tF5yUgPgFYrs41yzAca7IQu6G9qtFDJz/7ehh/9HoG+oqCCIHPuIOmS7Sd0wnkyJ\n"
+                + "Y7Y04jVXIb3a6f6AgBkEFP1nOT0z6kjT7vkA5LJ2y3MiDcXuRNMSta5PYVnrX8aZ\n"
+                + "yiqVUNi40peuZ2R8mAUSBvWgD7z2qWhF8YgDb7wWaFjg53I36vWKn90ZEti3wNCw\n"
+                + "qAVqixM+J0qJmQStgAc53i2aTMvAQu3A3snvH/PHTBo+5UL72n9S1kZyNCsVf1Qo\n"
+                + "n8jKTiRriEM+fMFlcgQP284EBFzYHyCXFb9O/hMjK2+6mY9euMB1U1aFFzM/Bg==\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        assertTrue(verifier.verify(certs, "foo.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+        assertTrue(verifier.verify(certs, "foo.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+    }
+
+    @Test public void verifyMultipleCn() throws Exception {
+        // CN=foo.com, CN=bar.com, CN=&#x82b1;&#x5b50;.co.jp
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIEbzCCA1egAwIBAgIJAIz+EYMBU6aXMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE2MTk0NVoXDTI4MTEwNTE2MTk0NVowgc0x\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNYXJ5bGFuZDEUMBIGA1UEBwwLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoMDmh0dHBjb21wb25lbnRzMRowGAYDVQQLDBF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczEQMA4GA1UEAwwHZm9vLmNvbTEQMA4GA1UEAwwHYmFyLmNvbTEV\n"
+                + "MBMGA1UEAwwM6Iqx5a2QLmNvLmpwMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyGOv\n"
+                + "loI+jKnRHWKuhYB+cTIEwZhagKJ0f3rIY8WNgujB7PlpgpjUg4pNjYGViGjg7zhf\n"
+                + "bjhCtlNGXyRBti3GcaHiBIIP5nyCNn+Ay8tSWGo5v5Zc8BQcwHf0ZHLN6sD9m2uV\n"
+                + "Sp/6UqjS5ZyhzF5FzvvUo3xw8fecdnStXQfHhkBnLpTjHE5t7iu1JVjTuE0pcBva\n"
+                + "h2dWqDNxiIOQtXyKW8Sag1YxaunxQGqRNykSFiEJindxOSAnAxK6q/wGqcZ3zvFB\n"
+                + "TcVVkji1u2QH4rOMP3PPxAIMkB8ONkdHTco1DmbE6BfDHArDqUYxqJUlPGlMqrKb\n"
+                + "3fCFiT3eXehwR7nlzQIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQf\n"
+                + "Fh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUnxR3vz86\n"
+                + "tso4gkJIFiza0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLSrNuzA1LKh6YNPg0w\n"
+                + "DQYJKoZIhvcNAQEFBQADggEBAGuZb8ai1NO2j4v3y9TLZvd5s0vh5/TE7n7RX+8U\n"
+                + "y37OL5k7x9nt0mM1TyAKxlCcY+9h6frue8MemZIILSIvMrtzccqNz0V1WKgA+Orf\n"
+                + "uUrabmn+CxHF5gpy6g1Qs2IjVYWA5f7FROn/J+Ad8gJYc1azOWCLQqSyfpNRLSvY\n"
+                + "EriQFEV63XvkJ8JrG62b+2OT2lqT4OO07gSPetppdlSa8NBSKP6Aro9RIX1ZjUZQ\n"
+                + "SpQFCfo02NO0uNRDPUdJx2huycdNb+AXHaO7eXevDLJ+QnqImIzxWiY6zLOdzjjI\n"
+                + "VBMkLHmnP7SjGSQ3XA4ByrQOxfOUTyLyE7NuemhHppuQPxE=\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "foo.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+        assertFalse(verifier.verify(certs, "bar.com", session));
+        assertFalse(verifier.verify(certs, "a.bar.com", session));
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("\u82b1\u5b50.co.jp", session));
+        assertFalse(verifier.verify(certs, "\u82b1\u5b50.co.jp", session));
+        assertFalse(verifier.verify(certs, "a.\u82b1\u5b50.co.jp", session));
+    }
+
+    @Test public void verifyWilcardCn() throws Exception {
+        // CN=*.foo.com
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIESDCCAzCgAwIBAgIJAIz+EYMBU6aUMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE2MTU1NVoXDTI4MTEwNTE2MTU1NVowgaYx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczESMBAGA1UEAxQJKi5mb28uY29tMSUwIwYJKoZIhvcNAQkBFhZq\n"
+                + "dWxpdXNkYXZpZXNAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"
+                + "CgKCAQEAyGOvloI+jKnRHWKuhYB+cTIEwZhagKJ0f3rIY8WNgujB7PlpgpjUg4pN\n"
+                + "jYGViGjg7zhfbjhCtlNGXyRBti3GcaHiBIIP5nyCNn+Ay8tSWGo5v5Zc8BQcwHf0\n"
+                + "ZHLN6sD9m2uVSp/6UqjS5ZyhzF5FzvvUo3xw8fecdnStXQfHhkBnLpTjHE5t7iu1\n"
+                + "JVjTuE0pcBvah2dWqDNxiIOQtXyKW8Sag1YxaunxQGqRNykSFiEJindxOSAnAxK6\n"
+                + "q/wGqcZ3zvFBTcVVkji1u2QH4rOMP3PPxAIMkB8ONkdHTco1DmbE6BfDHArDqUYx\n"
+                + "qJUlPGlMqrKb3fCFiT3eXehwR7nlzQIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCG\n"
+                + "SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4E\n"
+                + "FgQUnxR3vz86tso4gkJIFiza0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLSrNuz\n"
+                + "A1LKh6YNPg0wDQYJKoZIhvcNAQEFBQADggEBAH0ipG6J561UKUfgkeW7GvYwW98B\n"
+                + "N1ZooWX+JEEZK7+Pf/96d3Ij0rw9ACfN4bpfnCq0VUNZVSYB+GthQ2zYuz7tf/UY\n"
+                + "A6nxVgR/IjG69BmsBl92uFO7JTNtHztuiPqBn59pt+vNx4yPvno7zmxsfI7jv0ww\n"
+                + "yfs+0FNm7FwdsC1k47GBSOaGw38kuIVWqXSAbL4EX9GkryGGOKGNh0qvAENCdRSB\n"
+                + "G9Z6tyMbmfRY+dLSh3a9JwoEcBUso6EWYBakLbq4nG/nvYdYvG9ehrnLVwZFL82e\n"
+                + "l3Q/RK95bnA6cuRClGusLad0e6bjkBzx/VQ3VarDEpAkTLUGVAa0CLXtnyc=\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "foo.com", session));
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("www.foo.com", session));
+        assertFalse(verifier.verify(certs, "www.foo.com", session));
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("\u82b1\u5b50.foo.com", session));
+        assertFalse(verifier.verify(certs, "\u82b1\u5b50.foo.com", session));
+        assertFalse(verifier.verify(certs, "a.b.foo.com", session));
+    }
+
+    @Test public void verifyWilcardCnOnTld() throws Exception {
+        // It's the CA's responsibility to not issue broad-matching certificates!
+        // CN=*.co.jp
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIERjCCAy6gAwIBAgIJAIz+EYMBU6aVMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE2MTYzMFoXDTI4MTEwNTE2MTYzMFowgaQx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczEQMA4GA1UEAxQHKi5jby5qcDElMCMGCSqGSIb3DQEJARYWanVs\n"
+                + "aXVzZGF2aWVzQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n"
+                + "ggEBAMhjr5aCPoyp0R1iroWAfnEyBMGYWoCidH96yGPFjYLowez5aYKY1IOKTY2B\n"
+                + "lYho4O84X244QrZTRl8kQbYtxnGh4gSCD+Z8gjZ/gMvLUlhqOb+WXPAUHMB39GRy\n"
+                + "zerA/ZtrlUqf+lKo0uWcocxeRc771KN8cPH3nHZ0rV0Hx4ZAZy6U4xxObe4rtSVY\n"
+                + "07hNKXAb2odnVqgzcYiDkLV8ilvEmoNWMWrp8UBqkTcpEhYhCYp3cTkgJwMSuqv8\n"
+                + "BqnGd87xQU3FVZI4tbtkB+KzjD9zz8QCDJAfDjZHR03KNQ5mxOgXwxwKw6lGMaiV\n"
+                + "JTxpTKqym93whYk93l3ocEe55c0CAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgB\n"
+                + "hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE\n"
+                + "FJ8Ud78/OrbKOIJCSBYs2tDLXofYMB8GA1UdIwQYMBaAFHua2o+QmU5S0qzbswNS\n"
+                + "yoemDT4NMA0GCSqGSIb3DQEBBQUAA4IBAQA0sWglVlMx2zNGvUqFC73XtREwii53\n"
+                + "CfMM6mtf2+f3k/d8KXhLNySrg8RRlN11zgmpPaLtbdTLrmG4UdAHHYr8O4y2BBmE\n"
+                + "1cxNfGxxechgF8HX10QV4dkyzp6Z1cfwvCeMrT5G/V1pejago0ayXx+GPLbWlNeZ\n"
+                + "S+Kl0m3p+QplXujtwG5fYcIpaGpiYraBLx3Tadih39QN65CnAh/zRDhLCUzKyt9l\n"
+                + "UGPLEUDzRHMPHLnSqT1n5UU5UDRytbjJPXzF+l/+WZIsanefWLsxnkgAuZe/oMMF\n"
+                + "EJMryEzOjg4Tfuc5qM0EXoPcQ/JlheaxZ40p2IyHqbsWV4MRYuFH4bkM\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("foo.co.jp", session));
+        assertFalse(verifier.verify(certs, "foo.co.jp", session));
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("\u82b1\u5b50.co.jp", session));
+        assertFalse(verifier.verify(certs, "\u82b1\u5b50.co.jp", session));
+    }
+
+    /**
+     * Ignored due to incompatibilities between Android and Java on how non-ASCII
+     * subject alt names are parsed. Android fails to parse these, which means we
+     * fall back to the CN. The RI does parse them, so the CN is unused.
+     */
+    @Test @Ignore public void testWilcardNonAsciiSubjectAlt() throws Exception {
+        // CN=*.foo.com, subjectAlt=*.bar.com, subjectAlt=*.&#x82b1;&#x5b50;.co.jp
+        // (*.hanako.co.jp in kanji)
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIEcDCCA1igAwIBAgIJAIz+EYMBU6aWMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE2MTczMVoXDTI4MTEwNTE2MTczMVowgaYx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczESMBAGA1UEAxQJKi5mb28uY29tMSUwIwYJKoZIhvcNAQkBFhZq\n"
+                + "dWxpdXNkYXZpZXNAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"
+                + "CgKCAQEAyGOvloI+jKnRHWKuhYB+cTIEwZhagKJ0f3rIY8WNgujB7PlpgpjUg4pN\n"
+                + "jYGViGjg7zhfbjhCtlNGXyRBti3GcaHiBIIP5nyCNn+Ay8tSWGo5v5Zc8BQcwHf0\n"
+                + "ZHLN6sD9m2uVSp/6UqjS5ZyhzF5FzvvUo3xw8fecdnStXQfHhkBnLpTjHE5t7iu1\n"
+                + "JVjTuE0pcBvah2dWqDNxiIOQtXyKW8Sag1YxaunxQGqRNykSFiEJindxOSAnAxK6\n"
+                + "q/wGqcZ3zvFBTcVVkji1u2QH4rOMP3PPxAIMkB8ONkdHTco1DmbE6BfDHArDqUYx\n"
+                + "qJUlPGlMqrKb3fCFiT3eXehwR7nlzQIDAQABo4GiMIGfMAkGA1UdEwQCMAAwLAYJ\n"
+                + "YIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1Ud\n"
+                + "DgQWBBSfFHe/Pzq2yjiCQkgWLNrQy16H2DAfBgNVHSMEGDAWgBR7mtqPkJlOUtKs\n"
+                + "27MDUsqHpg0+DTAkBgNVHREEHTAbggkqLmJhci5jb22CDiou6Iqx5a2QLmNvLmpw\n"
+                + "MA0GCSqGSIb3DQEBBQUAA4IBAQBobWC+D5/lx6YhX64CwZ26XLjxaE0S415ajbBq\n"
+                + "DK7lz+Rg7zOE3GsTAMi+ldUYnhyz0wDiXB8UwKXl0SDToB2Z4GOgqQjAqoMmrP0u\n"
+                + "WB6Y6dpkfd1qDRUzI120zPYgSdsXjHW9q2H77iV238hqIU7qCvEz+lfqqWEY504z\n"
+                + "hYNlknbUnR525ItosEVwXFBJTkZ3Yw8gg02c19yi8TAh5Li3Ad8XQmmSJMWBV4XK\n"
+                + "qFr0AIZKBlg6NZZFf/0dP9zcKhzSriW27bY0XfzA6GSiRDXrDjgXq6baRT6YwgIg\n"
+                + "pgJsDbJtZfHnV1nd3M6zOtQPm1TIQpNmMMMd/DPrGcUQerD3\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        // try the foo.com variations
+        assertTrue(verifier.verify(certs, "foo.com", session));
+        assertTrue(verifier.verify(certs, "www.foo.com", session));
+        assertTrue(verifier.verify(certs, "\u82b1\u5b50.foo.com", session));
+        assertFalse(verifier.verify(certs, "a.b.foo.com", session));
+        // these checks test alternative subjects. The test data contains an
+        // alternative subject starting with a japanese kanji character. This is
+        // not supported by Android because the underlying implementation from
+        // harmony follows the definition from rfc 1034 page 10 for alternative
+        // subject names. This causes the code to drop all alternative subjects.
+        // assertFalse(verifier.verify("bar.com", session));
+        // assertTrue(verifier.verify("www.bar.com", session));
+        // assertTrue(verifier.verify("\u82b1\u5b50.bar.com", session));
+        // assertTrue(verifier.verify("a.b.bar.com", session));
+    }
+
+    @Test public void subjectAltUsesLocalDomainAndIp() throws Exception {
+        // cat cert.cnf
+        // [req]
+        // distinguished_name=distinguished_name
+        // req_extensions=req_extensions
+        // x509_extensions=x509_extensions
+        // [distinguished_name]
+        // [req_extensions]
+        // [x509_extensions]
+        // subjectAltName=DNS:localhost.localdomain,DNS:localhost,IP:127.0.0.1
+        //
+        // $ openssl req -x509 -nodes -days 36500 -subj '/CN=localhost' -config ./cert.cnf \
+        //     -newkey rsa:512 -out cert.pem
+        X509Certificate certificate = certificate(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIBWDCCAQKgAwIBAgIJANS1EtICX2AZMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV\n"
+                + "BAMTCWxvY2FsaG9zdDAgFw0xMjAxMDIxOTA4NThaGA8yMTExMTIwOTE5MDg1OFow\n"
+                + "FDESMBAGA1UEAxMJbG9jYWxob3N0MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPpt\n"
+                + "atK8r4/hf4hSIs0os/BSlQLbRBaK9AfBReM4QdAklcQqe6CHsStKfI8pp0zs7Ptg\n"
+                + "PmMdpbttL0O7mUboBC8CAwEAAaM1MDMwMQYDVR0RBCowKIIVbG9jYWxob3N0Lmxv\n"
+                + "Y2FsZG9tYWlugglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQEFBQADQQD0ntfL\n"
+                + "DCzOCv9Ma6Lv5o5jcYWVxvBSTsnt22hsJpWD1K7iY9lbkLwl0ivn73pG2evsAn9G\n"
+                + "X8YKH52fnHsCrhSD\n"
+                + "-----END CERTIFICATE-----");
+
+        assertEquals(new X500Principal("CN=localhost"), certificate.getSubjectX500Principal());
+        FakeSSLSession session = new FakeSSLSession(certificate);
+
+        X509Certificate[] certs = {};
+
+        assertTrue(verifier.verify(certs, "localhost", session));
+        assertTrue(verifier.verify(certs, "localhost.localdomain", session));
+        assertFalse(verifier.verify(certs, "local.host", session));
+
+        assertTrue(verifier.verify(certs, "127.0.0.1", session));
+        assertFalse(verifier.verify(certs, "127.0.0.2", session));
+    }
+
+    @Test public void wildcardsCannotMatchIpAddresses() throws Exception {
+        // openssl req -x509 -nodes -days 36500 -subj '/CN=*.0.0.1' -newkey rsa:512 -out cert.pem
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIBkjCCATygAwIBAgIJAMdemqOwd/BEMA0GCSqGSIb3DQEBBQUAMBIxEDAOBgNV\n"
+                + "BAMUByouMC4wLjEwIBcNMTAxMjIwMTY0NDI1WhgPMjExMDExMjYxNjQ0MjVaMBIx\n"
+                + "EDAOBgNVBAMUByouMC4wLjEwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAqY8c9Qrt\n"
+                + "YPWCvb7lclI+aDHM6fgbJcHsS9Zg8nUOh5dWrS7AgeA25wyaokFl4plBbbHQe2j+\n"
+                + "cCjsRiJIcQo9HwIDAQABo3MwcTAdBgNVHQ4EFgQUJ436TZPJvwCBKklZZqIvt1Yt\n"
+                + "JjEwQgYDVR0jBDswOYAUJ436TZPJvwCBKklZZqIvt1YtJjGhFqQUMBIxEDAOBgNV\n"
+                + "BAMUByouMC4wLjGCCQDHXpqjsHfwRDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB\n"
+                + "BQUAA0EAk9i88xdjWoewqvE+iMC9tD2obMchgFDaHH0ogxxiRaIKeEly3g0uGxIt\n"
+                + "fl2WRY8hb4x+zRrwsFaLEpdEvqcjOQ==\n"
+                + "-----END CERTIFICATE-----");
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "127.0.0.1", session));
+    }
+
+    /**
+     * Earlier implementations of Android's hostname verifier required that
+     * wildcard names wouldn't match "*.com" or similar. This was a nonstandard
+     * check that we've since dropped. It is the CA's responsibility to not hand
+     * out certificates that match so broadly.
+     */
+    @Test public void wildcardsDoesNotNeedTwoDots() throws Exception {
+        // openssl req -x509 -nodes -days 36500 -subj '/CN=*.com' -newkey rsa:512 -out cert.pem
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIBjDCCATagAwIBAgIJAOVulXCSu6HuMA0GCSqGSIb3DQEBBQUAMBAxDjAMBgNV\n"
+                + "BAMUBSouY29tMCAXDTEwMTIyMDE2NDkzOFoYDzIxMTAxMTI2MTY0OTM4WjAQMQ4w\n"
+                + "DAYDVQQDFAUqLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDJd8xqni+h7Iaz\n"
+                + "ypItivs9kPuiJUqVz+SuJ1C05SFc3PmlRCvwSIfhyD67fHcbMdl+A/LrIjhhKZJe\n"
+                + "1joO0+pFAgMBAAGjcTBvMB0GA1UdDgQWBBS4Iuzf5w8JdCp+EtBfdFNudf6+YzBA\n"
+                + "BgNVHSMEOTA3gBS4Iuzf5w8JdCp+EtBfdFNudf6+Y6EUpBIwEDEOMAwGA1UEAxQF\n"
+                + "Ki5jb22CCQDlbpVwkruh7jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA0EA\n"
+                + "U6LFxmZr31lFyis2/T68PpjAppc0DpNQuA2m/Y7oTHBDi55Fw6HVHCw3lucuWZ5d\n"
+                + "qUYo4ES548JdpQtcLrW2sA==\n"
+                + "-----END CERTIFICATE-----");
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("google.com", session));
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "google.com", session));
+    }
+
+    @Test public void subjectAltName() throws Exception {
+        // $ cat ./cert.cnf
+        // [req]
+        // distinguished_name=distinguished_name
+        // req_extensions=req_extensions
+        // x509_extensions=x509_extensions
+        // [distinguished_name]
+        // [req_extensions]
+        // [x509_extensions]
+        // subjectAltName=DNS:bar.com,DNS:baz.com
+        //
+        // $ openssl req -x509 -nodes -days 36500 -subj '/CN=foo.com' -config ./cert.cnf \
+        //     -newkey rsa:512 -out cert.pem
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIBPTCB6KADAgECAgkA7zoHaaqNGHQwDQYJKoZIhvcNAQEFBQAwEjEQMA4GA1UE\n"
+                + "AxMHZm9vLmNvbTAgFw0xMDEyMjAxODM5MzZaGA8yMTEwMTEyNjE4MzkzNlowEjEQ\n"
+                + "MA4GA1UEAxMHZm9vLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC+gmoSxF+8\n"
+                + "hbV+rgRQqHIJd50216OWQJbU3BvdlPbca779NYO4+UZWTFdBM8BdQqs3H4B5Agvp\n"
+                + "y7HeSff1F7XRAgMBAAGjHzAdMBsGA1UdEQQUMBKCB2Jhci5jb22CB2Jhei5jb20w\n"
+                + "DQYJKoZIhvcNAQEFBQADQQBXpZZPOY2Dy1lGG81JTr8L4or9jpKacD7n51eS8iqI\n"
+                + "oTznPNuXHU5bFN0AAGX2ij47f/EahqTpo5RdS95P4sVm\n"
+                + "-----END CERTIFICATE-----");
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "foo.com", session));
+        assertTrue(verifier.verify(certs, "bar.com", session));
+        assertTrue(verifier.verify(certs, "baz.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+        assertFalse(verifier.verify(certs, "quux.com", session));
+    }
+
+    @Test public void subjectAltNameWithWildcard() throws Exception {
+        // $ cat ./cert.cnf
+        // [req]
+        // distinguished_name=distinguished_name
+        // req_extensions=req_extensions
+        // x509_extensions=x509_extensions
+        // [distinguished_name]
+        // [req_extensions]
+        // [x509_extensions]
+        // subjectAltName=DNS:bar.com,DNS:*.baz.com
+        //
+        // $ openssl req -x509 -nodes -days 36500 -subj '/CN=foo.com' -config ./cert.cnf \
+        //     -newkey rsa:512 -out cert.pem
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIBPzCB6qADAgECAgkAnv/7Jv5r7pMwDQYJKoZIhvcNAQEFBQAwEjEQMA4GA1UE\n"
+                + "AxMHZm9vLmNvbTAgFw0xMDEyMjAxODQ2MDFaGA8yMTEwMTEyNjE4NDYwMVowEjEQ\n"
+                + "MA4GA1UEAxMHZm9vLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDAz2YXnyog\n"
+                + "YdYLSFr/OEgSumtwqtZKJTB4wqTW/eKbBCEzxnyUMxWZIqUGu353PzwfOuWp2re3\n"
+                + "nvVV+QDYQlh9AgMBAAGjITAfMB0GA1UdEQQWMBSCB2Jhci5jb22CCSouYmF6LmNv\n"
+                + "bTANBgkqhkiG9w0BAQUFAANBAB8yrSl8zqy07i0SNYx2B/FnvQY734pxioaqFWfO\n"
+                + "Bqo1ZZl/9aPHEWIwBrxYNVB0SGu/kkbt/vxqOjzzrkXukmI=\n"
+                + "-----END CERTIFICATE-----");
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "foo.com", session));
+        assertTrue(verifier.verify(certs, "bar.com", session));
+        assertTrue(verifier.verify(certs, "a.baz.com", session));
+        assertFalse(verifier.verify(certs, "baz.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+        assertFalse(verifier.verify(certs, "a.bar.com", session));
+        assertFalse(verifier.verify(certs, "quux.com", session));
+    }
+
+    // BEGIN Android-added: Verify behaviour with top level wildcard SAN. http://b/144694112
+    @Test
+    public void subjectAltNameWithToplevelWildcard() throws Exception {
+        // Default OkHostnameVerifier instance should allow SANs which
+        // have wildcards for top-level domains.  The strict instance should not.
+        //
+        // Certificate generated using:-
+        //     openssl req -x509 -nodes -days 36500 -subj "/CN=Google Inc" \
+        //         -addext "subjectAltName=DNS:*.com" -newkey rsa:512
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIBlTCCAT+gAwIBAgIUe1RB6C61ZW/SEQpKiywSEJOEOUMwDQYJKoZIhvcNAQEL\n"
+                + "BQAwFTETMBEGA1UEAwwKR29vZ2xlIEluYzAgFw0xOTExMjExMjE1NTBaGA8yMTE5\n"
+                + "MTAyODEyMTU1MFowFTETMBEGA1UEAwwKR29vZ2xlIEluYzBcMA0GCSqGSIb3DQEB\n"
+                + "AQUAA0sAMEgCQQCu24jT8hktpvnmcde4dqC6e7G5F4cNNLUFnTi3Ay9BzPH1r7sN\n"
+                + "v2lHTIQLKSlvjxa48mpeRBlOjDQigv7c+rfRAgMBAAGjZTBjMB0GA1UdDgQWBBQd\n"
+                + "myvYKfluxb0+kNEJoh1ZER2wUTAfBgNVHSMEGDAWgBQdmyvYKfluxb0+kNEJoh1Z\n"
+                + "ER2wUTAPBgNVHRMBAf8EBTADAQH/MBAGA1UdEQQJMAeCBSouY29tMA0GCSqGSIb3\n"
+                + "DQEBCwUAA0EAK710g2hQpXSmpbOQH4dHG61fkVDtM/kR/4/R61vDDqVkgOuyHqXl\n"
+                + "GUZFKHMeOZ8peQLT8b+5ik6pIO7Vu2pF6w==\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        assertTrue(OkHostnameVerifier.INSTANCE.verify(certs, "google.com", session));
+        assertFalse(OkHostnameVerifier.strictInstance().verify(certs, "google.com", session));
+    }
+    // END Android-added: Verify behaviour with top level wildcard SAN. http://b/144694112
+
+    // Android-changed: OkHostnameVerifier.verifyAsIpAddress not accessible on platform builds
+    @Test @Ignore public void verifyAsIpAddress() {
+        // IPv4
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("127.0.0.1"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("1.2.3.4"));
+
+        // IPv6
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("::1"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("2001:db8::1"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("::192.168.0.1"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("::ffff:192.168.0.1"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("FEDC:BA98:7654:3210:FEDC:BA98:7654:3210"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("1080:0:0:0:8:800:200C:417A"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("1080::8:800:200C:417A"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("FF01::101"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("0:0:0:0:0:0:13.1.68.3"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("0:0:0:0:0:FFFF:129.144.52.38"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("::13.1.68.3"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("::FFFF:129.144.52.38"));
+
+        // Hostnames
+        assertFalse(OkHostnameVerifier.verifyAsIpAddress("go"));
+        assertFalse(OkHostnameVerifier.verifyAsIpAddress("localhost"));
+        assertFalse(OkHostnameVerifier.verifyAsIpAddress("squareup.com"));
+        assertFalse(OkHostnameVerifier.verifyAsIpAddress("www.nintendo.co.jp"));
+    }
+
+    private X509Certificate certificate(String certificate) throws Exception {
+        return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(
+                new ByteArrayInputStream(certificate.getBytes(UTF_8)));
+    }
+
+    private SSLSession session(String certificate) throws Exception {
+        return new FakeSSLSession(certificate(certificate));
+    }
+}
diff --git a/common/src/test/java/org/conscrypt/MacTest.java b/common/src/test/java/org/conscrypt/MacTest.java
new file mode 100644
index 0000000..a80065a
--- /dev/null
+++ b/common/src/test/java/org/conscrypt/MacTest.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt;
+
+import static org.conscrypt.TestUtils.decodeHex;
+import static org.conscrypt.TestUtils.encodeHex;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.Provider;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import javax.crypto.Mac;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import tests.util.ServiceTester;
+
+
+@RunWith(JUnit4.class)
+public class MacTest {
+    private final List<String[]> testVectors = readTestVectors();
+
+    // Column indices in test vector CSV file
+    private static final int ALGORITHM_INDEX = 0;
+    private static final int KEY_INDEX = 1;
+    private static final int MESSAGE_INDEX = 2;
+    private static final int MAC_INDEX = 3;
+
+    // Number of splits to use when testing multiple buffers
+    private static final int NUM_SPLITS = 4;
+
+    private final Random random = new Random(System.currentTimeMillis());
+
+    private final Provider conscryptProvider = TestUtils.getConscryptProvider();
+
+    @BeforeClass
+    public static void setUp() {
+        TestUtils.assumeAllowsUnsignedCrypto();
+    }
+
+    @Test
+    public void knownAnswerTest() throws Exception {
+        for (String[] entry : testVectors) {
+            String algorithm = entry[ALGORITHM_INDEX];
+            String key = entry[KEY_INDEX];
+            String msg = entry[MESSAGE_INDEX];
+            String expected = entry[MAC_INDEX];
+
+            byte[] keyBytes = decodeHex(key);
+            byte[] msgBytes = decodeHex(msg);
+            byte[] expectedBytes = decodeHex(expected);
+            SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "RawBytes");
+
+            String baseFailMsg = String.format("Mac=%s\nKey=%s\nMsg=%s\nExpected=%s",
+                    algorithm, key, msg, expected);
+
+            // Calculate using Mac.update(byte[])
+            byte[] macBytes = generateMacUsingUpdate(algorithm, secretKey, msgBytes);
+            assertArrayEquals(failMessage("Using update()", baseFailMsg, macBytes),
+                    expectedBytes, macBytes);
+
+            // Calculate using Mac.final(byte[])
+            macBytes = generateMacUsingFinal(algorithm, secretKey, msgBytes);
+            assertArrayEquals(failMessage("Using final()", baseFailMsg, macBytes),
+                    expectedBytes, macBytes);
+
+            // Calculate using Mac.update(ByteBuffer) with a single non-direct ByteBuffer
+            ByteBuffer nondirectBuffer = ByteBuffer.wrap(msgBytes);
+            macBytes = generateMac(algorithm, secretKey, nondirectBuffer);
+            assertArrayEquals(failMessage("Non-direct ByteBuffer", baseFailMsg, macBytes),
+                    expectedBytes, macBytes);
+
+            // Calculate using Mac.update(ByteBuffer) with a single direct ByteBuffer
+            ByteBuffer directBuffer = ByteBuffer.allocateDirect(msgBytes.length);
+            directBuffer.put(msgBytes);
+            directBuffer.flip();
+            macBytes = generateMac(algorithm, secretKey, directBuffer);
+            assertArrayEquals(failMessage("Direct ByteBuffer", baseFailMsg, macBytes),
+                    expectedBytes, macBytes);
+
+            // Calculate using Mac.update(ByteBuffer) with a multiple non-direct ByteBuffers
+            nondirectBuffer.flip();
+            macBytes = generateMac(algorithm, secretKey, split(nondirectBuffer));
+            assertArrayEquals(failMessage("Multiple non-direct ByteBuffers", baseFailMsg, macBytes),
+                    expectedBytes, macBytes);
+
+            // Calculate using Mac.update(ByteBuffer) with a multiple direct ByteBuffers
+            directBuffer.flip();
+            macBytes = generateMac(algorithm, secretKey, split(directBuffer));
+            assertArrayEquals(failMessage("Multiple direct ByteBuffers", baseFailMsg, macBytes),
+                    expectedBytes, macBytes);
+
+            // Calculated using a pre-loved Mac
+            macBytes = generateReusingMac(algorithm, keyBytes, msgBytes);
+            assertArrayEquals(failMessage("Re-use Mac", baseFailMsg, macBytes),
+                    expectedBytes, macBytes);
+        }
+    }
+
+    @Test
+    public void serviceCreation() {
+        newMacServiceTester()
+            // Android KeyStore can only be initialised with its own private keys - tested elsewhere.
+            .skipProvider("AndroidKeyStore")
+            .skipProvider("AndroidKeyStoreBCWorkaround")
+            .run(new ServiceTester.Test() {
+                @Override
+                public void test(final Provider provider, final String algorithm) throws Exception {
+                    SecretKeySpec key = findAnyKey(algorithm);
+
+                    Mac mac = Mac.getInstance(algorithm);
+                    assertEquals(algorithm, mac.getAlgorithm());
+
+                    mac = Mac.getInstance(algorithm, provider);
+                    assertEquals(algorithm, mac.getAlgorithm());
+                    assertEquals(provider, mac.getProvider());
+                    if (key != null) {
+                        // TODO(prb) Ensure we have at least one test vector for every
+                        // MAC in Conscrypt and Android.
+                        mac.init(key);
+                        assertEquals(provider, mac.getProvider());
+                    }
+
+                    mac = Mac.getInstance(algorithm, provider.getName());
+                    assertEquals(algorithm, mac.getAlgorithm());
+                    assertEquals(provider, mac.getProvider());
+                    if (key != null) {
+                        mac.init(key);
+                        assertEquals(provider, mac.getProvider());
+                    }
+                }
+            });
+    }
+
+    @Test
+    public void invalidKeyThrows() {
+        newMacServiceTester()
+            // BC actually accepts RSA public keys for these algorithms for some reason.
+            .skipCombination("BC", "PBEWITHHMACSHA")
+            .skipCombination("BC", "PBEWITHHMACSHA1")
+            .run(new ServiceTester.Test() {
+                @Override
+                public void test(final Provider provider, final String algorithm) throws Exception {
+                    KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+                    generator.initialize(2048);
+                    KeyPair keyPair = generator.generateKeyPair();
+
+                    try {
+                        Mac mac = Mac.getInstance(algorithm, provider);
+                        mac.init(keyPair.getPublic(), null);
+                        fail();
+                    } catch (InvalidKeyException e) {
+                        // Expected
+                    }
+                }
+            });
+    }
+
+    @Test
+    public void uninitializedMacThrows() {
+        newMacServiceTester()
+            .run(new ServiceTester.Test() {
+                @Override
+                public void test(final Provider provider, final String algorithm) throws Exception {
+                    byte[] message = "Message".getBytes(StandardCharsets.UTF_8);
+
+                    try {
+                        Mac mac = Mac.getInstance(algorithm, provider);
+                        mac.update(message);
+                        fail();
+                    } catch (IllegalStateException e) {
+                        // Expected
+                    }
+                    try {
+                        Mac mac = Mac.getInstance(algorithm, provider);
+                        mac.doFinal(message);
+                        fail();
+                    } catch (IllegalStateException e) {
+                        // Expected
+                    }
+                    try {
+                        Mac mac = Mac.getInstance(algorithm, provider);
+                        mac.doFinal();
+                        fail();
+                    } catch (IllegalStateException e) {
+                        // Expected
+                    }
+                }
+            });
+
+    }
+
+    private ServiceTester newMacServiceTester() {
+        return ServiceTester.test("Mac")
+            // On Android 10 and 11 BC advertises these Macs but they are deprecated so throw
+            // on initialization.
+            .skipCombination("BC", "HMACMD5")
+            .skipCombination("BC", "HMACSHA1")
+            .skipCombination("BC", "HMACSHA224")
+            .skipCombination("BC", "HMACSHA256")
+            .skipCombination("BC", "HMACSHA384")
+            .skipCombination("BC", "HMACSHA512")
+            .skipCombination("BC", "PBEWITHHMACSHA224")
+            .skipCombination("BC", "PBEWITHHMACSHA256")
+            .skipCombination("BC", "PBEWITHHMACSHA384")
+            .skipCombination("BC", "PBEWITHHMACSHA512");
+    }
+
+    private static class DummyParameterSpec implements AlgorithmParameterSpec { }
+
+    @Test
+    public void algorithmParameters() {
+        ServiceTester.test("Mac")
+            // Android KeyStore can only be initialised with its own private keys - tested elsewhere.
+            .skipProvider("AndroidKeyStore")
+            .skipProvider("AndroidKeyStoreBCWorkaround")
+            .run(new ServiceTester.Test() {
+                @Override
+                public void test(final Provider provider, final String algorithm) throws Exception {
+                    SecretKeySpec key = findAnyKey(algorithm);
+                    if (key != null) {
+                        Mac mac = Mac.getInstance(algorithm, provider);
+                        // Equivalent to mac.init(key) - allowed
+                        mac.init(key, null);
+
+                        try {
+                            mac = Mac.getInstance(algorithm, provider);
+                            mac.init(key, new DummyParameterSpec());
+                            fail();
+                        } catch (InvalidAlgorithmParameterException exception) {
+                            // Expected
+                        }
+                    }
+                }
+            });
+    }
+
+    private SecretKeySpec findAnyKey(String algorithm) {
+        for (String[] entry : testVectors) {
+            if (entry[ALGORITHM_INDEX].equals(algorithm)) {
+                return new SecretKeySpec(decodeHex(entry[KEY_INDEX]), "RawBytes");
+            }
+        }
+        return null;
+    }
+
+    @Test
+    public void anyAlgorithmParametersThrows() throws Exception {
+        Set<String> seen = new HashSet<>();
+        for (String[] entry : testVectors) {
+            String algorithm = entry[ALGORITHM_INDEX];
+            if (!seen.contains(algorithm)) {
+                seen.add(algorithm);
+                byte[] keyBytes = decodeHex(entry[KEY_INDEX]);
+                SecretKeySpec key = new SecretKeySpec(keyBytes, "RawBytes");
+                Mac mac = Mac.getInstance(algorithm);
+                try {
+                    mac.init(key, new IvParameterSpec(keyBytes));
+                    fail(algorithm);
+                } catch (InvalidAlgorithmParameterException exception) {
+                    // Expected
+                }
+            }
+        }
+    }
+
+    private String failMessage(String test, String base, byte[] mac) {
+        return String.format("Test %s\n%s\nActual=  %s", test, base, encodeHex(mac));
+    }
+
+    // Splits a ByteBuffer into an array of NUM_SPLITS ByteBuffers containing the same data.
+    // If input.remaining < NUM_SPLITS then some buffers will be empty, which is fine.
+    private ByteBuffer[] split(ByteBuffer input) {
+        ByteBuffer[] buffers = new ByteBuffer[NUM_SPLITS];
+        int targetSize = (input.remaining() / NUM_SPLITS) + 1;
+        ByteBuffer buffer;
+        for (int i = 0; i < NUM_SPLITS; i++) {
+            int size = Math.min(targetSize, input.remaining());
+            buffer = input.isDirect() ? ByteBuffer.allocateDirect(size) : ByteBuffer.allocate(size);
+            buffers[i] = buffer;
+
+            int savedLimit = input.limit();
+            input.limit(input.position() + size);
+            buffer.put(input);
+            buffer.flip();
+            input.limit(savedLimit);
+        }
+        assertEquals(0, input.remaining());
+        return buffers;
+    }
+
+    private byte[] generateMacUsingUpdate(String algorithm, SecretKeySpec key, byte[] message)
+            throws Exception {
+        Mac mac = getConscryptMac(algorithm, key);
+        mac.update(message);
+        return mac.doFinal();
+    }
+
+    private byte[] generateMacUsingFinal(String algorithm, SecretKeySpec key, byte[] message)
+            throws Exception {
+        Mac mac = getConscryptMac(algorithm, key);
+        return mac.doFinal(message);
+    }
+
+    private byte[] generateMac(String algorithm, SecretKeySpec key, ByteBuffer buffer)
+            throws Exception {
+        return generateMac(algorithm, key, new ByteBuffer[] { buffer });
+    }
+
+    private byte[] generateMac(String algorithm, SecretKeySpec key, ByteBuffer[] buffers)
+            throws Exception {
+        Mac mac = getConscryptMac(algorithm, key);
+        for (ByteBuffer buffer : buffers) {
+            mac.update(buffer);
+        }
+        return mac.doFinal();
+    }
+
+    private byte[] generateReusingMac(String algorithm, byte[] keyBytes, byte[] message)
+            throws Exception {
+        Mac mac = getConscryptMac(algorithm);
+
+        // Mutate the original message and key and calculate a MAC from them
+        byte[] otherKeyBytes = new byte[keyBytes.length];
+        random.nextBytes(otherKeyBytes);
+        SecretKeySpec otherKey = new SecretKeySpec(otherKeyBytes, "RawBytes");
+        byte[] otherMessage = new byte[message.length];
+        random.nextBytes(otherMessage);
+        mac.init(otherKey);
+        mac.doFinal(otherMessage);
+
+        // Then re-use the same Mac with the original key and message
+        SecretKeySpec key = new SecretKeySpec(keyBytes, "RawBytes");
+        mac.reset();
+        mac.init(key);
+        mac.update(message);
+        return mac.doFinal();
+    }
+
+    private Mac getConscryptMac(String algorithm) throws Exception {
+        return getConscryptMac(algorithm, null);
+    }
+
+    private Mac getConscryptMac(String algorithm, SecretKeySpec key) throws Exception {
+        Mac mac = Mac.getInstance(algorithm, conscryptProvider);
+        assertNotNull(mac);
+        if (key != null) {
+            // Provider is not actually chosen until init
+            mac.init(key);
+            assertSame(conscryptProvider, mac.getProvider());
+        }
+        return mac;
+    }
+
+    private List<String[]> readTestVectors() {
+        try {
+            return TestUtils.readCsvResource("crypto/macs.csv");
+
+        } catch (IOException e) {
+            throw new AssertionError("Unable to load MAC test vectors", e);
+        }
+    }
+}
diff --git a/common/src/test/java/org/conscrypt/TrustManagerImplTest.java b/common/src/test/java/org/conscrypt/TrustManagerImplTest.java
index e1cce50..4d6a273 100644
--- a/common/src/test/java/org/conscrypt/TrustManagerImplTest.java
+++ b/common/src/test/java/org/conscrypt/TrustManagerImplTest.java
@@ -21,7 +21,6 @@
 import static org.junit.Assert.fail;
 
 import java.io.IOException;
-import java.lang.reflect.Method;
 import java.security.KeyStore;
 import java.security.Principal;
 import java.security.cert.Certificate;
@@ -30,8 +29,6 @@
 import java.util.Arrays;
 import java.util.List;
 import javax.net.ssl.HandshakeCompletedListener;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLParameters;
 import javax.net.ssl.SSLPeerUnverifiedException;
 import javax.net.ssl.SSLSession;
@@ -39,6 +36,7 @@
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.X509TrustManager;
 import org.conscrypt.java.security.TestKeyStore;
+import org.conscrypt.javax.net.ssl.TestHostnameVerifier;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -137,18 +135,14 @@
         String goodHostname = TestKeyStore.CERT_HOSTNAME;
         String badHostname = "definitelywrong.nopenopenope";
 
-        // The default hostname verifier on OpenJDK rejects all hostnames, so use our own
-        javax.net.ssl.HostnameVerifier oldDefault = HttpsURLConnection.getDefaultHostnameVerifier();
         try {
-            HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier());
-
             SSLParameters params = new SSLParameters();
 
             // Without endpoint identification this should pass despite the mismatched hostname
             params.setEndpointIdentificationAlgorithm(null);
 
             List<X509Certificate> certs = tmi.getTrustedChainForServer(chain, "RSA",
-                new FakeSSLSocket(new FakeSSLSession(badHostname, chain), params));
+                    new FakeSSLSocket(new FakeSSLSession(badHostname, chain), params));
             assertEquals(Arrays.asList(chain), certs);
 
             // Turn on endpoint identification
@@ -156,87 +150,65 @@
 
             try {
                 tmi.getTrustedChainForServer(chain, "RSA",
-                    new FakeSSLSocket(new FakeSSLSession(badHostname, chain), params));
+                        new FakeSSLSocket(new FakeSSLSession(badHostname, chain), params));
                 fail();
             } catch (CertificateException expected) {
             }
 
             certs = tmi.getTrustedChainForServer(chain, "RSA",
-                new FakeSSLSocket(new FakeSSLSession(goodHostname, chain), params));
+                    new FakeSSLSocket(new FakeSSLSession(goodHostname, chain), params));
             assertEquals(Arrays.asList(chain), certs);
 
             // Override the global default hostname verifier with a Conscrypt-specific one that
             // always passes.  Both scenarios should pass.
-            HostnameVerifier alwaysTrue = new HostnameVerifier() {
+            Conscrypt.setHostnameVerifier(tmi, new ConscryptHostnameVerifier() {
                 @Override
-                public boolean verify(String hostname, SSLSession session) {
+                public boolean verify(X509Certificate[] certificates, String s, SSLSession sslSession) {
                     return true;
                 }
-            };
-            HttpsURLConnection.setDefaultHostnameVerifier(alwaysTrue);
+            });
 
             certs = tmi.getTrustedChainForServer(chain, "RSA",
-                new FakeSSLSocket(new FakeSSLSession(badHostname, chain), params));
+                    new FakeSSLSocket(new FakeSSLSession(badHostname, chain), params));
             assertEquals(Arrays.asList(chain), certs);
 
             certs = tmi.getTrustedChainForServer(chain, "RSA",
-                new FakeSSLSocket(new FakeSSLSession(goodHostname, chain), params));
+                    new FakeSSLSocket(new FakeSSLSession(goodHostname, chain), params));
             assertEquals(Arrays.asList(chain), certs);
 
             // Now set an instance-specific verifier on the trust manager.  The bad hostname should
             // fail again.
-            Conscrypt.setHostnameVerifier(tmi, wrapVerifier(new TestHostnameVerifier()));
+            Conscrypt.setHostnameVerifier(tmi, Conscrypt.wrapHostnameVerifier(new TestHostnameVerifier()));
 
             try {
                 tmi.getTrustedChainForServer(chain, "RSA",
-                    new FakeSSLSocket(new FakeSSLSession(badHostname, chain), params));
+                        new FakeSSLSocket(new FakeSSLSession(badHostname, chain), params));
                 fail();
             } catch (CertificateException expected) {
             }
 
             certs = tmi.getTrustedChainForServer(chain, "RSA",
-                new FakeSSLSocket(new FakeSSLSession(goodHostname, chain), params));
+                    new FakeSSLSocket(new FakeSSLSession(goodHostname, chain), params));
             assertEquals(Arrays.asList(chain), certs);
 
             // Remove the instance-specific verifier, and both should pass again.
             Conscrypt.setHostnameVerifier(tmi, null);
 
-            certs = tmi.getTrustedChainForServer(chain, "RSA",
-                new FakeSSLSocket(new FakeSSLSession(badHostname, chain), params));
-            assertEquals(Arrays.asList(chain), certs);
+            try {
+                tmi.getTrustedChainForServer(chain, "RSA",
+                        new FakeSSLSocket(new FakeSSLSession(badHostname, chain), params));
+                fail();
+            } catch (CertificateException expected) {
+            }
 
             certs = tmi.getTrustedChainForServer(chain, "RSA",
-                new FakeSSLSocket(new FakeSSLSession(goodHostname, chain), params));
+                    new FakeSSLSocket(new FakeSSLSession(goodHostname, chain), params));
             assertEquals(Arrays.asList(chain), certs);
         } finally {
             Conscrypt.setDefaultHostnameVerifier(null);
-            HttpsURLConnection.setDefaultHostnameVerifier(oldDefault);
         }
     }
 
-    /*
-     * Wrap a HostnameVerifier in a ConscryptHostnameVerifier.
-     * In the Android platform ConscryptHostnameVerifier is a private API and the interface
-     * definition changed between Android 11 and Android 12.
-     * If an Android 12 Conscrypt module is present then there will also be a (non-public)
-     * method to wrap it with the correct interface.
-     * If an earlier module is present then the interface is the same as in the CTS 11 codebase
-     * and so we can just wrap it directly with an anonymous class.
-     * See also b/195615915
-     */
-    private ConscryptHostnameVerifier wrapVerifier(final HostnameVerifier verifier) throws Exception {
-        Method wrapMethod = TestUtils.findWrapVerifierMethod();
-        if (wrapMethod != null) {
-            return (ConscryptHostnameVerifier) wrapMethod.invoke(null, verifier);
-        }
-        return new ConscryptHostnameVerifier() {
-            @Override
-            public boolean verify(String hostname, SSLSession session) {
-                return  verifier.verify(hostname, session);
-            }
-        };
-    }
-
     private X509TrustManager trustManager(X509Certificate ca) throws Exception {
         KeyStore keyStore = TestKeyStore.createKeyStore();
         keyStore.setCertificateEntry("alias", ca);
@@ -504,8 +476,4 @@
             throw new UnsupportedOperationException();
         }
     }
-
-    private static class TestHostnameVerifier
-        extends org.conscrypt.javax.net.ssl.TestHostnameVerifier
-        implements ConscryptHostnameVerifier {}
 }
diff --git a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestEC.java b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestEC.java
index 4f813a9..938629e 100644
--- a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestEC.java
+++ b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestEC.java
@@ -27,21 +27,20 @@
 import java.util.List;
 import org.junit.ClassRule;
 import org.junit.rules.TestRule;
+import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import tests.util.ServiceTester;
 
 @RunWith(JUnit4.class)
-public class KeyFactoryTestEC extends
-    AbstractKeyFactoryTest<ECPublicKeySpec, ECPrivateKeySpec> {
-
-  // BEGIN Android-Added: Allow access to deprecated BC algorithms.
-  // Allow access to deprecated BC algorithms in this test, so we can ensure they
-  // continue to work
-  @ClassRule
-  public static TestRule enableDeprecatedBCAlgorithmsRule =
-      EnableDeprecatedBouncyCastleAlgorithmsRule.getInstance();
-  // END Android-Added: Allow access to deprecated BC algorithms.
+public class KeyFactoryTestEC extends AbstractKeyFactoryTest<ECPublicKeySpec, ECPrivateKeySpec> {
+    // BEGIN Android-Added: Allow access to deprecated BC algorithms.
+    // Allow access to deprecated BC algorithms in this test, so we can ensure they
+    // continue to work
+    @ClassRule
+    public static TestRule enableDeprecatedBCAlgorithmsRule =
+        EnableDeprecatedBouncyCastleAlgorithmsRule.getInstance();
+    // END Android-Added: Allow access to deprecated BC algorithms.
 
   public KeyFactoryTestEC() {
     super("EC", ECPublicKeySpec.class, ECPrivateKeySpec.class);
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 1d69ae6..6724535 100644
--- a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java
+++ b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java
@@ -19,7 +19,6 @@
 
 import java.security.KeyFactory;
 import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.Provider;
 import java.security.PublicKey;
@@ -42,7 +41,6 @@
 @RunWith(JUnit4.class)
 public class KeyFactoryTestRSA extends
         AbstractKeyFactoryTest<RSAPublicKeySpec, RSAPrivateKeySpec> {
-
     // BEGIN Android-Added: Allow access to deprecated BC algorithms.
     // Allow access to deprecated BC algorithms in this test, so we can ensure they
     // continue to work
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 c09193b..957be7a 100644
--- a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSACrt.java
+++ b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSACrt.java
@@ -18,9 +18,6 @@
 import java.security.KeyPair;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPublicKeySpec;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import tests.util.ServiceTester;
diff --git a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSACustom.java b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSACustom.java
index 2e30029..847b886 100644
--- a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSACustom.java
+++ b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSACustom.java
@@ -17,16 +17,11 @@
 
 import java.security.KeyPair;
 import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.RSAPrivateKeySpec;
 import java.security.spec.RSAPublicKeySpec;
 import java.util.Arrays;
 import java.util.List;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import tests.util.ServiceTester;
diff --git a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestXDH.java b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestXDH.java
new file mode 100644
index 0000000..1f6b5fd
--- /dev/null
+++ b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestXDH.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 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.java.security;
+
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import tests.util.ServiceTester;
+
+@RunWith(JUnit4.class)
+public class KeyFactoryTestXDH extends
+    AbstractKeyFactoryTest<X509EncodedKeySpec, PKCS8EncodedKeySpec> {
+
+  public KeyFactoryTestXDH() {
+    super("XDH", X509EncodedKeySpec.class, PKCS8EncodedKeySpec.class);
+  }
+
+  @Override
+  protected void check(KeyPair keyPair) throws Exception {
+    new KeyAgreementHelper("XDH").test(keyPair);
+  }
+
+  @Override
+  protected ServiceTester customizeTester(ServiceTester tester) {
+    // TODO: fix this test when Conscrypt's XDH keys can inherit from XECPublicKey and XECPrivateKey
+    return tester.skipProvider("SunEC");
+  }
+
+  @Override
+  protected List<KeyPair> getKeys() throws NoSuchAlgorithmException, InvalidKeySpecException {
+    return Arrays.asList(
+        new KeyPair(
+            DefaultKeys.getPublicKey("XDH"),
+            DefaultKeys.getPrivateKey("XDH")
+        ),
+        new KeyPair(
+            new TestPublicKey(DefaultKeys.getPublicKey("XDH")),
+            new TestPrivateKey(DefaultKeys.getPrivateKey("XDH"))
+        )
+    );
+  }
+}
diff --git a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java
index 62bcc58..b7b41ef 100644
--- a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java
+++ b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java
@@ -315,6 +315,20 @@
                     // so ignore it.
                     continue;
                 }
+                if ("XDH".equals(k.getAlgorithm())
+                        && "SunEC".equalsIgnoreCase(p.getName())
+                        && "11".equals(System.getProperty("java.specification.version"))) {
+                    // SunEC in OpenJDK 11 has a bug where the format specified in RFC 8410
+                    // Section 7. It uses a single OCTET STRING to represent the key instead
+                    // of an OCTET STRING inside of an OCTET STRING as defined in the RFC:
+                    // ("For the keys defined in this document, the private key is always an
+                    //   opaque byte sequence.  The ASN.1 type CurvePrivateKey is defined in
+                    //   this document to hold the byte sequence.  Thus, when encoding a
+                    //   OneAsymmetricKey object, the private key is wrapped in a
+                    //   CurvePrivateKey object and wrapped by the OCTET STRING of the
+                    //   "privateKey" field.")
+                    continue;
+                }
 
                 if ("PKCS#8".equals(k.getFormat())) {
                     PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encoded);
diff --git a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTestRSA.java b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTestRSA.java
index 6898433..035000f 100644
--- a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTestRSA.java
+++ b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTestRSA.java
@@ -21,10 +21,7 @@
 @RunWith(JUnit4.class)
 public class KeyPairGeneratorTestRSA extends AbstractKeyPairGeneratorTest {
 
-    @SuppressWarnings("unchecked")
     public KeyPairGeneratorTestRSA() {
         super("RSA", new CipherAsymmetricCryptHelper("RSA"));
     }
-
 }
-
diff --git a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTestXDH.java b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTestXDH.java
new file mode 100644
index 0000000..020be58
--- /dev/null
+++ b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTestXDH.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2009 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.java.security;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class KeyPairGeneratorTestXDH extends AbstractKeyPairGeneratorTest {
+
+    public KeyPairGeneratorTestXDH() {
+        super("XDH", new KeyAgreementHelper("XDH"));
+    }
+
+    @Override
+    protected int getKeySize() {
+        return 255;
+    }
+}
diff --git a/common/src/test/java/org/conscrypt/java/security/SignatureTest.java b/common/src/test/java/org/conscrypt/java/security/SignatureTest.java
index 177864d..bba0863 100644
--- a/common/src/test/java/org/conscrypt/java/security/SignatureTest.java
+++ b/common/src/test/java/org/conscrypt/java/security/SignatureTest.java
@@ -111,6 +111,10 @@
             // https://bugs.openjdk.java.net/browse/JDK-8044554), but skip verifying it all
             // the same.
             .skipProvider("SunPKCS11-NSS")
+            // We don't have code to generate key pairs for these yet.
+            .skipAlgorithm("Ed448")
+            .skipAlgorithm("Ed25519")
+            .skipAlgorithm("EdDSA")
             .run(new ServiceTester.Test() {
                 @Override
                 public void test(Provider provider, String algorithm) throws Exception {
@@ -2934,7 +2938,7 @@
      * randomized, so this won't be the exact signature you'll get out of
      * another signing operation unless you use a fixed RNG.
      */
-    public static final byte[] SHA1withDSA_Vector2Signature = new byte[] {
+    private static final byte[] SHA1withDSA_Vector2Signature = new byte[] {
         (byte) 0x30, (byte) 0x2d, (byte) 0x02, (byte) 0x15, (byte) 0x00, (byte) 0x88, (byte) 0xef, (byte) 0xac,
         (byte) 0x2b, (byte) 0x8b, (byte) 0xe2, (byte) 0x61, (byte) 0xc6, (byte) 0x2b, (byte) 0xea, (byte) 0xd5,
         (byte) 0x96, (byte) 0xbc, (byte) 0xb0, (byte) 0xa1, (byte) 0x30, (byte) 0x0c, (byte) 0x1f, (byte) 0xed,
@@ -2948,7 +2952,7 @@
      * randomized, so this won't be the exact signature you'll get out of
      * another signing operation unless you use a fixed RNG.
      */
-    public static final byte[] SHA224withDSA_Vector2Signature = new byte[] {
+    private static final byte[] SHA224withDSA_Vector2Signature = new byte[] {
         (byte) 0x30, (byte) 0x2D, (byte) 0x02, (byte) 0x15, (byte) 0x00, (byte) 0xAD, (byte) 0xE5, (byte) 0x6D,
         (byte) 0xF5, (byte) 0x11, (byte) 0x8D, (byte) 0x2E, (byte) 0x62, (byte) 0x5D, (byte) 0x98, (byte) 0x8A,
         (byte) 0xC4, (byte) 0x88, (byte) 0x7E, (byte) 0xE6, (byte) 0xA3, (byte) 0x44, (byte) 0x99, (byte) 0xEF,
@@ -2962,7 +2966,7 @@
      * randomized, so this won't be the exact signature you'll get out of
      * another signing operation unless you use a fixed RNG.
      */
-    public static final byte[] SHA256withDSA_Vector2Signature = new byte[] {
+    private static final byte[] SHA256withDSA_Vector2Signature = new byte[] {
         (byte) 0x30, (byte) 0x2D, (byte) 0x02, (byte) 0x14, (byte) 0x0A, (byte) 0xB1, (byte) 0x74, (byte) 0x45,
         (byte) 0xE1, (byte) 0x63, (byte) 0x43, (byte) 0x68, (byte) 0x65, (byte) 0xBC, (byte) 0xCA, (byte) 0x45,
         (byte) 0x27, (byte) 0x11, (byte) 0x4D, (byte) 0x52, (byte) 0xFB, (byte) 0x22, (byte) 0x93, (byte) 0xDD,
diff --git a/common/src/test/java/org/conscrypt/java/security/cert/CertificateFactoryTest.java b/common/src/test/java/org/conscrypt/java/security/cert/CertificateFactoryTest.java
index 94a7114..b5da74a 100644
--- a/common/src/test/java/org/conscrypt/java/security/cert/CertificateFactoryTest.java
+++ b/common/src/test/java/org/conscrypt/java/security/cert/CertificateFactoryTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -49,6 +50,7 @@
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Date;
 import java.util.GregorianCalendar;
 import java.util.Iterator;
@@ -205,6 +207,48 @@
         + "zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6z5nRUP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqA"
         + "ibAxWEEHXw==";
 
+    // Generated with openssl crl2pkcs7 -nocrl -certfile cert.pem
+    private static final String VALID_CERTIFICATE_PKCS7_PEM = "-----BEGIN PKCS7-----\n"
+            + "MIIDUgYJKoZIhvcNAQcCoIIDQzCCAz8CAQExADALBgkqhkiG9w0BBwGgggMlMIID\n"
+            + "ITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBMMQsw\n"
+            + "CQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRk\n"
+            + "LjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0xMTEy\n"
+            + "MTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw\n"
+            + "FAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcwFQYD\n"
+            + "VQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\n"
+            + "6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jNgtXj\n"
+            + "9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L05vu\n"
+            + "uWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAMBgNV\n"
+            + "HRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3RlLmNv\n"
+            + "bS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUFBwMC\n"
+            + "BglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRwOi8v\n"
+            + "b2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0ZS5j\n"
+            + "b20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUFAAOB\n"
+            + "gQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5u2ON\n"
+            + "gJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6z5nR\n"
+            + "UP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHX6EAMQA=\n"
+            + "-----END PKCS7-----\n";
+
+    private static final String VALID_CERTIFICATE_PKCS7_DER_BASE64 =
+            "MIIDUgYJKoZIhvcNAQcCoIIDQzCCAz8CAQExADALBgkqhkiG9w0BBwGgggMlMIID"
+            + "ITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBMMQsw"
+            + "CQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRk"
+            + "LjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0xMTEy"
+            + "MTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw"
+            + "FAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcwFQYD"
+            + "VQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA"
+            + "6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jNgtXj"
+            + "9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L05vu"
+            + "uWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAMBgNV"
+            + "HRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3RlLmNv"
+            + "bS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUFBwMC"
+            + "BglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRwOi8v"
+            + "b2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0ZS5j"
+            + "b20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUFAAOB"
+            + "gQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5u2ON"
+            + "gJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6z5nR"
+            + "UP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHX6EAMQA=";
+
     private static final String VALID_CRL_PEM =
         "-----BEGIN X509 CRL-----\n"
             + "MIIBUTCBuwIBATANBgkqhkiG9w0BAQsFADBVMQswCQYDVQQGEwJHQjEkMCIGA1UE\n"
@@ -239,6 +283,30 @@
             + "CCHWAw8WN9XSJ4nGxdRiacG/5vEIx00ICUGCeGcnqWsSnFtagDtvry2c4MMexbSP"
             + "nDN0LLg=";
 
+    // Generated with openssl crl2pkcs7 -in crl.pem
+    private static final String VALID_CRL_PKCS7_PEM = "-----BEGIN PKCS7-----\n"
+            + "MIIBggYJKoZIhvcNAQcCoIIBczCCAW8CAQExADALBgkqhkiG9w0BBwGgAKGCAVUw\n"
+            + "ggFRMIG7AgEBMA0GCSqGSIb3DQEBCwUAMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQK\n"
+            + "ExtDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAw\n"
+            + "DgYDVQQHEwdFcncgV2VuFw0xOTA4MDcxMDI3MTBaFw0xOTA5MDYxMDI3MTBaMCIw\n"
+            + "IAIBBxcNMTkwODA3MTAyNjU0WjAMMAoGA1UdFQQDCgEBoA4wDDAKBgNVHRQEAwIB\n"
+            + "AjANBgkqhkiG9w0BAQsFAAOBgQDMX8MuIi9kNfgWlKM0KfApFuWeEktnU00EAfFx\n"
+            + "Ft8Vjemyhu9sYY6PHMJBb/TeCSeAblWtJ91U4syZAOsDGkqp5ioUOPQcB6da6BsI\n"
+            + "IdYDDxY31dInicbF1GJpwb/m8QjHTQgJQYJ4ZyepaxKcW1qAO2+vLZzgwx7FtI+c\n"
+            + "M3QsuDEA\n"
+            + "-----END PKCS7-----\n";
+
+    private static final String VALID_CRL_PKCS7_DER_BASE64 =
+            "MIIBggYJKoZIhvcNAQcCoIIBczCCAW8CAQExADALBgkqhkiG9w0BBwGgAKGCAVUw"
+            + "ggFRMIG7AgEBMA0GCSqGSIb3DQEBCwUAMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQK"
+            + "ExtDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAw"
+            + "DgYDVQQHEwdFcncgV2VuFw0xOTA4MDcxMDI3MTBaFw0xOTA5MDYxMDI3MTBaMCIw"
+            + "IAIBBxcNMTkwODA3MTAyNjU0WjAMMAoGA1UdFQQDCgEBoA4wDDAKBgNVHRQEAwIB"
+            + "AjANBgkqhkiG9w0BAQsFAAOBgQDMX8MuIi9kNfgWlKM0KfApFuWeEktnU00EAfFx"
+            + "Ft8Vjemyhu9sYY6PHMJBb/TeCSeAblWtJ91U4syZAOsDGkqp5ioUOPQcB6da6BsI"
+            + "IdYDDxY31dInicbF1GJpwb/m8QjHTQgJQYJ4ZyepaxKcW1qAO2+vLZzgwx7FtI+c"
+            + "M3QsuDEA";
+
     private static final String INVALID_CRL_PEM =
         "-----BEGIN X509 CRL-----\n"
             + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
@@ -277,6 +345,15 @@
             + "AAAAAAAA\n"
             + "-----END X509 CRL-----\n";
 
+    // PKCS#7 file containing a small certificate. This input is small enough to use a two-byte
+    // length prefix.
+    private static final String SMALL_PKCS7_DER_BASE64 =
+            "MIHrBgkqhkiG9w0BBwKggd0wgdoCAQExADALBgkqhkiG9w0BBwGggcEwgb4wcgIB"
+            + "ADAFBgMrZXAwDDEKMAgGA1UEAxMBQTAeFw0xNDA0MjMyMzIxNTdaFw0xNDA1MjMy"
+            + "MzIxNTdaMAwxCjAIBgNVBAMTAUEwKjAFBgMrZXADIQDXWpgBgrEKt9VL/tPJZAc6"
+            + "DuFy89qmIyWvAhpo9wdRGjAFBgMrZXADQQBuCzqji8VP9xU8mHEMjXGChX7YP5J6"
+            + "64UyVKHKH9Z1u4wEbB8dJ3ScaWSLr+VHVKUhsrvcdCelnXRrrSD7xWALoQAxAA==";
+
     @Test
     public void test_generateCertificate() throws Exception {
         ServiceTester.test("CertificateFactory")
@@ -298,22 +375,56 @@
     }
 
     private void test_generateCertificate(CertificateFactory cf) throws Exception {
+        Certificate cert;
         {
             byte[] valid = VALID_CERTIFICATE_PEM.getBytes(Charset.defaultCharset());
             Certificate c = cf.generateCertificate(new ByteArrayInputStream(valid));
             assertNotNull(c);
+            cert = c;
         }
 
         {
             byte[] valid = VALID_CERTIFICATE_PEM_CRLF.getBytes(Charset.defaultCharset());
             Certificate c = cf.generateCertificate(new ByteArrayInputStream(valid));
             assertNotNull(c);
+            assertEquals(c, cert);
         }
 
         {
             byte[] valid = TestUtils.decodeBase64(VALID_CERTIFICATE_DER_BASE64);
             Certificate c = cf.generateCertificate(new ByteArrayInputStream(valid));
             assertNotNull(c);
+            assertEquals(c, cert);
+        }
+
+        // The RI only supports PKCS#7 blobs with generateCertificates, not
+        // generateCertificate. See https://github.com/google/conscrypt/issues/987
+        if (!StandardNames.IS_RI) {
+            byte[] valid = TestUtils.decodeBase64(VALID_CERTIFICATE_PKCS7_DER_BASE64);
+            Certificate c = cf.generateCertificate(new ByteArrayInputStream(valid));
+            assertNotNull(c);
+            assertEquals(c, cert);
+        }
+
+        {
+            byte[] valid = VALID_CERTIFICATE_PKCS7_PEM.getBytes(Charset.defaultCharset());
+            Collection<? extends Certificate> cs = cf.generateCertificates(new ByteArrayInputStream(valid));
+            assertEquals(1, cs.size());
+            assertEquals(cs.iterator().next(), cert);
+        }
+
+        {
+            byte[] valid = TestUtils.decodeBase64(VALID_CERTIFICATE_PKCS7_DER_BASE64);
+            Collection<? extends Certificate> cs = cf.generateCertificates(new ByteArrayInputStream(valid));
+            assertEquals(1, cs.size());
+            assertEquals(cs.iterator().next(), cert);
+        }
+
+        {
+            byte[] valid = TestUtils.decodeBase64(SMALL_PKCS7_DER_BASE64);
+            Collection<? extends Certificate> cs = cf.generateCertificates(new ByteArrayInputStream(valid));
+            assertEquals(1, cs.size());
+            assertNotNull(cs.iterator().next());
         }
 
         try {
@@ -647,7 +758,7 @@
             assertEquals(providerName, Arrays.toString(pathFromList.getEncoded(encoding)),
                     Arrays.toString(encoded));
         }
-        assertFalse(providerName, Arrays.toString(encoded).equals(Arrays.toString(encodedCopy)));
+        assertNotEquals(providerName, Arrays.toString(encoded), Arrays.toString(encodedCopy));
 
         encodedCopy[0] ^= (byte) 0xFF;
         assertEquals(providerName, Arrays.toString(encoded), Arrays.toString(encodedCopy));
@@ -685,7 +796,7 @@
         Object output = ois.readObject();
         assertTrue(providerName, output instanceof CertPath);
 
-        assertEquals(providerName, actualPath, (CertPath) output);
+        assertEquals(providerName, actualPath, output);
     }
 
     public static class KeyHolder {
@@ -783,12 +894,33 @@
         assertNotNull(c);
 
         valid = VALID_CRL_PEM_CRLF.getBytes(Charset.defaultCharset());
-        c = cf.generateCRL(new ByteArrayInputStream(valid));
-        assertNotNull(c);
+        CRL c2 = cf.generateCRL(new ByteArrayInputStream(valid));
+        assertNotNull(c2);
+        assertEquals(c, c2);
 
         valid = TestUtils.decodeBase64(VALID_CRL_DER_BASE64);
-        c = cf.generateCRL(new ByteArrayInputStream(valid));
+        c2 = cf.generateCRL(new ByteArrayInputStream(valid));
         assertNotNull(c);
+        assertEquals(c, c2);
+
+        // The RI only supports PKCS#7 with generateCRLs, not generateCRL.
+        // See https://github.com/google/conscrypt/issues/987
+        if (!StandardNames.IS_RI) {
+            valid = TestUtils.decodeBase64(VALID_CRL_PKCS7_DER_BASE64);
+            c2 = cf.generateCRL(new ByteArrayInputStream(valid));
+            assertNotNull(c);
+            assertEquals(c, c2);
+        }
+
+        valid = TestUtils.decodeBase64(VALID_CRL_PKCS7_DER_BASE64);
+        Collection<? extends CRL> crls = cf.generateCRLs(new ByteArrayInputStream(valid));
+        assertEquals(1, crls.size());
+        assertEquals(c, crls.iterator().next());
+
+        valid = VALID_CRL_PKCS7_PEM.getBytes(Charset.defaultCharset());
+        crls = cf.generateCRLs(new ByteArrayInputStream(valid));
+        assertEquals(1, crls.size());
+        assertEquals(c, crls.iterator().next());
 
         try {
             byte[] invalid = INVALID_CRL_PEM.getBytes(Charset.defaultCharset());
diff --git a/common/src/test/java/org/conscrypt/java/security/cert/X509CRLTest.java b/common/src/test/java/org/conscrypt/java/security/cert/X509CRLTest.java
index 6559477..50b1688 100644
--- a/common/src/test/java/org/conscrypt/java/security/cert/X509CRLTest.java
+++ b/common/src/test/java/org/conscrypt/java/security/cert/X509CRLTest.java
@@ -16,6 +16,7 @@
 
 package org.conscrypt.java.security.cert;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -33,6 +34,7 @@
 import java.security.cert.X509Certificate;
 import java.util.Collections;
 import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule;
+import org.conscrypt.TestUtils;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -102,6 +104,24 @@
             + "nDN0LLg=\n"
             + "-----END X509 CRL-----\n";
 
+    private static final String CRL_TBS_BASE64 =
+            "MIG7AgEBMA0GCSqGSIb3DQEBCwUAMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtD"
+            + "ZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYD"
+            + "VQQHEwdFcncgV2VuFw0xOTA4MDcxMDI3MTBaFw0xOTA5MDYxMDI3MTBaMCIwIAIB"
+            + "BxcNMTkwODA3MTAyNjU0WjAMMAoGA1UdFQQDCgEBoA4wDDAKBgNVHRQEAwIBAg==";
+
+    private static final String UNKNOWN_SIGNATURE_OID =
+        "-----BEGIN X509 CRL-----\n"
+            + "MIIBVzCBvgIBATAQBgwqhkiG9xIEAYS3CQIFADBVMQswCQYDVQQGEwJHQjEkMCIG\n"
+            + "A1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVXYWxl\n"
+            + "czEQMA4GA1UEBxMHRXJ3IFdlbhcNMTkwODA3MTAyNzEwWhcNMTkwOTA2MTAyNzEw\n"
+            + "WjAiMCACAQcXDTE5MDgwNzEwMjY1NFowDDAKBgNVHRUEAwoBAaAOMAwwCgYDVR0U\n"
+            + "BAMCAQIwEAYMKoZIhvcSBAGEtwkCBQADgYEAzF/DLiIvZDX4FpSjNCnwKRblnhJL\n"
+            + "Z1NNBAHxcRbfFY3psobvbGGOjxzCQW/03gkngG5VrSfdVOLMmQDrAxpKqeYqFDj0\n"
+            + "HAenWugbCCHWAw8WN9XSJ4nGxdRiacG/5vEIx00ICUGCeGcnqWsSnFtagDtvry2c\n"
+            + "4MMexbSPnDN0LLg=\n"
+            + "-----END X509 CRL-----\n";
+
     @Test
     public void testCrl() throws Exception {
         ServiceTester.test("CertificateFactory")
@@ -126,6 +146,9 @@
                     } catch (SignatureException expected) {
                     }
 
+                    byte[] expectedTBSCertList = TestUtils.decodeBase64(CRL_TBS_BASE64);
+                    assertArrayEquals(expectedTBSCertList, crl.getTBSCertList());
+
                     assertTrue(crl.isRevoked(revoked));
                     X509CRLEntry entry = crl.getRevokedCertificate(revoked);
                     assertEquals(CRLReason.KEY_COMPROMISE, entry.getRevocationReason());
@@ -140,4 +163,19 @@
                 }
             });
     }
+
+    @Test
+    public void testUnknownSigAlgOID() throws Exception {
+        ServiceTester.test("CertificateFactory")
+            .withAlgorithm("X509")
+            .run(new ServiceTester.Test() {
+                @Override
+                public void test(Provider p, String algorithm) throws Exception {
+                    CertificateFactory cf = CertificateFactory.getInstance("X509", p);
+                    X509CRL crl = (X509CRL) cf.generateCRL(new ByteArrayInputStream(
+                            UNKNOWN_SIGNATURE_OID.getBytes(StandardCharsets.US_ASCII)));
+                    assertEquals("1.2.840.113554.4.1.72585.2", crl.getSigAlgOID());
+                }
+            });
+    }
 }
diff --git a/common/src/test/java/org/conscrypt/java/security/cert/X509CertificateTest.java b/common/src/test/java/org/conscrypt/java/security/cert/X509CertificateTest.java
index 9257315..beddeb3 100644
--- a/common/src/test/java/org/conscrypt/java/security/cert/X509CertificateTest.java
+++ b/common/src/test/java/org/conscrypt/java/security/cert/X509CertificateTest.java
@@ -16,24 +16,38 @@
 
 package org.conscrypt.java.security.cert;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
 import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
 import java.nio.charset.Charset;
 import java.security.InvalidKeyException;
 import java.security.Provider;
-import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.CertificateParsingException;
 import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+import javax.security.auth.x500.X500Principal;
+import org.conscrypt.TestUtils;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import tests.util.Pair;
 import tests.util.ServiceTester;
 
 @RunWith(JUnit4.class)
@@ -135,6 +149,248 @@
             + "Qhy0YgIgYWr0qSCLqxUQv3oQHMUpSmfHtP0Pwvb3DbbH6lY7TkI=\n"
             + "-----END CERTIFICATE-----\n";
 
+    /**
+     * This cert is signed with OID 1.2.840.113554.4.1.72585.2 instead of a
+     * standard one.
+     */
+    private static final String UNKNOWN_SIGNATURE_OID =
+            "-----BEGIN CERTIFICATE-----\n"
+            + "MIIB2TCCAXugAwIBAgIJANlMBNpJfb/rMA4GDCqGSIb3EgQBhLcJAjBFMQswCQYD\n"
+            + "VQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQg\n"
+            + "V2lkZ2l0cyBQdHkgTHRkMB4XDTE0MDQyMzIzMjE1N1oXDTE0MDUyMzIzMjE1N1ow\n"
+            + "RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu\n"
+            + "dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA\n"
+            + "BOYraeK/ZZ+Xvi8eDZSKTNWXa7epHg1G+92pqR6d3LpaAefWl6gKGPnDxKMeVuJ8\n"
+            + "g0jbFhoc9R1+8ZQtS89yIsGjUDBOMB0GA1UdDgQWBBSrhNKsq5Xwgk4WeAdVV1/k\n"
+            + "Jo2C0TAfBgNVHSMEGDAWgBSrhNKsq5Xwgk4WeAdVV1/kJo2C0TAMBgNVHRMEBTAD\n"
+            + "AQH/MA4GDCqGSIb3EgQBhLcJAgNIADBFAiEA8qA1XlE6NsOCeZvuJ1CFjnAGdJVX\n"
+            + "0il0APS+FYddxAcCIHweeRRqIYPwenRoeV8UmZpotPHLnhVe5h8yUmFedckU\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /**
+     * This is an X.509v1 certificatea, so most fields are missing. It exists to test accessors
+     * correctly handle the lack of fields. It was constructed by hand, so the signature itself is
+     * invalid.
+     */
+    private static final String X509V1_CERT = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBGjCBwgIJANlMBNpJfb/rMAkGByqGSM49BAEwFjEUMBIGA1UEAwwLVGVzdCBJ\n"
+            + "c3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjAXMRUwEwYDVQQD\n"
+            + "DAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATmK2niv2Wf\n"
+            + "l74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI2xYaHPUd\n"
+            + "fvGULUvPciLBMAkGByqGSM49BAEDSAAwRQIhAPKgNV5ROjbDgnmb7idQhY5wBnSV\n"
+            + "V9IpdAD0vhWHXcQHAiB8HnkUaiGD8Hp0aHlfFJmaaLTxy54VXuYfMlJhXnXJFA==\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /**
+     * This is a certificate with many extensions filled it. It exists to test accessors correctly
+     * report fields. It was constructed by hand, so the signature itself is invalid. Add more
+     * fields as necessary with https://github.com/google/der-ascii.
+     */
+    private static final String MANY_EXTENSIONS = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIEADCCAuigAwIBAgIJALW2IrlaBKUhMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV\n"
+            + "BAMMC1Rlc3QgSXNzdWVyMB4XDTE2MDcwOTA0MzgwOVoXDTE2MDgwODA0MzgwOVow\n"
+            + "FzEVMBMGA1UEAwwMVGVzdCBTdWJqZWN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n"
+            + "MIIBCgKCAQEAugvahBkSAUF1fC49vb1bvlPrcl80kop1iLpiuYoz4Qptwy57+EWs\n"
+            + "sZBcHprZ5BkWf6PeGZ7F5AX1PyJbGHZLqvMCvViP6pd4MFox/igESISEHEixoiXC\n"
+            + "zepBrhtp5UQSjHD4D4hKtgdMgVxX+LRtwgW3mnu/vBu7rzpr/DS8io99p3lqZ1Ak\n"
+            + "y+aNlcMj6MYy8U+YFEevb/V0lRY9oqwmW7BHnXikm/vi6sjIS350U8zb/mRzYeIs\n"
+            + "2R65LUduTL50+UMgat9ocewI2dv8aO9Dph+8NdGtg8LFYyTTHcUxJoMr1PTOgnmE\n"
+            + "T19WJH4PrFwk7ZE1QJQQ1L4iKmPeQistuQIDAQABgQIEoIICA1CjggFGMIIBQjAP\n"
+            + "BgNVHRMECDAGAQH/AgEKMCEGA1UdJQQaMBgGCCsGAQUFBwMBBgwqhkiG9xIEAYS3\n"
+            + "CQIwfwYDVR0RBHgwdoETc3ViamVjdEBleGFtcGxlLmNvbYITc3ViamVjdC5leGFt\n"
+            + "cGxlLmNvbaQZMBcxFTATBgNVBAMMDFRlc3QgU3ViamVjdIYbaHR0cHM6Ly9leGFt\n"
+            + "cGxlLmNvbS9zdWJqZWN0hwR/AAABiAwqhkiG9xIEAYS3CQIwewYDVR0SBHQwcoES\n"
+            + "aXNzdWVyQGV4YW1wbGUuY29tghJpc3N1ZXIuZXhhbXBsZS5jb22kGDAWMRQwEgYD\n"
+            + "VQQDDAtUZXN0IElzc3VlcoYaaHR0cHM6Ly9leGFtcGxlLmNvbS9pc3N1ZXKHBH8A\n"
+            + "AAGIDCqGSIb3EgQBhLcJAjAOBgNVHQ8BAf8EBAMCBaAwDQYJKoZIhvcNAQELBQAD\n"
+            + "ggEBAD7Jg68SArYWlcoHfZAB90Pmyrt5H6D8LRi+W2Ri1fBNxREELnezWJ2scjl4\n"
+            + "UMcsKYp4Pi950gVN+62IgrImcCNvtb5I1Cfy/MNNur9ffas6X334D0hYVIQTePyF\n"
+            + "k3umI+2mJQrtZZyMPIKSY/sYGQHhGGX6wGK+GO/og0PQk/Vu6D+GU2XRnDV0YZg1\n"
+            + "lsAsHd21XryK6fDmNkEMwbIWrts4xc7scRrGHWy+iMf6/7p/Ak/SIicM4XSwmlQ8\n"
+            + "pPxAZPr+E2LoVd9pMpWUwpW2UbtO5wsGTrY5sO45tFNN/y+jtUheB1C2ijObG/tX\n"
+            + "ELaiyCdM+S/waeuv0MXtI4xnn1A=\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /**
+     * This is a certificate whose basicConstraints extension marks it as a CA, with no pathlen
+     * constraint.
+     */
+    private static final String BASIC_CONSTRAINTS_NO_PATHLEN = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBMzCB2qADAgECAgkA2UwE2kl9v+swCgYIKoZIzj0EAwIwFjEUMBIGA1UEAwwL\n"
+            + "VGVzdCBJc3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjAXMRUw\n"
+            + "EwYDVQQDDAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATm\n"
+            + "K2niv2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI\n"
+            + "2xYaHPUdfvGULUvPciLBoxAwDjAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMCA0gA\n"
+            + "MEUCIQDyoDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13EBwIgfB55FGohg/B6\n"
+            + "dGh5XxSZmmi08cueFV7mHzJSYV51yRQ=\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /**
+     * This is a certificate whose basicConstraints extension marks it as a CA with a pathlen
+     * constraint of zero.
+     */
+    private static final String BASIC_CONSTRAINTS_PATHLEN_0 = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBNjCB3aADAgECAgkA2UwE2kl9v+swCgYIKoZIzj0EAwIwFjEUMBIGA1UEAwwL\n"
+            + "VGVzdCBJc3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjAXMRUw\n"
+            + "EwYDVQQDDAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATm\n"
+            + "K2niv2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI\n"
+            + "2xYaHPUdfvGULUvPciLBoxMwETAPBgNVHRMECDAGAQH/AgEAMAoGCCqGSM49BAMC\n"
+            + "A0gAMEUCIQDyoDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13EBwIgfB55FGoh\n"
+            + "g/B6dGh5XxSZmmi08cueFV7mHzJSYV51yRQ=\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /**
+     * This is a certificate whose basicConstraints extension marks it as a leaf certificate.
+     */
+    private static final String BASIC_CONSTRAINTS_LEAF = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBMDCB16ADAgECAgkA2UwE2kl9v+swCgYIKoZIzj0EAwIwFjEUMBIGA1UEAwwL\n"
+            + "VGVzdCBJc3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjAXMRUw\n"
+            + "EwYDVQQDDAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATm\n"
+            + "K2niv2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI\n"
+            + "2xYaHPUdfvGULUvPciLBow0wCzAJBgNVHRMEAjAAMAoGCCqGSM49BAMCA0gAMEUC\n"
+            + "IQDyoDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13EBwIgfB55FGohg/B6dGh5\n"
+            + "XxSZmmi08cueFV7mHzJSYV51yRQ=\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /**
+     * This is a certificate with a pathlen constraint of 10, but there is an unrelated invalid
+     * subjectAltNames extension.
+     */
+    private static final String BASIC_CONSTRAINTS_PATHLEN_10_BAD_SAN =
+            "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBRjCB7aADAgECAgkA2UwE2kl9v+swCgYIKoZIzj0EAwIwFjEUMBIGA1UEAwwL\n"
+            + "VGVzdCBJc3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjAXMRUw\n"
+            + "EwYDVQQDDAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATm\n"
+            + "K2niv2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI\n"
+            + "2xYaHPUdfvGULUvPciLBoyMwITAPBgNVHRMECDAGAQH/AgEKMA4GA1UdEQQHSU5W\n"
+            + "QUxJRDAKBggqhkjOPQQDAgNIADBFAiEA8qA1XlE6NsOCeZvuJ1CFjnAGdJVX0il0\n"
+            + "APS+FYddxAcCIHweeRRqIYPwenRoeV8UmZpotPHLnhVe5h8yUmFedckU\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /**
+     * This is a certificate whose keyUsage extension has more than nine bits. The getKeyUsage()
+     * method internally rounds up to nine bits, so this tests what happens when it does not need to
+     * round.
+     */
+    private static final String LARGE_KEY_USAGE = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBNjCB3aADAgECAgkA2UwE2kl9v+swCgYIKoZIzj0EAwIwFjEUMBIGA1UEAwwL\n"
+            + "VGVzdCBJc3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjAXMRUw\n"
+            + "EwYDVQQDDAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATm\n"
+            + "K2niv2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI\n"
+            + "2xYaHPUdfvGULUvPciLBoxMwETAPBgNVHQ8BAf8EBQMDBaAAMAoGCCqGSM49BAMC\n"
+            + "A0gAMEUCIQDyoDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13EBwIgfB55FGoh\n"
+            + "g/B6dGh5XxSZmmi08cueFV7mHzJSYV51yRQ=\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /*
+     * OpenSSLX509Certificate needs to compensate for OpenSSL's AlgorithmIdentifier representation
+     * by re-encoding the parameter field. Test this behaves correctly against a variety of
+     * different parameter types.
+     */
+    private static final String SIGALG_NO_PARAMETER = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBKTCBzKADAgECAgkA2UwE2kl9v+swDgYMKoZIhvcSBAGEtwkCMBYxFDASBgNV\n"
+            + "BAMMC1Rlc3QgSXNzdWVyMB4XDTE0MDQyMzIzMjE1N1oXDTE0MDUyMzIzMjE1N1ow\n"
+            + "FzEVMBMGA1UEAwwMVGVzdCBTdWJqZWN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD\n"
+            + "QgAE5itp4r9ln5e+Lx4NlIpM1Zdrt6keDUb73ampHp3culoB59aXqAoY+cPEox5W\n"
+            + "4nyDSNsWGhz1HX7xlC1Lz3IiwTAOBgwqhkiG9xIEAYS3CQIDSAAwRQIhAPKgNV5R\n"
+            + "OjbDgnmb7idQhY5wBnSVV9IpdAD0vhWHXcQHAiB8HnkUaiGD8Hp0aHlfFJmaaLTx\n"
+            + "y54VXuYfMlJhXnXJFA==\n"
+            + "-----END CERTIFICATE-----\n";
+    private static final String SIGALG_NULL_PARAMETER = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBLTCBzqADAgECAgkA2UwE2kl9v+swEAYMKoZIhvcSBAGEtwkCBQAwFjEUMBIG\n"
+            + "A1UEAwwLVGVzdCBJc3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3\n"
+            + "WjAXMRUwEwYDVQQDDAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMB\n"
+            + "BwNCAATmK2niv2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8Sj\n"
+            + "HlbifINI2xYaHPUdfvGULUvPciLBMBAGDCqGSIb3EgQBhLcJAgUAA0gAMEUCIQDy\n"
+            + "oDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13EBwIgfB55FGohg/B6dGh5XxSZ\n"
+            + "mmi08cueFV7mHzJSYV51yRQ=\n"
+            + "-----END CERTIFICATE-----\n";
+    private static final String SIGALG_STRING_PARAMETER = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBNzCB06ADAgECAgkA2UwE2kl9v+swFQYMKoZIhvcSBAGEtwkCDAVwYXJhbTAW\n"
+            + "MRQwEgYDVQQDDAtUZXN0IElzc3VlcjAeFw0xNDA0MjMyMzIxNTdaFw0xNDA1MjMy\n"
+            + "MzIxNTdaMBcxFTATBgNVBAMMDFRlc3QgU3ViamVjdDBZMBMGByqGSM49AgEGCCqG\n"
+            + "SM49AwEHA0IABOYraeK/ZZ+Xvi8eDZSKTNWXa7epHg1G+92pqR6d3LpaAefWl6gK\n"
+            + "GPnDxKMeVuJ8g0jbFhoc9R1+8ZQtS89yIsEwFQYMKoZIhvcSBAGEtwkCDAVwYXJh\n"
+            + "bQNIADBFAiEA8qA1XlE6NsOCeZvuJ1CFjnAGdJVX0il0APS+FYddxAcCIHweeRRq\n"
+            + "IYPwenRoeV8UmZpotPHLnhVe5h8yUmFedckU\n"
+            + "-----END CERTIFICATE-----\n";
+    private static final String SIGALG_BOOLEAN_PARAMETER = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBLzCBz6ADAgECAgkA2UwE2kl9v+swEQYMKoZIhvcSBAGEtwkCAQH/MBYxFDAS\n"
+            + "BgNVBAMMC1Rlc3QgSXNzdWVyMB4XDTE0MDQyMzIzMjE1N1oXDTE0MDUyMzIzMjE1\n"
+            + "N1owFzEVMBMGA1UEAwwMVGVzdCBTdWJqZWN0MFkwEwYHKoZIzj0CAQYIKoZIzj0D\n"
+            + "AQcDQgAE5itp4r9ln5e+Lx4NlIpM1Zdrt6keDUb73ampHp3culoB59aXqAoY+cPE\n"
+            + "ox5W4nyDSNsWGhz1HX7xlC1Lz3IiwTARBgwqhkiG9xIEAYS3CQIBAf8DSAAwRQIh\n"
+            + "APKgNV5ROjbDgnmb7idQhY5wBnSVV9IpdAD0vhWHXcQHAiB8HnkUaiGD8Hp0aHlf\n"
+            + "FJmaaLTxy54VXuYfMlJhXnXJFA==\n"
+            + "-----END CERTIFICATE-----\n";
+    private static final String SIGALG_SEQUENCE_PARAMETER = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBLTCBzqADAgECAgkA2UwE2kl9v+swEAYMKoZIhvcSBAGEtwkCMAAwFjEUMBIG\n"
+            + "A1UEAwwLVGVzdCBJc3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3\n"
+            + "WjAXMRUwEwYDVQQDDAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMB\n"
+            + "BwNCAATmK2niv2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8Sj\n"
+            + "HlbifINI2xYaHPUdfvGULUvPciLBMBAGDCqGSIb3EgQBhLcJAjAAA0gAMEUCIQDy\n"
+            + "oDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13EBwIgfB55FGohg/B6dGh5XxSZ\n"
+            + "mmi08cueFV7mHzJSYV51yRQ=\n"
+            + "-----END CERTIFICATE-----\n";
+
+    private static Date dateFromUTC(int year, int month, int day, int hour, int minute, int second)
+            throws Exception {
+        Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+        c.set(year, month, day, hour, minute, second);
+        c.set(Calendar.MILLISECOND, 0);
+        return c.getTime();
+    }
+
+    private static X509Certificate certificateFromPEM(Provider p, String pem)
+            throws CertificateException {
+        CertificateFactory cf = CertificateFactory.getInstance("X509", p);
+        return (X509Certificate) cf.generateCertificate(
+                new ByteArrayInputStream(pem.getBytes(Charset.forName("US-ASCII"))));
+    }
+
+    private static List<Pair<Integer, String>> normalizeGeneralNames(Collection<List<?>> names) {
+        // Extract a more convenient type than Java's Collection<List<?>>.
+        List<Pair<Integer, String>> result = new ArrayList<Pair<Integer, String>>();
+        for (List<?> tuple : names) {
+            assertEquals(2, tuple.size());
+            int type = ((Integer) tuple.get(0)).intValue();
+            // TODO(davidben): Most name types are expected to have a String value, but some use
+            // byte[]. Update this logic when testing those name types. See
+            // X509Certificate.getSubjectAlternativeNames().
+            String value = (String) tuple.get(1);
+            result.add(Pair.of(type, value));
+        }
+        // Although there is a natural order (the order in the certificate), Java's API returns a
+        // Collection, so there is no guarantee of the provider using a particular order. Normalize
+        // the order before comparing.
+        Collections.sort(result, new Comparator<Pair<Integer, String>>() {
+            @Override
+            public int compare(Pair<Integer, String> a, Pair<Integer, String> b) {
+                int cmp = a.getFirst().compareTo(b.getFirst());
+                if (cmp != 0) {
+                    return cmp;
+                }
+                return a.getSecond().compareTo(b.getSecond());
+            }
+        });
+        return result;
+    }
+
+    private static void assertGeneralNamesEqual(
+            Collection<List<?>> expected, Collection<List<?>> actual) throws Exception {
+        assertEquals(normalizeGeneralNames(expected), normalizeGeneralNames(actual));
+    }
+
+    // Error Prone flags Date.equals(), but Instant and LocalDateTime are not available in Java 7.
+    // We could compare Date.getTime(), but this trips another warning in Error Prone. We do not use
+    // Date subclasses, so stick with Date.equals for now.
+    //
+    // https://errorprone.info/bugpattern/UndefinedEquals
+    @SuppressWarnings("UndefinedEquals")
+    private static void assertDatesEqual(Date expected, Date actual) throws Exception {
+        assertEquals(expected, actual);
+    }
+
     // See issue #539.
     @Test
     public void testMismatchedAlgorithm() throws Exception {
@@ -143,10 +399,8 @@
             .run(new ServiceTester.Test() {
                 @Override
                 public void test(Provider p, String algorithm) throws Exception {
-                    CertificateFactory cf = CertificateFactory.getInstance("X509", p);
                     try {
-                        Certificate c = cf.generateCertificate(new ByteArrayInputStream(
-                            MISMATCHED_ALGORITHM_CERT.getBytes(Charset.forName("US-ASCII"))));
+                        X509Certificate c = certificateFromPEM(p, MISMATCHED_ALGORITHM_CERT);
                         c.verify(c.getPublicKey());
                         fail();
                     } catch (CertificateException expected) {
@@ -169,9 +423,7 @@
                 @Override
                 public void test(Provider p, String algorithm) throws Exception {
                     try {
-                        CertificateFactory cf = CertificateFactory.getInstance("X509", p);
-                        Certificate c = cf.generateCertificate(new ByteArrayInputStream(
-                            EC_EXPLICIT_KEY_CERT.getBytes(Charset.forName("US-ASCII"))));
+                        X509Certificate c = certificateFromPEM(p, EC_EXPLICIT_KEY_CERT);
                         c.verify(c.getPublicKey());
                         fail();
                     } catch (InvalidKeyException expected) {
@@ -190,12 +442,224 @@
             .run(new ServiceTester.Test() {
                 @Override
                 public void test(Provider p, String algorithm) throws Exception {
-                    CertificateFactory cf = CertificateFactory.getInstance("X509", p);
-                    Certificate c = cf.generateCertificate(new ByteArrayInputStream(
-                        VALID_CERT.getBytes(Charset.forName("US-ASCII"))));
-                    assertEquals("SHA256WITHRSA",
-                        ((X509Certificate) c).getSigAlgName().toUpperCase());
+                    X509Certificate c = certificateFromPEM(p, VALID_CERT);
+                    assertEquals("SHA256WITHRSA", c.getSigAlgName().toUpperCase());
                 }
             });
     }
+
+    @Test
+    public void testUnknownSigAlgOID() throws Exception {
+        ServiceTester.test("CertificateFactory")
+            .withAlgorithm("X509")
+            .run(new ServiceTester.Test() {
+                @Override
+                public void test(Provider p, String algorithm) throws Exception {
+                    X509Certificate c = certificateFromPEM(p, UNKNOWN_SIGNATURE_OID);
+                    assertEquals("1.2.840.113554.4.1.72585.2", c.getSigAlgOID());
+                }
+            });
+    }
+
+    @Test
+    public void testV1Cert() throws Exception {
+        ServiceTester tester = ServiceTester.test("CertificateFactory").withAlgorithm("X509");
+        tester.run(new ServiceTester.Test() {
+            @Override
+            public void test(Provider p, String algorithm) throws Exception {
+                X509Certificate c = certificateFromPEM(p, X509V1_CERT);
+
+                // Check basic certificate properties.
+                assertEquals(1, c.getVersion());
+                assertEquals(new BigInteger("d94c04da497dbfeb", 16), c.getSerialNumber());
+                assertDatesEqual(
+                        dateFromUTC(2014, Calendar.APRIL, 23, 23, 21, 57), c.getNotBefore());
+                assertDatesEqual(dateFromUTC(2014, Calendar.MAY, 23, 23, 21, 57), c.getNotAfter());
+                assertEquals(new X500Principal("CN=Test Issuer"), c.getIssuerX500Principal());
+                assertEquals(new X500Principal("CN=Test Subject"), c.getSubjectX500Principal());
+                assertEquals("1.2.840.10045.4.1", c.getSigAlgOID());
+                String signatureHex = "3045022100f2a0355e513a36c382799bee27"
+                        + "50858e7006749557d2297400f4be15875dc4"
+                        + "0702207c1e79146a2183f07a7468795f1499"
+                        + "9a68b4f1cb9e155ee61f3252615e75c914";
+                assertArrayEquals(TestUtils.decodeHex(signatureHex), c.getSignature());
+
+                // ECDSA signature AlgorithmIdentifiers omit parameters.
+                assertNull(c.getSigAlgParams());
+
+                // The certificate does not have UIDs.
+                assertNull(c.getIssuerUniqueID());
+                assertNull(c.getSubjectUniqueID());
+
+                // The certificate does not have any extensions.
+                assertEquals(-1, c.getBasicConstraints());
+                assertNull(c.getExtendedKeyUsage());
+                assertNull(c.getIssuerAlternativeNames());
+                assertNull(c.getKeyUsage());
+                assertNull(c.getSubjectAlternativeNames());
+            }
+        });
+    }
+
+    @Test
+    public void testManyExtensions() throws Exception {
+        ServiceTester tester = ServiceTester.test("CertificateFactory").withAlgorithm("X509");
+        tester.run(new ServiceTester.Test() {
+            @Override
+            public void test(Provider p, String algorithm) throws Exception {
+                X509Certificate c = certificateFromPEM(p, MANY_EXTENSIONS);
+
+                assertEquals(3, c.getVersion());
+                assertEquals(new BigInteger("b5b622b95a04a521", 16), c.getSerialNumber());
+                assertDatesEqual(dateFromUTC(2016, Calendar.JULY, 9, 4, 38, 9), c.getNotBefore());
+                assertDatesEqual(dateFromUTC(2016, Calendar.AUGUST, 8, 4, 38, 9), c.getNotAfter());
+                assertEquals(new X500Principal("CN=Test Issuer"), c.getIssuerX500Principal());
+                assertEquals(new X500Principal("CN=Test Subject"), c.getSubjectX500Principal());
+                assertEquals("1.2.840.113549.1.1.11", c.getSigAlgOID());
+                String signatureHex = "3ec983af1202b61695ca077d9001f743e6ca"
+                        + "bb791fa0fc2d18be5b6462d5f04dc511042e"
+                        + "77b3589dac72397850c72c298a783e2f79d2"
+                        + "054dfbad8882b22670236fb5be48d427f2fc"
+                        + "c34dbabf5f7dab3a5f7df80f485854841378"
+                        + "fc85937ba623eda6250aed659c8c3c829263"
+                        + "fb181901e11865fac062be18efe88343d093"
+                        + "f56ee83f865365d19c357461983596c02c1d"
+                        + "ddb55ebc8ae9f0e636410cc1b216aedb38c5"
+                        + "ceec711ac61d6cbe88c7faffba7f024fd222"
+                        + "270ce174b09a543ca4fc4064fafe1362e855"
+                        + "df69329594c295b651bb4ee70b064eb639b0"
+                        + "ee39b4534dff2fa3b5485e0750b68a339b1b"
+                        + "fb5710b6a2c8274cf92ff069ebafd0c5ed23"
+                        + "8c679f50";
+                assertArrayEquals(TestUtils.decodeHex(signatureHex), c.getSignature());
+
+                // Although documented to only return null when there are no parameters, the SUN
+                // provider also returns null when the algorithm uses an explicit parameter with a
+                // value of ASN.1 NULL.
+                if (c.getSigAlgParams() != null) {
+                    assertArrayEquals(TestUtils.decodeHex("0500"), c.getSigAlgParams());
+                }
+
+                assertArrayEquals(new boolean[] {true, false, true, false}, c.getIssuerUniqueID());
+                assertArrayEquals(
+                        new boolean[] {false, true, false, true, false}, c.getSubjectUniqueID());
+                assertEquals(10, c.getBasicConstraints());
+                assertEquals(Arrays.asList("1.3.6.1.5.5.7.3.1", "1.2.840.113554.4.1.72585.2"),
+                        c.getExtendedKeyUsage());
+
+                // TODO(davidben): Test the other name types.
+                assertGeneralNamesEqual(
+                        Arrays.<List<?>>asList(Arrays.asList(1, "issuer@example.com"),
+                                Arrays.asList(2, "issuer.example.com"),
+                                Arrays.asList(4, "CN=Test Issuer"),
+                                Arrays.asList(6, "https://example.com/issuer"),
+                                // TODO(https://github.com/google/conscrypt/issues/938): Fix IPv6
+                                // handling and include it in this test.
+                                Arrays.asList(7, "127.0.0.1"),
+                                Arrays.asList(8, "1.2.840.113554.4.1.72585.2")),
+                        c.getIssuerAlternativeNames());
+                assertGeneralNamesEqual(
+                        Arrays.<List<?>>asList(Arrays.asList(1, "subject@example.com"),
+                                Arrays.asList(2, "subject.example.com"),
+                                Arrays.asList(4, "CN=Test Subject"),
+                                Arrays.asList(6, "https://example.com/subject"),
+                                // TODO(https://github.com/google/conscrypt/issues/938): Fix IPv6
+                                // handling and include it in this test.
+                                Arrays.asList(7, "127.0.0.1"),
+                                Arrays.asList(8, "1.2.840.113554.4.1.72585.2")),
+                        c.getSubjectAlternativeNames());
+
+                // Although the BIT STRING in the certificate only has three bits, getKeyUsage()
+                // rounds up to at least 9 bits.
+                assertArrayEquals(
+                        new boolean[] {true, false, true, false, false, false, false, false, false},
+                        c.getKeyUsage());
+            }
+        });
+    }
+
+    @Test
+    public void testBasicConstraints() throws Exception {
+        ServiceTester tester = ServiceTester.test("CertificateFactory").withAlgorithm("X509");
+        tester.run(new ServiceTester.Test() {
+            @Override
+            public void test(Provider p, String algorithm) throws Exception {
+                // Test some additional edge cases in getBasicConstraints() beyond that
+                // testManyExtensions() and testV1Cert() covered.
+
+                // If there is no pathLen constraint but the certificate is a CA,
+                // getBasicConstraints() returns Integer.MAX_VALUE.
+                X509Certificate c = certificateFromPEM(p, BASIC_CONSTRAINTS_NO_PATHLEN);
+                assertEquals(Integer.MAX_VALUE, c.getBasicConstraints());
+
+                // If there is a pathLen constraint of zero, getBasicConstraints() returns it.
+                c = certificateFromPEM(p, BASIC_CONSTRAINTS_PATHLEN_0);
+                assertEquals(0, c.getBasicConstraints());
+
+                // If there is basicConstraints extension indicating a leaf certficate,
+                // getBasicConstraints() returns -1. The accessor does not distinguish between no
+                // basicConstraints extension and a leaf one.
+                c = certificateFromPEM(p, BASIC_CONSTRAINTS_LEAF);
+                assertEquals(-1, c.getBasicConstraints());
+
+                // If some unrelated extension has a syntax error, and that syntax error does not
+                // fail when constructing the certificate, it should not interfere with
+                // getBasicConstraints().
+                try {
+                    c = certificateFromPEM(p, BASIC_CONSTRAINTS_PATHLEN_10_BAD_SAN);
+                } catch (CertificateParsingException e) {
+                    // The certificate has a syntax error, so it would also be valid for the
+                    // provider to reject the certificate at construction. X.509 is an extensible
+                    // format, so different implementations may notice errors at different points.
+                    c = null;
+                }
+                if (c != null) {
+                    assertEquals(10, c.getBasicConstraints());
+                }
+            }
+        });
+    }
+
+    @Test
+    public void testLargeKeyUsage() throws Exception {
+        ServiceTester tester = ServiceTester.test("CertificateFactory").withAlgorithm("X509");
+        tester.run(new ServiceTester.Test() {
+            @Override
+            public void test(Provider p, String algorithm) throws Exception {
+                X509Certificate c = certificateFromPEM(p, LARGE_KEY_USAGE);
+                assertArrayEquals(new boolean[] {true, false, true, false, false, false, false,
+                                          false, false, false, false},
+                        c.getKeyUsage());
+            }
+        });
+    }
+
+    @Test
+    public void testSigAlgParams() throws Exception {
+        ServiceTester tester = ServiceTester.test("CertificateFactory").withAlgorithm("X509");
+        tester.run(new ServiceTester.Test() {
+            @Override
+            public void test(Provider p, String algorithm) throws Exception {
+                X509Certificate c = certificateFromPEM(p, SIGALG_NO_PARAMETER);
+                assertNull(c.getSigAlgParams());
+
+                c = certificateFromPEM(p, SIGALG_NULL_PARAMETER);
+                // Although documented to only return null when there are no parameters, the SUN
+                // provider also returns null when the algorithm uses an explicit parameter with a
+                // value of ASN.1 NULL.
+                if (c.getSigAlgParams() != null) {
+                    assertArrayEquals(TestUtils.decodeHex("0500"), c.getSigAlgParams());
+                }
+
+                c = certificateFromPEM(p, SIGALG_STRING_PARAMETER);
+                assertArrayEquals(TestUtils.decodeHex("0c05706172616d"), c.getSigAlgParams());
+
+                c = certificateFromPEM(p, SIGALG_BOOLEAN_PARAMETER);
+                assertArrayEquals(TestUtils.decodeHex("0101ff"), c.getSigAlgParams());
+
+                c = certificateFromPEM(p, SIGALG_SEQUENCE_PARAMETER);
+                assertArrayEquals(TestUtils.decodeHex("3000"), c.getSigAlgParams());
+            }
+        });
+    }
 }
diff --git a/common/src/test/java/org/conscrypt/javax/crypto/CipherBasicsTest.java b/common/src/test/java/org/conscrypt/javax/crypto/CipherBasicsTest.java
index 725f7a1..4aef5bb 100644
--- a/common/src/test/java/org/conscrypt/javax/crypto/CipherBasicsTest.java
+++ b/common/src/test/java/org/conscrypt/javax/crypto/CipherBasicsTest.java
@@ -16,13 +16,11 @@
 
 package org.conscrypt.javax.crypto;
 
+import static org.conscrypt.TestUtils.decodeHex;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
 import java.security.AlgorithmParameters;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
@@ -31,7 +29,6 @@
 import java.security.Provider;
 import java.security.Security;
 import java.security.spec.AlgorithmParameterSpec;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -54,26 +51,26 @@
 @RunWith(JUnit4.class)
 public final class CipherBasicsTest {
 
-    private static final Map<String, String> BASIC_CIPHER_TO_TEST_DATA = new HashMap<String, String>();
+    private static final Map<String, String> BASIC_CIPHER_TO_TEST_DATA = new HashMap<>();
     static {
-        BASIC_CIPHER_TO_TEST_DATA.put("AES/ECB/NoPadding", "/crypto/aes-ecb.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("AES/CBC/NoPadding", "/crypto/aes-cbc.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("AES/CFB8/NoPadding", "/crypto/aes-cfb8.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("AES/CFB128/NoPadding", "/crypto/aes-cfb128.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("AES/OFB/NoPadding", "/crypto/aes-ofb.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/ECB/NoPadding", "/crypto/desede-ecb.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/CBC/NoPadding", "/crypto/desede-cbc.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/CFB8/NoPadding", "/crypto/desede-cfb8.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/CFB64/NoPadding", "/crypto/desede-cfb64.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/OFB/NoPadding", "/crypto/desede-ofb.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("ChaCha20", "/crypto/chacha20.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("AES/ECB/NoPadding", "crypto/aes-ecb.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("AES/CBC/NoPadding", "crypto/aes-cbc.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("AES/CFB8/NoPadding", "crypto/aes-cfb8.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("AES/CFB128/NoPadding", "crypto/aes-cfb128.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("AES/OFB/NoPadding", "crypto/aes-ofb.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/ECB/NoPadding", "crypto/desede-ecb.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/CBC/NoPadding", "crypto/desede-cbc.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/CFB8/NoPadding", "crypto/desede-cfb8.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/CFB64/NoPadding", "crypto/desede-cfb64.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/OFB/NoPadding", "crypto/desede-ofb.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("ChaCha20", "crypto/chacha20.csv");
     }
 
-    private static final Map<String, String> AEAD_CIPHER_TO_TEST_DATA = new HashMap<String, String>();
+    private static final Map<String, String> AEAD_CIPHER_TO_TEST_DATA = new HashMap<>();
     static {
-        AEAD_CIPHER_TO_TEST_DATA.put("AES/GCM/NoPadding", "/crypto/aes-gcm.csv");
-        AEAD_CIPHER_TO_TEST_DATA.put("AES/GCM-SIV/NoPadding", "/crypto/aes-gcm-siv.csv");
-        AEAD_CIPHER_TO_TEST_DATA.put("ChaCha20/Poly1305/NoPadding", "/crypto/chacha20-poly1305.csv");
+        AEAD_CIPHER_TO_TEST_DATA.put("AES/GCM/NoPadding", "crypto/aes-gcm.csv");
+        AEAD_CIPHER_TO_TEST_DATA.put("AES/GCM-SIV/NoPadding", "crypto/aes-gcm-siv.csv");
+        AEAD_CIPHER_TO_TEST_DATA.put("ChaCha20/Poly1305/NoPadding", "crypto/chacha20-poly1305.csv");
     }
 
     private static final int KEY_INDEX = 0;
@@ -117,13 +114,13 @@
                     continue;
                 }
 
-                List<String[]> data = readCsvResource(entry.getValue());
+                List<String[]> data = TestUtils.readCsvResource(entry.getValue());
                 for (String[] line : data) {
-                    Key key = new SecretKeySpec(toBytes(line[KEY_INDEX]),
+                    Key key = new SecretKeySpec(decodeHex(line[KEY_INDEX]),
                             getBaseAlgorithm(transformation));
-                    byte[] iv = toBytes(line[IV_INDEX]);
-                    byte[] plaintext = toBytes(line[PLAINTEXT_INDEX]);
-                    byte[] ciphertext = toBytes(line[CIPHERTEXT_INDEX]);
+                    byte[] iv = decodeHex(line[IV_INDEX]);
+                    byte[] plaintext = decodeHex(line[PLAINTEXT_INDEX]);
+                    byte[] ciphertext = decodeHex(line[CIPHERTEXT_INDEX]);
 
                     // Initialize the IV, if there is one
                     AlgorithmParameters params;
@@ -140,20 +137,20 @@
                                         + ", algorithm " + transformation
                                         + " reported the wrong output size",
                                 ciphertext.length, cipher.getOutputSize(plaintext.length));
-                        assertTrue("Provider " + p.getName()
-                                        + ", algorithm " + transformation
-                                        + " failed on encryption, data is " + Arrays.toString(line),
-                                Arrays.equals(ciphertext, cipher.doFinal(plaintext)));
+                        assertArrayEquals("Provider " + p.getName()
+                                + ", algorithm " + transformation
+                                + " failed on encryption, data is " + Arrays.toString(line),
+                                ciphertext, cipher.doFinal(plaintext));
 
                         cipher.init(Cipher.DECRYPT_MODE, key, params);
                         assertEquals("Provider " + p.getName()
                                         + ", algorithm " + transformation
                                         + " reported the wrong output size",
                                 plaintext.length, cipher.getOutputSize(ciphertext.length));
-                        assertTrue("Provider " + p.getName()
-                                        + ", algorithm " + transformation
-                                        + " failed on decryption, data is " + Arrays.toString(line),
-                                Arrays.equals(plaintext, cipher.doFinal(ciphertext)));
+                        assertArrayEquals("Provider " + p.getName()
+                                + ", algorithm " + transformation
+                                + " failed on decryption, data is " + Arrays.toString(line),
+                                plaintext, cipher.doFinal(ciphertext));
                     } catch (InvalidKeyException e) {
                         // Some providers may not support raw SecretKeySpec keys, that's allowed
                     }
@@ -162,6 +159,39 @@
         }
     }
 
+    public void arrayBasedAssessment(Cipher cipher, byte[] aad, byte[] tag, byte[] plaintext,
+                                     byte[] ciphertext, Key key, AlgorithmParameterSpec params,
+                                     String transformation, Provider p, String[] line) throws Exception {
+        cipher.init(Cipher.ENCRYPT_MODE, key, params);
+        if (aad.length > 0) {
+            cipher.updateAAD(aad);
+        }
+        byte[] combinedOutput = new byte[ciphertext.length + tag.length];
+        assertEquals("Provider " + p.getName()
+                        + ", algorithm " + transformation
+                        + " reported the wrong output size",
+                combinedOutput.length, cipher.getOutputSize(plaintext.length));
+        System.arraycopy(ciphertext, 0, combinedOutput, 0, ciphertext.length);
+        System.arraycopy(tag, 0, combinedOutput, ciphertext.length, tag.length);
+        assertArrayEquals("Provider " + p.getName()
+                + ", algorithm " + transformation
+                + " failed on encryption, data is " + Arrays.toString(line),
+                combinedOutput, cipher.doFinal(plaintext));
+
+        cipher.init(Cipher.DECRYPT_MODE, key, params);
+        if (aad.length > 0) {
+            cipher.updateAAD(aad);
+        }
+        assertEquals("Provider " + p.getName()
+                        + ", algorithm " + transformation
+                        + " reported the wrong output size",
+                plaintext.length, cipher.getOutputSize(combinedOutput.length));
+        assertArrayEquals("Provider " + p.getName()
+                + ", algorithm " + transformation
+                + " failed on decryption, data is " + Arrays.toString(line),
+                plaintext, cipher.doFinal(combinedOutput));
+    }
+
     @Test
     public void testAeadEncryption() throws Exception {
         TestUtils.assumeAEADAvailable();
@@ -169,6 +199,13 @@
             for (Map.Entry<String, String> entry : AEAD_CIPHER_TO_TEST_DATA.entrySet()) {
                 String transformation = entry.getKey();
 
+                // On Android 10 and below, BC can return AES/GCM/NoPadding when asked for
+                // AES/GCM-SIV/NoPadding. Android will never actually ship AES/GCM-SIV/NoPadding
+                // in BC, so skip that combination.
+                if (p.getName().equals("BC") && transformation.equals("AES/GCM-SIV/NoPadding")) {
+                    continue;
+                }
+
                 Cipher cipher;
                 try {
                     cipher = Cipher.getInstance(transformation, p);
@@ -177,15 +214,15 @@
                     continue;
                 }
 
-                List<String[]> data = readCsvResource(entry.getValue());
+                List<String[]> data = TestUtils.readCsvResource(entry.getValue());
                 for (String[] line : data) {
-                    Key key = new SecretKeySpec(toBytes(line[KEY_INDEX]),
+                    Key key = new SecretKeySpec(decodeHex(line[KEY_INDEX]),
                             getBaseAlgorithm(transformation));
-                    byte[] iv = toBytes(line[IV_INDEX]);
-                    byte[] plaintext = toBytes(line[PLAINTEXT_INDEX]);
-                    byte[] ciphertext = toBytes(line[CIPHERTEXT_INDEX]);
-                    byte[] tag = toBytes(line[TAG_INDEX]);
-                    byte[] aad = toBytes(line[AAD_INDEX]);
+                    byte[] iv = decodeHex(line[IV_INDEX]);
+                    byte[] plaintext = decodeHex(line[PLAINTEXT_INDEX]);
+                    byte[] ciphertext = decodeHex(line[CIPHERTEXT_INDEX]);
+                    byte[] tag = decodeHex(line[TAG_INDEX]);
+                    byte[] aad = decodeHex(line[AAD_INDEX]);
 
                     // Some ChaCha20 tests include truncated tags, which the Java API doesn't
                     // support.  Skip those tests.
@@ -201,34 +238,18 @@
                     }
 
                     try {
-                        cipher.init(Cipher.ENCRYPT_MODE, key, params);
-                        if (aad.length > 0) {
-                            cipher.updateAAD(aad);
-                        }
-                        byte[] combinedOutput = new byte[ciphertext.length + tag.length];
-                        assertEquals("Provider " + p.getName()
-                                        + ", algorithm " + transformation
-                                        + " reported the wrong output size",
-                                combinedOutput.length, cipher.getOutputSize(plaintext.length));
-                        System.arraycopy(ciphertext, 0, combinedOutput, 0, ciphertext.length);
-                        System.arraycopy(tag, 0, combinedOutput, ciphertext.length, tag.length);
-                        assertTrue("Provider " + p.getName()
-                                        + ", algorithm " + transformation
-                                        + " failed on encryption, data is " + Arrays.toString(line),
-                                Arrays.equals(combinedOutput, cipher.doFinal(plaintext)));
-
-                        cipher.init(Cipher.DECRYPT_MODE, key, params);
-                        if (aad.length > 0) {
-                            cipher.updateAAD(aad);
-                        }
-                        assertEquals("Provider " + p.getName()
-                                        + ", algorithm " + transformation
-                                        + " reported the wrong output size",
-                                plaintext.length, cipher.getOutputSize(combinedOutput.length));
-                        assertTrue("Provider " + p.getName()
-                                        + ", algorithm " + transformation
-                                        + " failed on decryption, data is " + Arrays.toString(line),
-                                Arrays.equals(plaintext, cipher.doFinal(combinedOutput)));
+                        arrayBasedAssessment(cipher, aad, tag, plaintext, ciphertext, key, params, transformation, p,
+                                line);
+                        bufferBasedAssessment(cipher, aad, tag, plaintext, ciphertext, key, params, transformation, p,
+                                false, false);
+                        bufferBasedAssessment(cipher, aad, tag, plaintext, ciphertext, key, params, transformation, p,
+                                true, true);
+                        bufferBasedAssessment(cipher, aad, tag, plaintext, ciphertext, key, params, transformation, p,
+                                true, false);
+                        bufferBasedAssessment(cipher, aad, tag, plaintext, ciphertext, key, params, transformation, p,
+                                false, true);
+                        sharedBufferBasedAssessment(cipher, aad, tag, plaintext, ciphertext, key, params,
+                                transformation, p);
                     } catch (InvalidKeyException e) {
                         // Some providers may not support raw SecretKeySpec keys, that's allowed
                     } catch (InvalidAlgorithmParameterException e) {
@@ -240,27 +261,131 @@
         }
     }
 
-    private static List<String[]> readCsvResource(String resourceName) throws IOException {
-        InputStream stream = CipherBasicsTest.class.getResourceAsStream(resourceName);
-        List<String[]> lines = new ArrayList<String[]>();
-        BufferedReader reader = null;
-        try {
-            reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
-            String line;
-            while ((line = reader.readLine()) != null) {
-                if (line.isEmpty() || line.startsWith("#")) {
-                    continue;
-                }
-                lines.add(line.split(",", -1));
-            }
-        } finally {
-            if (reader != null) {
-                reader.close();
-            }
+    public void sharedBufferBasedAssessment(Cipher cipher, byte[] aad, byte[] tag, byte[] _plaintext,
+                                      byte[] _ciphertext, Key key, AlgorithmParameterSpec params,
+                                      String transformation, Provider p) throws Exception {
+        cipher.init(Cipher.ENCRYPT_MODE, key, params);
+        if (aad.length > 0) {
+            cipher.updateAAD(aad);
         }
-        return lines;
+        byte[] _combinedOutput = new byte[_ciphertext.length + tag.length];
+        byte[] _commonBacking = new byte[_plaintext.length + _combinedOutput.length];
+
+        assertEquals("Provider " + p.getName()
+                        + ", algorithm " + transformation
+                        + " reported the wrong output size",
+                _combinedOutput.length, cipher.getOutputSize(_plaintext.length));
+        System.arraycopy(_ciphertext, 0, _combinedOutput, 0, _ciphertext.length);
+        System.arraycopy(tag, 0, _combinedOutput, _ciphertext.length, tag.length);
+        System.arraycopy(_plaintext, 0, _commonBacking, 0, _plaintext.length);
+        System.arraycopy(_combinedOutput, 0, _commonBacking, _plaintext.length, _combinedOutput.length);
+        ByteBuffer combinedOutput = ByteBuffer.wrap(_commonBacking);
+        ByteBuffer plaintext = combinedOutput.slice();
+        plaintext.limit(_plaintext.length);
+        combinedOutput.position(_plaintext.length);
+        // both byte buffers have been created from common backed array and have correct respecting positions and limits
+
+        combinedOutput.position(combinedOutput.limit());
+        ByteBuffer outputbuffer = ByteBuffer.allocate(cipher.getOutputSize(plaintext.remaining()));
+
+        cipher.doFinal(plaintext, outputbuffer);
+        assertEquals("Cipher doFinal did not encrypt correctly", combinedOutput, outputbuffer);
+        assertEquals(" input was not shifted", plaintext.position(), plaintext.limit());
+
+        cipher.init(Cipher.DECRYPT_MODE, key, params);
+        if (aad.length > 0) {
+            cipher.updateAAD(aad);
+        }
+        assertEquals("Provider " + p.getName()
+                        + ", algorithm " + transformation
+                        + " reported the wrong output size",
+                _plaintext.length, cipher.getOutputSize(_combinedOutput.length));
+        combinedOutput.position(_plaintext.length);
+
+        outputbuffer = ByteBuffer.allocate(cipher.getOutputSize(combinedOutput.remaining()));
+
+        combinedOutput.position(_plaintext.length);
+        plaintext.position(plaintext.limit());
+        cipher.doFinal(combinedOutput, outputbuffer);
+        assertEquals("Cipher doFinal did not decrypt correctly", plaintext, outputbuffer);
+        assertEquals(" input was not shifted", combinedOutput.position(), combinedOutput.limit());
     }
 
+    public void bufferBasedAssessment(Cipher cipher, byte[] aad, byte[] tag, byte[] _plaintext,
+                                           byte[] _ciphertext, Key key, AlgorithmParameterSpec params,
+                                           String transformation, Provider p, boolean inBoolDirect, boolean outBoolDirect) throws Exception {
+        cipher.init(Cipher.ENCRYPT_MODE, key, params);
+        if (aad.length > 0) {
+            cipher.updateAAD(aad);
+        }
+        byte[] _combinedOutput = new byte[_ciphertext.length + tag.length];
+        ByteBuffer plaintext = ByteBuffer.wrap(_plaintext);
+        if (inBoolDirect) {
+            ByteBuffer plaintext_ = plaintext;
+            int incap = plaintext_.remaining();
+            plaintext = ByteBuffer.allocateDirect(incap);
+            plaintext.mark();
+            plaintext.put(plaintext_);
+            plaintext.reset();
+        }
+
+        assertEquals("Provider " + p.getName()
+                        + ", algorithm " + transformation
+                        + " reported the wrong output size",
+                _combinedOutput.length, cipher.getOutputSize(_plaintext.length));
+        System.arraycopy(_ciphertext, 0, _combinedOutput, 0, _ciphertext.length);
+        System.arraycopy(tag, 0, _combinedOutput, _ciphertext.length, tag.length);
+
+        ByteBuffer combinedOutput = ByteBuffer.wrap(_combinedOutput);
+        if (outBoolDirect) {
+            ByteBuffer combinedOutput_ = combinedOutput;
+            int outcap = combinedOutput_.remaining();
+            combinedOutput = ByteBuffer.allocateDirect(outcap);
+            combinedOutput.mark();
+            combinedOutput.put(combinedOutput_);
+        }
+        combinedOutput.position(combinedOutput.limit());
+        ByteBuffer outputbuffer;
+        if (outBoolDirect) {
+            outputbuffer = ByteBuffer.allocateDirect(cipher.getOutputSize(plaintext.remaining()));
+        } else {
+            outputbuffer = ByteBuffer.allocate(cipher.getOutputSize(plaintext.remaining()));
+        }
+
+        cipher.doFinal(plaintext, outputbuffer);
+        assertEquals("Cipher doFinal did not encrypt correctly", combinedOutput, outputbuffer);
+        assertEquals(" input was not shifted", plaintext.position(), plaintext.limit());
+
+        cipher.init(Cipher.DECRYPT_MODE, key, params);
+        if (aad.length > 0) {
+            cipher.updateAAD(aad);
+        }
+        assertEquals("Provider " + p.getName()
+                        + ", algorithm " + transformation
+                        + " reported the wrong output size",
+                _plaintext.length, cipher.getOutputSize(_combinedOutput.length));
+        combinedOutput = ByteBuffer.wrap(_combinedOutput);
+        if (inBoolDirect) {
+            ByteBuffer combinedOutput_ = combinedOutput;
+            int incap = combinedOutput_.remaining();
+            combinedOutput = ByteBuffer.allocateDirect(incap);
+            combinedOutput.mark();
+            combinedOutput.put(combinedOutput_);
+            combinedOutput.reset();
+        }
+        if (outBoolDirect) {
+            outputbuffer = ByteBuffer.allocateDirect(cipher.getOutputSize(combinedOutput.remaining()));
+        } else {
+            outputbuffer = ByteBuffer.allocate(cipher.getOutputSize(combinedOutput.remaining()));
+        }
+        combinedOutput.position(0);
+        plaintext.position(plaintext.limit());
+        cipher.doFinal(combinedOutput, outputbuffer);
+        assertEquals("Cipher doFinal did not decrypt correctly", plaintext, outputbuffer);
+        assertEquals(" input was not shifted", combinedOutput.position(), combinedOutput.limit());
+    }
+
+
     /**
      * Returns the underlying cipher name given a cipher transformation.  For example,
      * passing {@code AES/CBC/NoPadding} returns {@code AES}.
@@ -272,7 +397,92 @@
         return transformation;
     }
 
-    private static byte[] toBytes(String hex) {
-        return TestUtils.decodeHex(hex, /* allowSingleChar= */ true);
+    /**
+     * Encryption with ByteBuffers should be copy-safe even if the buffers have different starting
+     * offsets and/or do not make the backing array visible.
+     *
+     * <p>Note that bugs in this often require a sizeable input to reproduce; the default
+     * implementation of engineUpdate(ByteBuffer, ByteBuffer) copies through 4KB bounce buffers, so we
+     * need to use something larger to see any problems - 8KB is what we use here.
+     *
+     * @see https://bugs.openjdk.java.net/browse/JDK-8181386
+     */
+    @Test
+    public void testByteBufferShiftedAlias() throws Exception {
+        byte[] ptVector = new byte[8192];
+
+        for (int i = 0; i < 3; i++) {
+            // outputOffset = offset relative to start of input.
+            for (int outputOffset = -1; outputOffset <= 1; outputOffset++) {
+
+                SecretKeySpec key = new SecretKeySpec(new byte[16], "AES");
+                GCMParameterSpec parameters = new GCMParameterSpec(128, new byte[12]);
+                Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+                cipher.init(Cipher.ENCRYPT_MODE, key, parameters);
+
+                ByteBuffer output, input, inputRO;
+
+                // We'll try three scenarios: Ordinary array backed buffers, array backed buffers where one
+                // is read-only, and direct byte buffers.
+                String mode;
+                // offsets relative to start of buffer
+                int inputOffsetInBuffer = 1;
+                int outputOffsetInBuffer = inputOffsetInBuffer + outputOffset;
+                int sliceLength = cipher.getOutputSize(ptVector.length);
+                int bufferSize = sliceLength + Math.max(inputOffsetInBuffer, outputOffsetInBuffer);
+
+                mode = "direct buffers";
+                ByteBuffer buf = ByteBuffer.allocateDirect(bufferSize);
+                output = buf.duplicate();
+                output.position(outputOffsetInBuffer);
+                output.limit(sliceLength + outputOffsetInBuffer);
+                output = output.slice();
+
+                input = buf.duplicate();
+                input.position(inputOffsetInBuffer);
+                input.limit(sliceLength + inputOffsetInBuffer);
+                input = input.slice();
+
+                inputRO = input.duplicate();
+
+                // Now that we have our overlapping 'input' and 'output' buffers, we can write our plaintext
+                // into the input buffer.
+                input.put(ptVector);
+                input.flip();
+                // Make sure the RO input buffer has the same limit in case the plaintext is shorter than
+                // sliceLength (which it generally will be for anything other than ECB or CTR mode)
+                inputRO.limit(input.limit());
+
+                try {
+                    int ctSize = cipher.doFinal(inputRO, output);
+
+                    // Now flip the buffers around and undo everything
+                    byte[] tmp = new byte[ctSize];
+                    output.flip();
+                    output.get(tmp);
+
+                    output.clear();
+                    input.clear();
+                    inputRO.clear();
+
+                    input.put(tmp);
+                    input.flip();
+                    inputRO.limit(input.limit());
+
+                    cipher.init(Cipher.DECRYPT_MODE, key, parameters);
+                    cipher.doFinal(inputRO, output);
+
+                    output.flip();
+                    assertEquals(ByteBuffer.wrap(ptVector), output);
+                } catch (Throwable t) {
+                    throw new AssertionError(
+                            "Overlapping buffers test failed with buffer type: "
+                                    + mode
+                                    + " and output offset "
+                                    + outputOffset,
+                            t);
+                }
+            }
+        }
     }
 }
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 65c7d15..8f4f896 100644
--- a/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java
+++ b/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java
@@ -70,6 +70,7 @@
 import javax.crypto.spec.PSource;
 import javax.crypto.spec.SecretKeySpec;
 import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule;
+import libcore.test.annotation.NonCts;
 import org.bouncycastle.asn1.x509.KeyUsage;
 import org.conscrypt.Conscrypt;
 import org.conscrypt.TestUtils;
@@ -378,18 +379,14 @@
         setExpectedBlockSize("AES/OFB/PKCS7PADDING", 16);
         setExpectedBlockSize("AES/OFB/NOPADDING", 16);
         setExpectedBlockSize("AES_128/CBC/PKCS5PADDING", 16);
-        setExpectedBlockSize("AES_128/CBC/PKCS7PADDING", 16);
         setExpectedBlockSize("AES_128/CBC/NOPADDING", 16);
         setExpectedBlockSize("AES_128/ECB/PKCS5PADDING", 16);
-        setExpectedBlockSize("AES_128/ECB/PKCS7PADDING", 16);
         setExpectedBlockSize("AES_128/ECB/NOPADDING", 16);
         setExpectedBlockSize("AES_128/GCM/NOPADDING", 16);
         setExpectedBlockSize("AES_128/GCM-SIV/NOPADDING", 16);
         setExpectedBlockSize("AES_256/CBC/PKCS5PADDING", 16);
-        setExpectedBlockSize("AES_256/CBC/PKCS7PADDING", 16);
         setExpectedBlockSize("AES_256/CBC/NOPADDING", 16);
         setExpectedBlockSize("AES_256/ECB/PKCS5PADDING", 16);
-        setExpectedBlockSize("AES_256/ECB/PKCS7PADDING", 16);
         setExpectedBlockSize("AES_256/ECB/NOPADDING", 16);
         setExpectedBlockSize("AES_256/GCM/NOPADDING", 16);
         setExpectedBlockSize("AES_256/GCM-SIV/NOPADDING", 16);
@@ -410,7 +407,6 @@
         }
 
         setExpectedBlockSize("ARC4", 0);
-        setExpectedBlockSize("ARCFOUR", 0);
         setExpectedBlockSize("CHACHA20", 0);
         setExpectedBlockSize("CHACHA20/POLY1305/NOPADDING", 0);
         setExpectedBlockSize("PBEWITHSHAAND40BITRC4", 0);
@@ -424,27 +420,9 @@
 
         setExpectedBlockSize("DESEDE", 8);
         setExpectedBlockSize("DESEDE/CBC/PKCS5PADDING", 8);
-        setExpectedBlockSize("DESEDE/CBC/PKCS7PADDING", 8);
         setExpectedBlockSize("DESEDE/CBC/NOPADDING", 8);
-        setExpectedBlockSize("DESEDE/CFB/PKCS5PADDING", 8);
-        setExpectedBlockSize("DESEDE/CFB/PKCS7PADDING", 8);
-        setExpectedBlockSize("DESEDE/CFB/NOPADDING", 8);
-        setExpectedBlockSize("DESEDE/CTR/PKCS5PADDING", 8);
-        setExpectedBlockSize("DESEDE/CTR/PKCS7PADDING", 8);
-        setExpectedBlockSize("DESEDE/CTR/NOPADDING", 8);
-        setExpectedBlockSize("DESEDE/CTS/PKCS5PADDING", 8);
-        setExpectedBlockSize("DESEDE/CTS/PKCS7PADDING", 8);
-        setExpectedBlockSize("DESEDE/CTS/NOPADDING", 8);
-        setExpectedBlockSize("DESEDE/ECB/PKCS5PADDING", 8);
-        setExpectedBlockSize("DESEDE/ECB/PKCS7PADDING", 8);
-        setExpectedBlockSize("DESEDE/ECB/NOPADDING", 8);
-        setExpectedBlockSize("DESEDE/OFB/PKCS5PADDING", 8);
-        setExpectedBlockSize("DESEDE/OFB/PKCS7PADDING", 8);
-        setExpectedBlockSize("DESEDE/OFB/NOPADDING", 8);
         setExpectedBlockSize("PBEWITHSHAAND2-KEYTRIPLEDES-CBC", 8);
         setExpectedBlockSize("PBEWITHSHAAND3-KEYTRIPLEDES-CBC", 8);
-        setExpectedBlockSize("PBEWITHMD5ANDTRIPLEDES", 8);
-        setExpectedBlockSize("PBEWITHSHA1ANDDESEDE", 8);
 
 
         if (StandardNames.IS_RI) {
@@ -2152,7 +2130,7 @@
     /*
      * echo -n 'This is a test of OAEP' | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_Plaintext = new byte[] {
+    private static final byte[] RSA_Vector2_Plaintext = new byte[] {
             (byte) 0x54, (byte) 0x68, (byte) 0x69, (byte) 0x73, (byte) 0x20, (byte) 0x69,
             (byte) 0x73, (byte) 0x20, (byte) 0x61, (byte) 0x20, (byte) 0x74, (byte) 0x65,
             (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x6f, (byte) 0x66, (byte) 0x20,
@@ -2164,7 +2142,7 @@
      * -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha1 -pkeyopt rsa_mgf1_md:sha1 \
      * | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA1_MGF1_SHA1 = new byte[] {
+    private static final byte[] RSA_Vector2_OAEP_SHA1_MGF1_SHA1 = new byte[] {
             (byte) 0x53, (byte) 0x71, (byte) 0x84, (byte) 0x2e, (byte) 0x01, (byte) 0x74,
             (byte) 0x82, (byte) 0xb3, (byte) 0x01, (byte) 0xac, (byte) 0x2b, (byte) 0xbd,
             (byte) 0x40, (byte) 0xa7, (byte) 0x5b, (byte) 0x60, (byte) 0xf1, (byte) 0xde,
@@ -2213,7 +2191,7 @@
     /*
      * echo -n 'This is a test of OAEP' | openssl pkeyutl -encrypt -inkey rsakey.pem -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha1 | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA256_MGF1_SHA1 = new byte[] {
+    private static final byte[] RSA_Vector2_OAEP_SHA256_MGF1_SHA1 = new byte[] {
             (byte) 0x25, (byte) 0x9f, (byte) 0xc3, (byte) 0x69, (byte) 0xbc, (byte) 0x3f,
             (byte) 0xe7, (byte) 0x9e, (byte) 0x76, (byte) 0xef, (byte) 0x6c, (byte) 0xd2,
             (byte) 0x2b, (byte) 0x7b, (byte) 0xf0, (byte) 0xeb, (byte) 0xc2, (byte) 0x28,
@@ -2262,7 +2240,7 @@
     /*
      * echo -n 'This is a test of OAEP' | openssl pkeyutl -encrypt -inkey /tmp/rsakey.txt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha1 -pkeyopt rsa_oaep_label:010203FFA00A | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA256_MGF1_SHA1_LABEL = new byte[] {
+    private static final byte[] RSA_Vector2_OAEP_SHA256_MGF1_SHA1_LABEL = new byte[] {
             (byte) 0x80, (byte) 0xb1, (byte) 0xf2, (byte) 0xc2, (byte) 0x03, (byte) 0xc5,
             (byte) 0xdf, (byte) 0xbd, (byte) 0xed, (byte) 0xfe, (byte) 0xe6, (byte) 0xff,
             (byte) 0xd3, (byte) 0x38, (byte) 0x1e, (byte) 0x6d, (byte) 0xae, (byte) 0x47,
@@ -2313,7 +2291,7 @@
      * -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha224 -pkeyopt rsa_mgf1_md:sha224 \
      * | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA224_MGF1_SHA224 = new byte[] {
+    private static final byte[] RSA_Vector2_OAEP_SHA224_MGF1_SHA224 = new byte[] {
             (byte) 0xae, (byte) 0xdd, (byte) 0xe6, (byte) 0xab, (byte) 0x00, (byte) 0xd6,
             (byte) 0x1e, (byte) 0x7e, (byte) 0x85, (byte) 0x63, (byte) 0xab, (byte) 0x51,
             (byte) 0x79, (byte) 0x92, (byte) 0xf1, (byte) 0xb9, (byte) 0x4f, (byte) 0x23,
@@ -2364,7 +2342,7 @@
      * -pkeyopt rsa_padding_mode:oaep -pkey rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha256 \
      * | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA256_MGF1_SHA256 = new byte[] {
+    private static final byte[] RSA_Vector2_OAEP_SHA256_MGF1_SHA256 = new byte[] {
             (byte) 0x6a, (byte) 0x2b, (byte) 0xb2, (byte) 0xa3, (byte) 0x26, (byte) 0xa6,
             (byte) 0x7a, (byte) 0x4a, (byte) 0x1f, (byte) 0xe5, (byte) 0xc8, (byte) 0x94,
             (byte) 0x11, (byte) 0x1a, (byte) 0x92, (byte) 0x07, (byte) 0x0a, (byte) 0xf4,
@@ -2415,7 +2393,7 @@
      * -pkeyopt rsa_padding_mode:oaep -pkey rsa_oaep_md:sha384 -pkeyopt rsa_mgf1_md:sha384 \
      * | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA384_MGF1_SHA384 = new byte[] {
+    private static final byte[] RSA_Vector2_OAEP_SHA384_MGF1_SHA384 = new byte[] {
             (byte) 0xa1, (byte) 0xb3, (byte) 0x3b, (byte) 0x34, (byte) 0x69, (byte) 0x9e,
             (byte) 0xd8, (byte) 0xa0, (byte) 0x37, (byte) 0x2c, (byte) 0xeb, (byte) 0xef,
             (byte) 0xf2, (byte) 0xaf, (byte) 0xfa, (byte) 0x63, (byte) 0x5d, (byte) 0x88,
@@ -2466,7 +2444,7 @@
      * -pkeyopt rsa_padding_mode:oaep -pkey rsa_oaep_md:sha512 -pkeyopt rsa_mgf1_md:sha512 \
      * | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA512_MGF1_SHA512 = new byte[] {
+    private static final byte[] RSA_Vector2_OAEP_SHA512_MGF1_SHA512 = new byte[] {
             (byte) 0x75, (byte) 0x0f, (byte) 0xf9, (byte) 0x21, (byte) 0xca, (byte) 0xcc,
             (byte) 0x0e, (byte) 0x13, (byte) 0x9e, (byte) 0x38, (byte) 0xa4, (byte) 0xa7,
             (byte) 0xee, (byte) 0x61, (byte) 0x6d, (byte) 0x56, (byte) 0xea, (byte) 0x36,
@@ -2515,7 +2493,7 @@
     /*
      * echo -n 'This is a test of OAEP' | openssl pkeyutl -encrypt -inkey /tmp/rsakey.txt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha512 -pkeyopt rsa_mgf1_md:sha512 -pkeyopt rsa_oaep_label:010203FFA00A | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA512_MGF1_SHA512_LABEL = new byte[] {
+    private static final byte[] RSA_Vector2_OAEP_SHA512_MGF1_SHA512_LABEL = new byte[] {
             (byte) 0x31, (byte) 0x3b, (byte) 0x23, (byte) 0xcf, (byte) 0x40, (byte) 0xfe,
             (byte) 0x15, (byte) 0x94, (byte) 0xd6, (byte) 0x81, (byte) 0x21, (byte) 0x69,
             (byte) 0x8e, (byte) 0x58, (byte) 0xd5, (byte) 0x0f, (byte) 0xa8, (byte) 0x72,
@@ -4668,6 +4646,8 @@
      * TODO(27995180): consider whether we keep this compatibility. Consider whether we only allow
      * if an IV is passed in the parameters.
      */
+    @NonCts(bug = 287231726, reason = "The test asserts buggy or non-breaking "
+            + "behaviors, but the behavior has been fixed in the future ART module version.")
     @Test
     public void test_PBKDF2WITHHMACSHA1_SKFactory_and_PBEAESCBC_Cipher_noIV() throws Exception {
         Assume.assumeNotNull(Security.getProvider("BC"));
diff --git a/common/src/test/java/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java b/common/src/test/java/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java
index b4df031..6464ba9 100644
--- a/common/src/test/java/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java
+++ b/common/src/test/java/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java
@@ -463,6 +463,19 @@
         if (providers == null) {
             return new Provider[0];
         }
+
+        // Do not test AndroidKeyStore as KeyAgreement provider. It only handles Android
+        // Keystore-backed keys. It's OKish not to test AndroidKeyStore here because it's tested by
+        // cts/tests/test/keystore.
+        List<Provider> filteredProvidersList = new ArrayList<Provider>(providers.length);
+        for (Provider provider : providers) {
+            if ("AndroidKeyStore".equals(provider.getName())) {
+                continue;
+            }
+            filteredProvidersList.add(provider);
+        }
+        providers = filteredProvidersList.toArray(new Provider[filteredProvidersList.size()]);
+
         // Sort providers by name to guarantee deterministic order in which providers are used in
         // the tests.
         return sortByName(providers);
diff --git a/common/src/test/java/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java b/common/src/test/java/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java
new file mode 100644
index 0000000..4cd0843
--- /dev/null
+++ b/common/src/test/java/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java
@@ -0,0 +1,102 @@
+package org.conscrypt.javax.crypto;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import javax.crypto.KeyAgreement;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+
+/**
+ * Tests for all registered X25519 and X448 {@link KeyAgreement} providers.
+ */
+@RunWith(JUnit4.class)
+public class XDHKeyAgreementTest {
+    private static final byte[] RFC_7748_X25519_OUR_PRIV_KEY = new byte[] {
+            (byte) 0x30, (byte) 0x2e, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x30, (byte) 0x05, (byte) 0x06,
+            (byte) 0x03, (byte) 0x2b, (byte) 0x65, (byte) 0x6e, (byte) 0x04, (byte) 0x22, (byte) 0x04, (byte) 0x20,
+            (byte) 0xa5, (byte) 0x46, (byte) 0xe3, (byte) 0x6b, (byte) 0xf0, (byte) 0x52, (byte) 0x7c, (byte) 0x9d,
+            (byte) 0x3b, (byte) 0x16, (byte) 0x15, (byte) 0x4b, (byte) 0x82, (byte) 0x46, (byte) 0x5e, (byte) 0xdd,
+            (byte) 0x62, (byte) 0x14, (byte) 0x4c, (byte) 0x0a, (byte) 0xc1, (byte) 0xfc, (byte) 0x5a, (byte) 0x18,
+            (byte) 0x50, (byte) 0x6a, (byte) 0x22, (byte) 0x44, (byte) 0xba, (byte) 0x44, (byte) 0x9a, (byte) 0xc4,
+    };
+
+    // Broken key for testing with JDK 11. Instead of wrapping OCTET STRING with OCTET STRING.
+    private static final byte[] RFC_7748_X25519_OUR_PRIV_KEY_BROKEN = new byte[] {
+            (byte) 0x30, (byte) 0x2c, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x30, (byte) 0x05, (byte) 0x06,
+            (byte) 0x03, (byte) 0x2b, (byte) 0x65, (byte) 0x6e, (byte) 0x04, (byte) 0x20,
+            (byte) 0xa5, (byte) 0x46, (byte) 0xe3, (byte) 0x6b, (byte) 0xf0, (byte) 0x52, (byte) 0x7c, (byte) 0x9d,
+            (byte) 0x3b, (byte) 0x16, (byte) 0x15, (byte) 0x4b, (byte) 0x82, (byte) 0x46, (byte) 0x5e, (byte) 0xdd,
+            (byte) 0x62, (byte) 0x14, (byte) 0x4c, (byte) 0x0a, (byte) 0xc1, (byte) 0xfc, (byte) 0x5a, (byte) 0x18,
+            (byte) 0x50, (byte) 0x6a, (byte) 0x22, (byte) 0x44, (byte) 0xba, (byte) 0x44, (byte) 0x9a, (byte) 0xc4,
+    };
+
+    private static final byte[] RFC_7748_X25519_THEIR_PUB_KEY = new byte[] {
+            (byte) 0x30, (byte) 0x2a, (byte) 0x30, (byte) 0x05, (byte) 0x06, (byte) 0x03, (byte) 0x2b, (byte) 0x65,
+            (byte) 0x6e, (byte) 0x03, (byte) 0x21, (byte) 0x00, (byte) 0xe6, (byte) 0xdb, (byte) 0x68, (byte) 0x67,
+            (byte) 0x58, (byte) 0x30, (byte) 0x30, (byte) 0xdb, (byte) 0x35, (byte) 0x94, (byte) 0xc1, (byte) 0xa4,
+            (byte) 0x24, (byte) 0xb1, (byte) 0x5f, (byte) 0x7c, (byte) 0x72, (byte) 0x66, (byte) 0x24, (byte) 0xec,
+            (byte) 0x26, (byte) 0xb3, (byte) 0x35, (byte) 0x3b, (byte) 0x10, (byte) 0xa9, (byte) 0x03, (byte) 0xa6,
+            (byte) 0xd0, (byte) 0xab, (byte) 0x1c, (byte) 0x4c,
+    };
+
+    private static final byte[] RFC_7748_X25519_SECRET = new byte[] {
+            (byte) 0xc3, (byte) 0xda, (byte) 0x55, (byte) 0x37, (byte) 0x9d, (byte) 0xe9, (byte) 0xc6, (byte) 0x90,
+            (byte) 0x8e, (byte) 0x94, (byte) 0xea, (byte) 0x4d, (byte) 0xf2, (byte) 0x8d, (byte) 0x08, (byte) 0x4f,
+            (byte) 0x32, (byte) 0xec, (byte) 0xcf, (byte) 0x03, (byte) 0x49, (byte) 0x1c, (byte) 0x71, (byte) 0xf7,
+            (byte) 0x54, (byte) 0xb4, (byte) 0x07, (byte) 0x55, (byte) 0x77, (byte) 0xa2, (byte) 0x85, (byte) 0x52,
+    };
+
+    private PrivateKey rfc7748X25519PrivateKey;
+    private PublicKey rfc7748X25519PublicKey;
+
+    private void setupKeys(Provider p) throws Exception {
+        KeyFactory kf = KeyFactory.getInstance("XDH", p);
+
+        byte[] privateKey;
+        if ("SunEC".equalsIgnoreCase(p.getName())
+                && "11".equals(System.getProperty("java.specification.version"))) {
+            // SunEC in OpenJDK 11 has a bug where the format specified in RFC 8410
+            // Section 7. It uses a single OCTET STRING to represent the key instead
+            // of an OCTET STRING inside of an OCTET STRING as defined in the RFC:
+            // ("For the keys defined in this document, the private key is always an
+            //   opaque byte sequence.  The ASN.1 type CurvePrivateKey is defined in
+            //   this document to hold the byte sequence.  Thus, when encoding a
+            //   OneAsymmetricKey object, the private key is wrapped in a
+            //   CurvePrivateKey object and wrapped by the OCTET STRING of the
+            //   "privateKey" field.")
+            privateKey = RFC_7748_X25519_OUR_PRIV_KEY_BROKEN;
+        } else {
+            privateKey = RFC_7748_X25519_OUR_PRIV_KEY;
+        }
+
+        rfc7748X25519PrivateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(privateKey));
+        rfc7748X25519PublicKey = kf.generatePublic(new X509EncodedKeySpec(RFC_7748_X25519_THEIR_PUB_KEY));
+    }
+
+    @Test
+    public void test_XDHKeyAgreement() throws Exception {
+        for (Provider p : Security.getProviders("KeyAgreement.XDH")) {
+            setupKeys(p);
+
+            KeyAgreement ka = KeyAgreement.getInstance("XDH", p);
+
+            test_x25519_keyAgreement_rfc7748_kat_success(ka);
+        }
+    }
+
+    private void test_x25519_keyAgreement_rfc7748_kat_success(KeyAgreement ka) throws Exception {
+        ka.init(rfc7748X25519PrivateKey);
+        ka.doPhase(rfc7748X25519PublicKey, true);
+
+        assertArrayEquals(RFC_7748_X25519_SECRET, ka.generateSecret());
+    }
+}
diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLContextTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLContextTest.java
index 29c0446..2c68c6f 100644
--- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLContextTest.java
+++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLContextTest.java
@@ -16,6 +16,7 @@
 
 package org.conscrypt.javax.net.ssl;
 
+import static org.conscrypt.TestUtils.isWindows;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
@@ -37,6 +38,7 @@
 import java.security.UnrecoverableKeyException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
@@ -664,14 +666,21 @@
     }
 
     private static void assertContentsInOrder(List<String> expected, String... actual) {
+        List<String> actualList = Arrays.asList(actual);
         if (expected.size() != actual.length) {
             fail("Unexpected length. Expected len <" + expected.size() + ">, actual len <"
                     + actual.length + ">, expected <" + expected + ">, actual <"
-                    + Arrays.asList(actual) + ">");
+                    + actualList + ">");
         }
-        if (!expected.equals(Arrays.asList(actual))) {
-            fail("Unexpected element(s). Expected <" + expected + ">, actual <"
-                    + Arrays.asList(actual) + ">");
+
+        if (isWindows()) {
+            // TODO(prbprbprb): CpuFeatures.isAESHardwareAccelerated is not reliable on windows
+            Collections.sort(actualList);
+            Collections.sort(expected);
+        }
+
+        if (!expected.equals(actualList)) {
+            fail("Unexpected element(s). Expected <" + expected + ">, actual <" + actualList + ">");
         }
     }
 
@@ -711,7 +720,7 @@
         }
 
         if (version[0] == 1) {
-            assert version[1] >= 6;
+            assertTrue(version[1] >= 6);
             return version[1];
         } else {
             return version[0];
diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineTest.java
index 825cfb8..bb81f3c 100644
--- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineTest.java
+++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineTest.java
@@ -17,6 +17,7 @@
 package org.conscrypt.javax.net.ssl;
 
 import static org.conscrypt.TestUtils.UTF_8;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -27,6 +28,7 @@
 import java.io.IOException;
 import java.net.Socket;
 import java.nio.ByteBuffer;
+import java.nio.ReadOnlyBufferException;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
@@ -48,6 +50,7 @@
 import javax.net.ssl.X509ExtendedKeyManager;
 import javax.net.ssl.X509ExtendedTrustManager;
 import org.conscrypt.TestUtils;
+import org.conscrypt.TestUtils.BufferType;
 import org.conscrypt.java.security.StandardNames;
 import org.conscrypt.java.security.TestKeyStore;
 import org.junit.Test;
@@ -782,7 +785,8 @@
             final boolean serverClientMode, final boolean[] finished) throws Exception {
         TestSSLContext c;
         if (!clientClientMode && serverClientMode) {
-            c = TestSSLContext.create(TestKeyStore.getServer(), TestKeyStore.getClient());
+            c = TestSSLContext.create(/* client= */ TestKeyStore.getServer(),
+                    /* server= */ TestKeyStore.getClient());
         } else {
             c = TestSSLContext.create();
         }
@@ -918,6 +922,187 @@
         c.close();
     }
 
+    @Test
+    public void wrapPreconditions() throws Exception {
+        ByteBuffer buffer = ByteBuffer.allocate(10);
+        ByteBuffer[] buffers = new ByteBuffer[] { buffer, buffer, buffer };
+        ByteBuffer[] badBuffers = new ByteBuffer[] { buffer, buffer, null, buffer };
+
+        // Client/server mode not set => IllegalStateException
+        try {
+            newUnconnectedEngine().wrap(buffer, buffer);
+            fail();
+        } catch (IllegalStateException e) {
+            // Expected
+        }
+
+        try {
+            newUnconnectedEngine().wrap(buffers, buffer);
+            fail();
+        } catch (IllegalStateException e) {
+            // Expected
+        }
+
+        try {
+            newUnconnectedEngine().wrap(buffers, 0, 1, buffer);
+            fail();
+        } catch (IllegalStateException e) {
+            // Expected
+        }
+
+        // Read-only destination => ReadOnlyBufferException
+        try {
+            newConnectedEngine().wrap(buffer, buffer.asReadOnlyBuffer());
+            fail();
+        } catch (ReadOnlyBufferException e) {
+            // Expected
+        }
+
+        try {
+            newConnectedEngine().wrap(buffers, buffer.asReadOnlyBuffer());
+            fail();
+        } catch (ReadOnlyBufferException e) {
+            // Expected
+        }
+
+        try {
+            newConnectedEngine().wrap(buffers, 0, 1, buffer.asReadOnlyBuffer());
+            fail();
+        } catch (ReadOnlyBufferException e) {
+            // Expected
+        }
+
+        // Null destination => IllegalArgumentException
+        try {
+            newConnectedEngine().wrap(buffer, null);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            newConnectedEngine().wrap(buffers,  null);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            newConnectedEngine().wrap(buffers, 0, 1, null);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        // Null source => IllegalArgumentException
+        try {
+            newConnectedEngine().wrap((ByteBuffer) null, buffer);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            newConnectedEngine().wrap((ByteBuffer[]) null, buffer);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            newConnectedEngine().wrap(null, 0, 1, buffer);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        // Null entries in buffer array => IllegalArgumentException
+        try {
+            newConnectedEngine().wrap(badBuffers, buffer);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            newConnectedEngine().wrap(badBuffers, 0, badBuffers.length, buffer);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        // Bad offset or length => IndexOutOfBoundsException
+        try {
+            newConnectedEngine().wrap(buffers, 0, 7, buffer);
+            fail();
+        } catch (IndexOutOfBoundsException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void bufferArrayOffsets() throws Exception{
+        TestSSLEnginePair pair = TestSSLEnginePair.create();
+        ByteBuffer tlsBuffer = ByteBuffer.allocate(600);
+        int bufferSize = 100;
+
+        for (BufferType bufferType : BufferType.values()) {
+            ByteBuffer[] sourceBuffers = bufferType.newRandomBuffers(
+                    bufferSize, bufferSize, bufferSize, bufferSize, bufferSize);
+            for (int offset = 0; offset < sourceBuffers.length; offset++) {
+                for (int length = 1; length < sourceBuffers.length - offset; length++) {
+                    // Reset source buffers
+                    for (ByteBuffer buffer : sourceBuffers) {
+                        if (buffer.remaining() == 0) {
+                            buffer.flip();
+                        }
+                        assertEquals(bufferSize, buffer.remaining());
+                    }
+                    // Make an array copy of what we expect to send
+                    byte[] sourceBytes = copyDataFromBuffers(sourceBuffers, offset, length);
+                    byte[] destinationBytes = new byte[sourceBytes.length];
+                    ByteBuffer destination = ByteBuffer.wrap(destinationBytes);
+                    // Send and compare
+                    tlsBuffer.clear();
+                    pair.client.wrap(sourceBuffers, offset, length, tlsBuffer);
+                    tlsBuffer.flip();
+                    pair.server.unwrap(tlsBuffer, destination);
+                    assertArrayEquals(sourceBytes, destinationBytes);
+                }
+            }
+        }
+    }
+
+    private byte[] copyDataFromBuffers(ByteBuffer[] buffers, int offset, int length) {
+        // NB avoids using Arrays.copyOfRange() to prevent any common bugs with
+        // ConscryptEngine.wrap().
+        int size = 0;
+        for (int i = offset; i < offset + length; i++) {
+            size += buffers[i].remaining();
+        }
+        byte[] data = new byte[size];
+        int dataOffset = 0;
+        for (int i = offset; i < offset + length; i++) {
+            ByteBuffer buffer = buffers[i];
+            int remaining = buffer.remaining();
+            buffer.get(data, dataOffset, remaining);
+            buffer.flip();
+            dataOffset += remaining;
+        }
+        return data;
+    }
+
+    private SSLEngine newUnconnectedEngine() {
+        TestSSLContext context = TestSSLContext.create();
+        return context.clientContext.createSSLEngine();
+    }
+
+    private SSLEngine newConnectedEngine() throws Exception {
+        TestSSLEnginePair pair = TestSSLEnginePair.create();
+        assertConnected(pair);
+        return pair.client;
+    }
+
     private void assertConnected(TestSSLEnginePair e) {
         assertConnected(e.client, e.server);
     }
diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java
index 1b84815..d4cdd2a 100644
--- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java
+++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java
@@ -37,6 +37,7 @@
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Random;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
@@ -755,7 +756,7 @@
                     });
             fail();
         } catch (SSLHandshakeException e) {
-            assertEquals(e.getMessage(), "SNI match failed: any.host");
+            assertEquals("SNI match failed: any.host", e.getMessage());
         }
     }
 
@@ -806,6 +807,104 @@
         assertTrue(serverAliasCalled.get());
     }
 
+    // Splits a ByteArray into an array of ByteBuffers each no bigger than the specified size.
+    private ByteBuffer[] splitDataIntoBuffers(byte[] sourceData, int size) {
+        int nbuf = ((sourceData.length - 1) / size) + 1;
+        ByteBuffer[] buffers = new ByteBuffer[nbuf];
+        int buffer = 0;
+        for (int offset = 0; offset < sourceData.length; offset += size, buffer++) {
+            buffers[buffer] = ByteBuffer.allocate(size);
+            int remaining = sourceData.length - offset;
+            buffers[buffer].put(sourceData, offset, Math.min( remaining, size));
+            buffers[buffer].flip();
+        }
+        return buffers;
+    }
+
+    // Sends dataSize bytes of application data, split into an array of ByteBuffers
+    // of size bufferSize and verifies it arrives intact. If offset is non-zero then
+    // additional invalid buffers will be added to the start and end of the buffer array
+    // in order to test the offset and length arguments of wrap().
+    private void sendAppDataInMultipleBuffers(
+            SSLEngine src, SSLEngine dst, int dataSize, int bufferSize)
+            throws SSLException {
+
+        // Generate random data and split into multiple.
+        byte[] sourceData = new byte[dataSize];
+        Random random = new Random(System.currentTimeMillis());
+        random.nextBytes(sourceData);
+        ByteBuffer[] sourceBuffers = splitDataIntoBuffers(sourceData, bufferSize);
+        int length = sourceBuffers.length;
+
+        // Ensure there is no pending outbound data or encrypted data and handshaking is complete.
+        ByteBuffer tlsBuffer = ByteBuffer.allocateDirect(src.getSession().getPacketBufferSize());
+        SSLEngineResult result = src.wrap(EMPTY_BUFFER, tlsBuffer);
+        assertEquals(Status.OK, result.getStatus());
+        assertEquals(HandshakeStatus.NOT_HANDSHAKING, result.getHandshakeStatus());
+        assertEquals(0, result.bytesConsumed());
+        assertEquals(0, result.bytesProduced());
+
+        // Ensure there is no pending inbound data
+        result = dst.unwrap(EMPTY_BUFFER, tlsBuffer);
+        assertEquals(Status.BUFFER_UNDERFLOW, result.getStatus());
+        assertEquals(0, result.bytesConsumed());
+        assertEquals(0, result.bytesProduced());
+
+        // Send the data.  wrap() should consume as many source buffers as needed but
+        // never generate more than one full packet buffer of TLS data, and so unwrap()
+        // should only be needed once per loop iteration.
+        int consumed = 0, produced = 0;
+        ByteBuffer destBuffer = ByteBuffer.allocate(dataSize);
+        while (consumed < dataSize) {
+            String message = String.format("sendData: dataSize=%d, bufSize=%d",
+                    dataSize, bufferSize);
+
+            tlsBuffer.clear();
+            result = src.wrap(sourceBuffers, 0, length, tlsBuffer);
+            assertEquals(message, Status.OK, result.getStatus());
+            consumed += result.bytesConsumed();
+
+            tlsBuffer.flip();
+            result = dst.unwrap(tlsBuffer, destBuffer);
+            assertEquals(message, Status.OK, result.getStatus());
+            produced += result.bytesProduced();
+        }
+        assertEquals(dataSize, consumed);
+        assertEquals(dataSize, produced);
+
+        // Compare source and destination data.
+        destBuffer.flip();
+        // destBuffer is non-direct so will always have a backing array
+        assertArrayEquals(sourceData, destBuffer.array());
+    }
+
+    /**
+     * Tests the multiple {@link ByteBuffer} cases of {@code wrap())} by sending blocks of
+     * application data split into arrays of ByteBuffers of different sizes.
+     *
+     * The main intention is to check that regardless of how the data is structured in
+     * buffers, each call to wrap() always generates a single TLS record that is smaller
+     * than the maximum allowed size, see https://github.com/google/conscrypt/issues/929
+     */
+    @Test
+    public void multipleBuffersOfDifferentSizes() throws Exception {
+        TestSSLEnginePair pair = TestSSLEnginePair.create();
+        SSLSession session = pair.client.getSession();
+        int appBufSize = session.getApplicationBufferSize();
+
+        int[] dataSizes = new int[] { 12, 512, 555, 1500, 8192, appBufSize, 5 * appBufSize};
+        int[] bufferSizes = new int[]
+	    { 53, 512, 8192, appBufSize, appBufSize - 53, appBufSize + 53, 5 * appBufSize};
+        for (int dataSize : dataSizes) {
+            for (int bufSize : bufferSizes) {
+                sendAppDataInMultipleBuffers(pair.client, pair.server, dataSize, bufSize);
+                sendAppDataInMultipleBuffers(pair.server, pair.client, dataSize, bufSize);
+                sendAppDataInMultipleBuffers(pair.client, pair.server, dataSize, bufSize);
+                sendAppDataInMultipleBuffers(pair.server, pair.client, dataSize, bufSize);
+            }
+        }
+    }
+
     private TestKeyStore addServerCertListener(final Runnable callback) {
         TestKeyStore store = TestKeyStore.getServer().copy();
         X509ExtendedKeyManager tm = new ForwardingX509ExtendedKeyManager(
diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionContextTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionContextTest.java
index 45ebd0e..0167162 100644
--- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionContextTest.java
+++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionContextTest.java
@@ -69,6 +69,7 @@
     }
 
     @Test
+    @SuppressWarnings("JdkObsolete") // Public API SSLSessionContext.getIds() uses Enumeration
     public void test_SSLSessionContext_getIds() {
         TestSSLContext c = newTestContext();
         assertSSLSessionContextSize(0, c);
@@ -95,6 +96,7 @@
     }
 
     @Test
+    @SuppressWarnings("JdkObsolete") // Public API SSLSessionContext.getIds() uses Enumeration
     public void test_SSLSessionContext_getSession() {
         TestSSLContext c = newTestContext();
         try {
diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java
index 9f48898..9f99c1f 100644
--- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java
+++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java
@@ -198,14 +198,22 @@
             fail();
         } catch (SSLPeerUnverifiedException expected) {
             // Ignored.
+        } catch (UnsupportedOperationException e) {
+            if (!StandardNames.IS_15_OR_UP) {
+                fail("Should only throw UnsupportedOperationException on OpenJDK 15 or up");
+            }
         }
         assertNotNull(s.client.getPeerCertificates());
-        TestKeyStore.assertChainLength(s.client.getPeerCertificateChain());
+        TestKeyStore.assertChainLength(s.client.getPeerCertificates());
         try {
             assertNull(s.server.getPeerCertificateChain());
             fail();
         } catch (SSLPeerUnverifiedException expected) {
             // Ignored.
+        } catch (UnsupportedOperationException e) {
+            if (!StandardNames.IS_15_OR_UP) {
+                fail("Should only throw UnsupportedOperationException on OpenJDK 15 or up");
+            }
         }
         s.close();
     }
diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java
index aa603b0..17c3e77 100644
--- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java
+++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java
@@ -72,12 +72,12 @@
 import org.conscrypt.tlswire.util.TlsProtocolVersion;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import tests.net.DelegatingSSLSocketFactory;
 import tests.util.ForEachRunner;
-import tests.util.ForEachRunner.Callback;
 import tests.util.Pair;
 
 @RunWith(JUnit4.class)
@@ -422,6 +422,7 @@
      * lower span of contiguous protocols is used in practice.
      */
     @Test
+    @Ignore("Needs TLS 1.0 or 1.1 which are scheduled for deprecation")
     public void test_SSLSocket_noncontiguousProtocols_useLower() throws Exception {
         TestSSLContext c = TestSSLContext.create();
         SSLContext clientContext = c.clientContext;
@@ -455,6 +456,7 @@
      * for both client and server isn't supported by the other.
      */
     @Test
+    @Ignore("Needs TLS 1.0 or 1.1 which are scheduled for deprecation")
     public void test_SSLSocket_noncontiguousProtocols_canNegotiate() throws Exception {
         TestSSLContext c = TestSSLContext.create();
         SSLContext clientContext = c.clientContext;
@@ -875,7 +877,7 @@
 
     @Test
     public void test_SSLSocket_ClientHello_cipherSuites() throws Exception {
-        ForEachRunner.runNamed(new Callback<SSLSocketFactory>() {
+        ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() {
             @Override
             public void run(SSLSocketFactory sslSocketFactory) throws Exception {
                 ClientHello clientHello = TlsTester
@@ -906,7 +908,7 @@
 
     @Test
     public void test_SSLSocket_ClientHello_supportedCurves() throws Exception {
-        ForEachRunner.runNamed(new Callback<SSLSocketFactory>() {
+        ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() {
             @Override
             public void run(SSLSocketFactory sslSocketFactory) throws Exception {
                 ClientHello clientHello = TlsTester
@@ -932,7 +934,7 @@
 
     @Test
     public void test_SSLSocket_ClientHello_clientProtocolVersion() throws Exception {
-        ForEachRunner.runNamed(new Callback<SSLSocketFactory>() {
+        ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() {
             @Override
             public void run(SSLSocketFactory sslSocketFactory) throws Exception {
                 ClientHello clientHello = TlsTester
@@ -944,7 +946,7 @@
 
     @Test
     public void test_SSLSocket_ClientHello_compressionMethods() throws Exception {
-        ForEachRunner.runNamed(new Callback<SSLSocketFactory>() {
+        ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() {
             @Override
             public void run(SSLSocketFactory sslSocketFactory) throws Exception {
                 ClientHello clientHello = TlsTester
@@ -1009,6 +1011,7 @@
 
     // Confirms that communication without the TLS_FALLBACK_SCSV cipher works as it always did.
     @Test
+    @Ignore("Needs TLS 1.0 or 1.1 which are scheduled for deprecation")
     public void test_SSLSocket_sendsNoTlsFallbackScsv_Fallback_Success() throws Exception {
         TestSSLContext context = TestSSLContext.create();
         // TLS_FALLBACK_SCSV is only applicable to TLS <= 1.2
@@ -1049,6 +1052,7 @@
     }
 
     @Test
+    @Ignore("Needs TLS 1.0 or 1.1 which are scheduled for deprecation")
     public void test_SSLSocket_sendsTlsFallbackScsv_InappropriateFallback_Failure()
             throws Exception {
         TestSSLContext context = TestSSLContext.create();
@@ -1102,6 +1106,7 @@
     }
 
     @Test
+    @Ignore("Needs TLS 1.0 or 1.1 which are scheduled for deprecation")
     public void test_SSLSocket_tlsFallback_byVersion() throws Exception {
         String[] supportedProtocols =
                 SSLContext.getDefault().getDefaultSSLParameters().getProtocols();
diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java
index 09e351d..01eb6c9 100644
--- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java
+++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java
@@ -16,6 +16,10 @@
 
 package org.conscrypt.javax.net.ssl;
 
+import static org.conscrypt.TestUtils.osName;
+import static org.conscrypt.TestUtils.isOsx;
+import static org.conscrypt.TestUtils.isLinux;
+import static org.conscrypt.TestUtils.isWindows;
 import static org.conscrypt.TestUtils.UTF_8;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -110,7 +114,6 @@
 import org.junit.runners.Parameterized;
 import tests.net.DelegatingSSLSocketFactory;
 import tests.util.ForEachRunner;
-import tests.util.ForEachRunner.Callback;
 import tests.util.Pair;
 
 /**
@@ -362,6 +365,7 @@
         client.addHandshakeCompletedListener(new HandshakeCompletedListener() {
             @Override
             public void handshakeCompleted(HandshakeCompletedEvent event) {
+                SSLSocket socket = null;
                 try {
                     SSLSession session = event.getSession();
                     String cipherSuite = event.getCipherSuite();
@@ -371,7 +375,7 @@
                         event.getPeerCertificateChain();
                     Principal peerPrincipal = event.getPeerPrincipal();
                     Principal localPrincipal = event.getLocalPrincipal();
-                    SSLSocket socket = event.getSocket();
+                    socket = event.getSocket();
                     assertNotNull(session);
                     byte[] id = session.getId();
                     assertNotNull(id);
@@ -408,16 +412,19 @@
                     assertNotNull(socket);
                     assertSame(client, socket);
                     assertNull(socket.getHandshakeSession());
+                } catch (RuntimeException e) {
+                    throw e;
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                } finally {
                     synchronized (handshakeCompletedListenerCalled) {
                         handshakeCompletedListenerCalled[0] = true;
                         handshakeCompletedListenerCalled.notify();
                     }
                     handshakeCompletedListenerCalled[0] = true;
-                    socket.removeHandshakeCompletedListener(this);
-                } catch (RuntimeException e) {
-                    throw e;
-                } catch (Exception e) {
-                    throw new RuntimeException(e);
+                    if (socket != null) {
+                        socket.removeHandshakeCompletedListener(this);
+                    }
                 }
             }
         });
@@ -1467,9 +1474,15 @@
      * thread will interrupt another thread blocked writing on the same
      * socket.
      *
+     * Currently disabled: If the victim thread is not actually blocked in a write
+     * call then ConscryptEngineSocket can corrupt the output due to unsynchronized
+     * concurrent access to the socket's output stream and cause flakes: b/161347005
+     * TODO(prb): Re-enable after underlying bug resolved
+     *
      * See also b/147323301 where close() triggered an infinite loop instead.
      */
     @Test
+    @Ignore
     public void test_SSLSocket_interrupt_write_withAutoclose() throws Exception {
         final TestSSLContext c = new TestSSLContext.Builder()
             .clientProtocol(clientVersion)
@@ -1480,6 +1493,8 @@
             underlying, c.host.getHostName(), c.port, true);
         final byte[] data = new byte[1024 * 64];
 
+        // TODO(b/161347005): Re-enable once engine-based socket interruption works correctly.
+        assumeFalse(isConscryptEngineSocket(wrapping));
         Future<Void> clientFuture = runAsync(new Callable<Void>() {
             @Override
             public Void call() throws Exception {
@@ -1493,13 +1508,6 @@
                     fail();
                 } catch (SocketException expected) {
                     assertTrue(expected.getMessage().contains("closed"));
-                } catch (SSLException e) {
-                    // TODO(b/159199048): Workaround for known TreeHugger
-                    // cf_x86 presubmit failure which doesn't occur on non-TreeHugger
-                    // cf_x86 or real devices.
-                    if (!e.getMessage().contains("Engine bytesProduced")) {
-                        throw e;
-                    }
                 }
                 return null;
             }
@@ -1553,7 +1561,7 @@
 
     @Test
     public void test_SSLSocket_ClientHello_SNI() throws Exception {
-        ForEachRunner.runNamed(new Callback<SSLSocketFactory>() {
+        ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() {
             @Override
             public void run(SSLSocketFactory sslSocketFactory) throws Exception {
                 ClientHello clientHello = TlsTester
@@ -1572,7 +1580,7 @@
     public void test_SSLSocket_ClientHello_ALPN() throws Exception {
         final String[] protocolList = new String[] { "h2", "http/1.1" };
         
-        ForEachRunner.runNamed(new Callback<SSLSocketFactory>() {
+        ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() {
             @Override
             public void run(SSLSocketFactory sslSocketFactory) throws Exception {
                 ClientHello clientHello = TlsTester.captureTlsHandshakeClientHello(executor,
@@ -2082,23 +2090,6 @@
         return "ConscryptEngineSocket".equals(clazz.getSimpleName());
     }
 
-    private static String osName() {
-        return System.getProperty("os.name").toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", "");
-    }
-
-    private static boolean isLinux() {
-        return osName().startsWith("linux");
-    }
-
-    private static boolean isWindows() {
-        return osName().startsWith("windows");
-    }
-
-    private static boolean isOsx() {
-        String name = osName();
-        return name.startsWith("macosx") || name.startsWith("osx");
-    }
-
     private <T> Future<T> runAsync(Callable<T> callable) {
         return executor.submit(callable);
     }
diff --git a/common/src/test/java/org/conscrypt/metrics/CipherSuiteTest.java b/common/src/test/java/org/conscrypt/metrics/CipherSuiteTest.java
new file mode 100644
index 0000000..2ada251
--- /dev/null
+++ b/common/src/test/java/org/conscrypt/metrics/CipherSuiteTest.java
@@ -0,0 +1,19 @@
+package org.conscrypt.metrics;
+
+import static org.junit.Assert.assertSame;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class CipherSuiteTest {
+
+    @Test
+    public void consistency() {
+        for (CipherSuite cipherSuite : CipherSuite.values()) {
+            assertSame(cipherSuite, CipherSuite.forName(cipherSuite.name()));
+        }
+        assertSame(CipherSuite.UNKNOWN_CIPHER_SUITE, CipherSuite.forName("random junk"));
+    }
+}
diff --git a/common/src/test/java/org/conscrypt/metrics/OptionalMethodTest.java b/common/src/test/java/org/conscrypt/metrics/OptionalMethodTest.java
new file mode 100644
index 0000000..5e85539
--- /dev/null
+++ b/common/src/test/java/org/conscrypt/metrics/OptionalMethodTest.java
@@ -0,0 +1,62 @@
+package org.conscrypt.metrics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class OptionalMethodTest {
+
+    @Test
+    public void workingMethod() {
+        OptionalMethod substring =
+                new OptionalMethod(String.class, "substring", int.class, int.class);
+        assertNotNull(substring);
+
+        assertEquals("put", substring.invoke("input", 2, 5));
+    }
+
+    @Test
+    public void nullClass() {
+        OptionalMethod substring =
+                new OptionalMethod(null, "substring", int.class, int.class);
+        assertNotNull(substring);
+
+        assertNull(substring.invoke("input", 2, 5));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullMethodName() {
+        new OptionalMethod(String.class, null, int.class, int.class);
+    }
+
+    @Test
+    public void nullArgumentClasses() {
+        OptionalMethod substring = new OptionalMethod(String.class, "substring", int.class, null);
+        assertNotNull(substring);
+
+        assertNull(substring.invoke("input", 2, 5));
+    }
+
+    @Test
+    public void noSuchMethodName() {
+        OptionalMethod subwrong =
+                new OptionalMethod(null, "subwrong", int.class, int.class);
+        assertNotNull(subwrong);
+
+        assertNull(subwrong.invoke("input", 2, 5));
+    }
+
+    @Test
+    public void noSuchMethodArgs() {
+        OptionalMethod subwrong =
+                new OptionalMethod(null, "substring", long.class, byte[].class);
+        assertNotNull(subwrong);
+
+        assertNull(subwrong.invoke("input", 2, 5));
+    }
+}
diff --git a/common/src/test/java/org/conscrypt/metrics/ProtocolTest.java b/common/src/test/java/org/conscrypt/metrics/ProtocolTest.java
new file mode 100644
index 0000000..5cc4358
--- /dev/null
+++ b/common/src/test/java/org/conscrypt/metrics/ProtocolTest.java
@@ -0,0 +1,20 @@
+package org.conscrypt.metrics;
+
+import static org.junit.Assert.assertSame;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ProtocolTest {
+    @Test
+    @Ignore("Test intended for MTS only.")
+    public void consistency() {
+        for (Protocol protocol : Protocol.values()) {
+            String name = protocol.name().replace('_', '.');
+            assertSame(protocol, Protocol.forName(name));
+        }
+    }
+}
diff --git a/common/src/test/resources/crypto/macs.csv b/common/src/test/resources/crypto/macs.csv
new file mode 100644
index 0000000..3eea581
--- /dev/null
+++ b/common/src/test/resources/crypto/macs.csv
@@ -0,0 +1,13 @@
+# AES-CMAC test vectors from RFC 4493
+# HMAC test vectors are a very small subset of NIST's
+# Data is in the format:
+# algorithm,key,message,mac
+AESCMAC,2b7e151628aed2a6abf7158809cf4f3c,,bb1d6929e95937287fa37d129b756746
+AESCMAC,2b7e151628aed2a6abf7158809cf4f3c,6bc1bee22e409f96e93d7e117393172a,070a16b46b4d4144f79bdd9dd04a287c
+AESCMAC,2b7e151628aed2a6abf7158809cf4f3c,6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411,dfa66747de9ae63030ca32611497c827
+AESCMAC,2b7e151628aed2a6abf7158809cf4f3c,6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710,51f0bebf7e3b9d92fc49741779363cfe
+HmacSHA224,0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b,4869205468657265,896fb1128abbdf196832107cd49df33f47b4b1169912ba4f53684b22
+HmacSHA256,0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b,4869205468657265,b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7
+HmacSHA512,0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b,4869205468657265,87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854
+HmacSHA224,4a656665,7768617420646f2079612077616e7420666f72206e6f7468696e673f,a30e01098bc6dbbf45690f3a7e9e6d0f8bbea2a39e6148008fd05e44
+HmacSHA256,4a656665,7768617420646f2079612077616e7420666f72206e6f7468696e673f,5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843
diff --git a/constants/build.gradle b/constants/build.gradle
index 977b21f..a43ea73 100644
--- a/constants/build.gradle
+++ b/constants/build.gradle
@@ -71,4 +71,4 @@
 }
 
 // Disable the javadoc task.
-tasks.withType(Javadoc).all { enabled = false }
+tasks.withType(Javadoc).configureEach { enabled = false }
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..5bac8ac
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+android.useAndroidX=true
diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle
index 94c8946..6b3ef1f 100644
--- a/gradle/publishing.gradle
+++ b/gradle/publishing.gradle
@@ -3,19 +3,6 @@
 
 def isSnapshot = project.version.contains('SNAPSHOT')
 
-signing {
-    required false
-    sign publishing.publications
-}
-
-tasks.register("signPublications").configure {
-    configurations.archives.allArtifacts.each {
-        if (it.type == 'jar' && it.classifier != 'sources' && it.classifier != 'javadoc') {
-            signJar(it.file.absolutePath)
-        }
-    }
-}
-
 publishing {
     publications {
         maven(MavenPublication) {
@@ -69,3 +56,17 @@
         }
     }
 }
+
+signing {
+    required false
+    sign publishing.publications.maven
+}
+
+signMavenPublication.doFirst {
+    publishing.publications.maven.artifacts.each {
+        if (it.file.absolutePath.endsWith('.jar') && it.classifier != 'sources' && it.classifier != 'javadoc') {
+            logger.info("Signing jar: ${it.file.absolutePath}")
+            signJar(it.file.absolutePath)
+        }
+   }
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 5c2d1cf..62d4c05 100644
--- a/gradle/wrapper/gradle-wrapper.jar
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0ebb310..186b715 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index 83f2acf..fbd7c51 100755
--- a/gradlew
+++ b/gradlew
@@ -82,6 +82,7 @@
 
 CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 
+
 # Determine the Java command to use to start the JVM.
 if [ -n "$JAVA_HOME" ] ; then
     if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -129,6 +130,7 @@
 if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
     APP_HOME=`cygpath --path --mixed "$APP_HOME"`
     CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    
     JAVACMD=`cygpath --unix "$JAVACMD"`
 
     # We build the pattern for arguments to be converted via cygpath
@@ -154,19 +156,19 @@
         else
             eval `echo args$i`="\"$arg\""
         fi
-        i=$((i+1))
+        i=`expr $i + 1`
     done
     case $i in
-        (0) set -- ;;
-        (1) set -- "$args0" ;;
-        (2) set -- "$args0" "$args1" ;;
-        (3) set -- "$args0" "$args1" "$args2" ;;
-        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
-        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
-        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
-        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
-        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
-        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
     esac
 fi
 
@@ -175,14 +177,9 @@
     for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
     echo " "
 }
-APP_ARGS=$(save "$@")
+APP_ARGS=`save "$@"`
 
 # Collect all arguments for the java command, following the shell quoting and substitution rules
 eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
 
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
-  cd "$(dirname "$0")"
-fi
-
 exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index 24467a1..a9f778a 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -29,6 +29,9 @@
 set APP_BASE_NAME=%~n0

 set APP_HOME=%DIRNAME%

 

+@rem Resolve any "." and ".." in APP_HOME to make it shorter.

+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

+

 @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

 set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

 

@@ -81,6 +84,7 @@
 

 set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

 

+

 @rem Execute Gradle

 "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

 

diff --git a/libcore-stub/build.gradle b/libcore-stub/build.gradle
index 411d2aa..7411459 100644
--- a/libcore-stub/build.gradle
+++ b/libcore-stub/build.gradle
@@ -16,4 +16,4 @@
 }
 
 // Disable the javadoc task.
-tasks.withType(Javadoc).all { enabled = false }
+tasks.withType(Javadoc).configureEach { enabled = false }
diff --git a/lint-baseline.xml b/lint-baseline.xml
new file mode 100644
index 0000000..70db717
--- /dev/null
+++ b/lint-baseline.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
+
+    <issue
+        id="NewApi"
+        message="Call requires API level R (current min is 29): `android.system.ErrnoException#rethrowAsSocketException`"
+        errorLine1="            throw errnoException.rethrowAsSocketException();"
+        errorLine2="                                 ~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="external/conscrypt/repackaged/platform/src/main/java/com/android/org/conscrypt/Platform.java"
+            line="134"
+            column="34"/>
+    </issue>
+
+</issues>
diff --git a/openjdk-uber/build.gradle b/openjdk-uber/build.gradle
index cf1b7f5..6755beb 100644
--- a/openjdk-uber/build.gradle
+++ b/openjdk-uber/build.gradle
@@ -34,7 +34,8 @@
     /**
      * Copy the native libraries to the resources directory.
      */
-    task copySharedLibs(type: Copy, dependsOn: configurations.uberJar) {
+    def copySharedLibs = tasks.register("copySharedLibs",  Copy) {
+        dependsOn configurations.uberJar
         from {
             configurations.uberJar.collect {
                 zipTree(it)
@@ -43,12 +44,15 @@
         include '/META-INF/native/**'
         into file(resourcesDir)
     }
-    jar.dependsOn copySharedLibs
+    tasks.named("jar").configure {
+        dependsOn copySharedLibs
+    }
 
     /**
      * Copy the object files to the classes directory.
      */
-    task copyClasses(type: Copy, dependsOn: configurations.uberJar) {
+    def copyClasses = tasks.register("copyClasses", Copy) {
+        dependsOn configurations.uberJar
         from {
             configurations.uberJar.collect {
                 zipTree(it)
@@ -57,15 +61,20 @@
         exclude '/META-INF/**'
         into file(classesDir)
     }
-    jar.dependsOn copyClasses
+    tasks.named("jar").configure {
+        dependsOn copyClasses
+    }
 
-    task copySources(type: Copy, dependsOn: ":conscrypt-openjdk:sourcesJar") {
+    def copySources = tasks.register("copySources", Copy) {
+        dependsOn ":conscrypt-openjdk:sourcesJar"
         from {
             project(":conscrypt-openjdk").sourceSets.main.java
         }
         into file(sourcesDir)
     }
-    sourcesJar.dependsOn copySources
+    tasks.named("sourcesJar").configure {
+        dependsOn copySources
+    }
 
     // Note that this assumes that the version of BoringSSL for each
     // artifact exactly matches the one on the current system.
@@ -84,7 +93,7 @@
     }
 } else {
     // Not building an uber jar - disable all tasks.
-    tasks.collect {
+    tasks.configureEach {
         it.enabled = false
     }
 }
diff --git a/openjdk/build.gradle b/openjdk/build.gradle
index c1e5181..56a2ea8 100644
--- a/openjdk/build.gradle
+++ b/openjdk/build.gradle
@@ -1,4 +1,9 @@
+plugins {
+    id 'com.github.johnrengelman.shadow' version '6.0.0'
+}
+
 import aQute.bnd.gradle.BundleTaskConvention
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
 import org.codehaus.groovy.runtime.InvokerHelper
 
 apply plugin: 'biz.aQute.bnd.builder'
@@ -82,12 +87,18 @@
     dependsOn generateProperties
 }
 
-task platformJar(type: Jar) {
+tasks.register("platformJar", Jar) {
     from sourceSets.platform.output
 }
 
+tasks.register("testJar", ShadowJar) {
+    classifier = 'tests'
+    configurations = [project.configurations.testRuntime]
+    from sourceSets.test.output
+}
+
 if (isExecutableOnPath('cpplint')) {
-    task cpplint(type: Exec) {
+    def cpplint = tasks.register("cpplint", Exec) {
         executable = 'cpplint'
 
         // TODO(nmittler): Is there a better way of getting the JNI sources?
@@ -168,8 +179,8 @@
     // This is listed as compile-only, but we absorb its contents.
     compileOnly project(':conscrypt-constants')
 
-    testImplementation project(':conscrypt-constants'),
-            project(':conscrypt-testing'),
+    testCompile project(':conscrypt-constants'),
+            project(path: ':conscrypt-testing', configuration: 'runtime'),
             libraries.junit,
             libraries.mockito
 
@@ -190,8 +201,7 @@
 def addNativeJar(nativeClassifier) {
     // Create a JAR for this configuration and add it to the output archives.
     SourceSet sourceSet = sourceSet(nativeClassifier)
-    def jarTaskName = sourceSet.jarTaskName
-    task "$jarTaskName"(type: Jar) { Jar t ->
+    def jarTask = tasks.register(sourceSet.jarTaskName, Jar) { Jar t ->
         // Depend on the regular classes task
         dependsOn classes
         manifest = jar.manifest
@@ -206,13 +216,11 @@
         }
     }
 
-    def jarTask = tasks["$jarTaskName"]
-
     // Add the jar task to the standard build.
     jar.dependsOn jarTask
 
     // Add it to the publishing archives list.
-    publishing.publications.maven.artifact jarTask
+    publishing.publications.maven.artifact jarTask.get()
 }
 
 
@@ -233,13 +241,13 @@
     include suiteClass, "org/conscrypt/ConscryptOpenJdkSuite.class"
 }
 
-task testEngineSocket(type: Test) {
+def testFdSocket = tasks.register("testFdSocket", Test) {
     include suiteClass, "org/conscrypt/ConscryptOpenJdkSuite.class"
     InvokerHelper.setProperties(testLogging, test.testLogging.properties)
     systemProperties = test.systemProperties
-    systemProperty "org.conscrypt.useEngineSocketByDefault", true
+    systemProperty "org.conscrypt.useEngineSocketByDefault", false
 }
-check.dependsOn testEngineSocket
+check.dependsOn testFdSocket
 
 // Tests that involve interoperation with the OpenJDK TLS provider (generally to
 // test renegotiation, since we don't support initiating renegotiation but do
@@ -247,7 +255,7 @@
 // if Conscrypt is installed as the default provider, so these need to live in
 // a different task than the other tests, most of which need Conscrypt to be
 // installed to function.
-task testInterop(type: Test) {
+def testInterop = tasks.register("testInterop", Test) {
     include "org/conscrypt/ConscryptEngineTest.class"
     include "org/conscrypt/RenegotiationTest.class"
 }
@@ -331,10 +339,18 @@
                         // Static link to BoringSSL
                         linker.args "-O3",
                                 "-fvisibility=hidden",
-                                "-lstdc++",
                                 "-lpthread",
                                 libPath + "/ssl/libssl.a",
                                 libPath + "/crypto/libcrypto.a"
+                        if (targetPlatform.operatingSystem.isLinux()) {
+                            // Static link libstdc++ and libgcc because
+                            // they are not available in some restrictive Linux
+                            // environments.
+                            linker.args "-static-libstdc++",
+                                    "-static-libgcc"
+                        } else {
+                            linker.args "-lstdc++"
+                        }
                     } else if (toolChain in VisualCpp) {
                         cppCompiler.define "DLL_EXPORT"
                         cppCompiler.define "WIN32_LEAN_AND_MEAN"
@@ -400,8 +416,8 @@
             def source = binary.sharedLibraryFile
 
             // Copies the native library to a resource location that will be included in the jar.
-            def copyTaskName = "copyNativeLib${sourceSetName}"
-            task "$copyTaskName"(type: Copy, dependsOn: binary.tasks.link) {
+            def copyTask = project.tasks.register("copyNativeLib${sourceSetName}", Copy) {
+                dependsOn binary.tasks.link
                 from source
                 // Rename the artifact to include the generated classifier
                 rename '(.+)(\\.[^\\.]+)', "\$1-$classifier\$2"
@@ -413,12 +429,14 @@
             if (osName == 'linux' && (!rootProject.hasProperty('nostrip') ||
                     !rootProject.nostrip.toBoolean())) {
                 def stripTask = binary.tasks.taskName("strip")
-                    task "$stripTask"(type: Exec) {
-                        dependsOn binary.tasks.link
-                        executable "strip"
-                        args binary.tasks.link.linkedFile.asFile.get()
-                    }
-                project.tasks["$copyTaskName"].dependsOn stripTask
+                project.tasks.register(stripTask as String, Exec) {
+                    dependsOn binary.tasks.link
+                    executable "strip"
+                    args binary.tasks.link.linkedFile.asFile.get()
+                }
+                copyTask.configure {
+                    dependsOn stripTask
+                }
             }
         }
     }
diff --git a/openjdk/src/main/java/org/conscrypt/Java8PlatformUtil.java b/openjdk/src/main/java/org/conscrypt/Java8PlatformUtil.java
index bb625be..2206b60 100644
--- a/openjdk/src/main/java/org/conscrypt/Java8PlatformUtil.java
+++ b/openjdk/src/main/java/org/conscrypt/Java8PlatformUtil.java
@@ -42,15 +42,6 @@
         }
     }
 
-    static void getSSLParameters(
-            SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket) {
-        getSSLParameters(params, impl);
-        if (impl.getUseSni() && AddressUtils.isValidSniHostname(socket.getHostname())) {
-            params.setServerNames(Collections.singletonList(
-                    (SNIServerName) new SNIHostName(socket.getHostname())));
-        }
-    }
-
     static void setSSLParameters(
             SSLParameters params, SSLParametersImpl impl, ConscryptEngine engine) {
         setSSLParameters(params, impl);
@@ -60,6 +51,16 @@
             engine.setHostname(sniHost);
         }
     }
+
+    static void getSSLParameters(
+            SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket) {
+        getSSLParameters(params, impl);
+        if (impl.getUseSni() && AddressUtils.isValidSniHostname(socket.getHostname())) {
+            params.setServerNames(Collections.singletonList(
+                    (SNIServerName) new SNIHostName(socket.getHostname())));
+        }
+    }
+
     static void getSSLParameters(
             SSLParameters params, SSLParametersImpl impl, ConscryptEngine engine) {
         getSSLParameters(params, impl);
diff --git a/openjdk/src/main/java/org/conscrypt/Platform.java b/openjdk/src/main/java/org/conscrypt/Platform.java
index 5f9d0f6..d102524 100644
--- a/openjdk/src/main/java/org/conscrypt/Platform.java
+++ b/openjdk/src/main/java/org/conscrypt/Platform.java
@@ -99,6 +99,7 @@
             getCurveNameMethod = ECParameterSpec.class.getDeclaredMethod("getCurveName");
             getCurveNameMethod.setAccessible(true);
         } catch (Exception ignored) {
+            // Method not available, leave it as null, which is checked before use
         }
         GET_CURVE_NAME_METHOD = getCurveNameMethod;
     }
@@ -193,9 +194,19 @@
         }
 
         try {
-            Field f_impl = Socket.class.getDeclaredField("impl");
-            f_impl.setAccessible(true);
-            Object socketImpl = f_impl.get(s);
+            Method m_getImpl = Socket.class.getDeclaredMethod("getImpl");
+            m_getImpl.setAccessible(true);
+            Object socketImpl = m_getImpl.invoke(s);
+            try {
+                Class<?> c_delegatingSocketImpl = Class.forName("java.net.DelegatingSocketImpl");
+                if (c_delegatingSocketImpl.isAssignableFrom(socketImpl.getClass())) {
+                    Method m_delegate = c_delegatingSocketImpl.getDeclaredMethod("delegate");
+                    m_delegate.setAccessible(true);
+                    socketImpl = m_delegate.invoke(socketImpl);
+                }
+            } catch (Exception ignored) {
+                // Ignored
+            }
             Field f_fd = SocketImpl.class.getDeclaredField("fd");
             f_fd.setAccessible(true);
             return (FileDescriptor) f_fd.get(socketImpl);
@@ -580,10 +591,8 @@
             return originalHostName;
         } catch (InvocationTargetException e) {
             throw new RuntimeException("Failed to get originalHostName", e);
-        } catch (ClassNotFoundException ignore) {
+        } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException ignore) {
             // passthrough and return addr.getHostAddress()
-        } catch (IllegalAccessException ignore) {
-        } catch (NoSuchMethodException ignore) {
         }
 
         return addr.getHostAddress();
@@ -657,9 +666,10 @@
         KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
         try {
             ks.load(null, null);
-        } catch (CertificateException ignored) {
         } catch (NoSuchAlgorithmException ignored) {
-        } catch (IOException ignored) {
+            // TODO(prb): Should this be re-thrown? It happens if "the algorithm used to check
+            // the integrity of the KeyStore cannot be found".
+        } catch (IOException | CertificateException ignored) {
             // We're not loading anything, so ignore it
         }
         // Find the highest-priority non-Conscrypt provider that provides a PKIX
@@ -707,7 +717,7 @@
         return null;
     }
 
-    static CertBlacklist newDefaultBlacklist() {
+    static CertBlocklist newDefaultBlocklist() {
         return null;
     }
 
@@ -785,4 +795,21 @@
             });
         }
     }
+
+    public static ConscryptHostnameVerifier getDefaultHostnameVerifier() {
+        return  OkHostnameVerifier.strictInstance();
+    }
+
+    @SuppressWarnings("unused")
+    static long getMillisSinceBoot() {
+        return 0;
+    }
+
+    @SuppressWarnings("unused")
+    static void countTlsHandshake(
+            boolean success, String protocol, String cipherSuite, long duration) {}
+
+    public static boolean isJavaxCertificateSupported() {
+        return JAVA_VERSION < 15;
+    }
 }
diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java b/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java
index f93c291..adb9184 100644
--- a/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java
+++ b/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java
@@ -19,24 +19,29 @@
 import static org.conscrypt.TestUtils.openTestFile;
 import static org.conscrypt.TestUtils.readTestFile;
 import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.Mockito.when;
 
 import java.io.IOException;
 import java.lang.reflect.Field;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
 import java.security.KeyManagementException;
 import java.security.KeyStore;
 import java.security.PrivateKey;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
+import java.util.Random;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -79,12 +84,6 @@
                     OpenSSLContextImpl context, ServerSocket server, SSLSocketFactory factory) {
                 return null;
             }
-
-            @Override
-            Socket newServerSocket(OpenSSLContextImpl context, ServerSocket server,
-                    SSLSocketFactory factory) throws IOException {
-                return server.accept();
-            }
         },
         PLAIN {
             @Override
@@ -92,11 +91,14 @@
                     SSLSocketFactory factory) throws IOException {
                 return new Socket(server.getInetAddress(), server.getLocalPort());
             }
-
+        },
+        CHANNEL {
             @Override
-            Socket newServerSocket(OpenSSLContextImpl context, ServerSocket server,
+            Socket newClientSocket(OpenSSLContextImpl context, ServerSocket server,
                     SSLSocketFactory factory) throws IOException {
-                return server.accept();
+                SocketChannel channel = SocketChannel.open();
+                channel.connect(server.getLocalSocketAddress());
+                return channel.socket();
             }
         },
         SSL {
@@ -121,8 +123,11 @@
 
         abstract Socket newClientSocket(OpenSSLContextImpl context, ServerSocket server,
                 SSLSocketFactory factory) throws IOException;
-        abstract Socket newServerSocket(OpenSSLContextImpl context, ServerSocket server,
-                SSLSocketFactory factory) throws IOException;
+
+        Socket newServerSocket(OpenSSLContextImpl context, ServerSocket server,
+                SSLSocketFactory factory) throws IOException {
+            return server.accept();
+        }
     }
 
     /**
@@ -191,15 +196,43 @@
         }
     }
 
-    @Parameters(name = "{0} wrapping {1}")
+    public enum ServerSocketType {
+        PLAIN {
+            @Override
+            public ServerSocket newServerSocket() throws IOException {
+                return new ServerSocket(0, 50, TestUtils.getLoopbackAddress());
+            }
+        },
+        CHANNEL {
+            @Override
+            public ServerSocket newServerSocket() throws IOException {
+                ServerSocketChannel channel = ServerSocketChannel.open();
+                InetAddress localAddress = TestUtils.getLoopbackAddress();
+                channel.socket().bind(new InetSocketAddress(localAddress.getHostName(), 0), 50);
+                return channel.socket();
+            }
+        };
+        public abstract ServerSocket newServerSocket() throws IOException;
+    }
+
+    @Parameters(name = "{0} wrapping {1} connecting to {2}")
     public static Object[][] data() {
         return new Object[][] {
-            {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.NONE},
-            {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.PLAIN},
+            {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.NONE, ServerSocketType.PLAIN},
+            {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.NONE, ServerSocketType.CHANNEL},
+            {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.PLAIN, ServerSocketType.PLAIN},
+            {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.PLAIN, ServerSocketType.CHANNEL},
+            {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.CHANNEL, ServerSocketType.PLAIN},
+            {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.CHANNEL, ServerSocketType.CHANNEL},
             // Not supported: {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.SSL},
-            {SocketType.ENGINE, UnderlyingSocketType.NONE},
-            {SocketType.ENGINE, UnderlyingSocketType.PLAIN},
-            {SocketType.ENGINE, UnderlyingSocketType.SSL}};
+            {SocketType.ENGINE, UnderlyingSocketType.NONE, ServerSocketType.PLAIN},
+            {SocketType.ENGINE, UnderlyingSocketType.NONE, ServerSocketType.CHANNEL},
+            {SocketType.ENGINE, UnderlyingSocketType.PLAIN, ServerSocketType.PLAIN},
+            {SocketType.ENGINE, UnderlyingSocketType.PLAIN, ServerSocketType.CHANNEL},
+            {SocketType.ENGINE, UnderlyingSocketType.CHANNEL, ServerSocketType.PLAIN},
+            {SocketType.ENGINE, UnderlyingSocketType.CHANNEL, ServerSocketType.CHANNEL},
+            {SocketType.ENGINE, UnderlyingSocketType.SSL, ServerSocketType.PLAIN},
+            {SocketType.ENGINE, UnderlyingSocketType.SSL, ServerSocketType.CHANNEL}};
     }
 
     @Parameter
@@ -208,6 +241,9 @@
     @Parameter(1)
     public UnderlyingSocketType underlyingSocketType;
 
+    @Parameter(2)
+    public ServerSocketType serverSocketType;
+
     private X509Certificate ca;
     private X509Certificate cert;
     private X509Certificate certEmbedded;
@@ -215,6 +251,7 @@
 
     private Field contextSSLParameters;
     private ExecutorService executor;
+    private final Random random = new Random(System.currentTimeMillis());
 
     @Before
     public void setUp() throws Exception {
@@ -388,7 +425,7 @@
         }
 
         void doHandshake() throws Exception {
-            ServerSocket listener = newServerSocket();
+            ServerSocket listener = serverSocketType.newServerSocket();
             Future<AbstractConscryptSocket> clientFuture = handshake(listener, clientHooks);
             Future<AbstractConscryptSocket> serverFuture = handshake(listener, serverHooks);
 
@@ -562,7 +599,7 @@
     @Test
     @SuppressWarnings("deprecation")
     public void setAlpnProtocolWithNullShouldSucceed() throws Exception {
-        ServerSocket listening = newServerSocket();
+        ServerSocket listening = serverSocketType.newServerSocket();
         OpenSSLSocketImpl clientSocket = null;
         try {
             Socket underlying = new Socket(listening.getInetAddress(), listening.getLocalPort());
@@ -583,7 +620,7 @@
     // http://b/27250522
     @Test
     public void test_setSoTimeout_doesNotCreateSocketImpl() throws Exception {
-        ServerSocket listening = newServerSocket();
+        ServerSocket listening = serverSocketType.newServerSocket();
         try {
             Socket underlying = new Socket(listening.getInetAddress(), listening.getLocalPort());
             Socket socket = socketType.newClientSocket(
@@ -646,7 +683,44 @@
         assertEquals(alpnProtocol, Conscrypt.getApplicationProtocol(connection.client));
     }
 
-    private static ServerSocket newServerSocket() throws IOException {
-        return new ServerSocket(0, 50, TestUtils.getLoopbackAddress());
+    @Test
+    public void dataFlows() throws Exception {
+        final TestConnection connection =
+                new TestConnection(new X509Certificate[] {cert, ca}, certKey);
+        connection.doHandshakeSuccess();
+
+        // Basic data flow assurance.  Send random buffers in each direction, each less than 16K
+        // so should fit in a single TLS packet.  50% chance of sending in each direction on
+        // each iteration to randomize the flow.
+        for (int i = 0; i < 50; i++) {
+            if (random.nextBoolean()) {
+                sendData(connection.client, connection.server, randomBuffer());
+            }
+            if (random.nextBoolean()) {
+                sendData(connection.server, connection.client, randomBuffer());
+            }
+        }
+    }
+
+    private void sendData(SSLSocket source, final SSLSocket destination, byte[] data)
+            throws Exception {
+        final byte[] received = new byte[data.length];
+
+        Future<Integer> readFuture = executor.submit(new Callable<Integer>() {
+            @Override
+            public Integer call() throws Exception {
+                return destination.getInputStream().read(received);
+            }
+        });
+
+        source.getOutputStream().write(data);
+        assertEquals(data.length, (int) readFuture.get());
+        assertArrayEquals(data, received);
+    }
+
+    private byte[] randomBuffer() {
+        byte[] buffer = new byte[random.nextInt(16 * 1024)];
+        random.nextBytes(buffer);
+        return buffer;
     }
 }
diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java
index c8a55f9..0a02e91 100644
--- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java
+++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java
@@ -157,6 +157,7 @@
     /**
      * Lazily create shared test certificates.
      */
+    @SuppressWarnings("JdkObsolete") // Public API KeyStore.aliases() uses Enumeration
     private static synchronized void initCerts() {
         if (SERVER_PRIVATE_KEY != null) {
             return;
diff --git a/openjdk/src/test/java/org/conscrypt/OpenSSLX509CertificateTest.java b/openjdk/src/test/java/org/conscrypt/OpenSSLX509CertificateTest.java
index 0c5a017..c510e0d 100644
--- a/openjdk/src/test/java/org/conscrypt/OpenSSLX509CertificateTest.java
+++ b/openjdk/src/test/java/org/conscrypt/OpenSSLX509CertificateTest.java
@@ -25,6 +25,8 @@
 import java.io.ObjectOutputStream;
 import java.io.ObjectStreamClass;
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
 import junit.framework.TestCase;
@@ -43,10 +45,28 @@
 
             // Mark the field as non-final on JVM that need it.
             try {
-                Field modifiersField = Field.class.getDeclaredField("modifiers");
-                modifiersField.setAccessible(true);
-                modifiersField.setInt(targetUID, targetUID.getModifiers() & ~Modifier.FINAL);
-            } catch (NoSuchFieldException ignored) {
+                Field modifiersField = null;
+                try {
+                    modifiersField = Field.class.getDeclaredField("modifiers");
+                } catch (NoSuchFieldException e) {
+                    try {
+                        Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
+                        getDeclaredFields0.setAccessible(true);
+                        Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
+                        for (Field field : fields) {
+                            if ("modifiers".equals(field.getName())) {
+                                modifiersField = field;
+                                break;
+                            }
+                        }
+                    } catch (NoSuchMethodException | InvocationTargetException ignored) {
+                    }
+                }
+                if (modifiersField != null) {
+                    modifiersField.setAccessible(true);
+                    modifiersField.setInt(targetUID, targetUID.getModifiers() & ~Modifier.FINAL);
+                }
+            } catch (Exception ignored) {
             }
 
             targetUID.set(null, clDesc.getSerialVersionUID());
@@ -106,31 +126,31 @@
                 cert.getTBSCertificate()));
 
         assertTrue(Arrays.equals(
-                certPoisoned.withDeletedExtension(CT_POISON_EXTENSION).getTBSCertificate(),
+                certPoisoned.getTBSCertificateWithoutExtension(CT_POISON_EXTENSION),
                 cert.getTBSCertificate()));
     }
 
     public void test_deletingExtensionMakesCopy() throws Exception {
-        /* Calling withDeletedExtension should not modify the original certificate, only make a copy.
+        /* Calling getTBSCertificateWithoutExtension should not modify the original certificate.
          * Make sure the extension is still present in the original object.
          */
         OpenSSLX509Certificate certPoisoned = loadTestCertificate("cert-ct-poisoned.pem");
         assertTrue(certPoisoned.getCriticalExtensionOIDs().contains(CT_POISON_EXTENSION));
 
-        OpenSSLX509Certificate certWithoutExtension = certPoisoned.withDeletedExtension(CT_POISON_EXTENSION);
-
+        certPoisoned.getTBSCertificateWithoutExtension(CT_POISON_EXTENSION);
         assertTrue(certPoisoned.getCriticalExtensionOIDs().contains(CT_POISON_EXTENSION));
-        assertFalse(certWithoutExtension.getCriticalExtensionOIDs().contains(CT_POISON_EXTENSION));
     }
 
     public void test_deletingMissingExtension() throws Exception {
-        /* withDeletedExtension should be safe to call on a certificate without the extension, and
-         * return an identical copy.
+        /* getTBSCertificateWithoutExtension should throw on a certificate without the extension.
          */
         OpenSSLX509Certificate cert = loadTestCertificate("cert.pem");
         assertFalse(cert.getCriticalExtensionOIDs().contains(CT_POISON_EXTENSION));
 
-        OpenSSLX509Certificate cert2 = cert.withDeletedExtension(CT_POISON_EXTENSION);
-        assertEquals(cert, cert2);
+        try {
+            cert.getTBSCertificateWithoutExtension(CT_POISON_EXTENSION);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
     }
 }
diff --git a/openjdk/src/test/java/org/conscrypt/ServerSessionContextTest.java b/openjdk/src/test/java/org/conscrypt/ServerSessionContextTest.java
index 1b1ed0b..9941d7f 100644
--- a/openjdk/src/test/java/org/conscrypt/ServerSessionContextTest.java
+++ b/openjdk/src/test/java/org/conscrypt/ServerSessionContextTest.java
@@ -34,6 +34,7 @@
     }
 
     @Override
+    @SuppressWarnings("JdkObsolete") // Public API SSLSessionContext.getIds() uses Enumeration
     int size(ServerSessionContext context) {
         int count = 0;
         Enumeration<byte[]> ids = context.getIds();
diff --git a/openjdk/src/test/resources/blacklist_test_chain.pem b/openjdk/src/test/resources/blocklist_test_chain.pem
similarity index 100%
rename from openjdk/src/test/resources/blacklist_test_chain.pem
rename to openjdk/src/test/resources/blocklist_test_chain.pem
diff --git a/openjdk/src/test/resources/blacklist_test_valid_ca.pem b/openjdk/src/test/resources/blocklist_test_valid_ca.pem
similarity index 100%
rename from openjdk/src/test/resources/blacklist_test_valid_ca.pem
rename to openjdk/src/test/resources/blocklist_test_valid_ca.pem
diff --git a/openjdk/src/test/resources/blacklist_test_valid_chain.pem b/openjdk/src/test/resources/blocklist_test_valid_chain.pem
similarity index 100%
rename from openjdk/src/test/resources/blacklist_test_valid_chain.pem
rename to openjdk/src/test/resources/blocklist_test_valid_chain.pem
diff --git a/openjdk/src/test/resources/test_blacklist_ca.pem b/openjdk/src/test/resources/test_blocklist_ca.pem
similarity index 100%
rename from openjdk/src/test/resources/test_blacklist_ca.pem
rename to openjdk/src/test/resources/test_blocklist_ca.pem
diff --git a/openjdk/src/test/resources/test_blacklist_ca_key.pem b/openjdk/src/test/resources/test_blocklist_ca_key.pem
similarity index 100%
rename from openjdk/src/test/resources/test_blacklist_ca_key.pem
rename to openjdk/src/test/resources/test_blocklist_ca_key.pem
diff --git a/openjdk/src/test/resources/test_intermediate.csr b/openjdk/src/test/resources/test_intermediate.csr
new file mode 100644
index 0000000..dbe8d2e
--- /dev/null
+++ b/openjdk/src/test/resources/test_intermediate.csr
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICXDCCAUQCAQAwFzEVMBMGA1UEAwwMaW50ZXJtZWRpYXRlMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqpirljyU3bstotA3bs1w97gBDbgSPX7HHPQa
+VQeILG49E6sS8E7Tst9Fm5b7/BKnh5R2ekk6XzpR1l8MrysxEtUi64pfcDOaEDsj
+Ry1Co+a2ZWEKzinrcfQmkk2oH5HyjiXn4lAz4I3PFzW0Ke0sLSgMtlWyhmD2sK3Y
+FzKHoSn1G5J2IGFVaArHhkG38YOvsdgw/xuMTzsYkwHNkwWR9LbUGI8Cb7hccAV6
+eWTgu0CEDJ4tnNu+RcoLgyrJOviNBB8PsxEFEEDPnirzZt3BP3/BQhEsAItNlmxW
+mOx46ifybwfwk7wZBmIvgeDnHr4MlQA5u6+epqxCfb9mw89yLwIDAQABoAAwDQYJ
+KoZIhvcNAQELBQADggEBAHvZLREVOImWgGafwB5b+r0qo2TP/bua3M16m6beNwvx
+df4H4Uym8CDd/53u1Bzicf19VR9ncjKt1GDnj+gTW+kVJ2S9mPyLbo7IEzM+rmEb
+fq/OHgKjhzTsUJ3rIf0w//XqjjBUvYFOXkF0D4BNL3cSE2aguOWeNKneGFwFZiEM
+4Zz2f17AGcRLJqcSJIFBDQzDAQkpxVf67TQpD9Q4yTjaJfdjlawPwJDkw4kHY9IM
++/eADQww6czzOTAYJxZBMJCuiWecHR37Kt+KOhqIOaWG/tRuJ72fyi/jdXItbCEw
+bO4soja7MnEgLL3+1se46zqMeZeBqVo9r3cMCVX6K8s=
+-----END CERTIFICATE REQUEST-----
diff --git a/openjdk/src/test/resources/test_intermediate_blockedroot.pem b/openjdk/src/test/resources/test_intermediate_blockedroot.pem
new file mode 100644
index 0000000..84b2c5a
--- /dev/null
+++ b/openjdk/src/test/resources/test_intermediate_blockedroot.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDFDCCAfygAwIBAgIUKEpmhiN42JxqaXIMn0ZdTuvIwkEwDQYJKoZIhvcNAQEL
+BQAwHDEaMBgGA1UEAwwRYmxhY2tsaXN0IHRlc3QgQ0EwHhcNMjMwMTE5MTc0MjQ1
+WhcNMzMwMTE2MTc0MjQ1WjAXMRUwEwYDVQQDDAxpbnRlcm1lZGlhdGUwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqmKuWPJTduy2i0DduzXD3uAENuBI9
+fscc9BpVB4gsbj0TqxLwTtOy30Wblvv8EqeHlHZ6STpfOlHWXwyvKzES1SLril9w
+M5oQOyNHLUKj5rZlYQrOKetx9CaSTagfkfKOJefiUDPgjc8XNbQp7SwtKAy2VbKG
+YPawrdgXMoehKfUbknYgYVVoCseGQbfxg6+x2DD/G4xPOxiTAc2TBZH0ttQYjwJv
+uFxwBXp5ZOC7QIQMni2c275FyguDKsk6+I0EHw+zEQUQQM+eKvNm3cE/f8FCESwA
+i02WbFaY7HjqJ/JvB/CTvBkGYi+B4OcevgyVADm7r56mrEJ9v2bDz3IvAgMBAAGj
+UzBRMB0GA1UdDgQWBBQzP1H/yV66YjFeq6M6vy+JTaG1+zAfBgNVHSMEGDAWgBTG
+uQuT0EWEs5bhdKZRvPGhz7MgkjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
+CwUAA4IBAQDTcXx9PghFT+H1bKJMDGSH8Jr8730dpKs6e3IuVgCs00YyvMBgRYbA
+v5ksMV80ZHOErim6JYTj8rpLSUXWYgq7xyFaTMWSt+YPPoXAkdW6p3fngyvCf9T2
+HqZenJTQw2g/xRDL6PTjWh5qumqipVuAR9ue4l+4fRb31VaDOL0U/OPkqjoD3C/c
+3ni9cglpzCRotTTGaSpIIpaBWy77HounXjreVn+JbYsEEx1S4CBo6+EJA+CEtQQo
+BSFBnvl62rfwNKHCEvMB1jmMELIATVxu1NL6fWp/bP3OTWxqLcJU4G3zf0M1qTLx
+fuoKzzyqdyjaeeE+ibr7sgyOU6Q/zk0Q
+-----END CERTIFICATE-----
diff --git a/openjdk/src/test/resources/test_intermediate_key.pem b/openjdk/src/test/resources/test_intermediate_key.pem
new file mode 100644
index 0000000..f841c95
--- /dev/null
+++ b/openjdk/src/test/resources/test_intermediate_key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqmKuWPJTduy2i
+0DduzXD3uAENuBI9fscc9BpVB4gsbj0TqxLwTtOy30Wblvv8EqeHlHZ6STpfOlHW
+XwyvKzES1SLril9wM5oQOyNHLUKj5rZlYQrOKetx9CaSTagfkfKOJefiUDPgjc8X
+NbQp7SwtKAy2VbKGYPawrdgXMoehKfUbknYgYVVoCseGQbfxg6+x2DD/G4xPOxiT
+Ac2TBZH0ttQYjwJvuFxwBXp5ZOC7QIQMni2c275FyguDKsk6+I0EHw+zEQUQQM+e
+KvNm3cE/f8FCESwAi02WbFaY7HjqJ/JvB/CTvBkGYi+B4OcevgyVADm7r56mrEJ9
+v2bDz3IvAgMBAAECggEAJH8B4mO2g1MqebTqzwEThGNwkkNJX6+SIF8WjQ9N8hdp
+aJ5GMHPktVUvupAL+4rwHUDFMfcdjkbXQDHYcFcgqgM/870IGuRnNsa4Dt+fbJoM
+dlbS7XUpYhkV8WG8sHhUOFXirFd1Kbqczb3W+8s6ErUJNy7RQQ9YZ0bhvmC4hGEw
+gW+rbpexwGLBSzUZCBJNy8ePi0akcEiaTHVS6ZjyQCIuRekSvJh/DTxZdhI9QV2K
+e2XONzCyCsJOEBxPXFEzXXApxPb6DmMnN2xzciQf+MppvUWFKDyDG2a/vNdgVr2O
+wXcvgQp5yl9dp+tyP4usSqZPgmRyDtSiaZFsptjMEQKBgQDbaYTDsnFAUmq3DTTp
+vVSNnugd1ss+LKMP3D+T986tRX4tVtB4a7Edrh5QLYmkXKZdPp+u6Yo0YDdNJGmP
+jnREwCT/YutcQ6TlyFNXwzE0Uf56fhHOaSKW6WFlX6kNfHvfJNu+1mKJS0nTH4yG
+NmxSjA8RcEkS9R4o1dTvth0fnwKBgQDHC0Epjiv3LMAIVH0HqWjl1gmfU1QUtkWQ
+3GELQeA2KeeVTwakIdKcZ0tr8qqwOIkxGD+Fr0HsST4d1GGmMZ1LM+PgEg2OoKM5
+aBNH2znFwqc8fdd/mBc1Vw4B2yCKrTAbKK/OOV19fi4rs1DARjesknnVAvom9CkK
+na9IoAZjcQKBgDmvPjZtHZU5ldDWagjhu+8XzhK6O+j2t1AeKaDvT6kCUi/9WQWv
+2nrhIhsWPc+2hA6TvkuwHqOygBeJ8S7K1wqUMaXrDdHN/vZienbiXHdS70KpDmlj
+/rIKXY7XXYysI60A9bzwhCtwXdJhwwIuIMB7DiMZkDypsOovfbIgAPwlAoGAKW3t
+RUIDYrJc0h8L2zFm1RgE7rXAdYMu3aURSe+PRJbaThih0D3+AXH6n+BlqMJLw/1B
+E4lUFmN0W28eWCJRlBqb3sLDMaG797Hy+WznDIOknZGv7i3w/rg9ASPkFRlRPwXr
++ee0zu8Zmxz6vNqgsfnXBABXow4FEOGbX2l3ivECgYEA1yVf+bG4CjG7hrUQmcLV
+5WwHOggGRLDOzUJdHq1VpZyN31dagMs7DuV/1xk3uwcxY5M5D5RjRWV0b3BKDCDT
+t8/f31FNxNf2JUPM9bwy2tFJO+ZXRFdOmm7S164IhSnv243OUbc6KGO0loBf4WBi
+31pgngO3pFBKxtpX81ABrMg=
+-----END PRIVATE KEY-----
diff --git a/openjdk/src/test/resources/test_intermediate_nonblockedroot.pem b/openjdk/src/test/resources/test_intermediate_nonblockedroot.pem
new file mode 100644
index 0000000..1f7bbd2
--- /dev/null
+++ b/openjdk/src/test/resources/test_intermediate_nonblockedroot.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCjCCAfKgAwIBAgIUZzC6NfXFwaGgeTHeleM4k2izk8UwDQYJKoZIhvcNAQEL
+BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yMzAxMTkxNzQyNTVaFw0zMzAxMTYx
+NzQyNTVaMBcxFTATBgNVBAMMDGludGVybWVkaWF0ZTCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAKqYq5Y8lN27LaLQN27NcPe4AQ24Ej1+xxz0GlUHiCxu
+PROrEvBO07LfRZuW+/wSp4eUdnpJOl86UdZfDK8rMRLVIuuKX3AzmhA7I0ctQqPm
+tmVhCs4p63H0JpJNqB+R8o4l5+JQM+CNzxc1tCntLC0oDLZVsoZg9rCt2Bcyh6Ep
+9RuSdiBhVWgKx4ZBt/GDr7HYMP8bjE87GJMBzZMFkfS21BiPAm+4XHAFenlk4LtA
+hAyeLZzbvkXKC4MqyTr4jQQfD7MRBRBAz54q82bdwT9/wUIRLACLTZZsVpjseOon
+8m8H8JO8GQZiL4Hg5x6+DJUAObuvnqasQn2/ZsPPci8CAwEAAaNTMFEwHQYDVR0O
+BBYEFDM/Uf/JXrpiMV6rozq/L4lNobX7MB8GA1UdIwQYMBaAFADwPCOf1D6SpTXG
+Brj1Oa03yOzTMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAE5y
+7OE6lmZlstL/15x8yZmUYzSXF0u365jfs1eJXDkWzn2BcmmzMSADn9spakDDZtN0
+daOpDGaB81TDjiBID1OwKMSRM1DmZzNI4PLFqWKUjWkwRtZjo5GsD0p/ATLV+S2z
+eQIHcqTcAH8ay1sBReig/plALKyseTk4R2799Gi+tA08RQ4cIsdxyUFSUc0nqgFV
+YsBM/cDeFCSYNwWLsNYAubJMIoUiKiweZ8bx+OoaS8Swc4p1M3Fk7lmh2g7APLjG
+RkiPF4Ta3c41yZxNW7tEP4CCPB3hm0OkEdW68zc8oOPiNt1sNL5szJI3+cVT+k+5
+4387ICBvTGLQRHL6avw=
+-----END CERTIFICATE-----
diff --git a/openjdk/src/test/resources/test_leaf.csr b/openjdk/src/test/resources/test_leaf.csr
new file mode 100644
index 0000000..bc9a9b5
--- /dev/null
+++ b/openjdk/src/test/resources/test_leaf.csr
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICVDCCATwCAQAwDzENMAsGA1UEAwwEbGVhZjCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBANVVRB6iPcrN0CLWUBXjO53vCdtKEdOBgCuMmA/2J8/YKc0g
+f/IGCFV/bZ9RNmMyHsu1Mp2tu6posxayCzxPVqW7XYbLIKxHSR3ZarTtm5KZRJMt
+R/ibpffrzrEOXy+7583sZPOdBNC7ujtY8sTTpL3ki2YmopX8TWINnl64WfltxYCQ
+o+ox7cjO0lwrqbUiRk5vLGhvuoX+EYB1sXxC2+RWMjh3QNi0StyJwBCS++O+JYaJ
+Mjo/8XCrkNuYyhn0Bmq6lMzT3Vu06skNpmDQnFgjcmQPET7yRBZeKFp5rNFcugjg
+wmrpGJvEnLBkWbUxZ78Edd5z4lVUifGgWeXI29kCAwEAAaAAMA0GCSqGSIb3DQEB
+CwUAA4IBAQB+gCFpLmV/ub4rVo4sA0bOiE7dN/wBA2tmaVVRxis/itZAFVze5o7P
+LRKyjunOJ75BnbiylGcYOfiMQbgBDGw/QHg4cIQwMHdGPHes1JAeF9XYTBgVJT3b
+qp1NTL7CDj8ry60wONDQ2X7Y7a2fU8LrfpflDv0+W0PaAK8/ptgi4a0rYPE9OHIC
+qQ5aiUJ8QQgnW9S2KCPRBjiJW9lZ7hr7A/TGe6/i9L2NS6KYPGw3PD34Vmvz/rwf
+jakm1GrQ3kGjPoc9yWSF60GLTAAXN+xF+9Htq+7PDmeo8krr28upSLokiO7uMWvQ
+2/0t7gM1ta7+gVuf9SdUpTOEZ6d+chKb
+-----END CERTIFICATE REQUEST-----
diff --git a/openjdk/src/test/resources/test_leaf_blockedroot.pem b/openjdk/src/test/resources/test_leaf_blockedroot.pem
new file mode 100644
index 0000000..472abf3
--- /dev/null
+++ b/openjdk/src/test/resources/test_leaf_blockedroot.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDDDCCAfSgAwIBAgIUODP0VFemD4Zonzl3hogYzAEZLLwwDQYJKoZIhvcNAQEL
+BQAwHDEaMBgGA1UEAwwRYmxhY2tsaXN0IHRlc3QgQ0EwHhcNMjMwMTE5MTcxODQw
+WhcNMzMwMTE2MTcxODQwWjAPMQ0wCwYDVQQDDARsZWFmMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA1VVEHqI9ys3QItZQFeM7ne8J20oR04GAK4yYD/Yn
+z9gpzSB/8gYIVX9tn1E2YzIey7Uyna27qmizFrILPE9WpbtdhssgrEdJHdlqtO2b
+kplEky1H+Jul9+vOsQ5fL7vnzexk850E0Lu6O1jyxNOkveSLZiailfxNYg2eXrhZ
++W3FgJCj6jHtyM7SXCuptSJGTm8saG+6hf4RgHWxfELb5FYyOHdA2LRK3InAEJL7
+474lhokyOj/xcKuQ25jKGfQGarqUzNPdW7TqyQ2mYNCcWCNyZA8RPvJEFl4oWnms
+0Vy6CODCaukYm8ScsGRZtTFnvwR13nPiVVSJ8aBZ5cjb2QIDAQABo1MwUTAdBgNV
+HQ4EFgQUMg9ifb+WI/uUTTf8Jd31XVHAI8MwHwYDVR0jBBgwFoAUxrkLk9BFhLOW
+4XSmUbzxoc+zIJIwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEA
+nxPXBF/Jrsig/5LMwaUyCX/VYb9W7W9tV6odKjghKxf6hA59VxevY/S/J0yK+mTJ
+PAQGZC0vkcWjmBDQW8w9J0sUkX+8OCdcxqXGRnryCJ1lB7i/UOLhkCyPA1jZGNti
+E43LYqs+iBxEvzPzeOggvXaE+ujtFZxCT5dLlzzVvTt9vomKvPmapC93ycorYjYV
+89K54mNqj7aZeCHTmyJxsZGzUhVDdp83Dnl8YopYpnHd7jr0xX8fqbL9WZf81sRn
+3u99Js6csv4Gi/ZDrbNONaUfpD5iH0Tm+2Kh7p6pI0lVBWaZzw59PNVGDRZp15sl
+HeCO1zZqxh3hj+gZW0Ao1Q==
+-----END CERTIFICATE-----
diff --git a/openjdk/src/test/resources/test_leaf_intermediate.pem b/openjdk/src/test/resources/test_leaf_intermediate.pem
new file mode 100644
index 0000000..12ae380
--- /dev/null
+++ b/openjdk/src/test/resources/test_leaf_intermediate.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDBzCCAe+gAwIBAgIUfCBnEPBeeqUTdsm3eI1CnjL8zIowDQYJKoZIhvcNAQEL
+BQAwFzEVMBMGA1UEAwwMaW50ZXJtZWRpYXRlMB4XDTIzMDExOTE3NDUyMFoXDTMz
+MDExNjE3NDUyMFowDzENMAsGA1UEAwwEbGVhZjCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBANVVRB6iPcrN0CLWUBXjO53vCdtKEdOBgCuMmA/2J8/YKc0g
+f/IGCFV/bZ9RNmMyHsu1Mp2tu6posxayCzxPVqW7XYbLIKxHSR3ZarTtm5KZRJMt
+R/ibpffrzrEOXy+7583sZPOdBNC7ujtY8sTTpL3ki2YmopX8TWINnl64WfltxYCQ
+o+ox7cjO0lwrqbUiRk5vLGhvuoX+EYB1sXxC2+RWMjh3QNi0StyJwBCS++O+JYaJ
+Mjo/8XCrkNuYyhn0Bmq6lMzT3Vu06skNpmDQnFgjcmQPET7yRBZeKFp5rNFcugjg
+wmrpGJvEnLBkWbUxZ78Edd5z4lVUifGgWeXI29kCAwEAAaNTMFEwHQYDVR0OBBYE
+FDIPYn2/liP7lE03/CXd9V1RwCPDMB8GA1UdIwQYMBaAFDM/Uf/JXrpiMV6rozq/
+L4lNobX7MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJh2wZtc
+aDji9cI8LKJAr7V3raQFfk6Oq999CIoLoH3MiaEyTG9/FC/ZlaGUpC49c63bsRu8
+AQuPQydVOjrVTeUB4x12qoDGdz1lze+2zeY2jbIsd5VBEF0gObdkwwHgFQXKH5Lf
+eSoBc4XPQ0I5dTYvR/P3+KX4fTyEmmjj+EWaH4yFPsW3JVu/2LrzI0IKq3+9VD0a
+dB/mI42lI65cEtW2zGI+CSQGt0FGXdVsXGfne87QNByVxYCyS0wzdfSla3yLLGdf
+8EJTqwZH0lKuRSf8xeNFr/pVXT9YfJWUAja7lIiwQVQfsD5MGsjNGcJMEDhM08PO
++lFxL3h3B85jIZw=
+-----END CERTIFICATE-----
diff --git a/openjdk/src/test/resources/blacklist_test_valid_ca.pem b/openjdk/src/test/resources/test_nonblocklist_ca.pem
similarity index 100%
copy from openjdk/src/test/resources/blacklist_test_valid_ca.pem
copy to openjdk/src/test/resources/test_nonblocklist_ca.pem
diff --git a/openjdk/src/test/resources/test_nonblocklist_ca_key.pem b/openjdk/src/test/resources/test_nonblocklist_ca_key.pem
new file mode 100644
index 0000000..1d6b8bd
--- /dev/null
+++ b/openjdk/src/test/resources/test_nonblocklist_ca_key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC1gY2VOIFa/lQf
+PiJpXIKBEVYlnJdsJ6izpwmgi+2M8qTZGxvaov8xv9tObYNyENakHfdXu4RAF7Z/
+cCw/ntJo0mEKVzJflm/gMa6/02e+znp5s0lKYFLGY6+++H81neS8NHWF1E2D0Yxa
+/47zQAw8QWI5T7Lw0uzWFT6gq2oTRpFBj4RatJhpXMX7/cSR3yV6Eeuq6vU9Ncce
+urqzo1tg3bI4FKn09xDbnbpb+rxShqv+nKDCMxt7f2HRSVSgLEe2z8mpq2PXVwAt
+v7TL+UXGZfniNG0wzO+PLGIYzctL64z6ebDy5LOIhrgcOSVE0mAMBA9XPuQiF65l
+O6Bk+++PAgMBAAECggEAB3Z+kCgyr60thCTFhd8TquObYT4XGliv9Y8NveyFPGy7
+D7zBZO9SVUT4RoJSpw1a5xa6VW59tzZZPtQE+AYf8RAfFy2KOObtqWJobexKY6f1
+lRFJThg38VomqD39kQJxnLX+nQtIAFne/G6SRuaXMQ4yUA9fNFz7QKi+0WX0Cxbs
+uMd0uTMnZorri/YGGMko/0jJrQXY4H1Oufc7Pawdr1jxJWLMqJ6Q26Xu4NGFcGFJ
+bHsCtewIFdLvBmV+ef5V+JDWItYC7AkIhbibsGkZFKFbPRo3z+kx/lkB3IluRVcD
+oH9wVm9YNkz51NH4FLdQPEGdySuzjiaF9RbDqp+w4QKBgQDKEesxK+Tm3q0AONxI
+ELsdyfLyo76NrYfcBYvQs/zevlmgqy3uSRNMWWQE3Yq+NQVGT9NjF8cQn3KN1C6F
+QIrGtbIAy1nQAG5KIp1OAkrG/dt3eVlP+9N5cJNZlQNEm0v3qA965hql5zjpM0wO
+sk1tvMMa9++Cj4A6s7LVcCpl7wKBgQDl8qPBMgocbBJl1y00/Cw6S8wso7yaGSGz
+OVpIpsSaVyVWWWF5H18/NlgrKKOALWkfREX0jbHH7FBxruUTz6FmEs/iE09JVsD5
+UMsYX9dMzbKUTtzw2vOKIJDNaWnbsTNDJlO89boUJrKnwQKAHfQZJU9uiiaoVIpK
+XrYLXZmwYQKBgQC6Yda+zw7eSCvoVYoRSqVc76YQWipsAdCbh94TjcDDL236PYor
+DOoo9RbFShcsJDmORhjjgM4TLg76dOjH7eVTLcpW4zofGhageNcBWing68wfoiVY
+Gh5QGB9BdKnEAT4L288Te+S+e8zJhJA1yg6TFpYbbO9VTMlo29Eq/7+LrwKBgBai
+arISreIcVTdHFgEYLXZTjbZ7K45zmNiedZ+fIs0adOdqBuk4SFTdkZI1/toYHjfg
+rY4kAHLwdP6ru4rWrklw4pubUPukGXyxEjzE+llqCgEFPkRLGRvolrhRfwUMDUK3
+3BhGi9l98aoHmqpnyGZNQONdn+6D29T0O7EktoMhAoGAbKArB67pRoAOYX+1rc+i
+OypUSXFUMyRNcuF8DHF9iKNU6/fXwmkPyfgUmZ/uIbTiiPaM+KI4/UBah4tKKXis
+srzxm/1fln0zHJW+h5SnMp483Juml1yZenhYVlyfMs8ZpdpLDZbylvc5SK8m8Fau
+75bAWg06Z28TEjq18O7L798=
+-----END PRIVATE KEY-----
diff --git a/platform/build.gradle b/platform/build.gradle
index 7a4eecd..eee8d25 100644
--- a/platform/build.gradle
+++ b/platform/build.gradle
@@ -99,7 +99,9 @@
             exclude module: 'appcompat-v7'
             exclude module: 'design'
         })
-        testImplementation project(':conscrypt-testing'),
+        testCompileOnly project(':conscrypt-android-stub'),
+                        project(':conscrypt-libcore-stub')
+        testImplementation project(path: ":conscrypt-testing", configuration: "runtime"),
                            libraries.junit
         compileOnly project(':conscrypt-android-stub'),
                     project(':conscrypt-libcore-stub')
@@ -109,7 +111,7 @@
     }
 
     // Disable running the tests.
-    tasks.withType(Test){
+    tasks.withType(Test).configureEach {
         enabled = false
     }
 
@@ -117,7 +119,7 @@
     logger.warn('Android SDK has not been detected. The Android Platform module will not be built.')
 
     // Disable all tasks
-    tasks.collect {
+    tasks.configureEach {
         it.enabled = false
     }
 }
diff --git a/platform/src/main/java/org/conscrypt/CertBlacklistImpl.java b/platform/src/main/java/org/conscrypt/CertBlocklistImpl.java
similarity index 82%
rename from platform/src/main/java/org/conscrypt/CertBlacklistImpl.java
rename to platform/src/main/java/org/conscrypt/CertBlocklistImpl.java
index c2cdd80..2428d4c 100644
--- a/platform/src/main/java/org/conscrypt/CertBlacklistImpl.java
+++ b/platform/src/main/java/org/conscrypt/CertBlocklistImpl.java
@@ -37,29 +37,29 @@
 import java.util.logging.Logger;
 
 @Internal
-public final class CertBlacklistImpl implements CertBlacklist {
-    private static final Logger logger = Logger.getLogger(CertBlacklistImpl.class.getName());
+public final class CertBlocklistImpl implements CertBlocklist {
+    private static final Logger logger = Logger.getLogger(CertBlocklistImpl.class.getName());
 
-    private final Set<BigInteger> serialBlacklist;
-    private final Set<ByteString> pubkeyBlacklist;
+    private final Set<BigInteger> serialBlocklist;
+    private final Set<ByteString> pubkeyBlocklist;
 
     /**
      * public for testing only.
      */
-    public CertBlacklistImpl(Set<BigInteger> serialBlacklist, Set<ByteString> pubkeyBlacklist) {
-        this.serialBlacklist = serialBlacklist;
-        this.pubkeyBlacklist = pubkeyBlacklist;
+    public CertBlocklistImpl(Set<BigInteger> serialBlocklist, Set<ByteString> pubkeyBlocklist) {
+        this.serialBlocklist = serialBlocklist;
+        this.pubkeyBlocklist = pubkeyBlocklist;
     }
 
-    public static CertBlacklist getDefault() {
+    public static CertBlocklist getDefault() {
         String androidData = System.getenv("ANDROID_DATA");
-        String blacklistRoot = androidData + "/misc/keychain/";
-        String defaultPubkeyBlacklistPath = blacklistRoot + "pubkey_blacklist.txt";
-        String defaultSerialBlacklistPath = blacklistRoot + "serial_blacklist.txt";
+        String blocklistRoot = androidData + "/misc/keychain/";
+        String defaultPubkeyBlocklistPath = blocklistRoot + "pubkey_blacklist.txt";
+        String defaultSerialBlocklistPath = blocklistRoot + "serial_blacklist.txt";
 
-        Set<ByteString> pubkeyBlacklist = readPublicKeyBlackList(defaultPubkeyBlacklistPath);
-        Set<BigInteger> serialBlacklist = readSerialBlackList(defaultSerialBlacklistPath);
-        return new CertBlacklistImpl(serialBlacklist, pubkeyBlacklist);
+        Set<ByteString> pubkeyBlocklist = readPublicKeyBlockList(defaultPubkeyBlocklistPath);
+        Set<BigInteger> serialBlocklist = readSerialBlockList(defaultSerialBlocklistPath);
+        return new CertBlocklistImpl(serialBlocklist, pubkeyBlocklist);
     }
 
     private static boolean isHex(String value) {
@@ -80,12 +80,13 @@
         return isHex(value);
     }
 
-    private static String readBlacklist(String path) {
+    private static String readBlocklist(String path) {
         try {
             return readFileAsString(path);
         } catch (FileNotFoundException ignored) {
+            // Ignored
         } catch (IOException e) {
-            logger.log(Level.WARNING, "Could not read blacklist", e);
+            logger.log(Level.WARNING, "Could not read blocklist", e);
         }
         return "";
     }
@@ -122,11 +123,12 @@
             } catch (RuntimeException rethrown) {
                 throw rethrown;
             } catch (Exception ignored) {
+                // Ignored
             }
         }
     }
 
-    private static Set<BigInteger> readSerialBlackList(String path) {
+    private static Set<BigInteger> readSerialBlockList(String path) {
 
         /* Start out with a base set of known bad values.
          *
@@ -153,9 +155,9 @@
         ));
 
         // attempt to augment it with values taken from gservices
-        String serialBlacklist = readBlacklist(path);
-        if (!serialBlacklist.equals("")) {
-            for (String value : serialBlacklist.split(",", -1)) {
+        String serialBlocklist = readBlocklist(path);
+        if (!serialBlocklist.equals("")) {
+            for (String value : serialBlocklist.split(",", -1)) {
                 try {
                     bl.add(new BigInteger(value, 16));
                 } catch (NumberFormatException e) {
@@ -168,13 +170,13 @@
         return Collections.unmodifiableSet(bl);
     }
 
-    private static Set<ByteString> readPublicKeyBlackList(String path) {
+    private static Set<ByteString> readPublicKeyBlockList(String path) {
 
         // start out with a base set of known bad values
         Set<ByteString> bl = new HashSet<ByteString>(toByteStrings(
-            // Blacklist test cert for CTS. The cert and key can be found in
-            // src/test/resources/blacklist_test_ca.pem and
-            // src/test/resources/blacklist_test_ca_key.pem.
+            // Blocklist test cert for CTS. The cert and key can be found in
+            // src/test/resources/blocklist_test_ca.pem and
+            // src/test/resources/blocklist_test_ca_key.pem.
             "bae78e6bed65a2bf60ddedde7fd91e825865e93d".getBytes(UTF_8),
             // From http://src.chromium.org/viewvc/chrome/branches/782/src/net/base/x509_certificate.cc?r1=98750&r2=98749&pathrev=98750
             // C=NL, O=DigiNotar, CN=DigiNotar Root CA/emailAddress=info@diginotar.nl
@@ -207,14 +209,14 @@
         ));
 
         // attempt to augment it with values taken from gservices
-        String pubkeyBlacklist = readBlacklist(path);
-        if (!pubkeyBlacklist.equals("")) {
-            for (String value : pubkeyBlacklist.split(",", -1)) {
+        String pubkeyBlocklist = readBlocklist(path);
+        if (!pubkeyBlocklist.equals("")) {
+            for (String value : pubkeyBlocklist.split(",", -1)) {
                 value = value.trim();
                 if (isPubkeyHash(value)) {
                     bl.add(new ByteString(value.getBytes(UTF_8)));
                 } else {
-                    logger.log(Level.WARNING, "Tried to blacklist invalid pubkey " + value);
+                    logger.log(Level.WARNING, "Tried to blocklist invalid pubkey " + value);
                 }
             }
         }
@@ -223,7 +225,7 @@
     }
 
     @Override
-    public boolean isPublicKeyBlackListed(PublicKey publicKey) {
+    public boolean isPublicKeyBlockListed(PublicKey publicKey) {
         byte[] encoded = publicKey.getEncoded();
         MessageDigest md;
         try {
@@ -233,8 +235,8 @@
             return false;
         }
         byte[] out = toHex(md.digest(encoded));
-        for (ByteString blacklisted : pubkeyBlacklist) {
-            if (Arrays.equals(blacklisted.bytes, out)) {
+        for (ByteString blocklisted : pubkeyBlocklist) {
+            if (Arrays.equals(blocklisted.bytes, out)) {
                 return true;
             }
         }
@@ -257,8 +259,8 @@
     }
 
     @Override
-    public boolean isSerialNumberBlackListed(BigInteger serial) {
-        return serialBlacklist.contains(serial);
+    public boolean isSerialNumberBlockListed(BigInteger serial) {
+        return serialBlocklist.contains(serial);
     }
 
     private static List<ByteString> toByteStrings(byte[]... allBytes) {
diff --git a/platform/src/main/java/org/conscrypt/Platform.java b/platform/src/main/java/org/conscrypt/Platform.java
index 7d6a002..7ce64d8 100644
--- a/platform/src/main/java/org/conscrypt/Platform.java
+++ b/platform/src/main/java/org/conscrypt/Platform.java
@@ -26,6 +26,7 @@
 import dalvik.system.CloseGuard;
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.lang.System;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -48,6 +49,7 @@
 import java.util.Collections;
 import java.util.List;
 import javax.crypto.spec.GCMParameterSpec;
+import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SNIHostName;
 import javax.net.ssl.SNIMatcher;
 import javax.net.ssl.SNIServerName;
@@ -63,6 +65,9 @@
 import org.conscrypt.ct.CTLogStoreImpl;
 import org.conscrypt.ct.CTPolicy;
 import org.conscrypt.ct.CTPolicyImpl;
+import org.conscrypt.metrics.CipherSuite;
+import org.conscrypt.metrics.ConscryptStatsLog;
+import org.conscrypt.metrics.Protocol;
 import sun.security.x509.AlgorithmId;
 
 final class Platform {
@@ -503,8 +508,8 @@
         return new TrustedCertificateStore();
     }
 
-    static CertBlacklist newDefaultBlacklist() {
-        return CertBlacklistImpl.getDefault();
+    static CertBlocklist newDefaultBlocklist() {
+        return CertBlocklistImpl.getDefault();
     }
 
     static CTLogStore newDefaultLogStore() {
@@ -530,4 +535,30 @@
         }
         return false;
     }
+
+    public static ConscryptHostnameVerifier getDefaultHostnameVerifier() {
+        return Conscrypt.wrapHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier());
+    }
+
+    /**
+     * Returns milliseconds elapsed since boot, including time spent in sleep.
+     * @return long number of milliseconds elapsed since boot
+     */
+    static long getMillisSinceBoot() {
+        return System.currentTimeMillis();
+    }
+
+    static void countTlsHandshake(
+            boolean success, String protocol, String cipherSuite, long duration) {
+        Protocol proto = Protocol.forName(protocol);
+        CipherSuite suite = CipherSuite.forName(cipherSuite);
+        int dur = (int) duration;
+
+        ConscryptStatsLog.write(ConscryptStatsLog.TLS_HANDSHAKE_REPORTED, success, proto.getId(),
+                suite.getId(), dur);
+    }
+
+    public static boolean isJavaxCertificateSupported() {
+        return true;
+    }
 }
diff --git a/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java b/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java
index c8f1625..333450d 100644
--- a/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java
+++ b/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java
@@ -59,9 +59,8 @@
  * <p>In addition to supporting the {@code
  * TrustedCertificateKeyStoreSpi} implementation, {@code
  * TrustedCertificateStore} also provides the additional public
- * methods {@link #isTrustAnchor} and {@link #findIssuer} to allow
- * efficient lookup operations for CAs again based on the file naming
- * convention.
+ * method  {@link #findIssuer} to allow  efficient lookup operations
+ * for CAs again based on the file naming convention.
  *
  * <p>The KeyChainService users the {@link installCertificate} and
  * {@link #deleteCertificateEntry} to install user CAs as well as
@@ -217,6 +216,7 @@
         return getCertificateFile(deletedDir, x).exists();
     }
 
+    @SuppressWarnings("JdkObsolete") // Used in public API TrustedCertificateKeyStoreSpi
     public Date getCreationDate(String alias) {
         // containsAlias check ensures the later fileForAlias result
         // was not a deleted system cert.
diff --git a/platform/src/main/java/org/conscrypt/ct/CTLogStoreImpl.java b/platform/src/main/java/org/conscrypt/ct/CTLogStoreImpl.java
index fee9c3f..0f37a8d 100644
--- a/platform/src/main/java/org/conscrypt/ct/CTLogStoreImpl.java
+++ b/platform/src/main/java/org/conscrypt/ct/CTLogStoreImpl.java
@@ -23,6 +23,7 @@
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
@@ -37,7 +38,7 @@
 
 @Internal
 public class CTLogStoreImpl implements CTLogStore {
-    private static final Charset US_ASCII = Charset.forName("US-ASCII");
+    private static final Charset US_ASCII = StandardCharsets.US_ASCII;
 
     /**
      * Thrown when parsing of a log file fails.
@@ -70,12 +71,13 @@
         defaultSystemLogDir = new File(ANDROID_ROOT + "/etc/security/ct_known_logs/");
     }
 
-    private File userLogDir;
-    private File systemLogDir;
-    private CTLogInfo[] fallbackLogs;
+    private final File userLogDir;
+    private final File systemLogDir;
+    private final CTLogInfo[] fallbackLogs;
 
-    private HashMap<ByteBuffer, CTLogInfo> logCache = new HashMap<>();
-    private Set<ByteBuffer> missingLogCache = Collections.synchronizedSet(new HashSet<ByteBuffer>());
+    private final HashMap<ByteBuffer, CTLogInfo> logCache = new HashMap<>();
+    private final Set<ByteBuffer> missingLogCache
+            = Collections.synchronizedSet(new HashSet<ByteBuffer>());
 
     public CTLogStoreImpl() {
         this(defaultUserLogDir,
@@ -116,13 +118,17 @@
             return loadLog(new File(userLogDir, filename));
         } catch (InvalidLogFileException e) {
             return null;
-        } catch (FileNotFoundException e) {}
+        } catch (FileNotFoundException e) {
+            // Ignored
+        }
 
         try {
             return loadLog(new File(systemLogDir, filename));
         } catch (InvalidLogFileException e) {
             return null;
-        } catch (FileNotFoundException e) {}
+        } catch (FileNotFoundException e) {
+            // Ignored
+        }
 
         // If the updateable logs dont exist then use the fallback logs.
         if (!userLogDir.exists()) {
diff --git a/platform/src/test/java/org/conscrypt/CertBlacklistTest.java b/platform/src/test/java/org/conscrypt/CertBlacklistTest.java
deleted file mode 100644
index 3a9a85a..0000000
--- a/platform/src/test/java/org/conscrypt/CertBlacklistTest.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2016 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.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.InputStream;
-import java.security.KeyStore;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Collection;
-import javax.net.ssl.X509TrustManager;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class CertBlacklistTest {
-
-    private static final String BLACKLIST_CA = "test_blacklist_ca.pem";
-    private static final String BLACKLISTED_CHAIN = "blacklist_test_chain.pem";
-    private static final String BLACKLIST_FALLBACK_VALID_CA = "blacklist_test_valid_ca.pem";
-    private static final String BLACKLISTED_VALID_CHAIN = "blacklist_test_valid_chain.pem";
-
-    /**
-     * Ensure that the test blacklisted CA is actually blacklisted by default.
-     */
-    @Test
-    public void testBlacklistedPublicKey() throws Exception {
-        // This class was renamed in the Android 12 based Conscrypt module.
-        // If such a module is installed, simply skip the test as it is covered by MTS
-        TestUtils.assumeBeforeAndroid12Mainline();
-        X509Certificate blacklistedCa = loadCertificate(BLACKLIST_CA);
-        CertBlacklist blacklist = CertBlacklistImpl.getDefault();
-        assertTrue(blacklist.isPublicKeyBlackListed(blacklistedCa.getPublicKey()));
-    }
-
-    /**
-     * Check that the blacklisted CA is rejected even if it used as a root of trust
-     */
-    @Test
-    public void testBlacklistedCaUntrusted() throws Exception {
-        X509Certificate blacklistedCa = loadCertificate(BLACKLIST_CA);
-        assertUntrusted(new X509Certificate[] {blacklistedCa}, getTrustManager(blacklistedCa));
-    }
-
-    /**
-     * Check that a chain that is rooted in a blacklisted trusted CA is rejected.
-     */
-    @Test
-    public void testBlacklistedRootOfTrust() throws Exception {
-        // Chain is leaf -> blacklisted
-        X509Certificate[] chain = loadCertificates(BLACKLISTED_CHAIN);
-        X509Certificate blacklistedCa = loadCertificate(BLACKLIST_CA);
-        assertUntrusted(chain, getTrustManager(blacklistedCa));
-    }
-
-    /** Test that the path building correctly routes around a blacklisted cert where there are
-     * other valid paths available. This prevents breakage where a cert was cross signed by a
-     * blacklisted CA but is still valid due to also being cross signed by CAs that remain trusted.
-     * Path:
-     *
-     * leaf -> intermediate -> blacklisted_ca
-     *               \
-     *                -------> trusted_ca
-     */
-    @Test
-    public void testBlacklistedIntermediateFallback() throws Exception {
-        X509Certificate[] chain = loadCertificates(BLACKLISTED_VALID_CHAIN);
-        X509Certificate blacklistedCa = loadCertificate(BLACKLIST_CA);
-        X509Certificate validCa = loadCertificate(BLACKLIST_FALLBACK_VALID_CA);
-        assertTrusted(chain, getTrustManager(blacklistedCa, validCa));
-        // Check that without the trusted_ca the chain is invalid (since it only chains to a
-        // blacklisted ca)
-        assertUntrusted(chain, getTrustManager(blacklistedCa));
-    }
-
-    private static X509Certificate loadCertificate(String file) throws Exception {
-        return loadCertificates(file)[0];
-    }
-
-    private static X509Certificate[] loadCertificates(String file) throws Exception {
-        CertificateFactory factory = CertificateFactory.getInstance("X.509");
-        try (InputStream is = TestUtils.openTestFile(file)) {
-            Collection<? extends Certificate> collection = factory.generateCertificates(is);
-            is.close();
-            X509Certificate[] certs = new X509Certificate[collection.size()];
-            int i = 0;
-            for (Certificate cert : collection) {
-                certs[i++] = (X509Certificate) cert;
-            }
-            return certs;
-        }
-    }
-
-    private static TrustManagerImpl getTrustManager(X509Certificate... trustedCas)
-            throws Exception {
-        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
-        ks.load(null);
-        int i = 0;
-        for (X509Certificate ca : trustedCas) {
-            ks.setCertificateEntry(String.valueOf(i++), ca);
-        }
-        return new TrustManagerImpl(ks);
-    }
-
-    private static void assertTrusted(X509Certificate[] certs, X509TrustManager tm)
-            throws Exception {
-        tm.checkServerTrusted(certs, "RSA");
-    }
-
-    private static void assertUntrusted(X509Certificate[] certs, X509TrustManager tm) {
-        try {
-            tm.checkServerTrusted(certs, "RSA");
-            fail();
-        } catch (CertificateException expected) {
-        }
-    }
-}
diff --git a/platform/src/test/java/org/conscrypt/CertBlocklistTest.java b/platform/src/test/java/org/conscrypt/CertBlocklistTest.java
new file mode 100644
index 0000000..39561e5
--- /dev/null
+++ b/platform/src/test/java/org/conscrypt/CertBlocklistTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.conscrypt;
+
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import javax.net.ssl.X509TrustManager;
+import junit.framework.TestCase;
+
+public class CertBlocklistTest extends TestCase {
+
+    private static final String BLOCKLIST_CA = "test_blocklist_ca.pem";
+    private static final String BLOCKLISTED_CHAIN = "blocklist_test_chain.pem";
+    private static final String BLOCKLIST_FALLBACK_VALID_CA = "blocklist_test_valid_ca.pem";
+    private static final String BLOCKLISTED_VALID_CHAIN = "blocklist_test_valid_chain.pem";
+
+    /**
+     * Ensure that the test blocklisted CA is actually blocklisted by default.
+     */
+    public void testBlocklistedPublicKey() throws Exception {
+        X509Certificate blocklistedCa = loadCertificate(BLOCKLIST_CA);
+        CertBlocklist blocklist = CertBlocklistImpl.getDefault();
+        assertTrue(blocklist.isPublicKeyBlockListed(blocklistedCa.getPublicKey()));
+    }
+
+    /**
+     * Check that the blocklisted CA is rejected even if it used as a root of trust
+     */
+    public void testBlocklistedCaUntrusted() throws Exception {
+        X509Certificate blocklistedCa = loadCertificate(BLOCKLIST_CA);
+        assertUntrusted(new X509Certificate[] {blocklistedCa}, getTrustManager(blocklistedCa));
+    }
+
+    /**
+     * Check that a chain that is rooted in a blocklisted trusted CA is rejected.
+     */
+    public void testBlocklistedRootOfTrust() throws Exception {
+        // Chain is leaf -> blocklisted
+        X509Certificate[] chain = loadCertificates(BLOCKLISTED_CHAIN);
+        X509Certificate blocklistedCa = loadCertificate(BLOCKLIST_CA);
+        assertUntrusted(chain, getTrustManager(blocklistedCa));
+    }
+
+    /** Test that the path building correctly routes around a blocklisted cert where there are
+     * other valid paths available. This prevents breakage where a cert was cross signed by a
+     * blocklisted CA but is still valid due to also being cross signed by CAs that remain trusted.
+     * Path:
+     *
+     * leaf -> intermediate -> blocklisted_ca
+     *               \
+     *                -------> trusted_ca
+     */
+    public void testBlocklistedIntermediateFallback() throws Exception {
+        X509Certificate[] chain = loadCertificates(BLOCKLISTED_VALID_CHAIN);
+        X509Certificate blocklistedCa = loadCertificate(BLOCKLIST_CA);
+        X509Certificate validCa = loadCertificate(BLOCKLIST_FALLBACK_VALID_CA);
+        assertTrusted(chain, getTrustManager(blocklistedCa, validCa));
+        // Check that without the trusted_ca the chain is invalid (since it only chains to a
+        // blocklisted ca)
+        assertUntrusted(chain, getTrustManager(blocklistedCa));
+    }
+
+    private static X509Certificate loadCertificate(String file) throws Exception {
+        return loadCertificates(file)[0];
+    }
+
+    private static X509Certificate[] loadCertificates(String file) throws Exception {
+        CertificateFactory factory = CertificateFactory.getInstance("X.509");
+        try (InputStream is = TestUtils.openTestFile(file)) {
+            Collection<? extends Certificate> collection = factory.generateCertificates(is);
+            is.close();
+            X509Certificate[] certs = new X509Certificate[collection.size()];
+            int i = 0;
+            for (Certificate cert : collection) {
+                certs[i++] = (X509Certificate) cert;
+            }
+            return certs;
+        }
+    }
+
+    private static TrustManagerImpl getTrustManager(X509Certificate... trustedCas)
+            throws Exception {
+        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+        ks.load(null);
+        int i = 0;
+        for (X509Certificate ca : trustedCas) {
+            ks.setCertificateEntry(String.valueOf(i++), ca);
+        }
+        return new TrustManagerImpl(ks);
+    }
+
+    private static void assertTrusted(X509Certificate[] certs, X509TrustManager tm)
+            throws Exception {
+        tm.checkServerTrusted(certs, "RSA");
+    }
+
+    private static void assertUntrusted(X509Certificate[] certs, X509TrustManager tm) {
+        try {
+            tm.checkServerTrusted(certs, "RSA");
+            fail();
+        } catch (CertificateException expected) {
+        }
+    }
+}
diff --git a/platform/src/test/java/org/conscrypt/TrustedCertificateStoreTest.java b/platform/src/test/java/org/conscrypt/TrustedCertificateStoreTest.java
index 33e6aac..679f630 100644
--- a/platform/src/test/java/org/conscrypt/TrustedCertificateStoreTest.java
+++ b/platform/src/test/java/org/conscrypt/TrustedCertificateStoreTest.java
@@ -878,6 +878,7 @@
         assertEquals(x, store.findIssuer(x));
     }
 
+    @SuppressWarnings("JdkObsolete") // Date is used in public API TrustedCertificateKeyStoreSpi
     private void assertTrusted(X509Certificate x, String alias) {
         assertEquals(x, store.getCertificate(alias));
         assertEquals(file(alias).lastModified(), store.getCreationDate(alias).getTime());
diff --git a/platform/src/test/java/org/conscrypt/metrics/MetricsTest.java b/platform/src/test/java/org/conscrypt/metrics/MetricsTest.java
new file mode 100644
index 0000000..2584469
--- /dev/null
+++ b/platform/src/test/java/org/conscrypt/metrics/MetricsTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.metrics;
+
+import static org.junit.Assert.assertEquals;
+
+import android.util.StatsEvent;
+import org.conscrypt.TestUtils;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MetricsTest {
+    public static final int TLS_HANDSHAKE_REPORTED = 317;
+
+    // Tests that ReflexiveEvent produces the same event as framework's.
+    @Test
+    @Ignore // Ignore on CTS 12 only: b/259508875
+    public void test_reflexiveEvent() throws Exception {
+        TestUtils.assumeStatsLogAvailable();
+
+        StatsEvent frameworkStatsEvent = StatsEvent.newBuilder()
+                                                 .setAtomId(TLS_HANDSHAKE_REPORTED)
+                                                 .writeBoolean(false)
+                                                 .writeInt(1) // protocol
+                                                 .writeInt(2) // cipher suite
+                                                 .writeInt(100) // duration
+                                                 .usePooledBuffer()
+                                                 .build();
+
+        ReflexiveStatsEvent reflexiveStatsEvent =
+                ReflexiveStatsEvent.buildEvent(TLS_HANDSHAKE_REPORTED, false, 1, 2, 100);
+        StatsEvent constructedEvent = (StatsEvent) reflexiveStatsEvent.getStatsEvent();
+
+        // TODO(nikitai): Figure out how to use hidden (@hide) getters from StatsEvent
+        // to eliminate the use of reflection
+        int fid = (Integer) frameworkStatsEvent.getClass()
+                          .getMethod("getAtomId")
+                          .invoke(frameworkStatsEvent);
+        int cid = (Integer) constructedEvent.getClass()
+                          .getMethod("getAtomId")
+                          .invoke(constructedEvent);
+        assertEquals(fid, cid);
+
+        int fnb = (Integer) frameworkStatsEvent.getClass()
+                          .getMethod("getNumBytes")
+                          .invoke(frameworkStatsEvent);
+        int cnb = (Integer) constructedEvent.getClass()
+                          .getMethod("getNumBytes")
+                          .invoke(constructedEvent);
+        assertEquals(fnb, cnb);
+
+        byte[] fbytes = (byte[]) frameworkStatsEvent.getClass()
+                                .getMethod("getBytes")
+                                .invoke(frameworkStatsEvent);
+        byte[] cbytes =
+                (byte[]) constructedEvent.getClass().getMethod("getBytes").invoke(constructedEvent);
+        for (int i = 0; i < fnb; i++) {
+            // skip encoded timestamp (bytes 1-8)
+            if (i < 1 || i > 8) {
+                assertEquals(fbytes[i], cbytes[i]);
+            }
+        }
+    }
+}
diff --git a/publicapi/src/main/java/android/net/ssl/SSLEngines.java b/publicapi/src/main/java/android/net/ssl/SSLEngines.java
index 13bd771..e5967db 100644
--- a/publicapi/src/main/java/android/net/ssl/SSLEngines.java
+++ b/publicapi/src/main/java/android/net/ssl/SSLEngines.java
@@ -18,7 +18,9 @@
 
 import com.android.org.conscrypt.Conscrypt;
 import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
 import libcore.util.NonNull;
+import libcore.util.Nullable;
 
 /**
  * Static utility methods for accessing additional functionality of supported instances of
@@ -55,4 +57,33 @@
         checkSupported(engine);
         Conscrypt.setUseSessionTickets(engine, useSessionTickets);
     }
+
+    /**
+     * Exports a value derived from the TLS master secret as described in RFC 5705.
+     *
+     * A number of protocols leverage Transport Layer Security (TLS) to perform key
+     * establishment but then use some of the keying material for their own purposes.
+     *
+     * This method allows an application to export keying material from a TLS connection.
+     * The exported material will be the same on the client and server if they pass in
+     * the same values for {@code label} and {@code context}.  See RFC 5705 for further
+     * details.
+     *
+     * @param engine the engine to use for exporting keying material
+     * @param label the label to use in calculating the exported value.  This must be
+     * an ASCII-only string.
+     * @param context the application-specific context value to use in calculating the
+     * exported value.  This may be {@code null} to use no application context, which is
+     * treated differently than an empty byte array.
+     * @param length the number of bytes of keying material to return.
+     * @return a value of the specified length, or {@code null} if the handshake has not yet
+     * completed or the connection has been closed.
+     * @throws SSLException if the value could not be exported.
+     */
+    @Nullable
+    public static byte[] exportKeyingMaterial(@NonNull SSLEngine engine, @NonNull String label,
+            @Nullable byte[] context, int length) throws SSLException {
+        checkSupported(engine);
+        return Conscrypt.exportKeyingMaterial(engine, label, context, length);
+    }
 }
diff --git a/publicapi/src/main/java/android/net/ssl/SSLSockets.java b/publicapi/src/main/java/android/net/ssl/SSLSockets.java
index a669404..0c04428 100644
--- a/publicapi/src/main/java/android/net/ssl/SSLSockets.java
+++ b/publicapi/src/main/java/android/net/ssl/SSLSockets.java
@@ -17,8 +17,10 @@
 package android.net.ssl;
 
 import com.android.org.conscrypt.Conscrypt;
+import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLSocket;
 import libcore.util.NonNull;
+import libcore.util.Nullable;
 
 /**
  * Static utility methods for accessing additional functionality of supported instances of
@@ -55,4 +57,33 @@
         checkSupported(socket);
         Conscrypt.setUseSessionTickets(socket, useSessionTickets);
     }
+
+    /**
+     * Exports a value derived from the TLS master secret as described in RFC 5705.
+     *
+     * A number of protocols leverage Transport Layer Security (TLS) to perform key
+     * establishment but then use some of the keying material for their own purposes.
+     *
+     * This method allows an application to export keying material from a TLS connection.
+     * The exported material will be the same on the client and server if they pass in
+     * the same values for {@code label} and {@code context}.  See RFC 5705 for further
+     * details.
+     *
+     * @param socket the socket to use for exporting keying material
+     * @param label the label to use in calculating the exported value.  This must be
+     * an ASCII-only string.
+     * @param context the application-specific context value to use in calculating the
+     * exported value.  This may be {@code null} to use no application context, which is
+     * treated differently than an empty byte array.
+     * @param length the number of bytes of keying material to return.
+     * @return a value of the specified length, or {@code null} if the handshake has not yet
+     * completed or the connection has been closed.
+     * @throws SSLException if the value could not be exported.
+     */
+    @Nullable
+    public static byte[] exportKeyingMaterial(@NonNull SSLSocket socket, @NonNull String label,
+            @Nullable byte[] context, int length) throws SSLException {
+        checkSupported(socket);
+        return Conscrypt.exportKeyingMaterial(socket, label, context, length);
+    }
 }
diff --git a/publicapi/src/test/java/android/net/ssl/SSLEnginesTest.java b/publicapi/src/test/java/android/net/ssl/SSLEnginesTest.java
index 66abec6..e4285f7 100644
--- a/publicapi/src/test/java/android/net/ssl/SSLEnginesTest.java
+++ b/publicapi/src/test/java/android/net/ssl/SSLEnginesTest.java
@@ -16,12 +16,15 @@
 
 package android.net.ssl;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
+import com.android.org.conscrypt.javax.net.ssl.TestSSLContext;
+import com.android.org.conscrypt.javax.net.ssl.TestSSLEnginePair;
 import com.android.org.conscrypt.tlswire.TlsTester;
 import com.android.org.conscrypt.tlswire.handshake.ClientHello;
 import com.android.org.conscrypt.tlswire.handshake.HelloExtension;
@@ -29,7 +32,6 @@
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLSession;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -40,11 +42,11 @@
 
     private static class BrokenSSLEngine extends SSLEngine {
         @Override public SSLEngineResult wrap(ByteBuffer[] byteBuffers, int i, int i1,
-                ByteBuffer byteBuffer) throws SSLException { throw new AssertionError(); }
+                ByteBuffer byteBuffer) { throw new AssertionError(); }
         @Override public SSLEngineResult unwrap(ByteBuffer byteBuffer, ByteBuffer[] byteBuffers,
-                int i, int i1) throws SSLException { throw new AssertionError(); }
+                int i, int i1)  { throw new AssertionError(); }
         @Override public Runnable getDelegatedTask() { throw new AssertionError(); }
-        @Override public void closeInbound() throws SSLException { throw new AssertionError(); }
+        @Override public void closeInbound()  { throw new AssertionError(); }
         @Override public boolean isInboundDone() { throw new AssertionError(); }
         @Override public void closeOutbound() { throw new AssertionError(); }
         @Override public boolean isOutboundDone() { throw new AssertionError(); }
@@ -55,7 +57,7 @@
         @Override public String[] getEnabledProtocols() { throw new AssertionError(); }
         @Override public void setEnabledProtocols(String[] strings) { throw new AssertionError(); }
         @Override public SSLSession getSession() { throw new AssertionError(); }
-        @Override public void beginHandshake() throws SSLException { throw new AssertionError(); }
+        @Override public void beginHandshake() { throw new AssertionError(); }
         @Override public SSLEngineResult.HandshakeStatus getHandshakeStatus() { throw new AssertionError(); }
         @Override public void setUseClientMode(boolean b) { throw new AssertionError(); }
         @Override public boolean getUseClientMode() { throw new AssertionError(); }
@@ -78,14 +80,13 @@
         assertFalse(SSLEngines.isSupportedEngine(e));
     }
 
-    @Test
-    public void testUseSessionTickets() throws Exception {
-        try {
-            SSLEngines.setUseSessionTickets(new BrokenSSLEngine(), true);
-            fail();
-        } catch (IllegalArgumentException expected) {
-        }
+    @Test(expected = IllegalArgumentException.class)
+    public void useSessionTickets_InvalidEngine() {
+        SSLEngines.setUseSessionTickets(new BrokenSSLEngine(), true);
+    }
 
+    @Test
+    public void useSessionTickets_ValidEngine() throws Exception {
         SSLEngine e = SSLContext.getDefault().createSSLEngine();
         e.setUseClientMode(true);
         SSLEngines.setUseSessionTickets(e, true);
@@ -111,4 +112,25 @@
 
         return TlsTester.parseClientHello(data);
     }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void exportKeyingMaterial_InvalidEngine() throws Exception {
+        SSLEngines.exportKeyingMaterial(new BrokenSSLEngine(), "label", null, 20);
+    }
+
+    @Test
+    public void exportKeyingMaterial_ValidEngine() throws Exception {
+        String label = "Some label";
+        int keyLength = 32;
+
+        TestSSLEnginePair pair = TestSSLEnginePair.create(TestSSLContext.create());
+
+        byte[] clientEkm = SSLEngines.exportKeyingMaterial(pair.client, label, null, keyLength);
+        byte[] serverEkm = SSLEngines.exportKeyingMaterial(pair.server, label, null, keyLength);
+        assertNotNull(clientEkm);
+        assertNotNull(serverEkm);
+        assertEquals(keyLength, clientEkm.length);
+        assertEquals(keyLength, serverEkm.length);
+        assertArrayEquals(clientEkm, serverEkm);
+    }
 }
diff --git a/publicapi/src/test/java/android/net/ssl/SSLSocketsTest.java b/publicapi/src/test/java/android/net/ssl/SSLSocketsTest.java
index 02df047..78a42c5 100644
--- a/publicapi/src/test/java/android/net/ssl/SSLSocketsTest.java
+++ b/publicapi/src/test/java/android/net/ssl/SSLSocketsTest.java
@@ -16,12 +16,14 @@
 
 package android.net.ssl;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
+import com.android.org.conscrypt.javax.net.ssl.TestSSLSocketPair;
 import com.android.org.conscrypt.tlswire.TlsTester;
 import com.android.org.conscrypt.tlswire.handshake.ClientHello;
 import com.android.org.conscrypt.tlswire.handshake.HelloExtension;
@@ -87,14 +89,13 @@
         assertFalse(SSLSockets.isSupportedSocket(s));
     }
 
-    @Test
-    public void testUseSessionTickets() throws Exception {
-        try {
-            SSLSockets.setUseSessionTickets(new BrokenSSLSocket(), true);
-            fail();
-        } catch (IllegalArgumentException expected) {
-        }
+    @Test(expected = IllegalArgumentException.class)
+    public void setUseSessionTickets_InvalidSocket() {
+        SSLSockets.setUseSessionTickets(new BrokenSSLSocket(), true);
+    }
 
+    @Test
+    public void setUseSessionTickets_ValidSocket() throws Exception {
         SSLSocket s = (SSLSocket) SSLSocketFactory.getDefault().createSocket();
         SSLSockets.setUseSessionTickets(s, true);
 
@@ -116,4 +117,26 @@
                 });
         assertNull(hello.findExtensionByType(HelloExtension.TYPE_SESSION_TICKET));
     }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void exportKeyingMaterial_InvalidSocket() throws Exception {
+        SSLSockets.exportKeyingMaterial(new BrokenSSLSocket(), "label", null, 20);
+    }
+
+    @Test
+    public void exportKeyingMaterial_ValidSocket() throws Exception {
+        TestSSLSocketPair pair = TestSSLSocketPair.create();
+        String label = "Some label";
+        int keyLength = 32;
+
+        pair.connect();
+
+        byte[] clientEkm = SSLSockets.exportKeyingMaterial(pair.client, label, null, keyLength);
+        byte[] serverEkm = SSLSockets.exportKeyingMaterial(pair.server, label, null, keyLength);
+        assertNotNull(clientEkm);
+        assertNotNull(serverEkm);
+        assertEquals(keyLength, clientEkm.length);
+        assertEquals(keyLength, serverEkm.length);
+        assertArrayEquals(clientEkm, serverEkm);
+    }
 }
diff --git a/release/Dockerfile b/release/Dockerfile
index f0d5164..c974566 100644
--- a/release/Dockerfile
+++ b/release/Dockerfile
@@ -46,9 +46,9 @@
 
 # Build and install CMake from source.
 WORKDIR /usr/src
-RUN git clone git://cmake.org/cmake.git CMake && \
+RUN git clone https://gitlab.kitware.com/cmake/cmake.git CMake && \
   cd CMake && \
-  git checkout v3.4.1 && \
+  git checkout tags/v3.5.2 && \
   mkdir /usr/src/CMake-build && \
   cd /usr/src/CMake-build && \
   /usr/src/CMake/bootstrap \
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/AbstractConscryptSocket.java b/repackaged/common/src/main/java/com/android/org/conscrypt/AbstractConscryptSocket.java
index 6284fd9..f95d995 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/AbstractConscryptSocket.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/AbstractConscryptSocket.java
@@ -517,7 +517,7 @@
      * Returns the hostname that was supplied during socket creation. No DNS resolution is
      * attempted before returning the hostname.
      */
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     String getHostname() {
         return peerHostname;
     }
@@ -529,7 +529,7 @@
      */
     @android.compat.annotation.
     UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
-            publicAlternatives = "Use {@link javax.net.ssl.SSLParameters#setServerNames}.")
+            publicAlternatives = "Use {@code javax.net.ssl.SSLParameters#setServerNames}.")
     void
     setHostname(String hostname) {
         peerHostname = hostname;
@@ -540,7 +540,7 @@
      * or the IP address in a textual representation. We do not want to perform reverse DNS
      * lookups on this address.
      */
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     String getHostnameOrIP() {
         if (peerHostname != null) {
             return peerHostname;
@@ -557,7 +557,7 @@
     /**
      * Note write timeouts are not part of the javax.net.ssl.SSLSocket API
      */
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException {
         throw new SocketException("Method setSoWriteTimeout() is not supported.");
     }
@@ -565,7 +565,7 @@
     /**
      * Note write timeouts are not part of the javax.net.ssl.SSLSocket API
      */
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     int getSoWriteTimeout() throws SocketException {
         return 0;
     }
@@ -574,7 +574,7 @@
      * Set the handshake timeout on this socket.  This timeout is specified in
      * milliseconds and will be used only during the handshake process.
      */
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     void setHandshakeTimeout(int handshakeTimeoutMilliseconds) throws SocketException {
         throw new SocketException("Method setHandshakeTimeout() is not supported.");
     }
@@ -646,7 +646,7 @@
      * @throws IllegalStateException if this is a client socket or if the handshake has already
      *         started.
      */
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     abstract void setChannelIdEnabled(boolean enabled);
 
     /**
@@ -659,7 +659,7 @@
      *         completed.
      * @throws SSLException if channel ID is available but could not be obtained.
      */
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     abstract byte[] getChannelId() throws SSLException;
 
     /**
@@ -674,14 +674,14 @@
      * @throws IllegalStateException if this is a server socket or if the handshake has already
      *         started.
      */
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     abstract void setChannelIdPrivateKey(PrivateKey privateKey);
 
     /**
      * Returns null always for backward compatibility.
      * @deprecated NPN is not supported
      */
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     @Deprecated
     byte[] getNpnSelectedProtocol() {
         return null;
@@ -691,7 +691,7 @@
      * This method does nothing and is kept for backward compatibility.
      * @deprecated NPN is not supported
      */
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     @Deprecated
     void setNpnProtocols(byte[] npnProtocols) {}
 
@@ -703,7 +703,7 @@
      */
     @android.compat.annotation.
     UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
-            publicAlternatives = "Use {@link javax.net.ssl.SSLSocket#getApplicationProtocol()}.")
+            publicAlternatives = "Use {@code javax.net.ssl.SSLSocket#getApplicationProtocol()}.")
     @Deprecated
     abstract byte[] getAlpnSelectedProtocol();
 
@@ -717,7 +717,7 @@
     @android.compat.annotation.
     UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
             publicAlternatives =
-                    "Use {@link javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}.")
+                    "Use {@code javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}.")
     @Deprecated
     abstract void setAlpnProtocols(String[] alpnProtocols);
 
@@ -732,7 +732,7 @@
     @android.compat.annotation.
     UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
             publicAlternatives =
-                    "Use {@link javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}.")
+                    "Use {@code javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}.")
     @Deprecated
     abstract void setAlpnProtocols(byte[] alpnProtocols);
 
@@ -744,7 +744,7 @@
     @android.compat.annotation.
     UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
             publicAlternatives =
-                    "Use {@link javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}.")
+                    "Use {@code javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}.")
     @SuppressWarnings("MissingOverride") // For compiling pre Java 9.
     abstract void setApplicationProtocols(String[] protocols);
 
@@ -754,7 +754,7 @@
     @android.compat.annotation.
     UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
             publicAlternatives =
-                    "Use {@link javax.net.ssl.SSLParameters#getApplicationProtocols()}.")
+                    "Use {@code javax.net.ssl.SSLParameters#getApplicationProtocols()}.")
     @SuppressWarnings("MissingOverride") // For compiling pre Java 9.
     abstract String[] getApplicationProtocols();
 
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/AbstractSessionContext.java b/repackaged/common/src/main/java/com/android/org/conscrypt/AbstractSessionContext.java
index d777735..6469d95 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/AbstractSessionContext.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/AbstractSessionContext.java
@@ -42,7 +42,6 @@
 
     final long sslCtxNativePointer = NativeCrypto.SSL_CTX_new();
 
-    @SuppressWarnings("serial")
     private final Map<ByteArray, NativeSslSession> sessions =
             new LinkedHashMap<ByteArray, NativeSslSession>() {
                 @Override
@@ -77,8 +76,7 @@
         // Make a copy of the IDs.
         final Iterator<NativeSslSession> iter;
         synchronized (sessions) {
-            iter = Arrays.asList(sessions.values().toArray(new NativeSslSession[sessions.size()]))
-                    .iterator();
+            iter = Arrays.asList(sessions.values().toArray(new NativeSslSession[0])).iterator();
         }
         return new Enumeration<byte[]>() {
             private NativeSslSession next;
@@ -189,6 +187,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     protected void finalize() throws Throwable {
         try {
             NativeCrypto.SSL_CTX_free(sslCtxNativePointer, this);
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/ActiveSession.java b/repackaged/common/src/main/java/com/android/org/conscrypt/ActiveSession.java
index a648395..3ad5950 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/ActiveSession.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/ActiveSession.java
@@ -34,7 +34,7 @@
  */
 final class ActiveSession implements ConscryptSession {
     private final NativeSsl ssl;
-    private AbstractSessionContext sessionContext;
+    private final AbstractSessionContext sessionContext;
     private byte[] id;
     private long creationTime;
     private String protocol;
@@ -42,6 +42,7 @@
     private String peerHost;
     private int peerPort = -1;
     private long lastAccessedTime = 0;
+    @SuppressWarnings("deprecation")
     private volatile javax.security.cert.X509Certificate[] peerCertificateChain;
     private X509Certificate[] localCertificates;
     private X509Certificate[] peerCertificates;
@@ -109,7 +110,7 @@
     @Override
     public List<byte[]> getStatusResponses() {
         if (peerCertificateOcspData == null) {
-            return Collections.<byte[]>emptyList();
+            return Collections.emptyList();
         }
 
         return Collections.singletonList(peerCertificateOcspData.clone());
@@ -206,8 +207,13 @@
      *         be verified.
      */
     @Override
+    @SuppressWarnings("deprecation") // Public API
     public javax.security.cert.X509Certificate[] getPeerCertificateChain()
             throws SSLPeerUnverifiedException {
+        if (!Platform.isJavaxCertificateSupported()) {
+            throw new UnsupportedOperationException("Use getPeerCertificates() instead");
+        }
+
         checkPeerCertificatesPresent();
         // TODO(nathanmittler): Should we clone?
         javax.security.cert.X509Certificate[] result = peerCertificateChain;
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/BufferUtils.java b/repackaged/common/src/main/java/com/android/org/conscrypt/BufferUtils.java
new file mode 100644
index 0000000..8861531
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/BufferUtils.java
@@ -0,0 +1,140 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright 2021 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 java.lang.Math.min;
+import static com.android.org.conscrypt.Preconditions.checkArgument;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Utility methods for dealing with arrays of ByteBuffers.
+ *
+ * @hide This class is not part of the Android public SDK API
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class BufferUtils {
+    private BufferUtils() {}
+
+    /**
+     * Throws {@link IllegalArgumentException} if any of the buffers in the array are null.
+     */
+    public static void checkNotNull(ByteBuffer[] buffers) {
+        for (ByteBuffer buffer : buffers) {
+            if (buffer == null) {
+                throw new IllegalArgumentException("Null buffer in array");
+            }
+        }
+    }
+
+    /**
+     * Returns the total number of bytes remaining in the buffer array.
+     */
+    public static long remaining(ByteBuffer[] buffers) {
+        long size = 0;
+        for (ByteBuffer buffer : buffers) {
+            size += buffer.remaining();
+        }
+        return size;
+    }
+
+    /**
+     * Marks {@code toConsume} bytes of data as consumed from the buffer array.
+     *
+     * @throws IllegalArgumentException if there are fewer than {@code toConsume} bytes remaining
+     */
+    public static void consume(ByteBuffer[] sourceBuffers, int toConsume) {
+        for (ByteBuffer sourceBuffer : sourceBuffers) {
+            int amount = min(sourceBuffer.remaining(), toConsume);
+            if (amount > 0) {
+                sourceBuffer.position(sourceBuffer.position() + amount);
+                toConsume -= amount;
+                if (toConsume == 0) {
+                    break;
+                }
+            }
+        }
+        if (toConsume > 0) {
+            throw new IllegalArgumentException("toConsume > data size");
+        }
+    }
+
+    /**
+     * Looks for a buffer in the buffer array which EITHER is larger than {@code minSize} AND
+     * has no preceding non-empty buffers OR is the only non-empty buffer in the array.
+     */
+    public static ByteBuffer getBufferLargerThan(ByteBuffer[] buffers, int minSize) {
+        int length = buffers.length;
+        for (int i = 0; i < length; i++) {
+            ByteBuffer buffer = buffers[i];
+            int remaining = buffer.remaining();
+            if (remaining > 0) {
+                if (remaining >= minSize) {
+                    return buffer;
+                }
+                for (int j = i + 1; j < length; j++) {
+                    if (buffers[j].remaining() > 0) {
+                        return null;
+                    }
+                }
+                return buffer;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Copies up to {@code maxAmount} bytes from a buffer array to {@code destination}.
+     * The copied data is <b>not</b> marked as consumed from the source buffers, on the
+     * assumption the copy will be passed to some method which will consume between 0 and
+     * {@code maxAmount} bytes which can then be reflected in the source array using the
+     * {@code consume()} method.
+     *
+     */
+    public static ByteBuffer copyNoConsume(
+            ByteBuffer[] buffers, ByteBuffer destination, int maxAmount) {
+        checkArgument(destination.remaining() >= maxAmount, "Destination buffer too small");
+        int needed = maxAmount;
+        for (ByteBuffer buffer : buffers) {
+            int remaining = buffer.remaining();
+            if (remaining > 0) {
+                // If this buffer can fit completely then copy it all, otherwise temporarily
+                // adjust its limit to fill so as to the output buffer completely
+                int oldPosition = buffer.position();
+                if (remaining <= needed) {
+                    destination.put(buffer);
+                    needed -= remaining;
+                } else {
+                    int oldLimit = buffer.limit();
+                    buffer.limit(buffer.position() + needed);
+                    destination.put(buffer);
+                    buffer.limit(oldLimit);
+                    needed = 0;
+                }
+                // Restore the buffer's position, the data won't get marked as consumed until
+                // outputBuffer has been successfully consumed.
+                buffer.position(oldPosition);
+                if (needed == 0) {
+                    break;
+                }
+            }
+        }
+        destination.flip();
+        return destination;
+    }
+}
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/ByteArray.java b/repackaged/common/src/main/java/com/android/org/conscrypt/ByteArray.java
index c4f7da3..6bd104d 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/ByteArray.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/ByteArray.java
@@ -42,6 +42,9 @@
             return false;
         }
         ByteArray lhs = (ByteArray) o;
+        if (hashCode != lhs.hashCode) {
+            return false;
+        }
         return Arrays.equals(bytes, lhs.bytes);
     }
 }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/CertBlacklist.java b/repackaged/common/src/main/java/com/android/org/conscrypt/CertBlocklist.java
similarity index 87%
rename from repackaged/common/src/main/java/com/android/org/conscrypt/CertBlacklist.java
rename to repackaged/common/src/main/java/com/android/org/conscrypt/CertBlocklist.java
index cecc8ba..14454d2 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/CertBlacklist.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/CertBlocklist.java
@@ -24,15 +24,15 @@
  * A set of certificates that are blacklisted from trust.
  * @hide This class is not part of the Android public SDK API
  */
-public interface CertBlacklist {
+public interface CertBlocklist {
 
     /**
      * Returns whether the given public key is in the blacklist.
      */
-    boolean isPublicKeyBlackListed(PublicKey publicKey);
+    boolean isPublicKeyBlockListed(PublicKey publicKey);
 
     /**
      * Returns whether the given serial number is in the blacklist.
      */
-    boolean isSerialNumberBlackListed(BigInteger serial);
+    boolean isSerialNumberBlockListed(BigInteger serial);
 }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/CertPinManager.java b/repackaged/common/src/main/java/com/android/org/conscrypt/CertPinManager.java
index ff3bda2..50a36da 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/CertPinManager.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/CertPinManager.java
@@ -25,7 +25,7 @@
  * Interface for classes that implement certificate pinning for use in {@link TrustManagerImpl}.
  * @hide This class is not part of the Android public SDK API
  */
-@libcore.api.CorePlatformApi
+@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
 @Internal
 public interface CertPinManager {
     /**
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/CertificatePriorityComparator.java b/repackaged/common/src/main/java/com/android/org/conscrypt/CertificatePriorityComparator.java
index 34d6222..defb37e 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/CertificatePriorityComparator.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/CertificatePriorityComparator.java
@@ -77,6 +77,7 @@
     }
 
     @Override
+    @SuppressWarnings("JdkObsolete") // Certificate uses Date
     public int compare(X509Certificate lhs, X509Certificate rhs) {
         int result;
         boolean lhsSelfSigned = lhs.getSubjectDN().equals(lhs.getIssuerDN());
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/ClientSessionContext.java b/repackaged/common/src/main/java/com/android/org/conscrypt/ClientSessionContext.java
index 2dbdd7b..41bc8d2 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/ClientSessionContext.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/ClientSessionContext.java
@@ -28,7 +28,7 @@
  * looking to reuse any session for a given host and port.
  * @hide This class is not part of the Android public SDK API
  */
-@libcore.api.CorePlatformApi
+@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
 @Internal
 public final class ClientSessionContext extends AbstractSessionContext {
     /**
@@ -54,7 +54,7 @@
      */
     @android.compat.annotation
             .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi
+            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
             public void setPersistentCache(SSLClientSessionCache persistentCache) {
         this.persistentCache = persistentCache;
     }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/Conscrypt.java b/repackaged/common/src/main/java/com/android/org/conscrypt/Conscrypt.java
index 33a3959..1eaed63 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/Conscrypt.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/Conscrypt.java
@@ -23,7 +23,9 @@
 import java.security.KeyManagementException;
 import java.security.PrivateKey;
 import java.security.Provider;
+import java.security.cert.X509Certificate;
 import java.util.Properties;
+import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLContextSpi;
@@ -31,6 +33,7 @@
 import javax.net.ssl.SSLEngineResult;
 import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSessionContext;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
@@ -41,7 +44,7 @@
  * Core API for creating and configuring all Conscrypt types.
  * @hide This class is not part of the Android public SDK API
  */
-@libcore.api.CorePlatformApi
+@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
 @SuppressWarnings("unused")
 public final class Conscrypt {
     private Conscrypt() {}
@@ -59,6 +62,17 @@
     }
 
     /**
+     * Return {@code true} if BoringSSL has been built in FIPS mode.
+     */
+    public static boolean isBoringSSLFIPSBuild() {
+        try {
+            return NativeCrypto.usesBoringSSL_FIPS_mode();
+        } catch (Throwable e) {
+            return false;
+        }
+    }
+
+    /**
      * @hide This class is not part of the Android public SDK API
      */
     public static class Version {
@@ -94,6 +108,7 @@
                 patch = Integer.parseInt(props.getProperty("com.android.org.conscrypt.version.patch", "-1"));
             }
         } catch (IOException e) {
+            // TODO(prb): This should probably be fatal or have some fallback behaviour
         } finally {
             IoUtils.closeQuietly(stream);
         }
@@ -212,7 +227,7 @@
     /**
      * Gets the default X.509 trust manager.
      */
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     @ExperimentalApi
     public static X509TrustManager getDefaultX509TrustManager() throws KeyManagementException {
         checkAvailability();
@@ -793,4 +808,17 @@
     public static ConscryptHostnameVerifier getHostnameVerifier(TrustManager trustManager) {
         return toConscrypt(trustManager).getHostnameVerifier();
     }
+
+    /**
+     * Wraps the HttpsURLConnection.HostnameVerifier into a ConscryptHostnameVerifier
+     */
+    public static ConscryptHostnameVerifier wrapHostnameVerifier(final HostnameVerifier verifier) {
+        return new ConscryptHostnameVerifier() {
+            @Override
+            public boolean verify(
+                    X509Certificate[] certificates, String hostname, SSLSession session) {
+                return verifier.verify(hostname, session);
+            }
+        };
+    }
 }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptCertStore.java b/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptCertStore.java
index 472e2da..213f28a 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptCertStore.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptCertStore.java
@@ -26,10 +26,9 @@
  * Android platform.
  * @hide This class is not part of the Android public SDK API
  */
-@libcore.api.CorePlatformApi
+@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
 @Internal
 public interface ConscryptCertStore {
-
     /**
      * Returns a stored CA certificate with the same name and public key as the
      * provided {@link X509Certificate}.
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptEngine.java b/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptEngine.java
index 59996ba..4e4bd62 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptEngine.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptEngine.java
@@ -66,7 +66,6 @@
 import static javax.net.ssl.SSLEngineResult.Status.CLOSED;
 import static javax.net.ssl.SSLEngineResult.Status.OK;
 
-import com.android.org.conscrypt.ExternalSession.Provider;
 import com.android.org.conscrypt.NativeRef.SSL_SESSION;
 import com.android.org.conscrypt.NativeSsl.BioWrapper;
 import com.android.org.conscrypt.SSLParametersImpl.AliasChooser;
@@ -81,6 +80,7 @@
 import java.security.cert.X509Certificate;
 import java.security.interfaces.ECKey;
 import java.security.spec.ECParameterSpec;
+import java.util.Arrays;
 import javax.crypto.SecretKey;
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLEngineResult;
@@ -157,12 +157,12 @@
      * The session object exposed externally from this class.
      */
     private final SSLSession externalSession =
-        Platform.wrapSSLSession(new ExternalSession(new Provider() {
-            @Override
-            public ConscryptSession provideSession() {
-                return ConscryptEngine.this.provideSession();
-            }
-        }));
+            Platform.wrapSSLSession(new ExternalSession(new ExternalSession.Provider() {
+                @Override
+                public ConscryptSession provideSession() {
+                    return ConscryptEngine.this.provideSession();
+                }
+            }));
 
     /**
      * Private key for the TLS Channel ID extension. This field is client-side only. Set during
@@ -587,7 +587,7 @@
     SSLSession handshakeSession() {
         synchronized (ssl) {
             if (state == STATE_HANDSHAKE_STARTED) {
-                return Platform.wrapSSLSession(new ExternalSession(new Provider() {
+                return Platform.wrapSSLSession(new ExternalSession(new ExternalSession.Provider() {
                     @Override
                     public ConscryptSession provideSession() {
                         return ConscryptEngine.this.provideHandshakeSession();
@@ -1389,6 +1389,11 @@
             throw new ReadOnlyBufferException();
         }
 
+        if ((srcsOffset != 0) || (srcsLength != srcs.length)) {
+            srcs = Arrays.copyOfRange(srcs, srcsOffset, srcsOffset + srcsLength);
+        }
+        BufferUtils.checkNotNull(srcs);
+
         synchronized (ssl) {
             switch (state) {
                 case STATE_MODE_SET:
@@ -1428,118 +1433,110 @@
                 // NEED_WRAP - just fall through to perform the wrap.
             }
 
-            int srcsLen = 0;
-            final int endOffset = srcsOffset + srcsLength;
-            for (int i = srcsOffset; i < endOffset; ++i) {
-                final ByteBuffer src = srcs[i];
-                if (src == null) {
-                    throw new IllegalArgumentException("srcs[" + i + "] is null");
-                }
-                if (srcsLen == SSL3_RT_MAX_PLAIN_LENGTH) {
-                    continue;
-                }
-
-                srcsLen += src.remaining();
-                if (srcsLen > SSL3_RT_MAX_PLAIN_LENGTH || srcsLen < 0) {
-                    // If srcLen > MAX_PLAINTEXT_LENGTH or secLen < 0 just set it to
-                    // MAX_PLAINTEXT_LENGTH.
-                    // This also help us to guard against overflow.
-                    // We not break out here as we still need to check for null entries in srcs[].
-                    srcsLen = SSL3_RT_MAX_PLAIN_LENGTH;
-                }
-            }
-
-            if (dst.remaining() < calculateOutNetBufSize(srcsLen)) {
+            int dataLength = (int) min(BufferUtils.remaining(srcs), SSL3_RT_MAX_PLAIN_LENGTH);
+            if (dst.remaining() < calculateOutNetBufSize(dataLength)) {
                 return new SSLEngineResult(
                     Status.BUFFER_OVERFLOW, getHandshakeStatusInternal(), 0, 0);
             }
 
             int bytesProduced = 0;
             int bytesConsumed = 0;
-        loop:
-            for (int i = srcsOffset; i < endOffset; ++i) {
-                final ByteBuffer src = srcs[i];
-                checkArgument(src != null, "srcs[%d] is null", i);
-                while (src.hasRemaining()) {
-                    final SSLEngineResult pendingNetResult;
-                    // Write plaintext application data to the SSL engine
-                    int result = writePlaintextData(
-                        src, min(src.remaining(), SSL3_RT_MAX_PLAIN_LENGTH - bytesConsumed));
-                    if (result > 0) {
-                        bytesConsumed += result;
+            if (dataLength > 0) {
+                // Try and find a single buffer to send, e.g. the first non-empty buffer has
+                // more than enough data remaining to fill a TLS record. Otherwise copy as much
+                // data as possible from the source buffers to fill a record. Note the we can't
+                // mark the data as consumed until we see how much the TLS layer actually consumes.
+                boolean isCopy = false;
+                ByteBuffer outputBuffer =
+                        BufferUtils.getBufferLargerThan(srcs, SSL3_RT_MAX_PLAIN_LENGTH);
+                if (outputBuffer == null) {
+                    // The buffer by getOrCreateLazyDirectBuffer() is also used by
+                    // writePlainTextDataHeap(), but by filling it here the write path will go via
+                    // writePlainTextDataDirect() and the cost will be approximately the same,
+                    // especially if compacting multiple non-direct buffers into a single
+                    // direct one.
+                    // TODO(): use bufferAllocator if set.
+                    // https://github.com/google/conscrypt/issues/974
+                    outputBuffer = BufferUtils.copyNoConsume(
+                            srcs, getOrCreateLazyDirectBuffer(), SSL3_RT_MAX_PLAIN_LENGTH);
+                    isCopy = true;
+                }
+                final SSLEngineResult pendingNetResult;
+                // Write plaintext application data to the SSL engine
+                int result = writePlaintextData(
+                        outputBuffer, min(SSL3_RT_MAX_PLAIN_LENGTH, outputBuffer.remaining()));
+                if (result > 0) {
+                    bytesConsumed = result;
+                    if (isCopy) {
+                        // Data was a copy, so mark it as consumed in the original buffers.
+                        BufferUtils.consume(srcs, bytesConsumed);
+                    }
 
-                        pendingNetResult = readPendingBytesFromBIO(
+                    pendingNetResult = readPendingBytesFromBIO(
                             dst, bytesConsumed, bytesProduced, handshakeStatus);
-                        if (pendingNetResult != null) {
-                            if (pendingNetResult.getStatus() != OK) {
-                                return pendingNetResult;
-                            }
-                            bytesProduced = pendingNetResult.bytesProduced();
+                    if (pendingNetResult != null) {
+                        if (pendingNetResult.getStatus() != OK) {
+                            return pendingNetResult;
                         }
-                        if (bytesConsumed == SSL3_RT_MAX_PLAIN_LENGTH) {
-                            // If we consumed the maximum amount of bytes for the plaintext length
-                            // break out of the loop and start to fill the dst buffer.
-                            break loop;
-                        }
-                    } else {
-                        int sslError = ssl.getError(result);
-                        switch (sslError) {
-                            case SSL_ERROR_ZERO_RETURN:
-                                // This means the connection was shutdown correctly, close inbound
-                                // and outbound
-                                closeAll();
-                                pendingNetResult = readPendingBytesFromBIO(
-                                        dst, bytesConsumed, bytesProduced, handshakeStatus);
-                                return pendingNetResult != null ? pendingNetResult
-                                                                : CLOSED_NOT_HANDSHAKING;
-                            case SSL_ERROR_WANT_READ:
-                                // If there is no pending data to read from BIO we should go back to
-                                // event loop and try
-                                // to read more data [1]. It is also possible that event loop will
-                                // detect the socket
-                                // has been closed. [1]
-                                // https://www.openssl.org/docs/manmaster/man3/SSL_write.html
-                                pendingNetResult = readPendingBytesFromBIO(
-                                        dst, bytesConsumed, bytesProduced, handshakeStatus);
-                                return pendingNetResult != null
-                                        ? pendingNetResult
-                                        : new SSLEngineResult(getEngineStatus(), NEED_UNWRAP,
-                                                  bytesConsumed, bytesProduced);
-                            case SSL_ERROR_WANT_WRITE:
-                                // SSL_ERROR_WANT_WRITE typically means that the underlying
-                                // transport is not writable
-                                // and we should set the "want write" flag on the selector and try
-                                // again when the
-                                // underlying transport is writable [1]. However we are not directly
-                                // writing to the
-                                // underlying transport and instead writing to a BIO buffer. The
-                                // OpenSsl documentation
-                                // says we should do the following [1]:
-                                //
-                                // "When using a buffering BIO, like a BIO pair, data must be
-                                // written into or retrieved
-                                // out of the BIO before being able to continue."
-                                //
-                                // So we attempt to drain the BIO buffer below, but if there is no
-                                // data this condition
-                                // is undefined and we assume their is a fatal error with the
-                                // openssl engine and close.
-                                // [1] https://www.openssl.org/docs/manmaster/man3/SSL_write.html
-                                pendingNetResult = readPendingBytesFromBIO(
-                                        dst, bytesConsumed, bytesProduced, handshakeStatus);
-                                return pendingNetResult != null ? pendingNetResult
-                                                                : NEED_WRAP_CLOSED;
-                            default:
-                                // Everything else is considered as error
-                                closeAll();
-                                throw newSslExceptionWithMessage("SSL_write");
-                        }
+                        bytesProduced = pendingNetResult.bytesProduced();
+                    }
+                } else {
+                    int sslError = ssl.getError(result);
+                    switch (sslError) {
+                        case SSL_ERROR_ZERO_RETURN:
+                            // This means the connection was shutdown correctly, close inbound
+                            // and outbound
+                            closeAll();
+                            pendingNetResult = readPendingBytesFromBIO(
+                                    dst, bytesConsumed, bytesProduced, handshakeStatus);
+                            return pendingNetResult != null ? pendingNetResult
+                                                            : CLOSED_NOT_HANDSHAKING;
+                        case SSL_ERROR_WANT_READ:
+                            // If there is no pending data to read from BIO we should go back to
+                            // event loop and try
+                            // to read more data [1]. It is also possible that event loop will
+                            // detect the socket
+                            // has been closed. [1]
+                            // https://www.openssl.org/docs/manmaster/man3/SSL_write.html
+                            pendingNetResult = readPendingBytesFromBIO(
+                                    dst, bytesConsumed, bytesProduced, handshakeStatus);
+                            return pendingNetResult != null
+                                    ? pendingNetResult
+                                    : new SSLEngineResult(getEngineStatus(), NEED_UNWRAP,
+                                            bytesConsumed, bytesProduced);
+                        case SSL_ERROR_WANT_WRITE:
+                            // SSL_ERROR_WANT_WRITE typically means that the underlying
+                            // transport is not writable
+                            // and we should set the "want write" flag on the selector and try
+                            // again when the
+                            // underlying transport is writable [1]. However we are not directly
+                            // writing to the
+                            // underlying transport and instead writing to a BIO buffer. The
+                            // OpenSsl documentation
+                            // says we should do the following [1]:
+                            //
+                            // "When using a buffering BIO, like a BIO pair, data must be
+                            // written into or retrieved
+                            // out of the BIO before being able to continue."
+                            //
+                            // So we attempt to drain the BIO buffer below, but if there is no
+                            // data this condition
+                            // is undefined and we assume their is a fatal error with the
+                            // openssl engine and close.
+                            // [1] https://www.openssl.org/docs/manmaster/man3/SSL_write.html
+                            pendingNetResult = readPendingBytesFromBIO(
+                                    dst, bytesConsumed, bytesProduced, handshakeStatus);
+                            return pendingNetResult != null ? pendingNetResult : NEED_WRAP_CLOSED;
+                        default:
+                            // Everything else is considered as error
+                            closeAll();
+                            throw newSslExceptionWithMessage("SSL_write: error " + sslError);
                     }
                 }
             }
-            // We need to check if pendingWrittenBytesInBIO was checked yet, as we may not checked
-            // if the srcs was
-            // empty, or only contained empty buffers.
+
+            // We need to check if pendingWrittenBytesInBIO was checked yet, as we may not have
+            // checked if the srcs was empty, or only contained empty buffers.
             if (bytesConsumed == 0) {
                 SSLEngineResult pendingNetResult =
                         readPendingBytesFromBIO(dst, 0, bytesProduced, handshakeStatus);
@@ -1547,9 +1544,6 @@
                     return pendingNetResult;
                 }
             }
-
-            // return new SSLEngineResult(OK, getHandshakeStatusInternal(), bytesConsumed,
-            // bytesProduced);
             return newResult(bytesConsumed, bytesProduced, handshakeStatus);
         }
     }
@@ -1671,16 +1665,19 @@
 
     private void closeAndFreeResources() {
         transitionTo(STATE_CLOSED);
-        if (!ssl.isClosed()) {
+        if (ssl != null) {
             ssl.close();
+        }
+        if (networkBio != null) {
             networkBio.close();
         }
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     protected void finalize() throws Throwable {
         try {
-            transitionTo(STATE_CLOSED);
+            closeAndFreeResources();
         } finally {
             super.finalize();
         }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptEngineSocket.java b/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptEngineSocket.java
index 0eee041..69a341f 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptEngineSocket.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptEngineSocket.java
@@ -61,6 +61,8 @@
     private SSLOutputStream out;
     private SSLInputStream in;
 
+    private long handshakeStartedMillis;
+
     private BufferAllocator bufferAllocator = ConscryptEngine.getDefaultBufferAllocator();
 
     // @GuardedBy("stateLock");
@@ -201,6 +203,7 @@
                     // Initialize the handshake if we haven't already.
                     if (state == STATE_NEW) {
                         state = STATE_HANDSHAKE_STARTED;
+                        handshakeStartedMillis = Platform.getMillisSinceBoot();
                         engine.beginHandshake();
                         in = new SSLInputStream();
                         out = new SSLOutputStream();
@@ -255,6 +258,9 @@
                     case FINISHED: {
                         // Handshake is complete.
                         finished = true;
+                        Platform.countTlsHandshake(true, engine.getSession().getProtocol(),
+                                engine.getSession().getCipherSuite(),
+                                Platform.getMillisSinceBoot() - handshakeStartedMillis);
                         break;
                     }
                     default: {
@@ -265,6 +271,9 @@
             }
         } catch (SSLException e) {
             drainOutgoingQueue();
+            Platform.countTlsHandshake(false, engine.getSession().getProtocol(),
+                    engine.getSession().getCipherSuite(),
+                    Platform.getMillisSinceBoot() - handshakeStartedMillis);
             close();
             throw e;
         } catch (IOException e) {
@@ -370,7 +379,7 @@
      */
     @android.compat.annotation.
     UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
-            publicAlternatives = "Use {@link javax.net.ssl.SSLParameters#setServerNames}.")
+            publicAlternatives = "Use {@code javax.net.ssl.SSLParameters#setServerNames}.")
     @Override
     public final void
     setHostname(String hostname) {
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptFileDescriptorSocket.java b/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptFileDescriptorSocket.java
index ebe99f4..67a1ede 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptFileDescriptorSocket.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptFileDescriptorSocket.java
@@ -108,16 +108,18 @@
      * The session object exposed externally from this class.
      */
     private final SSLSession externalSession =
-        Platform.wrapSSLSession(new ExternalSession(new Provider() {
-            @Override
-            public ConscryptSession provideSession() {
-                return ConscryptFileDescriptorSocket.this.provideSession();
-            }
-        }));
+            Platform.wrapSSLSession(new ExternalSession(new ExternalSession.Provider() {
+                @Override
+                public ConscryptSession provideSession() {
+                    return ConscryptFileDescriptorSocket.this.provideSession();
+                }
+            }));
 
     private int writeTimeoutMilliseconds = 0;
     private int handshakeTimeoutMilliseconds = -1; // -1 = same as timeout; 0 = infinite
 
+    private long handshakeStartedMillis;
+
     // The constructors should not be called except from the Platform class, because we may
     // want to construct a subclass instead.
     ConscryptFileDescriptorSocket(SSLParametersImpl sslParameters) throws IOException {
@@ -183,6 +185,7 @@
         checkOpen();
         synchronized (ssl) {
             if (state == STATE_NEW) {
+                handshakeStartedMillis = Platform.getMillisSinceBoot();
                 transitionTo(STATE_HANDSHAKE_STARTED);
             } else {
                 // We've either started the handshake already or have been closed.
@@ -228,6 +231,9 @@
                 // Update the session from the current state of the SSL object.
                 activeSession.onPeerCertificateAvailable(getHostnameOrIP(), getPort());
             } catch (CertificateException e) {
+                Platform.countTlsHandshake(false, activeSession.getProtocol(),
+                        activeSession.getCipherSuite(),
+                        Platform.getMillisSinceBoot() - handshakeStartedMillis);
                 SSLHandshakeException wrapper = new SSLHandshakeException(e.getMessage());
                 wrapper.initCause(e);
                 throw wrapper;
@@ -285,6 +291,10 @@
                 }
             }
         } catch (SSLProtocolException e) {
+            Platform.countTlsHandshake(false, activeSession.getProtocol(),
+                    activeSession.getCipherSuite(),
+                    Platform.getMillisSinceBoot() - handshakeStartedMillis);
+
             throw(SSLHandshakeException) new SSLHandshakeException("Handshake failed").initCause(e);
         } finally {
             // on exceptional exit, treat the socket as closed
@@ -338,6 +348,10 @@
 
         // The handshake has completed successfully ...
 
+        Platform.countTlsHandshake(true, activeSession.getProtocol(),
+                activeSession.getCipherSuite(),
+                Platform.getMillisSinceBoot() - handshakeStartedMillis);
+
         // First, update the state.
         synchronized (ssl) {
             if (state == STATE_CLOSED) {
@@ -715,7 +729,7 @@
     public final SSLSession getHandshakeSession() {
         synchronized (ssl) {
             if (state >= STATE_HANDSHAKE_STARTED && state < STATE_READY) {
-                return Platform.wrapSSLSession(new ExternalSession(new Provider() {
+                return Platform.wrapSSLSession(new ExternalSession(new ExternalSession.Provider() {
                     @Override
                     public ConscryptSession provideSession() {
                         return ConscryptFileDescriptorSocket.this.provideHandshakeSession();
@@ -788,7 +802,7 @@
      */
     @android.compat.annotation.
     UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
-            publicAlternatives = "Use {@link javax.net.ssl.SSLParameters#setServerNames}.")
+            publicAlternatives = "Use {@code javax.net.ssl.SSLParameters#setServerNames}.")
     @Override
     public final void
     setHostname(String hostname) {
@@ -959,7 +973,7 @@
      * Note write timeouts are not part of the javax.net.ssl.SSLSocket API
      */
     @Override
-    public final int getSoWriteTimeout() throws SocketException {
+    public final int getSoWriteTimeout() {
         return writeTimeoutMilliseconds;
     }
 
@@ -1071,6 +1085,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     protected final void finalize() throws Throwable {
         try {
             /*
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptHostnameVerifier.java b/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptHostnameVerifier.java
index a62cf9f..05784bc 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptHostnameVerifier.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/ConscryptHostnameVerifier.java
@@ -17,6 +17,7 @@
 
 package com.android.org.conscrypt;
 
+import java.security.cert.X509Certificate;
 import javax.net.ssl.SSLSession;
 
 /**
@@ -31,6 +32,5 @@
    * Returns whether the given hostname is allowable given the peer's authentication information
    * from the given session.
    */
-  boolean verify(String hostname, SSLSession session);
-
+  boolean verify(X509Certificate[] certs, String hostname, SSLSession session);
 }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/ExternalSession.java b/repackaged/common/src/main/java/com/android/org/conscrypt/ExternalSession.java
index fc7daee..ccdd7bb 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/ExternalSession.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/ExternalSession.java
@@ -48,7 +48,7 @@
  */
 final class ExternalSession implements ConscryptSession {
     // Use an initial capacity of 2 to keep it small in the average case.
-    private final HashMap<String, Object> values = new HashMap<String, Object>(2);
+    private final HashMap<String, Object> values = new HashMap<>(2);
     private final Provider provider;
 
     public ExternalSession(Provider provider) {
@@ -112,6 +112,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation") // Public API
     public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
         return provider.provideSession().getPeerCertificateChain();
     }
@@ -171,7 +172,7 @@
 
     @Override
     public String[] getValueNames() {
-        return values.keySet().toArray(new String[values.size()]);
+        return values.keySet().toArray(new String[0]);
     }
 
     @Override
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/FileClientSessionCache.java b/repackaged/common/src/main/java/com/android/org/conscrypt/FileClientSessionCache.java
index 7f56b7a..cdcae47 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/FileClientSessionCache.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/FileClientSessionCache.java
@@ -40,7 +40,7 @@
  * underlying directory at a time.
  * @hide This class is not part of the Android public SDK API
  */
-@libcore.api.CorePlatformApi
+@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
 @Internal
 public final class FileClientSessionCache {
     private static final Logger logger = Logger.getLogger(FileClientSessionCache.class.getName());
@@ -333,7 +333,7 @@
      */
     @android.compat.annotation
             .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi
+            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
             public static synchronized SSLClientSessionCache usingDirectory(File directory)
             throws IOException {
         FileClientSessionCache.Impl cache = caches.get(directory);
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/Java7ExtendedSSLSession.java b/repackaged/common/src/main/java/com/android/org/conscrypt/Java7ExtendedSSLSession.java
index 5b665da..2911023 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/Java7ExtendedSSLSession.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/Java7ExtendedSSLSession.java
@@ -135,6 +135,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation") // Public API
     public final X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
         return delegate.getPeerCertificateChain();
     }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/Java8ExtendedSSLSession.java b/repackaged/common/src/main/java/com/android/org/conscrypt/Java8ExtendedSSLSession.java
index 16c4c38..f57a35f 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/Java8ExtendedSSLSession.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/Java8ExtendedSSLSession.java
@@ -36,7 +36,7 @@
   public final List<SNIServerName> getRequestedServerNames() {
       String requestedServerName = delegate.getRequestedServerName();
       if (requestedServerName == null) {
-        return null;
+          return Collections.emptyList();
       }
 
       return Collections.singletonList((SNIServerName) new SNIHostName(requestedServerName));
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/KeyManagerImpl.java b/repackaged/common/src/main/java/com/android/org/conscrypt/KeyManagerImpl.java
index 4682e1b..e85392c 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/KeyManagerImpl.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/KeyManagerImpl.java
@@ -56,36 +56,34 @@
     /**
      * Creates Key manager
      */
+    @SuppressWarnings("JdkObsolete") // KeyStore#aliases is the only way of enumerating all entries
     KeyManagerImpl(KeyStore keyStore, char[] pwd) {
-        this.hash = new HashMap<String, PrivateKeyEntry>();
+        this.hash = new HashMap<>();
         final Enumeration<String> aliases;
         try {
             aliases = keyStore.aliases();
         } catch (KeyStoreException e) {
             return;
         }
-        for (; aliases.hasMoreElements();) {
+        while (aliases.hasMoreElements()) {
             final String alias = aliases.nextElement();
             try {
-                if (keyStore.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class)) {
-                    KeyStore.PrivateKeyEntry entry;
+                if (keyStore.entryInstanceOf(alias, PrivateKeyEntry.class)) {
+                    PrivateKeyEntry entry;
                     try {
-                        entry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(
+                        entry = (PrivateKeyEntry) keyStore.getEntry(
                                 alias, new KeyStore.PasswordProtection(pwd));
                     } catch (UnsupportedOperationException e) {
                         // If the KeyStore doesn't support getEntry(), as Android Keystore
                         // doesn't, fall back to reading the two values separately.
                         PrivateKey key = (PrivateKey) keyStore.getKey(alias, pwd);
                         Certificate[] certs = keyStore.getCertificateChain(alias);
-                        entry = new KeyStore.PrivateKeyEntry(key, certs);
+                        entry = new PrivateKeyEntry(key, certs);
                     }
                     hash.put(alias, entry);
                 }
-            } catch (KeyStoreException ignored) {
-                // Ignored.
-            } catch (UnrecoverableEntryException ignored) {
-                // Ignored.
-            } catch (NoSuchAlgorithmException ignored) {
+            } catch (KeyStoreException | UnrecoverableEntryException
+                    | NoSuchAlgorithmException ignored) {
                 // Ignored.
             }
         }
@@ -160,7 +158,7 @@
             return null;
         }
         List<Principal> issuersList = (issuers == null) ? null : Arrays.asList(issuers);
-        ArrayList<String> found = new ArrayList<String>();
+        ArrayList<String> found = new ArrayList<>();
         for (final Map.Entry<String, PrivateKeyEntry> entry : hash.entrySet()) {
             final String alias = entry.getKey();
             final Certificate[] chain = entry.getValue().getCertificateChain();
@@ -225,7 +223,7 @@
             }
         }
         if (!found.isEmpty()) {
-            return found.toArray(new String[found.size()]);
+            return found.toArray(new String[0]);
         }
         return null;
     }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/NativeCrypto.java b/repackaged/common/src/main/java/com/android/org/conscrypt/NativeCrypto.java
index df57d05..1edbd50 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/NativeCrypto.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/NativeCrypto.java
@@ -23,6 +23,7 @@
 import java.io.OutputStream;
 import java.net.SocketTimeoutException;
 import java.nio.Buffer;
+import java.nio.ByteBuffer;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.MessageDigest;
@@ -209,6 +210,13 @@
 
     static native int ECDSA_verify(byte[] data, byte[] sig, NativeRef.EVP_PKEY pkey);
 
+    // --- Curve25519 --------------
+
+    static native boolean X25519(byte[] out, byte[] privateKey, byte[] publicKey)
+            throws InvalidKeyException;
+
+    static native void X25519_keypair(byte[] outPublicKey, byte[] outPrivateKey);
+
     // --- Message digest functions --------------
 
     // These return const references
@@ -341,11 +349,33 @@
 
     static native int EVP_AEAD_CTX_seal(long evpAead, byte[] key, int tagLengthInBytes, byte[] out,
             int outOffset, byte[] nonce, byte[] in, int inOffset, int inLength, byte[] ad)
-            throws ShortBufferException, BadPaddingException, IndexOutOfBoundsException;
+            throws ShortBufferException, BadPaddingException;
+
+    static native int EVP_AEAD_CTX_seal_buf(long evpAead, byte[] key, int tagLengthInBytes,
+            ByteBuffer out, byte[] nonce, ByteBuffer input, byte[] ad)
+            throws ShortBufferException, BadPaddingException;
 
     static native int EVP_AEAD_CTX_open(long evpAead, byte[] key, int tagLengthInBytes, byte[] out,
             int outOffset, byte[] nonce, byte[] in, int inOffset, int inLength, byte[] ad)
-            throws ShortBufferException, BadPaddingException, IndexOutOfBoundsException;
+            throws ShortBufferException, BadPaddingException;
+
+    static native int EVP_AEAD_CTX_open_buf(long evpAead, byte[] key, int tagLengthInBytes,
+            ByteBuffer out, byte[] nonce, ByteBuffer input, byte[] ad)
+            throws ShortBufferException, BadPaddingException;
+
+    // --- CMAC functions ------------------------------------------------------
+
+    static native long CMAC_CTX_new();
+
+    static native void CMAC_CTX_free(long ctx);
+
+    static native void CMAC_Init(NativeRef.CMAC_CTX ctx, byte[] key);
+
+    static native void CMAC_Update(NativeRef.CMAC_CTX ctx, byte[] in, int inOffset, int inLength);
+
+    static native void CMAC_UpdateDirect(NativeRef.CMAC_CTX ctx, long inPtr, int inLength);
+
+    static native byte[] CMAC_Final(NativeRef.CMAC_CTX ctx);
 
     // --- HMAC functions ------------------------------------------------------
 
@@ -427,8 +457,6 @@
 
     static native void X509_free(long x509ctx, OpenSSLX509Certificate holder);
 
-    static native long X509_dup(long x509ctx, OpenSSLX509Certificate holder);
-
     static native int X509_cmp(long x509ctx1, OpenSSLX509Certificate holder, long x509ctx2, OpenSSLX509Certificate holder2);
 
     static native void X509_print_ex(long bioCtx, long x509ctx, OpenSSLX509Certificate holder, long nmflag, long certflag);
@@ -474,7 +502,10 @@
     static native void X509_verify(long x509ctx, OpenSSLX509Certificate holder, NativeRef.EVP_PKEY pkeyCtx)
             throws BadPaddingException;
 
-    static native byte[] get_X509_cert_info_enc(long x509ctx, OpenSSLX509Certificate holder);
+    static native byte[] get_X509_tbs_cert(long x509ctx, OpenSSLX509Certificate holder);
+
+    static native byte[] get_X509_tbs_cert_without_ext(
+            long x509ctx, OpenSSLX509Certificate holder, String oid);
 
     static native byte[] get_X509_signature(long x509ctx, OpenSSLX509Certificate holder);
 
@@ -535,8 +566,6 @@
 
     static native byte[] X509_CRL_get_ext_oid(long x509CrlCtx, OpenSSLX509CRL holder, String oid);
 
-    static native void X509_delete_ext(long x509, OpenSSLX509Certificate holder, String oid);
-
     static native long X509_CRL_get_version(long x509CrlCtx, OpenSSLX509CRL holder);
 
     static native long X509_CRL_get_ext(long x509CrlCtx, OpenSSLX509CRL holder, String oid);
@@ -1437,26 +1466,12 @@
             SSLHandshakeCallbacks shc) throws IOException;
 
     /**
-     * Writes data from the given array to the BIO.
-     */
-    static native int ENGINE_SSL_write_BIO_heap(long ssl, NativeSsl ssl_holder, long bioRef, byte[] sourceJava,
-            int sourceOffset, int sourceLength, SSLHandshakeCallbacks shc)
-            throws IOException, IndexOutOfBoundsException;
-
-    /**
      * Reads data from the given BIO into a direct {@link java.nio.ByteBuffer}.
      */
     static native int ENGINE_SSL_read_BIO_direct(long ssl, NativeSsl ssl_holder, long bioRef, long address, int len,
             SSLHandshakeCallbacks shc) throws IOException;
 
     /**
-     * Reads data from the given BIO into an array.
-     */
-    static native int ENGINE_SSL_read_BIO_heap(long ssl, NativeSsl ssl_holder, long bioRef, byte[] destJava,
-            int destOffset, int destLength, SSLHandshakeCallbacks shc)
-            throws IOException, IndexOutOfBoundsException;
-
-    /**
      * Forces the SSL object to process any data pending in the BIO.
      */
     static native void ENGINE_SSL_force_read(long ssl, NativeSsl ssl_holder,
@@ -1470,6 +1485,11 @@
             throws IOException;
 
     /**
+     * Return {@code true} if BoringSSL has been built in FIPS mode.
+     */
+    static native boolean usesBoringSSL_FIPS_mode();
+
+    /**
      * Used for testing only.
      */
     static native int BIO_read(long bioRef, byte[] buffer) throws IOException;
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/NativeRef.java b/repackaged/common/src/main/java/com/android/org/conscrypt/NativeRef.java
index cc981c2..d33ac62 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/NativeRef.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/NativeRef.java
@@ -47,6 +47,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     protected void finalize() throws Throwable {
         try {
             if (address != 0) {
@@ -59,6 +60,17 @@
 
     abstract void doFree(long context);
 
+    static final class CMAC_CTX extends NativeRef {
+        CMAC_CTX(long nativePointer) {
+            super(nativePointer);
+        }
+
+        @Override
+        void doFree(long context) {
+            NativeCrypto.CMAC_CTX_free(context);
+        }
+    }
+
     static final class EC_GROUP extends NativeRef {
         EC_GROUP(long ctx) {
             super(ctx);
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/NativeSsl.java b/repackaged/common/src/main/java/com/android/org/conscrypt/NativeSsl.java
index 4675927..82b2cc3 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/NativeSsl.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/NativeSsl.java
@@ -26,11 +26,15 @@
 import static com.android.org.conscrypt.NativeConstants.SSL_VERIFY_NONE;
 import static com.android.org.conscrypt.NativeConstants.SSL_VERIFY_PEER;
 
+import com.android.org.conscrypt.NativeCrypto.SSLHandshakeCallbacks;
+import com.android.org.conscrypt.SSLParametersImpl.AliasChooser;
+import com.android.org.conscrypt.SSLParametersImpl.PSKCallbacks;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.SocketException;
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.security.InvalidKeyException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
@@ -47,9 +51,6 @@
 import javax.net.ssl.X509KeyManager;
 import javax.net.ssl.X509TrustManager;
 import javax.security.auth.x500.X500Principal;
-import com.android.org.conscrypt.NativeCrypto.SSLHandshakeCallbacks;
-import com.android.org.conscrypt.SSLParametersImpl.AliasChooser;
-import com.android.org.conscrypt.SSLParametersImpl.PSKCallbacks;
 
 /**
  * A utility wrapper that abstracts operations on the underlying native SSL instance.
@@ -212,7 +213,7 @@
             byte[][] asn1DerEncodedPrincipals)
             throws SSLException, CertificateEncodingException {
         Set<String> keyTypesSet = SSLUtils.getSupportedClientKeyTypes(keyTypeBytes, signatureAlgs);
-        String[] keyTypes = keyTypesSet.toArray(new String[keyTypesSet.size()]);
+        String[] keyTypes = keyTypesSet.toArray(new String[0]);
 
         X500Principal[] issuers;
         if (asn1DerEncodedPrincipals == null) {
@@ -637,6 +638,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     protected final void finalize() throws Throwable {
         try {
             close();
@@ -656,27 +658,51 @@
         }
 
         int getPendingWrittenBytes() {
-            if (bio != 0) {
-                return NativeCrypto.SSL_pending_written_bytes_in_BIO(bio);
-            } else {
-                return 0;
+            lock.readLock().lock();
+            try {
+                return (bio == 0L) ? 0 : NativeCrypto.SSL_pending_written_bytes_in_BIO(bio);
+            } finally {
+                lock.readLock().unlock();
             }
         }
 
         int writeDirectByteBuffer(long address, int length) throws IOException {
-            return NativeCrypto.ENGINE_SSL_write_BIO_direct(
-                    ssl, NativeSsl.this, bio, address, length, handshakeCallbacks);
+            lock.readLock().lock();
+            try {
+                if (isClosed()) {
+                    throw new SSLException("Connection closed");
+                }
+                return NativeCrypto.ENGINE_SSL_write_BIO_direct(
+                        ssl, NativeSsl.this, bio, address, length, handshakeCallbacks);
+            } finally {
+                lock.readLock().unlock();
+            }
         }
 
         int readDirectByteBuffer(long destAddress, int destLength) throws IOException {
-            return NativeCrypto.ENGINE_SSL_read_BIO_direct(
-                    ssl, NativeSsl.this, bio, destAddress, destLength, handshakeCallbacks);
+            lock.readLock().lock();
+            try {
+                if (isClosed()) {
+                    throw new SSLException("Connection closed");
+                }
+                return NativeCrypto.ENGINE_SSL_read_BIO_direct(
+                        ssl, NativeSsl.this, bio, destAddress, destLength, handshakeCallbacks);
+            } finally {
+                lock.readLock().unlock();
+            }
         }
 
         void close() {
-            long toFree = bio;
-            bio = 0L;
-            NativeCrypto.BIO_free_all(toFree);
+            lock.writeLock().lock();
+            try {
+                long toFree = bio;
+                bio = 0L;
+                if (toFree != 0L) {
+                    NativeCrypto.BIO_free_all(toFree);
+                }
+            } finally {
+                lock.writeLock().unlock();
+            }
         }
     }
 }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/NativeSslSession.java b/repackaged/common/src/main/java/com/android/org/conscrypt/NativeSslSession.java
index 5ed56e1..6ecb553 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/NativeSslSession.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/NativeSslSession.java
@@ -429,7 +429,7 @@
                 }
 
                 @Override
-                public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
+                public Certificate[] getPeerCertificates() {
                     throw new UnsupportedOperationException();
                 }
 
@@ -439,13 +439,13 @@
                 }
 
                 @Override
-                public X509Certificate[] getPeerCertificateChain()
-                        throws SSLPeerUnverifiedException {
+                @SuppressWarnings("deprecation")
+                public X509Certificate[] getPeerCertificateChain() {
                     throw new UnsupportedOperationException();
                 }
 
                 @Override
-                public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+                public Principal getPeerPrincipal() {
                     throw new UnsupportedOperationException();
                 }
 
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OkHostnameVerifier.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OkHostnameVerifier.java
new file mode 100644
index 0000000..33dd05b
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OkHostnameVerifier.java
@@ -0,0 +1,288 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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 java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+
+/**
+ * A HostnameVerifier consistent with <a
+ * href="http://www.ietf.org/rfc/rfc2818.txt">RFC 2818</a>.
+ * @hide This class is not part of the Android public SDK API
+ */
+public final class OkHostnameVerifier implements ConscryptHostnameVerifier {
+    // Android-changed: Add a mode which disallows top-level domain wildcards. b/144694112
+    // public static final OkHostnameVerifier INSTANCE = new OkHostnameVerifier();
+    public static final OkHostnameVerifier INSTANCE = new OkHostnameVerifier(false);
+
+    /**
+     * Quick and dirty pattern to differentiate IP addresses from hostnames. This
+     * is an approximation of Android's private InetAddress#isNumeric API.
+     *
+     * <p>This matches IPv6 addresses as a hex string containing at least one
+     * colon, and possibly including dots after the first colon. It matches IPv4
+     * addresses as strings containing only decimal digits and dots. This pattern
+     * matches strings like "a:.23" and "54" that are neither IP addresses nor
+     * hostnames; they will be verified as IP addresses (which is a more strict
+     * verification).
+     */
+    private static final Pattern VERIFY_AS_IP_ADDRESS = Pattern.compile(
+            "([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)");
+
+    private static final int ALT_DNS_NAME = 2;
+    private static final int ALT_IPA_NAME = 7;
+
+    // BEGIN Android-changed: Add a mode which disallows top-level domain wildcards. b/144694112
+    // private OkHostnameVerifier() {
+    // }
+    private final boolean strictWildcardMode;
+
+    private OkHostnameVerifier(boolean strictWildcardMode) {
+        this.strictWildcardMode = strictWildcardMode;
+    }
+
+    public static OkHostnameVerifier strictInstance() {
+        return new OkHostnameVerifier(true);
+    }
+    // END Android-changed: Add a mode which disallows top-level domain wildcards. b/144694112
+
+    @Override
+    public boolean verify(X509Certificate[] certs, String host, SSLSession session) {
+        if (certs.length > 0) {
+            return verify(host, certs[0]);
+        } else {
+            try {
+                Certificate[] certificates = session.getPeerCertificates();
+                return verify(host, (X509Certificate) certificates[0]);
+            } catch (SSLException e) {
+                return false;
+            }
+        }
+    }
+
+    public boolean verify(String host, X509Certificate certificate) {
+        return verifyAsIpAddress(host)
+                ? verifyIpAddress(host, certificate)
+                : verifyHostName(host, certificate);
+    }
+
+    static boolean verifyAsIpAddress(String host) {
+        return VERIFY_AS_IP_ADDRESS.matcher(host).matches();
+    }
+
+    /**
+     * Returns true if {@code certificate} matches {@code ipAddress}.
+     */
+    private boolean verifyIpAddress(String ipAddress, X509Certificate certificate) {
+        List<String> altNames = getSubjectAltNames(certificate, ALT_IPA_NAME);
+        for (int i = 0, size = altNames.size(); i < size; i++) {
+            if (ipAddress.equalsIgnoreCase(altNames.get(i))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if {@code certificate} matches {@code hostName}.
+     */
+    @SuppressWarnings("UnusedVariable")
+    private boolean verifyHostName(String hostName, X509Certificate certificate) {
+        hostName = hostName.toLowerCase(Locale.US);
+        boolean hasDns = false;
+        List<String> altNames = getSubjectAltNames(certificate, ALT_DNS_NAME);
+        for (int i = 0, size = altNames.size(); i < size; i++) {
+            hasDns = true;
+            if (verifyHostName(hostName, altNames.get(i))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // BEGIN Android-removed: Ignore common name in hostname verification. http://b/70278814
+    /*
+    if (!hasDns) {
+      X500Principal principal = certificate.getSubjectX500Principal();
+      // RFC 2818 advises using the most specific name for matching.
+      String cn = new DistinguishedNameParser(principal).findMostSpecific("cn");
+      if (cn != null) {
+        return verifyHostName(hostName, cn);
+      }
+    }
+    */
+    // END Android-removed: Ignore common name in hostname verification. http://b/70278814
+
+    public static List<String> allSubjectAltNames(X509Certificate certificate) {
+        List<String> altIpaNames = getSubjectAltNames(certificate, ALT_IPA_NAME);
+        List<String> altDnsNames = getSubjectAltNames(certificate, ALT_DNS_NAME);
+        List<String> result = new ArrayList<>(altIpaNames.size() + altDnsNames.size());
+        result.addAll(altIpaNames);
+        result.addAll(altDnsNames);
+        return result;
+    }
+
+    @SuppressWarnings("MixedMutabilityReturnType")
+    private static List<String> getSubjectAltNames(X509Certificate certificate, int type) {
+        List<String> result = new ArrayList<>();
+        try {
+            Collection<?> subjectAltNames = certificate.getSubjectAlternativeNames();
+            if (subjectAltNames == null) {
+                return Collections.emptyList();
+            }
+            for (Object subjectAltName : subjectAltNames) {
+                List<?> entry = (List<?>) subjectAltName;
+                if (entry == null || entry.size() < 2) {
+                    continue;
+                }
+                Integer altNameType = (Integer) entry.get(0);
+                if (altNameType == null) {
+                    continue;
+                }
+                if (altNameType == type) {
+                    String altName = (String) entry.get(1);
+                    if (altName != null) {
+                        result.add(altName);
+                    }
+                }
+            }
+            return result;
+        } catch (CertificateParsingException e) {
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * Returns {@code true} iff {@code hostName} matches the domain name {@code pattern}.
+     *
+     * @param hostName lower-case host name.
+     * @param pattern  domain name pattern from certificate. May be a wildcard pattern such as
+     *                 {@code *.android.com}.
+     */
+    private boolean verifyHostName(String hostName, String pattern) {
+        // Basic sanity checks
+        // Check length == 0 instead of .isEmpty() to support Java 5.
+        if (hostName == null || hostName.length() == 0 || hostName.startsWith(".")
+                || hostName.endsWith("..")) {
+            // Invalid domain name
+            return false;
+        }
+        if (pattern == null || pattern.length() == 0 || pattern.startsWith(".")
+                || pattern.endsWith("..")) {
+            // Invalid pattern/domain name
+            return false;
+        }
+
+        // Normalize hostName and pattern by turning them into absolute domain names if they are not
+        // yet absolute. This is needed because server certificates do not normally contain absolute
+        // names or patterns, but they should be treated as absolute. At the same time, any hostName
+        // presented to this method should also be treated as absolute for the purposes of matching
+        // to the server certificate.
+        //   www.android.com  matches www.android.com
+        //   www.android.com  matches www.android.com.
+        //   www.android.com. matches www.android.com.
+        //   www.android.com. matches www.android.com
+        if (!hostName.endsWith(".")) {
+            hostName += '.';
+        }
+        if (!pattern.endsWith(".")) {
+            pattern += '.';
+        }
+        // hostName and pattern are now absolute domain names.
+
+        pattern = pattern.toLowerCase(Locale.US);
+        // hostName and pattern are now in lower case -- domain names are case-insensitive.
+
+        if (!pattern.contains("*")) {
+            // Not a wildcard pattern -- hostName and pattern must match exactly.
+            return hostName.equals(pattern);
+        }
+        // Wildcard pattern
+
+        // WILDCARD PATTERN RULES:
+        // 1. Asterisk (*) is only permitted in the left-most domain name label and must be the
+        //    only character in that label (i.e., must match the whole left-most label).
+        //    For example, *.example.com is permitted, while *a.example.com, a*.example.com,
+        //    a*b.example.com, a.*.example.com are not permitted.
+        // 2. Asterisk (*) cannot match across domain name labels.
+        //    For example, *.example.com matches test.example.com but does not match
+        //    sub.test.example.com.
+        // 3. Wildcard patterns for single-label domain names are not permitted.
+        // 4. Android-added: if strictWildcardMode is true then wildcards matching top-level domains,
+        //    e.g. *.com, are not permitted.
+
+        if (!pattern.startsWith("*.") || pattern.indexOf('*', 1) != -1) {
+            // Asterisk (*) is only permitted in the left-most domain name label and must be the only
+            // character in that label
+            return false;
+        }
+
+        // Optimization: check whether hostName is too short to match the pattern. hostName must be at
+        // least as long as the pattern because asterisk must match the whole left-most label and
+        // hostName starts with a non-empty label. Thus, asterisk has to match one or more characters.
+        if (hostName.length() < pattern.length()) {
+            // hostName too short to match the pattern.
+            return false;
+        }
+
+        if ("*.".equals(pattern)) {
+            // Wildcard pattern for single-label domain name -- not permitted.
+            return false;
+        }
+
+        // BEGIN Android-added: Disallow top-level wildcards in strict mode. http://b/144694112
+        if (strictWildcardMode) {
+            // By this point we know the pattern has been normalised and starts with a wildcard,
+            // i.e. "*.domainpart."
+            String domainPart = pattern.substring(2, pattern.length() - 1);
+            // If the domain part contains no dots then this pattern will match top level domains.
+            if (domainPart.indexOf('.') < 0) {
+                return false;
+            }
+        }
+        // END Android-added: Disallow top-level wildcards in strict mode. http://b/144694112
+
+        // hostName must end with the region of pattern following the asterisk.
+        String suffix = pattern.substring(1);
+        if (!hostName.endsWith(suffix)) {
+            // hostName does not end with the suffix
+            return false;
+        }
+
+        // Check that asterisk did not match across domain name labels.
+        int suffixStartIndexInHostName = hostName.length() - suffix.length();
+        if ((suffixStartIndexInHostName > 0)
+                && (hostName.lastIndexOf('.', suffixStartIndexInHostName - 1) != -1)) {
+            // Asterisk is matching across domain name labels -- not permitted.
+            return false;
+        }
+
+        // hostName matches pattern
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLAeadCipher.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLAeadCipher.java
index 566c1fc..383ba4e 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLAeadCipher.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLAeadCipher.java
@@ -37,6 +37,11 @@
 @Internal
 public abstract class OpenSSLAeadCipher extends OpenSSLCipher {
     /**
+     * Controls whether no-copy optimizations for direct ByteBuffers are enabled.
+     */
+    private static final boolean ENABLE_BYTEBUFFER_OPTIMIZATIONS = true;
+
+    /**
      * The default tag size when one is not specified. Default to
      * full-length tags (128-bits or 16 octets).
      */
@@ -88,7 +93,7 @@
      */
     int tagLengthInBytes;
 
-    public OpenSSLAeadCipher(Mode mode) {
+    protected OpenSSLAeadCipher(Mode mode) {
         super(mode, Padding.NOPADDING);
     }
 
@@ -225,6 +230,48 @@
     }
 
     @Override
+    protected int engineDoFinal(ByteBuffer input, ByteBuffer output)
+            throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
+        if (!ENABLE_BYTEBUFFER_OPTIMIZATIONS) {
+            return super.engineDoFinal(input, output);
+        }
+        if (input == null || output == null) {
+            throw new NullPointerException("Null ByteBuffer Error");
+        }
+        if (getOutputSizeForFinal(input.remaining()) > output.remaining()) {
+            throw new ShortBufferWithoutStackTraceException("Insufficient Bytes for Output Buffer");
+        }
+        if (output.isReadOnly()) {
+            throw new IllegalArgumentException("Cannot write to Read Only ByteBuffer");
+        }
+        if (bufCount != 0) {
+            return super.engineDoFinal(input, output); // traditional case
+        }
+        int bytesWritten;
+        if (!input.isDirect()) {
+            int incap = input.remaining();
+            ByteBuffer inputClone = ByteBuffer.allocateDirect(incap);
+            inputClone.mark();
+            inputClone.put(input);
+            inputClone.reset();
+            input = inputClone;
+        }
+        if (!output.isDirect()) {
+            ByteBuffer outputClone =
+                    ByteBuffer.allocateDirect(getOutputSizeForFinal(input.remaining()));
+            bytesWritten = doFinalInternal(input, outputClone);
+            output.put(outputClone);
+            input.position(input.limit()); // API reasons
+        } else {
+            bytesWritten = doFinalInternal(input, output);
+            output.position(output.position() + bytesWritten);
+            input.position(input.limit()); // API reasons
+        }
+
+        return bytesWritten;
+    }
+
+    @Override
     protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
             int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
         BadPaddingException {
@@ -285,6 +332,28 @@
         }
     }
 
+    int doFinalInternal(ByteBuffer input, ByteBuffer output)
+            throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
+        checkInitialization();
+        final int bytesWritten;
+        try {
+            if (isEncrypting()) {
+                bytesWritten = NativeCrypto.EVP_AEAD_CTX_seal_buf(
+                        evpAead, encodedKey, tagLengthInBytes, output, iv, input, aad);
+            } else {
+                bytesWritten = NativeCrypto.EVP_AEAD_CTX_open_buf(
+                        evpAead, encodedKey, tagLengthInBytes, output, iv, input, aad);
+            }
+        } catch (BadPaddingException e) {
+            throwAEADBadTagExceptionIfAvailable(e.getMessage(), e.getCause());
+            throw e;
+        }
+        if (isEncrypting()) {
+            mustInitialize = true;
+        }
+        return bytesWritten;
+    }
+
     @Override
     int doFinalInternal(byte[] output, int outputOffset, int maximumLen)
             throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLBIOSink.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLBIOSink.java
deleted file mode 100644
index 76fc6e1..0000000
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLBIOSink.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/* GENERATED SOURCE. DO NOT MODIFY. */
-/*
- * Copyright (C) 2014 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 java.io.ByteArrayOutputStream;
-
-/**
- * Wraps a BoringSSL BIO to act as a place to write out data.
- */
-final class OpenSSLBIOSink {
-    private final long ctx;
-    private final ByteArrayOutputStream buffer;
-    private int position;
-
-    static OpenSSLBIOSink create() {
-        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
-        return new OpenSSLBIOSink(buffer);
-    }
-
-    private OpenSSLBIOSink(ByteArrayOutputStream buffer) {
-        ctx = NativeCrypto.create_BIO_OutputStream(buffer);
-        this.buffer = buffer;
-    }
-
-    int available() {
-        return buffer.size() - position;
-    }
-
-    void reset() {
-        buffer.reset();
-        position = 0;
-    }
-
-    long skip(long byteCount) {
-        int maxLength = Math.min(available(), (int) byteCount);
-        position += maxLength;
-        if (position == buffer.size()) {
-            reset();
-        }
-        return maxLength;
-    }
-
-    long getContext() {
-        return ctx;
-    }
-
-    byte[] toByteArray() {
-        return buffer.toByteArray();
-    }
-
-    int position() {
-        return position;
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            NativeCrypto.BIO_free_all(ctx);
-        } finally {
-            super.finalize();
-        }
-    }
-}
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLBIOSource.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLBIOSource.java
deleted file mode 100644
index dbbd985..0000000
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLBIOSource.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/* GENERATED SOURCE. DO NOT MODIFY. */
-/*
- * Copyright (C) 2014 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 java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-
-/**
- * Wrapped by a BoringSSL BIO to act as a source of bytes.
- */
-final class OpenSSLBIOSource {
-    private OpenSSLBIOInputStream source;
-
-    static OpenSSLBIOSource wrap(ByteBuffer buffer) {
-        return new OpenSSLBIOSource(
-            new OpenSSLBIOInputStream(new ByteBufferInputStream(buffer), false));
-    }
-
-    private OpenSSLBIOSource(OpenSSLBIOInputStream source) {
-        this.source = source;
-    }
-
-    long getContext() {
-        return source.getBioContext();
-    }
-
-    private synchronized void release() {
-        if (source != null) {
-            NativeCrypto.BIO_free_all(source.getBioContext());
-            source = null;
-        }
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            release();
-        } finally {
-            super.finalize();
-        }
-    }
-
-    private static class ByteBufferInputStream extends InputStream {
-        private final ByteBuffer source;
-
-        ByteBufferInputStream(ByteBuffer source) {
-            this.source = source;
-        }
-
-        @Override
-        public int read() throws IOException {
-            if (source.remaining() > 0) {
-                return source.get();
-            } else {
-                return -1;
-            }
-        }
-
-        @Override
-        public int available() throws IOException {
-            return source.limit() - source.position();
-        }
-
-        @Override
-        public int read(byte[] buffer) throws IOException {
-            int originalPosition = source.position();
-            source.get(buffer);
-            return source.position() - originalPosition;
-        }
-
-        @Override
-        public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
-            int toRead = Math.min(source.remaining(), byteCount);
-            int originalPosition = source.position();
-            source.get(buffer, byteOffset, toRead);
-            return source.position() - originalPosition;
-        }
-
-        @Override
-        public void reset() throws IOException {
-            source.reset();
-        }
-
-        @Override
-        public long skip(long byteCount) throws IOException {
-            long originalPosition = source.position();
-            source.position((int) (originalPosition + byteCount));
-            return source.position() - originalPosition;
-        }
-    }
-}
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLBaseDHKeyAgreement.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLBaseDHKeyAgreement.java
new file mode 100644
index 0000000..3261847
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLBaseDHKeyAgreement.java
@@ -0,0 +1,160 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 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 java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import javax.crypto.KeyAgreementSpi;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Generic base classe for Diffie-Hellman style key agreement backed by the OpenSSL engine.
+ * @hide This class is not part of the Android public SDK API
+ */
+@Internal
+public abstract class OpenSSLBaseDHKeyAgreement<T> extends KeyAgreementSpi {
+
+    /** OpenSSL handle of the private key. Only available after the engine has been initialized. */
+    private T mPrivateKey;
+
+    /**
+     * Expected length (in bytes) of the agreed key ({@link #mResult}). Only available after the
+     * engine has been initialized.
+     */
+    private int mExpectedResultLength;
+
+    /** Agreed key. Only available after {@link #engineDoPhase(Key, boolean)} completes. */
+    private byte[] mResult;
+
+    protected OpenSSLBaseDHKeyAgreement() {}
+
+    @Override
+    public Key engineDoPhase(Key key, boolean lastPhase) throws InvalidKeyException {
+        if (mPrivateKey == null) {
+            throw new IllegalStateException("Not initialized");
+        }
+        if (!lastPhase) {
+            throw new IllegalStateException("DH only has one phase");
+        }
+
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        }
+        if (!(key instanceof PublicKey)) {
+            throw new InvalidKeyException("Not a public key: " + key.getClass());
+        }
+        T openSslPublicKey = convertPublicKey((PublicKey) key);
+
+        byte[] buffer = new byte[mExpectedResultLength];
+        int actualResultLength = computeKey(buffer, openSslPublicKey, mPrivateKey);
+        byte[] result;
+        if (actualResultLength == -1) {
+            throw new RuntimeException("Engine returned -1");
+        } else if (actualResultLength == mExpectedResultLength) {
+            // The output is as long as expected -- use the whole buffer
+            result = buffer;
+        } else if (actualResultLength < mExpectedResultLength) {
+            // The output is shorter than expected -- use only what's produced by the engine
+            result = new byte[actualResultLength];
+            System.arraycopy(buffer, 0, mResult, 0, mResult.length);
+        } else {
+            // The output is longer than expected
+            throw new RuntimeException("Engine produced a longer than expected result. Expected: "
+                + mExpectedResultLength + ", actual: " + actualResultLength);
+        }
+        mResult = result;
+
+        return null; // No intermediate key
+    }
+
+    protected abstract T convertPublicKey(PublicKey key) throws InvalidKeyException;
+
+    protected abstract T convertPrivateKey(PrivateKey key) throws InvalidKeyException;
+
+    /**
+     * Given the public key {@code theirPublicKey} and the private key {@code ourPrivateKey} write the shared secret
+     * to {@code buffer} and return the actual output size.
+     */
+    protected abstract int computeKey(byte[] buffer, T theirPublicKey, T ourPrivateKey) throws InvalidKeyException;
+
+    @Override
+    protected int engineGenerateSecret(byte[] sharedSecret, int offset)
+            throws ShortBufferException {
+        checkCompleted();
+        int available = sharedSecret.length - offset;
+        if (mResult.length > available) {
+            throw new ShortBufferWithoutStackTraceException(
+                    "Needed: " + mResult.length + ", available: " + available);
+        }
+
+        System.arraycopy(mResult, 0, sharedSecret, offset, mResult.length);
+        return mResult.length;
+    }
+
+    @Override
+    protected byte[] engineGenerateSecret() {
+        checkCompleted();
+        return mResult;
+    }
+
+    @Override
+    protected SecretKey engineGenerateSecret(String algorithm) {
+        checkCompleted();
+        return new SecretKeySpec(engineGenerateSecret(), algorithm);
+    }
+
+    @Override
+    protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException {
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        }
+        if (!(key instanceof PrivateKey)) {
+            throw new InvalidKeyException("Not a private key: " + key.getClass());
+        }
+
+        T privateKey = convertPrivateKey((PrivateKey) key);
+        mExpectedResultLength = getOutputSize(privateKey);
+        mPrivateKey = privateKey;
+    }
+
+    /** Returns the expected result length for the given {@code key}. */
+    protected abstract int getOutputSize(T key);
+
+    @Override
+    protected void engineInit(Key key, AlgorithmParameterSpec params,
+            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
+        // ECDH doesn't need an AlgorithmParameterSpec
+        if (params != null) {
+          throw new InvalidAlgorithmParameterException("No algorithm parameters supported");
+        }
+        engineInit(key, random);
+    }
+
+    private void checkCompleted() {
+        if (mResult == null) {
+            throw new IllegalStateException("Key agreement not completed");
+        }
+    }
+}
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLCipherRSA.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLCipherRSA.java
index 1a934b3..3910212 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLCipherRSA.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLCipherRSA.java
@@ -382,7 +382,7 @@
      * @hide This class is not part of the Android public SDK API
      */
     public abstract static class DirectRSA extends OpenSSLCipherRSA {
-        public DirectRSA(int padding) {
+        protected DirectRSA(int padding) {
             super(padding);
         }
 
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLECDHKeyAgreement.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLECDHKeyAgreement.java
index 4d659a5..69f614b 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLECDHKeyAgreement.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLECDHKeyAgreement.java
@@ -17,137 +17,39 @@
 
 package com.android.org.conscrypt;
 
-import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
-import java.security.Key;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.spec.AlgorithmParameterSpec;
-import javax.crypto.KeyAgreementSpi;
-import javax.crypto.SecretKey;
-import javax.crypto.ShortBufferException;
-import javax.crypto.spec.SecretKeySpec;
 
 /**
  * Elliptic Curve Diffie-Hellman key agreement backed by the OpenSSL engine.
  * @hide This class is not part of the Android public SDK API
  */
 @Internal
-public final class OpenSSLECDHKeyAgreement extends KeyAgreementSpi {
-
-    /** OpenSSL handle of the private key. Only available after the engine has been initialized. */
-    private OpenSSLKey mOpenSslPrivateKey;
-
-    /**
-     * Expected length (in bytes) of the agreed key ({@link #mResult}). Only available after the
-     * engine has been initialized.
-     */
-    private int mExpectedResultLength;
-
-    /** Agreed key. Only available after {@link #engineDoPhase(Key, boolean)} completes. */
-    private byte[] mResult;
-
+public final class OpenSSLECDHKeyAgreement extends OpenSSLBaseDHKeyAgreement<OpenSSLKey> {
     public OpenSSLECDHKeyAgreement() {}
 
     @Override
-    public Key engineDoPhase(Key key, boolean lastPhase) throws InvalidKeyException {
-        if (mOpenSslPrivateKey == null) {
-            throw new IllegalStateException("Not initialized");
-        }
-        if (!lastPhase) {
-            throw new IllegalStateException("ECDH only has one phase");
-        }
-
-        if (key == null) {
-            throw new InvalidKeyException("key == null");
-        }
-        if (!(key instanceof PublicKey)) {
-            throw new InvalidKeyException("Not a public key: " + key.getClass());
-        }
-        OpenSSLKey openSslPublicKey = OpenSSLKey.fromPublicKey((PublicKey) key);
-
-        byte[] buffer = new byte[mExpectedResultLength];
-        int actualResultLength = NativeCrypto.ECDH_compute_key(
-                buffer,
-                0,
-                openSslPublicKey.getNativeRef(),
-                mOpenSslPrivateKey.getNativeRef());
-        byte[] result;
-        if (actualResultLength == -1) {
-            throw new RuntimeException("Engine returned " + actualResultLength);
-        } else if (actualResultLength == mExpectedResultLength) {
-            // The output is as long as expected -- use the whole buffer
-            result = buffer;
-        } else if (actualResultLength < mExpectedResultLength) {
-            // The output is shorter than expected -- use only what's produced by the engine
-            result = new byte[actualResultLength];
-            System.arraycopy(buffer, 0, mResult, 0, mResult.length);
-        } else {
-            // The output is longer than expected
-            throw new RuntimeException("Engine produced a longer than expected result. Expected: "
-                + mExpectedResultLength + ", actual: " + actualResultLength);
-        }
-        mResult = result;
-
-        return null; // No intermediate key
+    protected OpenSSLKey convertPublicKey(PublicKey key) throws InvalidKeyException {
+        return OpenSSLKey.fromPublicKey(key);
     }
 
     @Override
-    protected int engineGenerateSecret(byte[] sharedSecret, int offset)
-            throws ShortBufferException {
-        checkCompleted();
-        int available = sharedSecret.length - offset;
-        if (mResult.length > available) {
-            throw new ShortBufferWithoutStackTraceException(
-                    "Needed: " + mResult.length + ", available: " + available);
-        }
-
-        System.arraycopy(mResult, 0, sharedSecret, offset, mResult.length);
-        return mResult.length;
+    protected OpenSSLKey convertPrivateKey(PrivateKey key) throws InvalidKeyException {
+        return OpenSSLKey.fromPrivateKey(key);
     }
 
     @Override
-    protected byte[] engineGenerateSecret() {
-        checkCompleted();
-        return mResult;
+    protected int computeKey(byte[] buffer, OpenSSLKey theirPublicKey, OpenSSLKey ourPrivateKey)
+            throws InvalidKeyException {
+        return NativeCrypto.ECDH_compute_key(
+                buffer, 0, theirPublicKey.getNativeRef(), ourPrivateKey.getNativeRef());
     }
 
     @Override
-    protected SecretKey engineGenerateSecret(String algorithm) {
-        checkCompleted();
-        return new SecretKeySpec(engineGenerateSecret(), algorithm);
-    }
-
-    @Override
-    protected void engineInit(Key key, SecureRandom random) throws InvalidKeyException {
-        if (key == null) {
-            throw new InvalidKeyException("key == null");
-        }
-        if (!(key instanceof PrivateKey)) {
-            throw new InvalidKeyException("Not a private key: " + key.getClass());
-        }
-
-        OpenSSLKey openSslKey = OpenSSLKey.fromPrivateKey((PrivateKey) key);
+    protected int getOutputSize(OpenSSLKey openSslKey) {
         int fieldSizeBits = NativeCrypto.EC_GROUP_get_degree(new NativeRef.EC_GROUP(
                 NativeCrypto.EC_KEY_get1_group(openSslKey.getNativeRef())));
-        mExpectedResultLength = (fieldSizeBits + 7) / 8;
-        mOpenSslPrivateKey = openSslKey;
-    }
-
-    @Override
-    protected void engineInit(Key key, AlgorithmParameterSpec params,
-            SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
-        // ECDH doesn't need an AlgorithmParameterSpec
-        if (params != null) {
-          throw new InvalidAlgorithmParameterException("No algorithm parameters supported");
-        }
-        engineInit(key, random);
-    }
-
-    private void checkCompleted() {
-        if (mResult == null) {
-            throw new IllegalStateException("Key agreement not completed");
-        }
+        return (fieldSizeBits + 7) / 8;
     }
 }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLECPrivateKey.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLECPrivateKey.java
index 15c3e04..6bb0c72 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLECPrivateKey.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLECPrivateKey.java
@@ -42,9 +42,9 @@
 
     private static final String ALGORITHM = "EC";
 
-    protected transient OpenSSLKey key;
+    private transient OpenSSLKey key;
 
-    protected transient OpenSSLECGroupContext group;
+    private transient OpenSSLECGroupContext group;
 
     OpenSSLECPrivateKey(OpenSSLECGroupContext group, OpenSSLKey key) {
         this.group = group;
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLECPublicKey.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLECPublicKey.java
index e5b66e0..37fd102 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLECPublicKey.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLECPublicKey.java
@@ -38,9 +38,9 @@
 
     private static final String ALGORITHM = "EC";
 
-    protected transient OpenSSLKey key;
+    private transient OpenSSLKey key;
 
-    protected transient OpenSSLECGroupContext group;
+    private transient OpenSSLECGroupContext group;
 
     OpenSSLECPublicKey(OpenSSLECGroupContext group, OpenSSLKey key) {
         this.group = group;
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLEvpCipher.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLEvpCipher.java
index a9b2d46..765282d 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLEvpCipher.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLEvpCipher.java
@@ -50,7 +50,7 @@
      */
     private int modeBlockSize;
 
-    public OpenSSLEvpCipher(Mode mode, Padding padding) {
+    protected OpenSSLEvpCipher(Mode mode, Padding padding) {
         super(mode, padding);
     }
 
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLEvpCipherAES.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLEvpCipherAES.java
index 0085903c..ee65b30 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLEvpCipherAES.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLEvpCipherAES.java
@@ -91,7 +91,7 @@
             /**
              * @hide This class is not part of the Android public SDK API
              */
-            public static class NoPadding extends CBC {
+            public static class NoPadding extends AES.CBC {
                 public NoPadding() {
                     super(Padding.NOPADDING);
                 }
@@ -100,7 +100,7 @@
             /**
              * @hide This class is not part of the Android public SDK API
              */
-            public static class PKCS5Padding extends CBC {
+            public static class PKCS5Padding extends AES.CBC {
                 public PKCS5Padding() {
                     super(Padding.PKCS5PADDING);
                 }
@@ -127,7 +127,7 @@
             /**
              * @hide This class is not part of the Android public SDK API
              */
-            public static class NoPadding extends ECB {
+            public static class NoPadding extends AES.ECB {
                 public NoPadding() {
                     super(Padding.NOPADDING);
                 }
@@ -136,7 +136,7 @@
             /**
              * @hide This class is not part of the Android public SDK API
              */
-            public static class PKCS5Padding extends ECB {
+            public static class PKCS5Padding extends AES.ECB {
                 public PKCS5Padding() {
                     super(Padding.PKCS5PADDING);
                 }
@@ -176,7 +176,7 @@
             /**
              * @hide This class is not part of the Android public SDK API
              */
-            public static class NoPadding extends CBC {
+            public static class NoPadding extends AES_128.CBC {
                 public NoPadding() {
                     super(Padding.NOPADDING);
                 }
@@ -185,7 +185,7 @@
             /**
              * @hide This class is not part of the Android public SDK API
              */
-            public static class PKCS5Padding extends CBC {
+            public static class PKCS5Padding extends AES_128.CBC {
                 public PKCS5Padding() {
                     super(Padding.PKCS5PADDING);
                 }
@@ -212,7 +212,7 @@
             /**
              * @hide This class is not part of the Android public SDK API
              */
-            public static class NoPadding extends ECB {
+            public static class NoPadding extends AES_128.ECB {
                 public NoPadding() {
                     super(Padding.NOPADDING);
                 }
@@ -221,7 +221,7 @@
             /**
              * @hide This class is not part of the Android public SDK API
              */
-            public static class PKCS5Padding extends ECB {
+            public static class PKCS5Padding extends AES_128.ECB {
                 public PKCS5Padding() {
                     super(Padding.PKCS5PADDING);
                 }
@@ -255,7 +255,7 @@
             /**
              * @hide This class is not part of the Android public SDK API
              */
-            public static class NoPadding extends CBC {
+            public static class NoPadding extends AES_256.CBC {
                 public NoPadding() {
                     super(Padding.NOPADDING);
                 }
@@ -264,7 +264,7 @@
             /**
              * @hide This class is not part of the Android public SDK API
              */
-            public static class PKCS5Padding extends CBC {
+            public static class PKCS5Padding extends AES_256.CBC {
                 public PKCS5Padding() {
                     super(Padding.PKCS5PADDING);
                 }
@@ -291,7 +291,7 @@
             /**
              * @hide This class is not part of the Android public SDK API
              */
-            public static class NoPadding extends ECB {
+            public static class NoPadding extends AES_256.ECB {
                 public NoPadding() {
                     super(Padding.NOPADDING);
                 }
@@ -300,7 +300,7 @@
             /**
              * @hide This class is not part of the Android public SDK API
              */
-            public static class PKCS5Padding extends ECB {
+            public static class PKCS5Padding extends AES_256.ECB {
                 public PKCS5Padding() {
                     super(Padding.PKCS5PADDING);
                 }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLMac.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLMac.java
index 5acb91b..cca9721 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLMac.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLMac.java
@@ -32,18 +32,10 @@
  */
 @Internal
 public abstract class OpenSSLMac extends MacSpi {
-    private NativeRef.HMAC_CTX ctx;
-
-    /**
-     * Holds the EVP_MD for the hashing algorithm, e.g.
-     * EVP_get_digestbyname("sha1");
-     */
-    private final long evp_md;
-
     /**
      * The secret key used in this keyed MAC.
      */
-    private byte[] keyBytes;
+    protected byte[] keyBytes;
 
     /**
      * Holds the output size of the message digest.
@@ -55,11 +47,20 @@
      */
     private final byte[] singleByte = new byte[1];
 
-    private OpenSSLMac(long evp_md, int size) {
-        this.evp_md = evp_md;
+    private OpenSSLMac(int size) {
         this.size = size;
     }
 
+    /**
+     * Creates and initializes the relevant BoringSSL *MAC context.
+     */
+    protected abstract void resetContext();
+
+    /**
+     * Passes the contents of a direct ByteBuffer to the relevant BoringSSL *MAC function.
+     */
+    protected abstract void updateDirect(long ptr, int len);
+
     @Override
     protected int engineGetMacLength() {
         return size;
@@ -84,15 +85,6 @@
         resetContext();
     }
 
-    private final void resetContext() {
-        NativeRef.HMAC_CTX ctxLocal = new NativeRef.HMAC_CTX(NativeCrypto.HMAC_CTX_new());
-        if (keyBytes != null) {
-            NativeCrypto.HMAC_Init_ex(ctxLocal, keyBytes, evp_md);
-        }
-
-        this.ctx = ctxLocal;
-    }
-
     @Override
     protected void engineUpdate(byte input) {
         singleByte[0] = input;
@@ -100,17 +92,10 @@
     }
 
     @Override
-    protected void engineUpdate(byte[] input, int offset, int len) {
-        final NativeRef.HMAC_CTX ctxLocal = ctx;
-        NativeCrypto.HMAC_Update(ctxLocal, input, offset, len);
-    }
-
-    @Override
     protected void engineUpdate(ByteBuffer input) {
         // Optimization: Avoid copying/allocation for direct buffers because their contents are
         // stored as a contiguous region in memory and thus can be efficiently accessed from native
         // code.
-
         if (!input.hasRemaining()) {
             return;
         }
@@ -122,7 +107,7 @@
 
         long baseAddress = NativeCrypto.getDirectBufferAddress(input);
         if (baseAddress == 0) {
-            // Direct buffer's contents can't be accessed from JNI  -- superclass's implementation
+            // Direct buffers' contents can't be accessed from JNI  -- superclass's implementation
             // is good enough to handle this.
             super.engineUpdate(input);
             return;
@@ -139,19 +124,19 @@
             throw new RuntimeException("Negative remaining amount");
         }
 
-        final NativeRef.HMAC_CTX ctxLocal = ctx;
-        NativeCrypto.HMAC_UpdateDirect(ctxLocal, ptr, len);
+        updateDirect(ptr, len);
         input.position(position + len);
     }
 
     @Override
     protected byte[] engineDoFinal() {
-        final NativeRef.HMAC_CTX ctxLocal = ctx;
-        final byte[] output = NativeCrypto.HMAC_Final(ctxLocal);
+        final byte[] output = doFinal();
         resetContext();
         return output;
     }
 
+    protected abstract byte[] doFinal();
+
     @Override
     protected void engineReset() {
         resetContext();
@@ -160,7 +145,52 @@
     /**
      * @hide This class is not part of the Android public SDK API
      */
-    public static final class HmacMD5 extends OpenSSLMac {
+    public static class Hmac extends OpenSSLMac {
+        private NativeRef.HMAC_CTX ctx;
+
+        /**
+         * Holds the EVP_MD for the hashing algorithm, e.g.
+         * EVP_get_digestbyname("sha1");
+         */
+        private final long evp_md;
+
+        public Hmac(long evp_md, int size) {
+            super(size);
+            this.evp_md = evp_md;
+        }
+
+        @Override
+        protected void resetContext() {
+            NativeRef.HMAC_CTX ctxLocal = new NativeRef.HMAC_CTX(NativeCrypto.HMAC_CTX_new());
+            if (keyBytes != null) {
+                NativeCrypto.HMAC_Init_ex(ctxLocal, keyBytes, evp_md);
+            }
+            this.ctx = ctxLocal;
+        }
+
+        @Override
+        protected void engineUpdate(byte[] input, int offset, int len) {
+            final NativeRef.HMAC_CTX ctxLocal = ctx;
+            NativeCrypto.HMAC_Update(ctxLocal, input, offset, len);
+        }
+
+        @Override
+        protected void updateDirect(long ptr, int len) {
+            final NativeRef.HMAC_CTX ctxLocal = ctx;
+            NativeCrypto.HMAC_UpdateDirect(ctxLocal, ptr, len);
+        }
+
+        @Override
+        protected byte[] doFinal() {
+            final NativeRef.HMAC_CTX ctxLocal = ctx;
+            return NativeCrypto.HMAC_Final(ctxLocal);
+        }
+    }
+
+    /**
+     * @hide This class is not part of the Android public SDK API
+     */
+    public static final class HmacMD5 extends Hmac {
         public HmacMD5() {
             super(EvpMdRef.MD5.EVP_MD, EvpMdRef.MD5.SIZE_BYTES);
         }
@@ -169,7 +199,7 @@
     /**
      * @hide This class is not part of the Android public SDK API
      */
-    public static final class HmacSHA1 extends OpenSSLMac {
+    public static final class HmacSHA1 extends Hmac {
         public HmacSHA1() {
             super(EvpMdRef.SHA1.EVP_MD, EvpMdRef.SHA1.SIZE_BYTES);
         }
@@ -178,7 +208,7 @@
     /**
      * @hide This class is not part of the Android public SDK API
      */
-    public static final class HmacSHA224 extends OpenSSLMac {
+    public static final class HmacSHA224 extends Hmac {
         public HmacSHA224() throws NoSuchAlgorithmException {
             super(EvpMdRef.SHA224.EVP_MD, EvpMdRef.SHA224.SIZE_BYTES);
         }
@@ -187,7 +217,7 @@
     /**
      * @hide This class is not part of the Android public SDK API
      */
-    public static final class HmacSHA256 extends OpenSSLMac {
+    public static final class HmacSHA256 extends Hmac {
         public HmacSHA256() throws NoSuchAlgorithmException {
             super(EvpMdRef.SHA256.EVP_MD, EvpMdRef.SHA256.SIZE_BYTES);
         }
@@ -196,7 +226,7 @@
     /**
      * @hide This class is not part of the Android public SDK API
      */
-    public static final class HmacSHA384 extends OpenSSLMac {
+    public static final class HmacSHA384 extends Hmac {
         public HmacSHA384() throws NoSuchAlgorithmException {
             super(EvpMdRef.SHA384.EVP_MD, EvpMdRef.SHA384.SIZE_BYTES);
         }
@@ -205,9 +235,47 @@
     /**
      * @hide This class is not part of the Android public SDK API
      */
-    public static final class HmacSHA512 extends OpenSSLMac {
+    public static final class HmacSHA512 extends Hmac {
         public HmacSHA512() {
             super(EvpMdRef.SHA512.EVP_MD, EvpMdRef.SHA512.SIZE_BYTES);
         }
     }
+
+    /**
+     * @hide This class is not part of the Android public SDK API
+     */
+    public static final class AesCmac extends OpenSSLMac {
+        private NativeRef.CMAC_CTX ctx;
+
+        public AesCmac() {
+            super(16);
+        }
+
+        @Override
+        protected void resetContext() {
+            NativeRef.CMAC_CTX ctxLocal = new NativeRef.CMAC_CTX(NativeCrypto.CMAC_CTX_new());
+            if (keyBytes != null) {
+                NativeCrypto.CMAC_Init(ctxLocal, keyBytes);
+            }
+            this.ctx = ctxLocal;
+        }
+
+        @Override
+        protected void updateDirect(long ptr, int len) {
+            final NativeRef.CMAC_CTX ctxLocal = ctx;
+            NativeCrypto.CMAC_UpdateDirect(ctxLocal, ptr, len);
+        }
+
+        @Override
+        protected byte[] doFinal() {
+            final NativeRef.CMAC_CTX ctxLocal = ctx;
+            return NativeCrypto.CMAC_Final(ctxLocal);
+        }
+
+        @Override
+        protected void engineUpdate(byte[] input, int offset, int len) {
+            final NativeRef.CMAC_CTX ctxLocal = ctx;
+            NativeCrypto.CMAC_Update(ctxLocal, input, offset, len);
+        }
+    }
 }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLProvider.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLProvider.java
index 6e6808b..42a5d33 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLProvider.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLProvider.java
@@ -34,7 +34,7 @@
  */
 @libcore.
 api.IntraCoreApi
-@libcore.api.CorePlatformApi
+@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
 @Internal
 public final class OpenSSLProvider extends Provider {
     private static final long serialVersionUID = 2996752495318905136L;
@@ -43,6 +43,8 @@
 
     private static final String STANDARD_EC_PRIVATE_KEY_INTERFACE_CLASS_NAME =
             "java.security.interfaces.ECPrivateKey";
+    private static final String STANDARD_XEC_PRIVATE_KEY_INTERFACE_CLASS_NAME =
+            "java.security.interfaces.XECPrivateKey";
     private static final String STANDARD_RSA_PRIVATE_KEY_INTERFACE_CLASS_NAME =
             "java.security.interfaces.RSAPrivateKey";
     private static final String STANDARD_RSA_PUBLIC_KEY_INTERFACE_CLASS_NAME =
@@ -52,7 +54,7 @@
             .UnsupportedAppUsage
             @libcore.api
             .IntraCoreApi
-            @libcore.api.CorePlatformApi
+            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
             public OpenSSLProvider() {
         this(Platform.getDefaultProviderName());
     }
@@ -200,6 +202,9 @@
         put("Alg.Alias.KeyPairGenerator.1.2.840.10045.2.1", "EC");
         put("Alg.Alias.KeyPairGenerator.1.3.133.16.840.63.0.2", "EC");
 
+        put("KeyPairGenerator.XDH", PREFIX + "OpenSSLXDHKeyPairGenerator");
+        put("Alg.Alias.KeyPairGenerator.1.3.101.110", "XDH");
+
         /* == KeyFactory == */
         put("KeyFactory.RSA", PREFIX + "OpenSSLRSAKeyFactory");
         put("Alg.Alias.KeyFactory.1.2.840.113549.1.1.1", "RSA");
@@ -210,12 +215,16 @@
         put("Alg.Alias.KeyFactory.1.2.840.10045.2.1", "EC");
         put("Alg.Alias.KeyFactory.1.3.133.16.840.63.0.2", "EC");
 
+        put("KeyFactory.XDH", PREFIX + "OpenSSLXDHKeyFactory");
+        put("Alg.Alias.KeyFactory.1.3.101.110", "XDH");
+
         /* == SecretKeyFactory == */
         put("SecretKeyFactory.DESEDE", PREFIX + "DESEDESecretKeyFactory");
         put("Alg.Alias.SecretKeyFactory.TDEA", "DESEDE");
 
         /* == KeyAgreement == */
         putECDHKeyAgreementImplClass("OpenSSLECDHKeyAgreement");
+        putXDHKeyAgreementImplClass("OpenSSLXDHKeyAgreement");
 
         /* == Signatures == */
         putSignatureImplClass("MD5withRSA", "OpenSSLSignature$MD5RSA");
@@ -498,6 +507,8 @@
         put("Alg.Alias.Mac.HMAC/SHA512", "HmacSHA512");
         put("Alg.Alias.Mac.PBEWITHHMACSHA512", "HmacSHA512");
 
+        putMacImplClass("AESCMAC", "OpenSSLMac$AesCmac");
+
         /* === Certificate === */
 
         put("CertificateFactory.X509", PREFIX + "OpenSSLX509CertificateFactory");
@@ -596,6 +607,19 @@
                 supportedKeyFormats);
     }
 
+    private void putXDHKeyAgreementImplClass(String className) {
+        // Accept only keys for which any of the following is true:
+        // * the key is from this provider (subclass of OpenSSLKeyHolder),
+        // * the key provides its key material in "PKCS#8" encoding via Key.getEncoded.
+        // * the key is a transparent XEC private key (subclass of XECPrivateKey).
+        String supportedKeyClasses = PREFIX + "OpenSSLKeyHolder"
+                + "|" + STANDARD_XEC_PRIVATE_KEY_INTERFACE_CLASS_NAME + "|" + PREFIX
+                + "OpenSSLX25519PrivateKey";
+        String supportedKeyFormats = "PKCS#8";
+        putImplClassWithKeyConstraints(
+                "KeyAgreement.XDH", PREFIX + className, supportedKeyClasses, supportedKeyFormats);
+    }
+
     private void putImplClassWithKeyConstraints(String typeAndAlgName,
             String fullyQualifiedClassName,
             String supportedKeyClasses,
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLSocketImpl.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLSocketImpl.java
index dd7279f..ca2542c 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLSocketImpl.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLSocketImpl.java
@@ -31,7 +31,7 @@
  * Conscrypt's extended socket API before the introduction of the {@link Conscrypt} class.
  * @hide This class is not part of the Android public SDK API
  */
-@libcore.api.CorePlatformApi
+@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
 @Internal
 public abstract class OpenSSLSocketImpl extends AbstractConscryptSocket {
     OpenSSLSocketImpl() throws IOException {
@@ -61,7 +61,7 @@
         super(socket, hostname, port, autoClose);
     }
 
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     @Override
     public String getHostname() {
         return super.getHostname();
@@ -69,15 +69,15 @@
 
     @android.compat.annotation
             .UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
-                    publicAlternatives = "Use {@link javax.net.ssl.SSLParameters#setServerNames}.")
-            @libcore.api.CorePlatformApi
+                    publicAlternatives = "Use {@code javax.net.ssl.SSLParameters#setServerNames}.")
+            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
             @Override
             public void
             setHostname(String hostname) {
         super.setHostname(hostname);
     }
 
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     @Override
     public String getHostnameOrIP() {
         return super.getHostnameOrIP();
@@ -89,22 +89,22 @@
     }
 
     @android.compat.annotation
-            .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi
+            .UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
             @Override
             public void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException {
         super.setSoWriteTimeout(writeTimeoutMilliseconds);
     }
 
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     @Override
     public int getSoWriteTimeout() throws SocketException {
         return super.getSoWriteTimeout();
     }
 
     @android.compat.annotation
-            .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi
+            .UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
             @Override
             public void setHandshakeTimeout(int handshakeTimeoutMilliseconds)
             throws SocketException {
@@ -118,21 +118,22 @@
             .UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
                     publicAlternatives =
                             "Use {@link android.net.ssl.SSLSockets#setUseSessionTickets}.")
-            @libcore.api.CorePlatformApi
+            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
             @Override
             public abstract void
             setUseSessionTickets(boolean useSessionTickets);
 
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     @Override
     public abstract void setChannelIdEnabled(boolean enabled);
 
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     @Override
     public abstract byte[] getChannelId() throws SSLException;
 
-    @android.compat.annotation.UnsupportedAppUsage
-    @libcore.api.CorePlatformApi
+    @android.compat.
+    annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     @Override
     public abstract void setChannelIdPrivateKey(PrivateKey privateKey);
 
@@ -141,7 +142,7 @@
      */
     @android.compat.annotation
             .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi
+            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
             @Override
             @Deprecated
             public final byte[] getNpnSelectedProtocol() {
@@ -153,7 +154,7 @@
      */
     @android.compat.annotation
             .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi
+            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
             @Override
             @Deprecated
             public final void setNpnProtocols(byte[] npnProtocols) {
@@ -166,7 +167,7 @@
     @android.compat.annotation.
     UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
             publicAlternatives =
-                    "Use {@link javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}.")
+                    "Use {@code javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}.")
     @Override
     @Deprecated
     public final void
@@ -180,8 +181,8 @@
     @android.compat.annotation
             .UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
                     publicAlternatives =
-                            "Use {@link javax.net.ssl.SSLSocket#getApplicationProtocol()}.")
-            @libcore.api.CorePlatformApi
+                            "Use {@code javax.net.ssl.SSLSocket#getApplicationProtocol()}.")
+            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
             @Override
             @Deprecated
             public final byte[] getAlpnSelectedProtocol() {
@@ -194,8 +195,8 @@
     @android.compat.annotation
             .UnsupportedAppUsage(maxTargetSdk = dalvik.annotation.compat.VersionCodes.Q,
                     publicAlternatives =
-                            "Use {@link javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}.")
-            @libcore.api.CorePlatformApi
+                            "Use {@code javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}.")
+            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
             @Override
             @Deprecated
             public final void
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX25519Key.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX25519Key.java
new file mode 100644
index 0000000..00e7f91
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX25519Key.java
@@ -0,0 +1,11 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.org.conscrypt;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface OpenSSLX25519Key {
+    int X25519_KEY_SIZE_BYTES = 32;
+
+    byte[] getU();
+}
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX25519PrivateKey.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX25519PrivateKey.java
new file mode 100644
index 0000000..faa7871
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX25519PrivateKey.java
@@ -0,0 +1,110 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.org.conscrypt;
+
+import java.security.PrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Arrays;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public class OpenSSLX25519PrivateKey implements OpenSSLX25519Key, PrivateKey {
+    private static final byte[] PKCS8_PREAMBLE = new byte[]{
+            0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x04, 0x22, 0x04, 0x20,
+    };
+
+    private static final byte[] PKCS8_PREAMBLE_WITH_NULL = new byte[] {
+            0x30, 0x30, 0x02, 0x01, 0x00, 0x30, 0x07, 0x06, 0x03, 0x2B, 0x65, 0x6E, 0x05, 0x00, 0x04, 0x22, 0x04, 0x20,
+    };
+
+    private byte[] uCoordinate;
+
+    public OpenSSLX25519PrivateKey(PKCS8EncodedKeySpec keySpec) throws InvalidKeySpecException {
+        byte[] encoded = keySpec.getEncoded();
+        if (encoded == null || !"PKCS#8".equals(keySpec.getFormat())) {
+            throw new InvalidKeySpecException("Key must be encoded in PKCS#8 format");
+        }
+
+        int preambleLength = matchesPreamble(PKCS8_PREAMBLE, encoded) | matchesPreamble(PKCS8_PREAMBLE_WITH_NULL, encoded);
+        if (preambleLength == 0) {
+            throw new InvalidKeySpecException("Key size is not correct size");
+        }
+
+        uCoordinate = Arrays.copyOfRange(encoded, PKCS8_PREAMBLE.length, encoded.length);
+    }
+
+    private static int matchesPreamble(byte[] preamble, byte[] encoded) {
+        if (encoded.length != (preamble.length + X25519_KEY_SIZE_BYTES)) {
+            return 0;
+        }
+        int cmp = 0;
+        for (int i = 0; i < preamble.length; i++) {
+            cmp |= encoded[i] ^ preamble[i];
+        }
+        if (cmp != 0) {
+            return 0;
+        }
+        return preamble.length;
+    }
+
+    public OpenSSLX25519PrivateKey(byte[] coordinateBytes) {
+        uCoordinate = coordinateBytes.clone();
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return "XDH";
+    }
+
+    @Override
+    public String getFormat() {
+        return "PKCS#8";
+    }
+
+    @Override
+    public byte[] getEncoded() {
+        if (uCoordinate == null) {
+            throw new IllegalStateException("key is destroyed");
+        }
+
+        byte[] encoded = Arrays.copyOf(PKCS8_PREAMBLE, PKCS8_PREAMBLE.length + uCoordinate.length);
+        System.arraycopy(uCoordinate, 0, encoded, PKCS8_PREAMBLE.length, uCoordinate.length);
+        return encoded;
+    }
+
+    @Override
+    public byte[] getU() {
+        if (uCoordinate == null) {
+            throw new IllegalStateException("key is destroyed");
+        }
+
+        return uCoordinate.clone();
+    }
+
+    @Override
+    public void destroy() {
+        if (uCoordinate != null) {
+            Arrays.fill(uCoordinate, (byte) 0);
+            uCoordinate = null;
+        }
+    }
+
+    @Override
+    public boolean isDestroyed() {
+        return uCoordinate == null;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof OpenSSLX25519PrivateKey)) return false;
+        OpenSSLX25519PrivateKey that = (OpenSSLX25519PrivateKey) o;
+        return Arrays.equals(uCoordinate, that.uCoordinate);
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(uCoordinate);
+    }
+}
\ No newline at end of file
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX25519PublicKey.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX25519PublicKey.java
new file mode 100644
index 0000000..1c10e4a
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX25519PublicKey.java
@@ -0,0 +1,105 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.org.conscrypt;
+
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public class OpenSSLX25519PublicKey implements OpenSSLX25519Key, PublicKey {
+    private static final byte[] X509_PREAMBLE = new byte[] {
+            0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00,
+    };
+
+    private static final byte[] X509_PREAMBLE_WITH_NULL = new byte[] {
+            0x30, 0x2C, 0x30, 0x07, 0x06, 0x03, 0x2B, 0x65, 0x6E, 0x05, 0x00, 0x03, 0x21, 0x00,
+    };
+
+    private byte[] uCoordinate;
+
+    public OpenSSLX25519PublicKey(X509EncodedKeySpec keySpec) throws InvalidKeySpecException {
+        byte[] encoded = keySpec.getEncoded();
+        if (encoded == null || !"X.509".equals(keySpec.getFormat())) {
+            throw new InvalidKeySpecException("Encoding must be in X.509 format");
+        }
+
+        int preambleLength = matchesPreamble(X509_PREAMBLE, encoded) | matchesPreamble(X509_PREAMBLE_WITH_NULL, encoded);
+        if (preambleLength == 0) {
+            throw new InvalidKeySpecException("Key size is not correct size");
+        }
+
+        uCoordinate = Arrays.copyOfRange(encoded, preambleLength, encoded.length);
+    }
+
+    private static int matchesPreamble(byte[] preamble, byte[] encoded) {
+        if (encoded.length != (preamble.length + X25519_KEY_SIZE_BYTES)) {
+            return 0;
+        }
+        int cmp = 0;
+        for (int i = 0; i < preamble.length; i++) {
+            cmp |= encoded[i] ^ preamble[i];
+        }
+        if (cmp != 0) {
+            return 0;
+        }
+        return preamble.length;
+    }
+
+    public OpenSSLX25519PublicKey(byte[] coordinateBytes) {
+        uCoordinate = coordinateBytes.clone();
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return "XDH";
+    }
+
+    @Override
+    public String getFormat() {
+        return "X.509";
+    }
+
+    @Override
+    public byte[] getEncoded() {
+        if (uCoordinate == null) {
+            throw new IllegalStateException("key is destroyed");
+        }
+
+        byte[] encoded = Arrays.copyOf(X509_PREAMBLE, X509_PREAMBLE.length + X25519_KEY_SIZE_BYTES);
+        System.arraycopy(uCoordinate, 0, encoded, X509_PREAMBLE.length, uCoordinate.length);
+        return encoded;
+    }
+
+    @Override
+    public byte[] getU() {
+        if (uCoordinate == null) {
+            throw new IllegalStateException("key is destroyed");
+        }
+
+        return uCoordinate.clone();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (uCoordinate == null) {
+            throw new IllegalStateException("key is destroyed");
+        }
+
+        if (this == o) return true;
+        if (!(o instanceof OpenSSLX25519PublicKey)) return false;
+        OpenSSLX25519PublicKey that = (OpenSSLX25519PublicKey) o;
+        return Arrays.equals(uCoordinate, that.uCoordinate);
+    }
+
+    @Override
+    public int hashCode() {
+        if (uCoordinate == null) {
+            throw new IllegalStateException("key is destroyed");
+        }
+
+        return Arrays.hashCode(uCoordinate);
+    }
+}
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509CRL.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509CRL.java
index 4f639a0..f0fb680 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509CRL.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509CRL.java
@@ -99,12 +99,12 @@
             bis.release();
         }
 
-        final List<OpenSSLX509CRL> certs = new ArrayList<OpenSSLX509CRL>(certRefs.length);
-        for (int i = 0; i < certRefs.length; i++) {
-            if (certRefs[i] == 0) {
+        final List<OpenSSLX509CRL> certs = new ArrayList<>(certRefs.length);
+        for (long certRef : certRefs) {
+            if (certRef == 0) {
                 continue;
             }
-            certs.add(new OpenSSLX509CRL(certRefs[i]));
+            certs.add(new OpenSSLX509CRL(certRef));
         }
         return certs;
     }
@@ -139,12 +139,12 @@
             bis.release();
         }
 
-        final List<OpenSSLX509CRL> certs = new ArrayList<OpenSSLX509CRL>(certRefs.length);
-        for (int i = 0; i < certRefs.length; i++) {
-            if (certRefs[i] == 0) {
+        final List<OpenSSLX509CRL> certs = new ArrayList<>(certRefs.length);
+        for (long certRef : certRefs) {
+            if (certRef == 0) {
                 continue;
             }
-            certs.add(new OpenSSLX509CRL(certRefs[i]));
+            certs.add(new OpenSSLX509CRL(certRef));
         }
         return certs;
     }
@@ -165,7 +165,7 @@
             return null;
         }
 
-        return new HashSet<String>(Arrays.asList(critOids));
+        return new HashSet<>(Arrays.asList(critOids));
     }
 
     @Override
@@ -190,7 +190,7 @@
             return null;
         }
 
-        return new HashSet<String>(Arrays.asList(nonCritOids));
+        return new HashSet<>(Arrays.asList(nonCritOids));
     }
 
     @Override
@@ -221,9 +221,9 @@
         }
     }
 
-    private void verifyInternal(PublicKey key, String sigProvider) throws CRLException,
-            NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
-            SignatureException {
+    private void verifyInternal(PublicKey key, String sigProvider)
+            throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
+                   SignatureException {
         String sigAlg = getSigAlgName();
         if (sigAlg == null) {
             sigAlg = getSigAlgOID();
@@ -330,7 +330,7 @@
             return null;
         }
 
-        final Set<OpenSSLX509CRLEntry> crlSet = new HashSet<OpenSSLX509CRLEntry>();
+        final Set<OpenSSLX509CRLEntry> crlSet = new HashSet<>();
         for (long entryRef : entryRefs) {
             try {
                 crlSet.add(new OpenSSLX509CRLEntry(entryRef));
@@ -343,7 +343,7 @@
     }
 
     @Override
-    public byte[] getTBSCertList() throws CRLException {
+    public byte[] getTBSCertList() {
         return NativeCrypto.get_X509_CRL_crl_enc(mContext, this);
     }
 
@@ -413,6 +413,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     protected void finalize() throws Throwable {
         try {
             if (mContext != 0) {
@@ -422,5 +423,4 @@
             super.finalize();
         }
     }
-
 }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509CertPath.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509CertPath.java
index a76c1fc..eaa9f9a 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509CertPath.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509CertPath.java
@@ -158,6 +158,7 @@
                 try {
                     inStream.reset();
                 } catch (IOException ignored) {
+                    // Ignored
                 }
             }
             throw new CertificateException(e);
@@ -221,6 +222,7 @@
                 try {
                     inStream.reset();
                 } catch (IOException ignored) {
+                    // Ignored
                 }
             }
             throw new CertificateException(e);
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509Certificate.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509Certificate.java
index 1cc0fd1..6f664fc 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509Certificate.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509Certificate.java
@@ -132,16 +132,15 @@
         if (certRefs == null) {
             // To avoid returning a immutable list in only one path, we create an
             // empty list here instead of using Collections.emptyList()
-            return new ArrayList<OpenSSLX509Certificate>();
+            return new ArrayList<>();
         }
 
-        final List<OpenSSLX509Certificate> certs = new ArrayList<OpenSSLX509Certificate>(
-                certRefs.length);
-        for (int i = 0; i < certRefs.length; i++) {
-            if (certRefs[i] == 0) {
+        final List<OpenSSLX509Certificate> certs = new ArrayList<>(certRefs.length);
+        for (long certRef : certRefs) {
+            if (certRef == 0) {
                 continue;
             }
-            certs.add(new OpenSSLX509Certificate(certRefs[i]));
+            certs.add(new OpenSSLX509Certificate(certRef));
         }
         return certs;
     }
@@ -180,13 +179,12 @@
             bis.release();
         }
 
-        final List<OpenSSLX509Certificate> certs = new ArrayList<OpenSSLX509Certificate>(
-                certRefs.length);
-        for (int i = 0; i < certRefs.length; i++) {
-            if (certRefs[i] == 0) {
+        final List<OpenSSLX509Certificate> certs = new ArrayList<>(certRefs.length);
+        for (long certRef : certRefs) {
+            if (certRef == 0) {
                 continue;
             }
-            certs.add(new OpenSSLX509Certificate(certRefs[i]));
+            certs.add(new OpenSSLX509Certificate(certRef));
         }
         return certs;
     }
@@ -218,7 +216,7 @@
             return null;
         }
 
-        return new HashSet<String>(Arrays.asList(critOids));
+        return new HashSet<>(Arrays.asList(critOids));
     }
 
     @Override
@@ -242,7 +240,7 @@
             return null;
         }
 
-        return new HashSet<String>(Arrays.asList(nonCritOids));
+        return new HashSet<>(Arrays.asList(nonCritOids));
     }
 
     @Override
@@ -251,14 +249,16 @@
     }
 
     @Override
-    public void checkValidity() throws CertificateExpiredException,
-            CertificateNotYetValidException {
+    @SuppressWarnings("JdkObsolete") // Needed for API compatibility
+    public void checkValidity()
+            throws CertificateExpiredException, CertificateNotYetValidException {
         checkValidity(new Date());
     }
 
     @Override
-    public void checkValidity(Date date) throws CertificateExpiredException,
-            CertificateNotYetValidException {
+    @SuppressWarnings("JdkObsolete") // Needed for API compatibility
+    public void checkValidity(Date date)
+            throws CertificateExpiredException, CertificateNotYetValidException {
         if (getNotBefore().compareTo(date) > 0) {
             throw new CertificateNotYetValidException("Certificate not valid until "
                     + getNotBefore().toString() + " (compared to " + date.toString() + ")");
@@ -302,7 +302,7 @@
 
     @Override
     public byte[] getTBSCertificate() throws CertificateEncodingException {
-        return NativeCrypto.get_X509_cert_info_enc(mContext, this);
+        return NativeCrypto.get_X509_tbs_cert(mContext, this);
     }
 
     @Override
@@ -379,9 +379,7 @@
         return NativeCrypto.i2d_X509(mContext, this);
     }
 
-    private void verifyOpenSSL(OpenSSLKey pkey) throws CertificateException,
-                                                       NoSuchAlgorithmException,
-                                                       InvalidKeyException, SignatureException {
+    private void verifyOpenSSL(OpenSSLKey pkey) throws CertificateException, SignatureException {
         try {
             NativeCrypto.X509_verify(mContext, this, pkey.getNativeRef());
         } catch (RuntimeException e) {
@@ -391,9 +389,9 @@
         }
     }
 
-    private void verifyInternal(PublicKey key, String sigProvider) throws CertificateException,
-            NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
-            SignatureException {
+    private void verifyInternal(PublicKey key, String sigProvider)
+            throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException,
+                   SignatureException, CertificateEncodingException {
         final Signature sig;
         if (sigProvider == null) {
             sig = Signature.getInstance(getSigAlgName());
@@ -417,7 +415,7 @@
             return;
         }
 
-        verifyInternal(key, (String) null);
+        verifyInternal(key, null);
     }
 
     @Override
@@ -471,8 +469,8 @@
         try {
             OpenSSLKey pkey = new OpenSSLKey(NativeCrypto.X509_get_pubkey(mContext, this));
             return pkey.getPublicKey();
-        } catch (NoSuchAlgorithmException ignored) {
-        } catch (InvalidKeyException ignored) {
+        } catch (NoSuchAlgorithmException | InvalidKeyException ignored) {
+            // Ignored
         }
 
         /* Try generating the key using other Java providers. */
@@ -481,8 +479,8 @@
         try {
             KeyFactory kf = KeyFactory.getInstance(oid);
             return kf.generatePublic(new X509EncodedKeySpec(encoded));
-        } catch (NoSuchAlgorithmException ignored) {
-        } catch (InvalidKeySpecException ignored) {
+        } catch (NoSuchAlgorithmException | InvalidKeySpecException ignored) {
+            // Ignored
         }
 
         /*
@@ -505,7 +503,7 @@
     }
 
     @Override
-    public List<String> getExtendedKeyUsage() throws CertificateParsingException {
+    public List<String> getExtendedKeyUsage() {
         String[] extUsage = NativeCrypto.get_X509_ex_xkusage(mContext, this);
         if (extUsage == null) {
             return null;
@@ -519,9 +517,9 @@
             return null;
         }
 
-        Collection<List<?>> coll = new ArrayList<List<?>>(altNameArray.length);
-        for (int i = 0; i < altNameArray.length; i++) {
-            coll.add(Collections.unmodifiableList(Arrays.asList(altNameArray[i])));
+        Collection<List<?>> coll = new ArrayList<>(altNameArray.length);
+        for (Object[] objects : altNameArray) {
+            coll.add(Collections.unmodifiableList(Arrays.asList(objects)));
         }
 
         return Collections.unmodifiableCollection(coll);
@@ -570,19 +568,14 @@
     }
 
     /**
-     * Delete an extension.
-     *
-     * A modified copy of the certificate is returned. The original object
-     * is unchanged.
-     * If the extension is not present, an unmodified copy is returned.
+     * Returns a re-encoded TBSCertificate with the extension identified by oid removed.
      */
-    public OpenSSLX509Certificate withDeletedExtension(String oid) {
-        OpenSSLX509Certificate copy = new OpenSSLX509Certificate(NativeCrypto.X509_dup(mContext, this), notBefore, notAfter);
-        NativeCrypto.X509_delete_ext(copy.getContext(), copy, oid);
-        return copy;
+    public byte[] getTBSCertificateWithoutExtension(String oid) {
+        return NativeCrypto.get_X509_tbs_cert_without_ext(mContext, this, oid);
     }
 
     @Override
+    @SuppressWarnings("deprecation")
     protected void finalize() throws Throwable {
         try {
             if (mContext != 0) {
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509CertificateFactory.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509CertificateFactory.java
index 542228d..a131de7 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509CertificateFactory.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLX509CertificateFactory.java
@@ -62,6 +62,40 @@
         }
     }
 
+    private static boolean isMaybePkcs7(byte[] header) {
+        // The outer tag must be SEQUENCE.
+        if (header.length < 2 || header[0] != 0x30) {
+            return false;
+        }
+
+        // Bytes are signed in Java.
+        int lengthByte = header[1] & 0xff;
+
+        // Skip the length prefix to find the tag of the first child of SEQUENCE. This function is
+        // intentionally lax and does not attempt to parse the length itself. It is only necessary
+        // to return true on PKCS#7 inputs and false on X.509 inputs. Other structures can go either
+        // way.
+        int idx = 2;
+        if (lengthByte <= 0x80) {
+            // Short-form or indefinite length.
+        } else if (lengthByte == 0x81) {
+            idx += 1;
+        } else if (lengthByte == 0x82) {
+            idx += 2;
+        } else if (lengthByte == 0x83) {
+            idx += 3;
+        } else if (lengthByte == 0x84) {
+            idx += 4;
+        } else {
+            // BoringSSL stops at 4-byte lengths. A 5-byte length would require a 4GiB input.
+            return false;
+        }
+
+        // The first element of a PKCS#7 structure is OBJECT IDENTIFIER, which has tag 6. The first
+        // element of an X.509 structure is never OBJECT IDENTIFIER.
+        return idx < header.length && header[idx] == 0x06;
+    }
+
     /**
      * The code for X509 Certificates and CRL is pretty much the same. We use
      * this abstract class to share the code between them. This makes it ugly,
@@ -90,19 +124,10 @@
                 pbis.unread(buffer, 0, len);
 
                 if (buffer[0] == '-') {
-                    if (len == PKCS7_MARKER.length && Arrays.equals(PKCS7_MARKER, buffer)) {
-                        List<? extends T> items = fromPkcs7PemInputStream(pbis);
-                        if (items.size() == 0) {
-                            return null;
-                        }
-                        items.get(0);
-                    } else {
-                        return fromX509PemInputStream(pbis);
-                    }
+                    return fromX509PemInputStream(pbis);
                 }
 
-                /* PKCS#7 bags have a byte 0x06 at position 4 in the stream. */
-                if (buffer[4] == 0x06) {
+                if (isMaybePkcs7(buffer)) {
                     List<? extends T> certs = fromPkcs7DerInputStream(pbis);
                     if (certs.size() == 0) {
                         return null;
@@ -116,6 +141,7 @@
                     try {
                         inStream.reset();
                     } catch (IOException ignored) {
+                        // If resetting the stream fails, there's not much we can do
                     }
                 }
                 throw new ParsingException(e);
@@ -158,8 +184,7 @@
                     return fromPkcs7PemInputStream(pbis);
                 }
 
-                /* PKCS#7 bags have a byte 0x06 at position 4 in the stream. */
-                if (buffer[4] == 0x06) {
+                if (isMaybePkcs7(buffer)) {
                     return fromPkcs7DerInputStream(pbis);
                 }
             } catch (Exception e) {
@@ -167,6 +192,7 @@
                     try {
                         inStream.reset();
                     } catch (IOException ignored) {
+                        // If resetting the stream fails, there's not much we can do
                     }
                 }
                 throw new ParsingException(e);
@@ -199,6 +225,7 @@
                         try {
                             inStream.reset();
                         } catch (IOException ignored) {
+                            // If resetting the stream fails, there's not much we can do
                         }
                     }
 
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLXDHKeyAgreement.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLXDHKeyAgreement.java
new file mode 100644
index 0000000..0ee62dd
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLXDHKeyAgreement.java
@@ -0,0 +1,68 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 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 java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+
+/**
+ * Elliptic Curve Diffie-Hellman key agreement backed by the OpenSSL engine.
+ * @hide This class is not part of the Android public SDK API
+ */
+@Internal
+public final class OpenSSLXDHKeyAgreement extends OpenSSLBaseDHKeyAgreement<byte[]> {
+    public OpenSSLXDHKeyAgreement() {
+    }
+
+    @Override
+    protected byte[] convertPublicKey(PublicKey key) throws InvalidKeyException {
+        if (!(key instanceof OpenSSLX25519PublicKey)) {
+            throw new InvalidKeyException("Only OpenSSLX25519PublicKey accepted");
+        }
+
+        return ((OpenSSLX25519PublicKey) key).getU();
+    }
+
+    @Override
+    protected byte[] convertPrivateKey(PrivateKey key) throws InvalidKeyException {
+        if (!(key instanceof OpenSSLX25519PrivateKey)) {
+            throw new InvalidKeyException("Only OpenSSLX25519PublicKey accepted");
+        }
+
+        return ((OpenSSLX25519PrivateKey) key).getU();
+    }
+
+    @Override
+    protected int computeKey(byte[] buffer, byte[] theirPublicKey, byte[] ourPrivateKey) throws InvalidKeyException {
+        if (!NativeCrypto.X25519(
+                buffer,
+                ourPrivateKey,
+                theirPublicKey)) {
+            throw new InvalidKeyException("Error running X25519");
+        }
+
+        return OpenSSLX25519Key.X25519_KEY_SIZE_BYTES;
+    }
+
+    @Override
+    protected int getOutputSize(byte[] key) {
+        // We only support X25519 which is 32-byte (256-bit)
+        return OpenSSLX25519Key.X25519_KEY_SIZE_BYTES;
+    }
+}
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLXDHKeyFactory.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLXDHKeyFactory.java
new file mode 100644
index 0000000..e8a815f
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLXDHKeyFactory.java
@@ -0,0 +1,217 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2013 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 java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyFactorySpi;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+/**
+ * An implementation of a {@link KeyFactorySpi} for EC keys based on BoringSSL.
+ * @hide This class is not part of the Android public SDK API
+ */
+@Internal
+public final class OpenSSLXDHKeyFactory extends KeyFactorySpi {
+
+    public OpenSSLXDHKeyFactory() {}
+
+    @Override
+    protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException {
+        if (keySpec == null) {
+            throw new InvalidKeySpecException("keySpec == null");
+        }
+
+        if (keySpec instanceof X509EncodedKeySpec) {
+            return new OpenSSLX25519PublicKey((X509EncodedKeySpec) keySpec);
+        }
+        throw new InvalidKeySpecException("Must use ECPublicKeySpec or X509EncodedKeySpec; was "
+                + keySpec.getClass().getName());
+    }
+
+    @Override
+    protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException {
+        if (keySpec == null) {
+            throw new InvalidKeySpecException("keySpec == null");
+        }
+
+        if (keySpec instanceof PKCS8EncodedKeySpec) {
+            return new OpenSSLX25519PrivateKey((PKCS8EncodedKeySpec) keySpec);
+        }
+        throw new InvalidKeySpecException("Must use ECPrivateKeySpec or PKCS8EncodedKeySpec; was "
+                + keySpec.getClass().getName());
+    }
+
+    @Override
+    protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
+            throws InvalidKeySpecException {
+        if (key == null) {
+            throw new InvalidKeySpecException("key == null");
+        }
+
+        if (keySpec == null) {
+            throw new InvalidKeySpecException("keySpec == null");
+        }
+
+        if (!"XDH".equals(key.getAlgorithm())) {
+            throw new InvalidKeySpecException("Key must be an XDH key");
+        }
+
+        Class<?> publicKeySpec = getJavaPublicKeySpec();
+        Class<?> privateKeySpec = getJavaPrivateKeySpec();
+
+        if (publicKeySpec != null && key instanceof PublicKey && publicKeySpec.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"X.509".equals(key.getFormat()) || encoded == null) {
+                throw new InvalidKeySpecException("Not a valid X.509 encoding");
+            }
+            OpenSSLX25519PublicKey publicKey = (OpenSSLX25519PublicKey) engineGeneratePublic(new X509EncodedKeySpec(encoded));
+            @SuppressWarnings("unchecked")
+            T result = (T) constructJavaPublicKeySpec(publicKeySpec, publicKey);
+            return result;
+        } else if (privateKeySpec != null && key instanceof PrivateKey && privateKeySpec.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"PKCS#8".equals(key.getFormat()) || encoded == null) {
+                throw new InvalidKeySpecException("Not a valid PKCS#8 encoding");
+            }
+            OpenSSLX25519PrivateKey privateKey = (OpenSSLX25519PrivateKey) engineGeneratePrivate(new PKCS8EncodedKeySpec(encoded));
+            @SuppressWarnings("unchecked")
+            T result = (T) constructJavaPrivateKeySpec(privateKeySpec, privateKey);
+            return result;
+        } else if (key instanceof PrivateKey && PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"PKCS#8".equals(key.getFormat())) {
+                throw new InvalidKeySpecException("Encoding type must be PKCS#8; was "
+                        + key.getFormat());
+            } else if (encoded == null) {
+                throw new InvalidKeySpecException("Key is not encodable");
+            }
+            @SuppressWarnings("unchecked") T result = (T) new PKCS8EncodedKeySpec(encoded);
+            return result;
+        } else if (key instanceof PublicKey && X509EncodedKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"X.509".equals(key.getFormat())) {
+                throw new InvalidKeySpecException("Encoding type must be X.509; was "
+                        + key.getFormat());
+            } else if (encoded == null) {
+                throw new InvalidKeySpecException("Key is not encodable");
+            }
+            @SuppressWarnings("unchecked") T result = (T) new X509EncodedKeySpec(encoded);
+            return result;
+        }
+
+        throw new InvalidKeySpecException("Unsupported key type and key spec combination; key="
+                + key.getClass().getName() + ", keySpec=" + keySpec.getName());
+    }
+
+    @Override
+    protected Key engineTranslateKey(Key key) throws InvalidKeyException {
+        if (key == null) {
+            throw new InvalidKeyException("key == null");
+        }
+        if ((key instanceof OpenSSLX25519PublicKey) || (key instanceof OpenSSLX25519PrivateKey)) {
+            return key;
+        } else if ((key instanceof PrivateKey) && "PKCS#8".equals(key.getFormat())) {
+            byte[] encoded = key.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Key does not support encoding");
+            }
+            try {
+                return engineGeneratePrivate(new PKCS8EncodedKeySpec(encoded));
+            } catch (InvalidKeySpecException e) {
+                throw new InvalidKeyException(e);
+            }
+        } else if ((key instanceof PublicKey) && "X.509".equals(key.getFormat())) {
+            byte[] encoded = key.getEncoded();
+            if (encoded == null) {
+                throw new InvalidKeyException("Key does not support encoding");
+            }
+            try {
+                return engineGeneratePublic(new X509EncodedKeySpec(encoded));
+            } catch (InvalidKeySpecException e) {
+                throw new InvalidKeyException(e);
+            }
+        } else {
+            throw new InvalidKeyException("Key must be EC public or private key; was "
+                    + key.getClass().getName());
+        }
+    }
+
+    private static Class<?> getJavaPrivateKeySpec() {
+        try {
+            return Class.forName("java.security.spec.XECPrivateKeySpec");
+        } catch (ClassNotFoundException ignored) {
+            return null;
+        }
+    }
+
+    private static Class<?> getJavaPublicKeySpec() {
+        try {
+            return Class.forName("java.security.spec.XECPublicKeySpec");
+        } catch (ClassNotFoundException ignored) {
+            return null;
+        }
+    }
+
+    private KeySpec constructJavaPrivateKeySpec(Class<?> privateKeySpec, OpenSSLX25519PrivateKey privateKey) throws InvalidKeySpecException {
+        if (privateKeySpec == null) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPrivateKeySpec");
+        }
+
+        try {
+            Constructor<?> c = privateKeySpec.getConstructor(AlgorithmParameterSpec.class, byte[].class);
+            @SuppressWarnings("unchecked")
+            KeySpec result = (KeySpec) c.newInstance(new OpenSSLXECParameterSpec(OpenSSLXECParameterSpec.X25519), privateKey.getU());
+            return result;
+        } catch (NoSuchMethodException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPrivateKeySpec", e);
+        } catch (InstantiationException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPrivateKeySpec", e);
+        } catch (IllegalAccessException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPrivateKeySpec", e);
+        } catch (InvocationTargetException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPrivateKeySpec", e);
+        }
+    }
+
+    private KeySpec constructJavaPublicKeySpec(Class<?> publicKeySpec, OpenSSLX25519PublicKey publicKey) throws InvalidKeySpecException {
+        try {
+            Constructor<?> c = publicKeySpec.getConstructor(AlgorithmParameterSpec.class, BigInteger.class);
+            @SuppressWarnings("unchecked")
+            KeySpec result = (KeySpec) c.newInstance(new OpenSSLXECParameterSpec(OpenSSLXECParameterSpec.X25519), new BigInteger(1, publicKey.getU()));
+            return result;
+        } catch (NoSuchMethodException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPublicKeySpec", e);
+        } catch (InstantiationException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPublicKeySpec", e);
+        } catch (IllegalAccessException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPublicKeySpec", e);
+        } catch (InvocationTargetException e) {
+            throw new InvalidKeySpecException("Could not find java.security.spec.XECPublicKeySpec", e);
+        }
+    }
+}
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLXDHKeyPairGenerator.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLXDHKeyPairGenerator.java
new file mode 100644
index 0000000..1672a78
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLXDHKeyPairGenerator.java
@@ -0,0 +1,59 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2012 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 java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * An implementation of {@link KeyPairGenerator} for XDH keys which uses BoringSSL to perform all the
+ * operations. This only supports X25519 keys.
+ * @hide This class is not part of the Android public SDK API
+ */
+@Internal
+public final class OpenSSLXDHKeyPairGenerator extends KeyPairGenerator {
+    private static final String ALGORITHM = "XDH";
+
+    public OpenSSLXDHKeyPairGenerator() {
+        super(ALGORITHM);
+    }
+
+    @Override
+    public KeyPair generateKeyPair() {
+        byte[] publicKeyBytes = new byte[OpenSSLX25519Key.X25519_KEY_SIZE_BYTES];
+        byte[] privateKeyBytes = new byte[OpenSSLX25519Key.X25519_KEY_SIZE_BYTES];
+
+        NativeCrypto.X25519_keypair(publicKeyBytes, privateKeyBytes);
+
+        return new KeyPair(new OpenSSLX25519PublicKey(publicKeyBytes), new OpenSSLX25519PrivateKey(privateKeyBytes));
+    }
+
+    @Override
+    public void initialize(int keysize, SecureRandom random) {
+    }
+
+    @Override
+    public void initialize(AlgorithmParameterSpec param, SecureRandom random)
+            throws InvalidAlgorithmParameterException {
+        throw new InvalidAlgorithmParameterException(
+                "No AlgorithmParameterSpec classes are supported");
+    }
+}
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLXECParameterSpec.java b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLXECParameterSpec.java
new file mode 100644
index 0000000..d8f3746
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/OpenSSLXECParameterSpec.java
@@ -0,0 +1,22 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.org.conscrypt;
+
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * Parameter markers to assist in future compatibility should other XEC curves be supported.
+ */
+@Internal
+class OpenSSLXECParameterSpec implements AlgorithmParameterSpec {
+    public static final String X25519 = "1.3.101.110";
+
+    private final String oid;
+
+    public OpenSSLXECParameterSpec(String oid) {
+        this.oid = oid;
+    }
+
+    public String getOid() {
+        return oid;
+    }
+}
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/PSKKeyManager.java b/repackaged/common/src/main/java/com/android/org/conscrypt/PSKKeyManager.java
index 6bc09ad..0243cb6 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/PSKKeyManager.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/PSKKeyManager.java
@@ -82,7 +82,7 @@
  * The following example illustrates how to create an {@code SSLContext} which enables the use of
  * TLS-PSK in {@code SSLSocket}, {@code SSLServerSocket} and {@code SSLEngine} instances obtained
  * from it.
- * <pre> {@code
+ * <pre>
  * PSKKeyManager myPskKeyManager = ...;
  *
  * SSLContext sslContext = SSLContext.getInstance("TLS");
@@ -93,7 +93,7 @@
  *         );
  *
  * SSLSocket sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(...);
- * }</pre>
+ * </pre>
  *
  * @deprecated This abstraction is deprecated because it does not work with TLS 1.3.
  * @hide This class is not part of the Android public SDK API
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/SSLClientSessionCache.java b/repackaged/common/src/main/java/com/android/org/conscrypt/SSLClientSessionCache.java
index c63861d..0460f5b 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/SSLClientSessionCache.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/SSLClientSessionCache.java
@@ -31,7 +31,7 @@
  * the {@code SSLClientSessionCache} implementation.
  * @hide This class is not part of the Android public SDK API
  */
-@libcore.api.CorePlatformApi
+@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
 @Internal
 public interface SSLClientSessionCache {
     /**
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/SSLNullSession.java b/repackaged/common/src/main/java/com/android/org/conscrypt/SSLNullSession.java
index f2fc5d1..fd1311a 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/SSLNullSession.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/SSLNullSession.java
@@ -117,6 +117,7 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation") // Public API
     public javax.security.cert.X509Certificate[] getPeerCertificateChain()
             throws SSLPeerUnverifiedException {
         throw new SSLPeerUnverifiedException("No peer certificate");
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/SSLParametersImpl.java b/repackaged/common/src/main/java/com/android/org/conscrypt/SSLParametersImpl.java
index 946f71e..af73a43 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/SSLParametersImpl.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/SSLParametersImpl.java
@@ -241,7 +241,7 @@
     /**
      * @return X.509 trust manager or {@code null} for none.
      */
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     X509TrustManager getX509TrustManager() {
         return x509TrustManager;
     }
@@ -280,7 +280,7 @@
      * Sets the list of available protocols for use in SSL connection.
      * @throws IllegalArgumentException if {@code protocols == null}
      */
-    @android.compat.annotation.UnsupportedAppUsage
+    @android.compat.annotation.UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     void setEnabledProtocols(String[] protocols) {
         if (protocols == null) {
             throw new IllegalArgumentException("protocols == null");
@@ -581,7 +581,9 @@
             } else if (km != null) {
                 try {
                     return DuckTypedPSKKeyManager.getInstance(km);
-                } catch (NoSuchMethodException ignored) {}
+                } catch (NoSuchMethodException ignored) {
+                    // This PSKKeyManager doesn't support the required methods, go to the next
+                }
             }
         }
         return null;
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/SSLUtils.java b/repackaged/common/src/main/java/com/android/org/conscrypt/SSLUtils.java
index 8b2123c..029ee42 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/SSLUtils.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/SSLUtils.java
@@ -276,7 +276,7 @@
      */
     static Set<String> getSupportedClientKeyTypes(byte[] clientCertificateTypes,
             int[] signatureAlgs) {
-        Set<String> fromClientCerts = new HashSet<String>(clientCertificateTypes.length);
+        Set<String> fromClientCerts = new HashSet<>(clientCertificateTypes.length);
         for (byte keyTypeCode : clientCertificateTypes) {
             String keyType = SSLUtils.getClientKeyType(keyTypeCode);
             if (keyType == null) {
@@ -286,7 +286,7 @@
             fromClientCerts.add(keyType);
         }
         // Signature algorithms are listed in preference order
-        Set<String> fromSigAlgs = new LinkedHashSet<String>(signatureAlgs.length);
+        Set<String> fromSigAlgs = new LinkedHashSet<>(signatureAlgs.length);
         for (int signatureAlg : signatureAlgs) {
             String keyType = SSLUtils.getClientKeyTypeFromSignatureAlg(signatureAlg);
             if (keyType == null) {
@@ -320,6 +320,7 @@
     /**
      * Converts the peer certificates into a cert chain.
      */
+    @SuppressWarnings("deprecation") // Used in public Conscrypt APIs
     static javax.security.cert.X509Certificate[] toCertificateChain(X509Certificate[] certificates)
             throws SSLPeerUnverifiedException {
         try {
@@ -333,11 +334,11 @@
             return chain;
         } catch (CertificateEncodingException e) {
             SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
-            exception.initCause(exception);
+            exception.initCause(e);
             throw exception;
         } catch (CertificateException e) {
             SSLPeerUnverifiedException exception = new SSLPeerUnverifiedException(e.getMessage());
-            exception.initCause(exception);
+            exception.initCause(e);
             throw exception;
         }
     }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/SessionSnapshot.java b/repackaged/common/src/main/java/com/android/org/conscrypt/SessionSnapshot.java
index ef78b27..5950080 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/SessionSnapshot.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/SessionSnapshot.java
@@ -65,7 +65,7 @@
 
     @Override
     public List<byte[]> getStatusResponses() {
-        List<byte[]> ret = new ArrayList<byte[]>(statusResponses.size());
+        List<byte[]> ret = new ArrayList<>(statusResponses.size());
         for (byte[] resp : statusResponses) {
             ret.add(resp.clone());
         }
@@ -142,8 +142,13 @@
     }
 
     @Override
+    @SuppressWarnings("deprecation") // Public API
     public javax.security.cert.X509Certificate[] getPeerCertificateChain()
-        throws SSLPeerUnverifiedException {
+            throws SSLPeerUnverifiedException {
+        if (!Platform.isJavaxCertificateSupported()) {
+            throw new UnsupportedOperationException("Use getPeerCertificates() instead");
+        }
+
         throw new SSLPeerUnverifiedException("No peer certificates");
     }
 
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/ShortBufferWithoutStackTraceException.java b/repackaged/common/src/main/java/com/android/org/conscrypt/ShortBufferWithoutStackTraceException.java
index aaed92b..d01ca64 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/ShortBufferWithoutStackTraceException.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/ShortBufferWithoutStackTraceException.java
@@ -36,7 +36,8 @@
         super(msg);
     }
 
-    @Override public Throwable fillInStackTrace() {
+    @Override
+    public synchronized Throwable fillInStackTrace() {
         return this;
     }
 }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/TrustManagerImpl.java b/repackaged/common/src/main/java/com/android/org/conscrypt/TrustManagerImpl.java
index 4e2115a..79945c4 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/TrustManagerImpl.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/TrustManagerImpl.java
@@ -84,10 +84,9 @@
  * @see javax.net.ssl.X509ExtendedTrustManager
  * @hide This class is not part of the Android public SDK API
  */
-@libcore.api.CorePlatformApi
+@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
 @Internal
 public final class TrustManagerImpl extends X509ExtendedTrustManager {
-
     private static final Logger logger = Logger.getLogger(TrustManagerImpl.class.getName());
 
     /**
@@ -138,7 +137,7 @@
 
     private final Exception err;
     private final CertificateFactory factory;
-    private final CertBlacklist blacklist;
+    private final CertBlocklist blocklist;
     private CTVerifier ctVerifier;
     private CTPolicy ctPolicy;
 
@@ -149,12 +148,10 @@
 
     /**
      * Creates X509TrustManager based on a keystore
-     *
-     * @param keyStore
      */
     @android.compat.annotation
             .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi
+            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
             public TrustManagerImpl(KeyStore keyStore) {
         this(keyStore, null);
     }
@@ -163,24 +160,23 @@
         this(keyStore, manager, null);
     }
 
-    @libcore.api.CorePlatformApi
-    public TrustManagerImpl(KeyStore keyStore, CertPinManager manager,
-            ConscryptCertStore certStore) {
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    public TrustManagerImpl(
+            KeyStore keyStore, CertPinManager manager, ConscryptCertStore certStore) {
         this(keyStore, manager, certStore, null);
     }
 
-    public TrustManagerImpl(KeyStore keyStore, CertPinManager manager,
-            ConscryptCertStore certStore,
-                            CertBlacklist blacklist) {
-        this(keyStore, manager, certStore, blacklist, null, null, null);
+    public TrustManagerImpl(KeyStore keyStore, CertPinManager manager, ConscryptCertStore certStore,
+            CertBlocklist blocklist) {
+        this(keyStore, manager, certStore, blocklist, null, null, null);
     }
 
     /**
      * For testing only.
      */
-    public TrustManagerImpl(KeyStore keyStore, CertPinManager manager,
-            ConscryptCertStore certStore, CertBlacklist blacklist, CTLogStore ctLogStore,
-            CTVerifier ctVerifier, CTPolicy ctPolicy) {
+    public TrustManagerImpl(KeyStore keyStore, CertPinManager manager, ConscryptCertStore certStore,
+            CertBlocklist blocklist, CTLogStore ctLogStore, CTVerifier ctVerifier,
+            CTPolicy ctPolicy) {
         CertPathValidator validatorLocal = null;
         CertificateFactory factoryLocal = null;
         KeyStore rootKeyStoreLocal = null;
@@ -212,8 +208,8 @@
             errLocal = e;
         }
 
-        if (blacklist == null) {
-            blacklist = Platform.newDefaultBlacklist();
+        if (blocklist == null) {
+            blocklist = Platform.newDefaultBlocklist();
         }
         if (ctLogStore == null) {
             ctLogStore = Platform.newDefaultLogStore();
@@ -232,11 +228,12 @@
         this.intermediateIndex = new TrustedCertificateIndex();
         this.acceptedIssuers = acceptedIssuersLocal;
         this.err = errLocal;
-        this.blacklist = blacklist;
+        this.blocklist = blocklist;
         this.ctVerifier = new CTVerifier(ctLogStore);
         this.ctPolicy = ctPolicy;
     }
 
+    @SuppressWarnings("JdkObsolete") // KeyStore#aliases is the only API available
     private static X509Certificate[] acceptedIssuers(KeyStore ks) {
         try {
             // Note that unlike the PKIXParameters code to create a Set of
@@ -270,7 +267,7 @@
         return trustAnchors;
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     @Override
     public void checkClientTrusted(X509Certificate[] chain, String authType)
             throws CertificateException {
@@ -295,7 +292,7 @@
         return session;
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     @Override
     public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
             throws CertificateException {
@@ -309,7 +306,7 @@
         checkTrusted(chain, authType, session, parameters, true /* client auth */);
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     @Override
     public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
             throws CertificateException {
@@ -331,7 +328,7 @@
      */
     @android.compat.annotation
             .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi
+            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
             public List<X509Certificate> checkServerTrusted(X509Certificate[] chain,
                     String authType, String hostname) throws CertificateException {
         return checkTrusted(chain, null /* ocspData */, null /* tlsSctData */, authType, hostname,
@@ -343,9 +340,9 @@
      *
      * Throws {@link CertificateException} when no trusted chain can be found from {@code certs}.
      */
-    @libcore.api.CorePlatformApi
-    public List<X509Certificate> getTrustedChainForServer(X509Certificate[] certs,
-            String authType, Socket socket) throws CertificateException {
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    public List<X509Certificate> getTrustedChainForServer(
+            X509Certificate[] certs, String authType, Socket socket) throws CertificateException {
         SSLSession session = null;
         SSLParameters parameters = null;
         if (socket instanceof SSLSocket) {
@@ -361,9 +358,9 @@
      *
      * Throws {@link CertificateException} when no trusted chain can be found from {@code certs}.
      */
-    @libcore.api.CorePlatformApi
-    public List<X509Certificate> getTrustedChainForServer(X509Certificate[] certs,
-            String authType, SSLEngine engine) throws CertificateException {
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    public List<X509Certificate> getTrustedChainForServer(X509Certificate[] certs, String authType,
+            SSLEngine engine) throws CertificateException {
         SSLSession session = engine.getHandshakeSession();
         if (session == null) {
             throw new CertificateException("Not in handshake; no session available");
@@ -395,7 +392,7 @@
         return checkTrusted(chain, authType, session, null, false /* client auth */);
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public void handleTrustStorageUpdate() {
         if (acceptedIssuers == null) {
             trustedCertificateIndex.reset();
@@ -420,7 +417,7 @@
             String identificationAlgorithm = parameters.getEndpointIdentificationAlgorithm();
             if ("HTTPS".equalsIgnoreCase(identificationAlgorithm)) {
                 ConscryptHostnameVerifier verifier = getHttpsVerifier();
-                if (!verifier.verify(hostname, session)) {
+                if (!verifier.verify(certs, hostname, session)) {
                     throw new CertificateException("No subjectAltNames on the certificate match");
                 }
             }
@@ -429,7 +426,7 @@
     }
 
     @SuppressWarnings("unchecked")
-    private byte[] getOcspDataFromSession(SSLSession session) {
+    private static byte[] getOcspDataFromSession(SSLSession session) {
         List<byte[]> ocspResponses = null;
         if (session instanceof ConscryptSession) {
             ConscryptSession opensslSession = (ConscryptSession) session;
@@ -443,10 +440,9 @@
                 if (rawResponses instanceof List) {
                     ocspResponses = (List<byte[]>) rawResponses;
                 }
-            } catch (NoSuchMethodException ignored) {
-            } catch (SecurityException ignored) {
-            } catch (IllegalAccessException ignored) {
-            } catch (IllegalArgumentException ignored) {
+            } catch (NoSuchMethodException | SecurityException | IllegalAccessException
+                    | IllegalArgumentException ignored) {
+                // Method not available, fall through and return null
             } catch (InvocationTargetException e) {
                 throw new RuntimeException(e.getCause());
             }
@@ -473,10 +469,9 @@
             if (rawData instanceof byte[]) {
                 data = (byte[]) rawData;
             }
-        } catch (NoSuchMethodException ignored) {
-        } catch (SecurityException ignored) {
-        } catch (IllegalAccessException ignored) {
-        } catch (IllegalArgumentException ignored) {
+        } catch (NoSuchMethodException | SecurityException | IllegalAccessException
+                | IllegalArgumentException ignored) {
+            // Method not available, fall through and return null
         } catch (InvocationTargetException e) {
             throw new RuntimeException(e.getCause());
         }
@@ -546,8 +541,8 @@
             current = trustAnchorChain.get(trustAnchorChain.size() - 1).getTrustedCert();
         }
 
-        // Check that the certificate isn't blacklisted.
-        checkBlacklist(current);
+        // Check that the certificate isn't blocklisted.
+        checkBlocklist(current);
 
         // 1. If the current certificate in the chain is self-signed verify the chain as is.
         if (current.getIssuerDN().equals(current.getSubjectDN())) {
@@ -687,9 +682,9 @@
             if (pinManager != null) {
                 pinManager.checkChainPinning(host, wholeChain);
             }
-            // Check whole chain against the blacklist
+            // Check whole chain against the blocklist
             for (X509Certificate cert : wholeChain) {
-                checkBlacklist(cert);
+                checkBlocklist(cert);
             }
 
             // Check CT (if required).
@@ -736,9 +731,9 @@
         }
     }
 
-    private void checkBlacklist(X509Certificate cert) throws CertificateException {
-        if (blacklist != null && blacklist.isPublicKeyBlackListed(cert.getPublicKey())) {
-            throw new CertificateException("Certificate blacklisted by public key: " + cert);
+    private void checkBlocklist(X509Certificate cert) throws CertificateException {
+        if (blocklist != null && blocklist.isPublicKeyBlockListed(cert.getPublicKey())) {
+            throw new CertificateException("Certificate blocklisted by public key: " + cert);
         }
     }
 
@@ -958,7 +953,7 @@
             return trustAnchor;
         }
         if (trustedCertificateStore == null) {
-            // not trusted and no TrustedCertificateStore to check
+            // not trusted and no TrustedCertificateStore to check.
             return null;
         }
         // probe KeyStore for a cert. AndroidCAStore stores its
@@ -1018,24 +1013,11 @@
         return hostnameVerifier;
     }
 
-    private enum GlobalHostnameVerifierAdapter implements ConscryptHostnameVerifier {
-        INSTANCE;
-
-        @Override
-        public boolean verify(String hostname, SSLSession session) {
-            return HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session);
-        }
-    }
-
     private ConscryptHostnameVerifier getHttpsVerifier() {
         if (hostnameVerifier != null) {
             return hostnameVerifier;
         }
-        ConscryptHostnameVerifier defaultVerifier = getDefaultHostnameVerifier();
-        if (defaultVerifier != null) {
-            return defaultVerifier;
-        }
-        return GlobalHostnameVerifierAdapter.INSTANCE;
+        return Platform.getDefaultHostnameVerifier();
     }
 
     public void setCTEnabledOverride(boolean enabled) {
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/TrustedCertificateIndex.java b/repackaged/common/src/main/java/com/android/org/conscrypt/TrustedCertificateIndex.java
index 5df6c41..c6e199e 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/TrustedCertificateIndex.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/TrustedCertificateIndex.java
@@ -36,14 +36,13 @@
  * time instead of O(N).
  * @hide This class is not part of the Android public SDK API
  */
-@libcore.api.CorePlatformApi
+@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
 @Internal
 public final class TrustedCertificateIndex {
-
     private final Map<X500Principal, List<TrustAnchor>> subjectToTrustAnchors
             = new HashMap<X500Principal, List<TrustAnchor>>();
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public TrustedCertificateIndex() {}
 
     public TrustedCertificateIndex(Set<TrustAnchor> anchors) {
@@ -56,7 +55,7 @@
         }
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public TrustAnchor index(X509Certificate cert) {
         TrustAnchor anchor = new TrustAnchor(cert, null);
         index(anchor);
@@ -104,7 +103,7 @@
         }
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public TrustAnchor findByIssuerAndSignature(X509Certificate cert) {
         X500Principal issuer = cert.getIssuerX500Principal();
         synchronized (subjectToTrustAnchors) {
@@ -125,13 +124,14 @@
                     cert.verify(publicKey);
                     return anchor;
                 } catch (Exception ignored) {
+                    // Ignored
                 }
             }
         }
         return null;
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public TrustAnchor findBySubjectAndPublicKey(X509Certificate cert) {
         X500Principal subject = cert.getSubjectX500Principal();
         synchronized (subjectToTrustAnchors) {
@@ -178,7 +178,7 @@
         return null;
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public Set<TrustAnchor> findAllByIssuerAndSignature(X509Certificate cert) {
         X500Principal issuer = cert.getIssuerX500Principal();
         synchronized (subjectToTrustAnchors) {
@@ -203,10 +203,10 @@
                     cert.verify(publicKey);
                     result.add(anchor);
                 } catch (Exception ignored) {
+                    // Ignored
                 }
             }
             return result;
         }
     }
-
 }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/ct/CTVerifier.java b/repackaged/common/src/main/java/com/android/org/conscrypt/ct/CTVerifier.java
index 6851354..3d6945f 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/ct/CTVerifier.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/ct/CTVerifier.java
@@ -167,7 +167,7 @@
      * @param origin used to create the SignedCertificateTimestamp instances.
      */
     @SuppressWarnings("MixedMutabilityReturnType")
-    private List<SignedCertificateTimestamp> getSCTsFromSCTList(
+    private static List<SignedCertificateTimestamp> getSCTsFromSCTList(
             byte[] data, SignedCertificateTimestamp.Origin origin) {
         if (data == null) {
             return Collections.emptyList();
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/ct/CertificateEntry.java b/repackaged/common/src/main/java/com/android/org/conscrypt/ct/CertificateEntry.java
index 69c9367..84dabe7 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/ct/CertificateEntry.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/ct/CertificateEntry.java
@@ -77,6 +77,8 @@
     }
 
     /**
+     * Creates a CertificateEntry with type PRECERT_ENTRY
+     *
      * @throws IllegalArgumentException if issuerKeyHash isn't 32 bytes
      */
     public static CertificateEntry createForPrecertificate(byte[] tbsCertificate, byte[] issuerKeyHash) {
@@ -90,8 +92,7 @@
                 throw new CertificateException("Certificate does not contain embedded signed timestamps");
             }
 
-            OpenSSLX509Certificate preCert = leaf.withDeletedExtension(CTConstants.X509_SCT_LIST_OID);
-            byte[] tbs = preCert.getTBSCertificate();
+            byte[] tbs = leaf.getTBSCertificateWithoutExtension(CTConstants.X509_SCT_LIST_OID);
 
             byte[] issuerKey = issuer.getPublicKey().getEncoded();
             MessageDigest md = MessageDigest.getInstance("SHA-256");
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/io/IoUtils.java b/repackaged/common/src/main/java/com/android/org/conscrypt/io/IoUtils.java
index 84c3926..e7f9830 100644
--- a/repackaged/common/src/main/java/com/android/org/conscrypt/io/IoUtils.java
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/io/IoUtils.java
@@ -39,6 +39,7 @@
             } catch (RuntimeException rethrown) {
                 throw rethrown;
             } catch (Exception ignored) {
+                // Ignored
             }
         }
     }
@@ -51,6 +52,7 @@
             try {
                 socket.close();
             } catch (Exception ignored) {
+                // Ignored
             }
         }
     }
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/CipherSuite.java b/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/CipherSuite.java
new file mode 100644
index 0000000..1ab9131
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/CipherSuite.java
@@ -0,0 +1,83 @@
+/* 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.metrics;
+
+import com.android.org.conscrypt.Internal;
+
+/**
+ * Cipher suites to metric mapping for metrics instrumentation.
+ *
+ * Must be in sync with frameworks/base/cmds/statsd/src/atoms.proto
+ *
+ * Ids are based on IANA's database of SSL/TLS cipher suites
+ * @see https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4
+ * @hide This class is not part of the Android public SDK API
+ */
+@Internal
+public enum CipherSuite {
+    UNKNOWN_CIPHER_SUITE(0x0000),
+
+    // Supported but not enabled
+    TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(0xC00A),
+    TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(0xC014),
+    TLS_RSA_WITH_AES_256_CBC_SHA(0x0035),
+    TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(0xC009),
+    TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xC013),
+    TLS_RSA_WITH_AES_128_CBC_SHA(0x002F),
+    TLS_RSA_WITH_3DES_EDE_CBC_SHA(0x000A),
+
+    // TLSv1.2 cipher suites
+    TLS_RSA_WITH_AES_128_GCM_SHA256(0x009C),
+    TLS_RSA_WITH_AES_256_GCM_SHA384(0x009D),
+    TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xC02F),
+    TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xC030),
+    TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xC02B),
+    TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xC02C),
+    TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(0xCCA9),
+    TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256(0xCCA8),
+
+    // Pre-Shared Key (PSK) cipher suites
+    TLS_PSK_WITH_AES_128_CBC_SHA(0x008C),
+    TLS_PSK_WITH_AES_256_CBC_SHA(0x008D),
+    TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA(0xC035),
+    TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA(0xC036),
+    TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256(0xCCAC),
+
+    // TLS 1.3 cipher suites
+    TLS_AES_128_GCM_SHA256(0x1301),
+    TLS_AES_256_GCM_SHA384(0x1302),
+    TLS_CHACHA20_POLY1305_SHA256(0x1303),
+    ;
+
+    final short id;
+
+    public int getId() {
+        return this.id;
+    }
+
+    public static CipherSuite forName(String name) {
+        try {
+            return CipherSuite.valueOf(name);
+        } catch (IllegalArgumentException e) {
+            return CipherSuite.UNKNOWN_CIPHER_SUITE;
+        }
+    }
+
+    private CipherSuite(int id) {
+        this.id = (short) id;
+    }
+}
\ No newline at end of file
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/ConscryptStatsLog.java b/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/ConscryptStatsLog.java
new file mode 100644
index 0000000..498ef08
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/ConscryptStatsLog.java
@@ -0,0 +1,47 @@
+/* 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.metrics;
+
+import com.android.org.conscrypt.Internal;
+
+/**
+ * Reimplement with reflection calls the logging class,
+ * generated by frameworks/statsd.
+ *
+ * In case atom is changed, generate new wrapper with stats-log-api-gen
+ * tool as shown below and add corresponding methods to ReflexiveStatsEvent's
+ * newEvent() method.
+ *
+ * $ stats-log-api-gen \
+ *   --java "common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java" \
+ *   --module conscrypt \
+ *   --javaPackage org.conscrypt.metrics \
+ *   --javaClass ConscryptStatsLog
+ * @hide This class is not part of the Android public SDK API
+ **/
+@Internal
+public final class ConscryptStatsLog {
+    public static final int TLS_HANDSHAKE_REPORTED = 317;
+
+    public static void write(
+            int code, boolean success, int protocol, int cipherSuite, int duration) {
+        ReflexiveStatsEvent event =
+                ReflexiveStatsEvent.buildEvent(code, success, protocol, cipherSuite, duration);
+
+        ReflexiveStatsLog.write(event);
+    }
+}
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/OptionalMethod.java b/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/OptionalMethod.java
new file mode 100644
index 0000000..eec769c
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/OptionalMethod.java
@@ -0,0 +1,86 @@
+/* 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.metrics;
+
+import com.android.org.conscrypt.Internal;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Helper class to handle reflexive loading and invocation of methods which may be absent.
+ *
+ * @hide This class is not part of the Android public SDK API
+ * @hide This class is not part of the Android public SDK API
+ */
+@Internal
+public final class OptionalMethod {
+    private final Method cachedMethod;
+
+    /**
+     * Instantiates a new OptionalMethod.
+     * <p>Does not throw any exceptions if the class or method can't be loaded, or if any parameter
+     * classes are {@code null} and instead behaves as a no-op, always returning {@code null}.
+     *
+     * @param clazz the Class to search for methods on
+     * @param methodName the name of the {@code Method} on {@code clazz}
+     * @param methodParams list of {@code Classes} of the {@code Method's} parameters
+     *
+     * @throws NullPointerException if the method name is {@code null}
+     */
+    public OptionalMethod(Class<?> clazz, String methodName, Class<?>... methodParams) {
+        this.cachedMethod = initializeMethod(clazz, methodName, methodParams);
+    }
+
+    private static Method initializeMethod(
+            Class<?> clazz, String methodName, Class<?>... methodParams) {
+        try {
+            for (Class<?> paramClass : methodParams) {
+                if (paramClass == null) {
+                    return null;
+                }
+            }
+            if (clazz != null) {
+                return clazz.getMethod(checkNotNull(methodName), methodParams);
+            }
+        } catch (NoSuchMethodException ignored) {
+            // Ignored
+        }
+        return null;
+    }
+
+    public Object invoke(Object target, Object... args) {
+        // no-op if failed to load method in constructor
+        if (cachedMethod == null) {
+            return null;
+        }
+        try {
+            return cachedMethod.invoke(target, args);
+        } catch (IllegalAccessException ignored) {
+            // Ignored
+        } catch (InvocationTargetException ignored) {
+            // Ignored
+        }
+        return null;
+    }
+
+    private static <T> T checkNotNull(T reference) {
+        if (reference == null) {
+            throw new NullPointerException();
+        }
+        return reference;
+    }
+}
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/Protocol.java b/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/Protocol.java
new file mode 100644
index 0000000..3165dc1
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/Protocol.java
@@ -0,0 +1,63 @@
+/* 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.metrics;
+
+import com.android.org.conscrypt.Internal;
+
+/**
+ * Protocols to metric mapping for metrics instrumentation.
+ *
+ * Must be in sync with frameworks/base/cmds/statsd/src/atoms.proto
+ * @hide This class is not part of the Android public SDK API
+ */
+@Internal
+public enum Protocol {
+    UNKNOWN_PROTO(0),
+    SSLv3(1),
+    TLSv1(2),
+    TLSv1_1(3),
+    TLSv1_2(4),
+    TLSv1_3(5),
+    ;
+
+    final byte id;
+
+    public int getId() {
+        return this.id;
+    }
+
+    public static Protocol forName(String name) {
+        switch (name) {
+            case "SSLv3":
+                return SSLv3;
+            case "TLSv1":
+                return TLSv1;
+            case "TLSv1.1":
+                return TLSv1_1;
+            case "TLSv1.2":
+                return TLSv1_2;
+            case "TLSv1.3":
+                return TLSv1_3;
+            default:
+                return UNKNOWN_PROTO;
+        }
+    }
+
+    private Protocol(int id) {
+        this.id = (byte) id;
+    }
+}
\ No newline at end of file
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/ReflexiveStatsEvent.java b/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/ReflexiveStatsEvent.java
new file mode 100644
index 0000000..db447ed
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/ReflexiveStatsEvent.java
@@ -0,0 +1,127 @@
+/* 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.metrics;
+
+import com.android.org.conscrypt.Internal;
+
+/**
+ * Reflection wrapper around android.util.StatsEvent.
+ * @hide This class is not part of the Android public SDK API
+ */
+@Internal
+public class ReflexiveStatsEvent {
+    private static final OptionalMethod newBuilder;
+    private static final Class<?> c_statsEvent;
+
+    static {
+        c_statsEvent = initStatsEventClass();
+        newBuilder = new OptionalMethod(c_statsEvent, "newBuilder");
+    }
+
+    private static Class<?> initStatsEventClass() {
+        try {
+            return Class.forName("android.util.StatsEvent");
+        } catch (ClassNotFoundException ignored) {
+            return null;
+        }
+    }
+
+    private Object statsEvent;
+
+    private ReflexiveStatsEvent(Object statsEvent) {
+        this.statsEvent = statsEvent;
+    }
+
+    public Object getStatsEvent() {
+        return statsEvent;
+    }
+
+    public static ReflexiveStatsEvent.Builder newBuilder() {
+        return new ReflexiveStatsEvent.Builder();
+    }
+
+    public static ReflexiveStatsEvent buildEvent(
+            int atomId, boolean success, int protocol, int cipherSuite, int duration) {
+        ReflexiveStatsEvent.Builder builder = ReflexiveStatsEvent.newBuilder();
+        builder.setAtomId(atomId);
+        builder.writeBoolean(success);
+        builder.writeInt(protocol);
+        builder.writeInt(cipherSuite);
+        builder.writeInt(duration);
+        builder.usePooledBuffer();
+        return builder.build();
+    }
+
+    /**
+     * @hide This class is not part of the Android public SDK API
+     */
+    public static final class Builder {
+        private static final Class<?> c_statsEvent_Builder;
+        private static final OptionalMethod setAtomId;
+        private static final OptionalMethod writeBoolean;
+        private static final OptionalMethod writeInt;
+        private static final OptionalMethod build;
+        private static final OptionalMethod usePooledBuffer;
+
+        static {
+            c_statsEvent_Builder = initStatsEventBuilderClass();
+            setAtomId = new OptionalMethod(c_statsEvent_Builder, "setAtomId", int.class);
+            writeBoolean = new OptionalMethod(c_statsEvent_Builder, "writeBoolean", boolean.class);
+            writeInt = new OptionalMethod(c_statsEvent_Builder, "writeInt", int.class);
+            build = new OptionalMethod(c_statsEvent_Builder, "build");
+            usePooledBuffer = new OptionalMethod(c_statsEvent_Builder, "usePooledBuffer");
+        }
+
+        private static Class<?> initStatsEventBuilderClass() {
+            try {
+                return Class.forName("android.util.StatsEvent$Builder");
+            } catch (ClassNotFoundException ignored) {
+                return null;
+            }
+        }
+
+        private Object builder;
+
+        private Builder() {
+            this.builder = newBuilder.invoke(null);
+        }
+
+        public Builder setAtomId(final int atomId) {
+            setAtomId.invoke(this.builder, atomId);
+            return this;
+        }
+
+        public Builder writeBoolean(final boolean value) {
+            writeBoolean.invoke(this.builder, value);
+            return this;
+        }
+
+        public Builder writeInt(final int value) {
+            writeInt.invoke(this.builder, value);
+            return this;
+        }
+
+        public void usePooledBuffer() {
+            usePooledBuffer.invoke(this.builder);
+        }
+
+        public ReflexiveStatsEvent build() {
+            Object statsEvent = build.invoke(this.builder);
+            return new ReflexiveStatsEvent(statsEvent);
+        }
+    }
+}
\ No newline at end of file
diff --git a/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/ReflexiveStatsLog.java b/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/ReflexiveStatsLog.java
new file mode 100644
index 0000000..6fcce54
--- /dev/null
+++ b/repackaged/common/src/main/java/com/android/org/conscrypt/metrics/ReflexiveStatsLog.java
@@ -0,0 +1,58 @@
+/* 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.metrics;
+
+import com.android.org.conscrypt.Internal;
+
+/**
+ * Reflection wrapper around android.util.StatsLog.
+ * @hide This class is not part of the Android public SDK API
+ */
+@Internal
+public class ReflexiveStatsLog {
+    private static final Class<?> c_statsLog;
+    private static final Class<?> c_statsEvent;
+    private static final OptionalMethod write;
+
+    static {
+        c_statsLog = initStatsLogClass();
+        c_statsEvent = initStatsEventClass();
+        write = new OptionalMethod(c_statsLog, "write", c_statsEvent);
+    }
+
+    private static Class<?> initStatsLogClass() {
+        try {
+            return Class.forName("android.util.StatsLog");
+        } catch (ClassNotFoundException ignored) {
+            return null;
+        }
+    }
+
+    private static Class<?> initStatsEventClass() {
+        try {
+            return Class.forName("android.util.StatsEvent");
+        } catch (ClassNotFoundException ignored) {
+            return null;
+        }
+    }
+
+    private ReflexiveStatsLog() {}
+
+    public static void write(ReflexiveStatsEvent event) {
+        write.invoke(null, event.getStatsEvent());
+    }
+}
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/BufferUtilsTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/BufferUtilsTest.java
new file mode 100644
index 0000000..ef26e0d
--- /dev/null
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/BufferUtilsTest.java
@@ -0,0 +1,212 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright 2021 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.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import java.nio.ByteBuffer;
+import com.android.org.conscrypt.TestUtils.BufferType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+@RunWith(Parameterized.class)
+public class BufferUtilsTest {
+    private static final int K64 = 64 * 1024;
+    private static final int K16 = 16 * 1024;
+
+    private static final int[][] TEST_SIZES = {
+            // All even numbers as several tests use size/2
+            { 0 },
+            { 0, 0, 0, 0 },
+            { 0, 0, 0, 2 },
+            { 2, 0, 0, 0 },
+            { 100, 200, 300 },
+            { 1000, 2000, 3000 },
+            { K16 },
+            { 0, 0, K16 },
+            { K16, 0, 0 },
+            { K64 },
+            { 0, 0, K64 },
+            { K64, 0, 0 },
+            { 100, 100, K64 },
+            { K64, 100, 100 },
+            { K64, K64, K64 },
+    };
+
+
+    @Parameters(name = "{0}")
+    public static BufferType[] data() {
+        return new BufferType[] { BufferType.HEAP, BufferType.DIRECT };
+    }
+
+    @Parameter
+    public BufferType bufferType;
+
+    @Test
+    public void checkNotNull() {
+        for (int[] sizes : TEST_SIZES) {
+            BufferUtils.checkNotNull(bufferType.newRandomBuffers(sizes));
+        }
+
+        ByteBuffer[] buffers = bufferType.newRandomBuffers(10, 10, 10, 10, 10);
+        buffers[2] = null;
+        try {
+            BufferUtils.checkNotNull(buffers);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void remaining() {
+        for (int[] sizes : TEST_SIZES) {
+            assertEquals(arraySum(sizes),
+                    BufferUtils.remaining(bufferType.newRandomBuffers(sizes)));
+        }
+    }
+
+    @Test
+    public void consume() {
+        for (int[] sizes : TEST_SIZES) {
+            ByteBuffer[] buffers = bufferType.newRandomBuffers(sizes);
+            int totalSize = arraySum(sizes);
+
+            BufferUtils.consume(buffers, 0);
+            assertEquals(totalSize, BufferUtils.remaining(buffers));
+
+            BufferUtils.consume(buffers,totalSize / 2);
+            assertEquals(totalSize / 2, BufferUtils.remaining(buffers));
+
+            BufferUtils.consume(buffers,totalSize / 2);
+            assertEquals(0, BufferUtils.remaining(buffers));
+
+            if (totalSize > 0) {
+                try {
+                    BufferUtils.consume(buffers, totalSize / 2);
+                    fail("Managed to consume past end of buffer array");
+                } catch (IllegalArgumentException e) {
+                    // Expected
+                }
+            }
+        }
+    }
+
+    @Test
+    public void copyNoConsume() {
+        for (BufferType destinationType : BufferType.values()) {
+            for (int[] sizes : TEST_SIZES) {
+                ByteBuffer[] buffers = bufferType.newRandomBuffers(sizes);
+                int totalSize = arraySum(sizes);
+
+                ByteBuffer destination = destinationType.newBuffer(totalSize);
+                BufferUtils.copyNoConsume(buffers, destination, totalSize);
+                assertEquals(totalSize, BufferUtils.remaining(buffers));
+
+                assertArrayEquals(toArray(buffers), toArray(destination));
+            }
+        }
+    }
+
+    private static byte[] toArray(ByteBuffer... buffers) {
+        byte[] bytes = new byte[(int) BufferUtils.remaining(buffers)];
+        int offset = 0;
+        for (ByteBuffer buffer : buffers) {
+            int length = buffer.remaining();
+            if (length > 0) {
+                buffer.get(bytes, offset, length);
+                offset += length;
+            }
+        }
+        return bytes;
+    }
+
+    @Test
+    public void getBufferLargerThan_allSmall() {
+        ByteBuffer[] buffers = bufferType.newRandomBuffers(100, 200, 300, 400);
+
+        assertNull(BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 300);
+        assertNull(BufferUtils.getBufferLargerThan(buffers, K16));
+        assertSame(buffers[2], BufferUtils.getBufferLargerThan(buffers, 100));
+
+        BufferUtils.consume(buffers, 300);
+        assertSame(buffers[3], BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 200);
+        assertSame(buffers[3], BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 200);
+        ByteBuffer buffer = BufferUtils.getBufferLargerThan(buffers, K16);
+        assertNull(buffer);
+    }
+
+    @Test
+    public void getBufferLargerThan_oneLarge() {
+        ByteBuffer[] buffers = bufferType.newRandomBuffers(100, K64, 300, 400);
+
+        assertNull(BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 100);
+        assertSame(buffers[1], BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 1024); // 63K remaining in buffers[1]
+        assertSame(buffers[1], BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 60 * 1024); // 3K remaining in buffers[1]
+        assertNull(BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 3 * 1024);
+        assertEquals(0, buffers[1].remaining());
+        assertNull(BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 300);
+        assertSame(buffers[3], BufferUtils.getBufferLargerThan(buffers, K16));
+
+        BufferUtils.consume(buffers, 400);
+        ByteBuffer buffer = BufferUtils.getBufferLargerThan(buffers, K16);
+        assertNull(buffer);
+    }
+
+    @Test
+    public void getBufferLargerThan_onlyOneBuffer() {
+        ByteBuffer[] buffers = bufferType.newRandomBuffers(0, 0, 100, 0, 0);
+
+        assertSame(buffers[2], BufferUtils.getBufferLargerThan(buffers, K16));
+    }
+
+    private int arraySum(int[] sizes) {
+        int sum = 0;
+        for (int i : sizes) {
+            sum += i;
+        }
+        return sum;
+    }
+}
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/HostnameVerifierTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/HostnameVerifierTest.java
new file mode 100644
index 0000000..f3da396
--- /dev/null
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/HostnameVerifierTest.java
@@ -0,0 +1,662 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You 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.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.Charset;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.security.auth.x500.X500Principal;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests for our hostname verifier. Most of these tests are from AOSP, which
+ * itself includes tests from the Apache HTTP Client test suite.
+ * @hide This class is not part of the Android public SDK API
+ */
+@RunWith(Parameterized.class)
+public final class HostnameVerifierTest {
+    /**
+     * @hide This class is not part of the Android public SDK API
+     */
+    public static final class FakeSSLSession extends com.android.org.conscrypt.javax.net.ssl.FakeSSLSession {
+
+        private final Certificate[] certificates;
+
+        public FakeSSLSession(Certificate... certificates) throws Exception {
+            super("FakeHost");
+            this.certificates = certificates;
+        }
+
+        @Override
+        public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
+            if (certificates.length == 0) {
+                throw new SSLPeerUnverifiedException("peer not authenticated");
+            }
+            return certificates;
+        }
+    }
+
+    private static final Charset UTF_8 = Charset.forName("UTF-8");
+    // BEGIN Android-changed: Run tests for both default and strict verifiers. http://b/144694112
+    // private HostnameVerifier verifier = OkHostnameVerifier.INSTANCE;
+    @Parameters()
+    public static Collection<Object[]> data() {
+        // Both verifiers should behave the same in all tests except for
+        // subjectAltNameWithToplevelWildcard(), and that test is not parameterized for clarity.
+        return Arrays.asList(new Object[][] {
+                { OkHostnameVerifier.INSTANCE },
+                { OkHostnameVerifier.strictInstance() }
+        });
+    }
+
+    @Parameter
+    public OkHostnameVerifier verifier;
+    // END Android-changed: Run tests for both default and strict verifiers. http://b/144694112
+
+    @Test public void verify() throws Exception {
+        FakeSSLSession session = new FakeSSLSession();
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs,"localhost", session));
+    }
+
+    @Test public void verifyCn() throws Exception {
+        // CN=foo.com
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIERjCCAy6gAwIBAgIJAIz+EYMBU6aQMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE1MzE0MVoXDTI4MTEwNTE1MzE0MVowgaQx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczEQMA4GA1UEAxMHZm9vLmNvbTElMCMGCSqGSIb3DQEJARYWanVs\n"
+                + "aXVzZGF2aWVzQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n"
+                + "ggEBAMhjr5aCPoyp0R1iroWAfnEyBMGYWoCidH96yGPFjYLowez5aYKY1IOKTY2B\n"
+                + "lYho4O84X244QrZTRl8kQbYtxnGh4gSCD+Z8gjZ/gMvLUlhqOb+WXPAUHMB39GRy\n"
+                + "zerA/ZtrlUqf+lKo0uWcocxeRc771KN8cPH3nHZ0rV0Hx4ZAZy6U4xxObe4rtSVY\n"
+                + "07hNKXAb2odnVqgzcYiDkLV8ilvEmoNWMWrp8UBqkTcpEhYhCYp3cTkgJwMSuqv8\n"
+                + "BqnGd87xQU3FVZI4tbtkB+KzjD9zz8QCDJAfDjZHR03KNQ5mxOgXwxwKw6lGMaiV\n"
+                + "JTxpTKqym93whYk93l3ocEe55c0CAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgB\n"
+                + "hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE\n"
+                + "FJ8Ud78/OrbKOIJCSBYs2tDLXofYMB8GA1UdIwQYMBaAFHua2o+QmU5S0qzbswNS\n"
+                + "yoemDT4NMA0GCSqGSIb3DQEBBQUAA4IBAQC3jRmEya6sQCkmieULcvx8zz1euCk9\n"
+                + "fSez7BEtki8+dmfMXe3K7sH0lI8f4jJR0rbSCjpmCQLYmzC3NxBKeJOW0RcjNBpO\n"
+                + "c2JlGO9auXv2GDP4IYiXElLJ6VSqc8WvDikv0JmCCWm0Zga+bZbR/EWN5DeEtFdF\n"
+                + "815CLpJZNcYwiYwGy/CVQ7w2TnXlG+mraZOz+owr+cL6J/ZesbdEWfjoS1+cUEhE\n"
+                + "HwlNrAu8jlZ2UqSgskSWlhYdMTAP9CPHiUv9N7FcT58Itv/I4fKREINQYjDpvQcx\n"
+                + "SaTYb9dr5sB4WLNglk7zxDtM80H518VvihTcP7FHL+Gn6g4j5fkI98+S\n"
+                + "-----END CERTIFICATE-----\n");
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("foo.com", session));
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "foo.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+        assertFalse(verifier.verify(certs, "bar.com", session));
+    }
+
+    @Test public void verifyNonAsciiCn() throws Exception {
+        // CN=&#x82b1;&#x5b50;.co.jp
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIESzCCAzOgAwIBAgIJAIz+EYMBU6aTMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE1NDIxNVoXDTI4MTEwNTE1NDIxNVowgakx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNYXJ5bGFuZDEUMBIGA1UEBwwLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoMDmh0dHBjb21wb25lbnRzMRowGAYDVQQLDBF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczEVMBMGA1UEAwwM6Iqx5a2QLmNvLmpwMSUwIwYJKoZIhvcNAQkB\n"
+                + "FhZqdWxpdXNkYXZpZXNAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n"
+                + "MIIBCgKCAQEAyGOvloI+jKnRHWKuhYB+cTIEwZhagKJ0f3rIY8WNgujB7PlpgpjU\n"
+                + "g4pNjYGViGjg7zhfbjhCtlNGXyRBti3GcaHiBIIP5nyCNn+Ay8tSWGo5v5Zc8BQc\n"
+                + "wHf0ZHLN6sD9m2uVSp/6UqjS5ZyhzF5FzvvUo3xw8fecdnStXQfHhkBnLpTjHE5t\n"
+                + "7iu1JVjTuE0pcBvah2dWqDNxiIOQtXyKW8Sag1YxaunxQGqRNykSFiEJindxOSAn\n"
+                + "AxK6q/wGqcZ3zvFBTcVVkji1u2QH4rOMP3PPxAIMkB8ONkdHTco1DmbE6BfDHArD\n"
+                + "qUYxqJUlPGlMqrKb3fCFiT3eXehwR7nlzQIDAQABo3sweTAJBgNVHRMEAjAAMCwG\n"
+                + "CWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV\n"
+                + "HQ4EFgQUnxR3vz86tso4gkJIFiza0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLS\n"
+                + "rNuzA1LKh6YNPg0wDQYJKoZIhvcNAQEFBQADggEBALJ27i3okV/KvlDp6KMID3gd\n"
+                + "ITl68PyItzzx+SquF8gahMh016NX73z/oVZoVUNdftla8wPUB1GwIkAnGkhQ9LHK\n"
+                + "spBdbRiCj0gMmLCsX8SrjFvr7cYb2cK6J/fJe92l1tg/7Y4o7V/s4JBe/cy9U9w8\n"
+                + "a0ctuDmEBCgC784JMDtT67klRfr/2LlqWhlOEq7pUFxRLbhpquaAHSOjmIcWnVpw\n"
+                + "9BsO7qe46hidgn39hKh1WjKK2VcL/3YRsC4wUi0PBtFW6ScMCuMhgIRXSPU55Rae\n"
+                + "UIlOdPjjr1SUNWGId1rD7W16Scpwnknn310FNxFMHVI0GTGFkNdkilNCFJcIoRA=\n"
+                + "-----END CERTIFICATE-----\n");
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("\u82b1\u5b50.co.jp", session));
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "\u82b1\u5b50.co.jp", session));
+        assertFalse(verifier.verify(certs, "a.\u82b1\u5b50.co.jp", session));
+    }
+
+    @Test public void verifySubjectAlt() throws Exception {
+        // CN=foo.com, subjectAlt=bar.com
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIEXDCCA0SgAwIBAgIJAIz+EYMBU6aRMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE1MzYyOVoXDTI4MTEwNTE1MzYyOVowgaQx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczEQMA4GA1UEAxMHZm9vLmNvbTElMCMGCSqGSIb3DQEJARYWanVs\n"
+                + "aXVzZGF2aWVzQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n"
+                + "ggEBAMhjr5aCPoyp0R1iroWAfnEyBMGYWoCidH96yGPFjYLowez5aYKY1IOKTY2B\n"
+                + "lYho4O84X244QrZTRl8kQbYtxnGh4gSCD+Z8gjZ/gMvLUlhqOb+WXPAUHMB39GRy\n"
+                + "zerA/ZtrlUqf+lKo0uWcocxeRc771KN8cPH3nHZ0rV0Hx4ZAZy6U4xxObe4rtSVY\n"
+                + "07hNKXAb2odnVqgzcYiDkLV8ilvEmoNWMWrp8UBqkTcpEhYhCYp3cTkgJwMSuqv8\n"
+                + "BqnGd87xQU3FVZI4tbtkB+KzjD9zz8QCDJAfDjZHR03KNQ5mxOgXwxwKw6lGMaiV\n"
+                + "JTxpTKqym93whYk93l3ocEe55c0CAwEAAaOBkDCBjTAJBgNVHRMEAjAAMCwGCWCG\n"
+                + "SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4E\n"
+                + "FgQUnxR3vz86tso4gkJIFiza0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLSrNuz\n"
+                + "A1LKh6YNPg0wEgYDVR0RBAswCYIHYmFyLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEA\n"
+                + "dQyprNZBmVnvuVWjV42sey/PTfkYShJwy1j0/jcFZR/ypZUovpiHGDO1DgL3Y3IP\n"
+                + "zVQ26uhUsSw6G0gGRiaBDe/0LUclXZoJzXX1qpS55OadxW73brziS0sxRgGrZE/d\n"
+                + "3g5kkio6IED47OP6wYnlmZ7EKP9cqjWwlnvHnnUcZ2SscoLNYs9rN9ccp8tuq2by\n"
+                + "88OyhKwGjJfhOudqfTNZcDzRHx4Fzm7UsVaycVw4uDmhEHJrAsmMPpj/+XRK9/42\n"
+                + "2xq+8bc6HojdtbCyug/fvBZvZqQXSmU8m8IVcMmWMz0ZQO8ee3QkBHMZfCy7P/kr\n"
+                + "VbWx/uETImUu+NZg22ewEw==\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "foo.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+        assertTrue(verifier.verify(certs, "bar.com", session));
+        assertFalse(verifier.verify(certs, "a.bar.com", session));
+    }
+
+    /**
+     * Ignored due to incompatibilities between Android and Java on how non-ASCII
+     * subject alt names are parsed. Android fails to parse these, which means we
+     * fall back to the CN. The RI does parse them, so the CN is unused.
+     */
+    @Test @Ignore public void verifyNonAsciiSubjectAlt() throws Exception {
+        // CN=foo.com, subjectAlt=bar.com, subjectAlt=&#x82b1;&#x5b50;.co.jp
+        // (hanako.co.jp in kanji)
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIEajCCA1KgAwIBAgIJAIz+EYMBU6aSMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE1MzgxM1oXDTI4MTEwNTE1MzgxM1owgaQx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczEQMA4GA1UEAxMHZm9vLmNvbTElMCMGCSqGSIb3DQEJARYWanVs\n"
+                + "aXVzZGF2aWVzQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n"
+                + "ggEBAMhjr5aCPoyp0R1iroWAfnEyBMGYWoCidH96yGPFjYLowez5aYKY1IOKTY2B\n"
+                + "lYho4O84X244QrZTRl8kQbYtxnGh4gSCD+Z8gjZ/gMvLUlhqOb+WXPAUHMB39GRy\n"
+                + "zerA/ZtrlUqf+lKo0uWcocxeRc771KN8cPH3nHZ0rV0Hx4ZAZy6U4xxObe4rtSVY\n"
+                + "07hNKXAb2odnVqgzcYiDkLV8ilvEmoNWMWrp8UBqkTcpEhYhCYp3cTkgJwMSuqv8\n"
+                + "BqnGd87xQU3FVZI4tbtkB+KzjD9zz8QCDJAfDjZHR03KNQ5mxOgXwxwKw6lGMaiV\n"
+                + "JTxpTKqym93whYk93l3ocEe55c0CAwEAAaOBnjCBmzAJBgNVHRMEAjAAMCwGCWCG\n"
+                + "SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4E\n"
+                + "FgQUnxR3vz86tso4gkJIFiza0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLSrNuz\n"
+                + "A1LKh6YNPg0wIAYDVR0RBBkwF4IHYmFyLmNvbYIM6Iqx5a2QLmNvLmpwMA0GCSqG\n"
+                + "SIb3DQEBBQUAA4IBAQBeZs7ZIYyKtdnVxVvdLgwySEPOE4pBSXii7XYv0Q9QUvG/\n"
+                + "++gFGQh89HhABzA1mVUjH5dJTQqSLFvRfqTHqLpxSxSWqMHnvRM4cPBkIRp/XlMK\n"
+                + "PlXadYtJLPTgpbgvulA1ickC9EwlNYWnowZ4uxnfsMghW4HskBqaV+PnQ8Zvy3L0\n"
+                + "12c7Cg4mKKS5pb1HdRuiD2opZ+Hc77gRQLvtWNS8jQvd/iTbh6fuvTKfAOFoXw22\n"
+                + "sWIKHYrmhCIRshUNohGXv50m2o+1w9oWmQ6Dkq7lCjfXfUB4wIbggJjpyEtbNqBt\n"
+                + "j4MC2x5rfsLKKqToKmNE7pFEgqwe8//Aar1b+Qj+\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        assertTrue(verifier.verify(certs, "foo.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+        // these checks test alternative subjects. The test data contains an
+        // alternative subject starting with a japanese kanji character. This is
+        // not supported by Android because the underlying implementation from
+        // harmony follows the definition from rfc 1034 page 10 for alternative
+        // subject names. This causes the code to drop all alternative subjects.
+        // assertTrue(verifier.verify("bar.com", session));
+        // assertFalse(verifier.verify("a.bar.com", session));
+        // assertFalse(verifier.verify("a.\u82b1\u5b50.co.jp", session));
+    }
+
+    @Test public void verifySubjectAltOnly() throws Exception {
+        // subjectAlt=foo.com
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIESjCCAzKgAwIBAgIJAIz+EYMBU6aYMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE2MjYxMFoXDTI4MTEwNTE2MjYxMFowgZIx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNYXJ5bGFuZDEUMBIGA1UEBwwLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoMDmh0dHBjb21wb25lbnRzMRowGAYDVQQLDBF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczElMCMGCSqGSIb3DQEJARYWanVsaXVzZGF2aWVzQGdtYWlsLmNv\n"
+                + "bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMhjr5aCPoyp0R1iroWA\n"
+                + "fnEyBMGYWoCidH96yGPFjYLowez5aYKY1IOKTY2BlYho4O84X244QrZTRl8kQbYt\n"
+                + "xnGh4gSCD+Z8gjZ/gMvLUlhqOb+WXPAUHMB39GRyzerA/ZtrlUqf+lKo0uWcocxe\n"
+                + "Rc771KN8cPH3nHZ0rV0Hx4ZAZy6U4xxObe4rtSVY07hNKXAb2odnVqgzcYiDkLV8\n"
+                + "ilvEmoNWMWrp8UBqkTcpEhYhCYp3cTkgJwMSuqv8BqnGd87xQU3FVZI4tbtkB+Kz\n"
+                + "jD9zz8QCDJAfDjZHR03KNQ5mxOgXwxwKw6lGMaiVJTxpTKqym93whYk93l3ocEe5\n"
+                + "5c0CAwEAAaOBkDCBjTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NM\n"
+                + "IEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUnxR3vz86tso4gkJIFiza\n"
+                + "0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLSrNuzA1LKh6YNPg0wEgYDVR0RBAsw\n"
+                + "CYIHZm9vLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAjl78oMjzFdsMy6F1sGg/IkO8\n"
+                + "tF5yUgPgFYrs41yzAca7IQu6G9qtFDJz/7ehh/9HoG+oqCCIHPuIOmS7Sd0wnkyJ\n"
+                + "Y7Y04jVXIb3a6f6AgBkEFP1nOT0z6kjT7vkA5LJ2y3MiDcXuRNMSta5PYVnrX8aZ\n"
+                + "yiqVUNi40peuZ2R8mAUSBvWgD7z2qWhF8YgDb7wWaFjg53I36vWKn90ZEti3wNCw\n"
+                + "qAVqixM+J0qJmQStgAc53i2aTMvAQu3A3snvH/PHTBo+5UL72n9S1kZyNCsVf1Qo\n"
+                + "n8jKTiRriEM+fMFlcgQP284EBFzYHyCXFb9O/hMjK2+6mY9euMB1U1aFFzM/Bg==\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        assertTrue(verifier.verify(certs, "foo.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+        assertTrue(verifier.verify(certs, "foo.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+    }
+
+    @Test public void verifyMultipleCn() throws Exception {
+        // CN=foo.com, CN=bar.com, CN=&#x82b1;&#x5b50;.co.jp
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIEbzCCA1egAwIBAgIJAIz+EYMBU6aXMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE2MTk0NVoXDTI4MTEwNTE2MTk0NVowgc0x\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNYXJ5bGFuZDEUMBIGA1UEBwwLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoMDmh0dHBjb21wb25lbnRzMRowGAYDVQQLDBF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczEQMA4GA1UEAwwHZm9vLmNvbTEQMA4GA1UEAwwHYmFyLmNvbTEV\n"
+                + "MBMGA1UEAwwM6Iqx5a2QLmNvLmpwMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyGOv\n"
+                + "loI+jKnRHWKuhYB+cTIEwZhagKJ0f3rIY8WNgujB7PlpgpjUg4pNjYGViGjg7zhf\n"
+                + "bjhCtlNGXyRBti3GcaHiBIIP5nyCNn+Ay8tSWGo5v5Zc8BQcwHf0ZHLN6sD9m2uV\n"
+                + "Sp/6UqjS5ZyhzF5FzvvUo3xw8fecdnStXQfHhkBnLpTjHE5t7iu1JVjTuE0pcBva\n"
+                + "h2dWqDNxiIOQtXyKW8Sag1YxaunxQGqRNykSFiEJindxOSAnAxK6q/wGqcZ3zvFB\n"
+                + "TcVVkji1u2QH4rOMP3PPxAIMkB8ONkdHTco1DmbE6BfDHArDqUYxqJUlPGlMqrKb\n"
+                + "3fCFiT3eXehwR7nlzQIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQf\n"
+                + "Fh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUnxR3vz86\n"
+                + "tso4gkJIFiza0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLSrNuzA1LKh6YNPg0w\n"
+                + "DQYJKoZIhvcNAQEFBQADggEBAGuZb8ai1NO2j4v3y9TLZvd5s0vh5/TE7n7RX+8U\n"
+                + "y37OL5k7x9nt0mM1TyAKxlCcY+9h6frue8MemZIILSIvMrtzccqNz0V1WKgA+Orf\n"
+                + "uUrabmn+CxHF5gpy6g1Qs2IjVYWA5f7FROn/J+Ad8gJYc1azOWCLQqSyfpNRLSvY\n"
+                + "EriQFEV63XvkJ8JrG62b+2OT2lqT4OO07gSPetppdlSa8NBSKP6Aro9RIX1ZjUZQ\n"
+                + "SpQFCfo02NO0uNRDPUdJx2huycdNb+AXHaO7eXevDLJ+QnqImIzxWiY6zLOdzjjI\n"
+                + "VBMkLHmnP7SjGSQ3XA4ByrQOxfOUTyLyE7NuemhHppuQPxE=\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "foo.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+        assertFalse(verifier.verify(certs, "bar.com", session));
+        assertFalse(verifier.verify(certs, "a.bar.com", session));
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("\u82b1\u5b50.co.jp", session));
+        assertFalse(verifier.verify(certs, "\u82b1\u5b50.co.jp", session));
+        assertFalse(verifier.verify(certs, "a.\u82b1\u5b50.co.jp", session));
+    }
+
+    @Test public void verifyWilcardCn() throws Exception {
+        // CN=*.foo.com
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIESDCCAzCgAwIBAgIJAIz+EYMBU6aUMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE2MTU1NVoXDTI4MTEwNTE2MTU1NVowgaYx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczESMBAGA1UEAxQJKi5mb28uY29tMSUwIwYJKoZIhvcNAQkBFhZq\n"
+                + "dWxpdXNkYXZpZXNAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"
+                + "CgKCAQEAyGOvloI+jKnRHWKuhYB+cTIEwZhagKJ0f3rIY8WNgujB7PlpgpjUg4pN\n"
+                + "jYGViGjg7zhfbjhCtlNGXyRBti3GcaHiBIIP5nyCNn+Ay8tSWGo5v5Zc8BQcwHf0\n"
+                + "ZHLN6sD9m2uVSp/6UqjS5ZyhzF5FzvvUo3xw8fecdnStXQfHhkBnLpTjHE5t7iu1\n"
+                + "JVjTuE0pcBvah2dWqDNxiIOQtXyKW8Sag1YxaunxQGqRNykSFiEJindxOSAnAxK6\n"
+                + "q/wGqcZ3zvFBTcVVkji1u2QH4rOMP3PPxAIMkB8ONkdHTco1DmbE6BfDHArDqUYx\n"
+                + "qJUlPGlMqrKb3fCFiT3eXehwR7nlzQIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCG\n"
+                + "SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4E\n"
+                + "FgQUnxR3vz86tso4gkJIFiza0Mteh9gwHwYDVR0jBBgwFoAUe5raj5CZTlLSrNuz\n"
+                + "A1LKh6YNPg0wDQYJKoZIhvcNAQEFBQADggEBAH0ipG6J561UKUfgkeW7GvYwW98B\n"
+                + "N1ZooWX+JEEZK7+Pf/96d3Ij0rw9ACfN4bpfnCq0VUNZVSYB+GthQ2zYuz7tf/UY\n"
+                + "A6nxVgR/IjG69BmsBl92uFO7JTNtHztuiPqBn59pt+vNx4yPvno7zmxsfI7jv0ww\n"
+                + "yfs+0FNm7FwdsC1k47GBSOaGw38kuIVWqXSAbL4EX9GkryGGOKGNh0qvAENCdRSB\n"
+                + "G9Z6tyMbmfRY+dLSh3a9JwoEcBUso6EWYBakLbq4nG/nvYdYvG9ehrnLVwZFL82e\n"
+                + "l3Q/RK95bnA6cuRClGusLad0e6bjkBzx/VQ3VarDEpAkTLUGVAa0CLXtnyc=\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "foo.com", session));
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("www.foo.com", session));
+        assertFalse(verifier.verify(certs, "www.foo.com", session));
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("\u82b1\u5b50.foo.com", session));
+        assertFalse(verifier.verify(certs, "\u82b1\u5b50.foo.com", session));
+        assertFalse(verifier.verify(certs, "a.b.foo.com", session));
+    }
+
+    @Test public void verifyWilcardCnOnTld() throws Exception {
+        // It's the CA's responsibility to not issue broad-matching certificates!
+        // CN=*.co.jp
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIERjCCAy6gAwIBAgIJAIz+EYMBU6aVMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE2MTYzMFoXDTI4MTEwNTE2MTYzMFowgaQx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczEQMA4GA1UEAxQHKi5jby5qcDElMCMGCSqGSIb3DQEJARYWanVs\n"
+                + "aXVzZGF2aWVzQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\n"
+                + "ggEBAMhjr5aCPoyp0R1iroWAfnEyBMGYWoCidH96yGPFjYLowez5aYKY1IOKTY2B\n"
+                + "lYho4O84X244QrZTRl8kQbYtxnGh4gSCD+Z8gjZ/gMvLUlhqOb+WXPAUHMB39GRy\n"
+                + "zerA/ZtrlUqf+lKo0uWcocxeRc771KN8cPH3nHZ0rV0Hx4ZAZy6U4xxObe4rtSVY\n"
+                + "07hNKXAb2odnVqgzcYiDkLV8ilvEmoNWMWrp8UBqkTcpEhYhCYp3cTkgJwMSuqv8\n"
+                + "BqnGd87xQU3FVZI4tbtkB+KzjD9zz8QCDJAfDjZHR03KNQ5mxOgXwxwKw6lGMaiV\n"
+                + "JTxpTKqym93whYk93l3ocEe55c0CAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgB\n"
+                + "hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE\n"
+                + "FJ8Ud78/OrbKOIJCSBYs2tDLXofYMB8GA1UdIwQYMBaAFHua2o+QmU5S0qzbswNS\n"
+                + "yoemDT4NMA0GCSqGSIb3DQEBBQUAA4IBAQA0sWglVlMx2zNGvUqFC73XtREwii53\n"
+                + "CfMM6mtf2+f3k/d8KXhLNySrg8RRlN11zgmpPaLtbdTLrmG4UdAHHYr8O4y2BBmE\n"
+                + "1cxNfGxxechgF8HX10QV4dkyzp6Z1cfwvCeMrT5G/V1pejago0ayXx+GPLbWlNeZ\n"
+                + "S+Kl0m3p+QplXujtwG5fYcIpaGpiYraBLx3Tadih39QN65CnAh/zRDhLCUzKyt9l\n"
+                + "UGPLEUDzRHMPHLnSqT1n5UU5UDRytbjJPXzF+l/+WZIsanefWLsxnkgAuZe/oMMF\n"
+                + "EJMryEzOjg4Tfuc5qM0EXoPcQ/JlheaxZ40p2IyHqbsWV4MRYuFH4bkM\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("foo.co.jp", session));
+        assertFalse(verifier.verify(certs, "foo.co.jp", session));
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("\u82b1\u5b50.co.jp", session));
+        assertFalse(verifier.verify(certs, "\u82b1\u5b50.co.jp", session));
+    }
+
+    /**
+     * Ignored due to incompatibilities between Android and Java on how non-ASCII
+     * subject alt names are parsed. Android fails to parse these, which means we
+     * fall back to the CN. The RI does parse them, so the CN is unused.
+     */
+    @Test @Ignore public void testWilcardNonAsciiSubjectAlt() throws Exception {
+        // CN=*.foo.com, subjectAlt=*.bar.com, subjectAlt=*.&#x82b1;&#x5b50;.co.jp
+        // (*.hanako.co.jp in kanji)
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIEcDCCA1igAwIBAgIJAIz+EYMBU6aWMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD\n"
+                + "VQQGEwJDQTELMAkGA1UECBMCQkMxEjAQBgNVBAcTCVZhbmNvdXZlcjEWMBQGA1UE\n"
+                + "ChMNd3d3LmN1Y2JjLmNvbTEUMBIGA1UECxQLY29tbW9uc19zc2wxHTAbBgNVBAMU\n"
+                + "FGRlbW9faW50ZXJtZWRpYXRlX2NhMSUwIwYJKoZIhvcNAQkBFhZqdWxpdXNkYXZp\n"
+                + "ZXNAZ21haWwuY29tMB4XDTA2MTIxMTE2MTczMVoXDTI4MTEwNTE2MTczMVowgaYx\n"
+                + "CzAJBgNVBAYTAlVTMREwDwYDVQQIEwhNYXJ5bGFuZDEUMBIGA1UEBxMLRm9yZXN0\n"
+                + "IEhpbGwxFzAVBgNVBAoTDmh0dHBjb21wb25lbnRzMRowGAYDVQQLExF0ZXN0IGNl\n"
+                + "cnRpZmljYXRlczESMBAGA1UEAxQJKi5mb28uY29tMSUwIwYJKoZIhvcNAQkBFhZq\n"
+                + "dWxpdXNkYXZpZXNAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n"
+                + "CgKCAQEAyGOvloI+jKnRHWKuhYB+cTIEwZhagKJ0f3rIY8WNgujB7PlpgpjUg4pN\n"
+                + "jYGViGjg7zhfbjhCtlNGXyRBti3GcaHiBIIP5nyCNn+Ay8tSWGo5v5Zc8BQcwHf0\n"
+                + "ZHLN6sD9m2uVSp/6UqjS5ZyhzF5FzvvUo3xw8fecdnStXQfHhkBnLpTjHE5t7iu1\n"
+                + "JVjTuE0pcBvah2dWqDNxiIOQtXyKW8Sag1YxaunxQGqRNykSFiEJindxOSAnAxK6\n"
+                + "q/wGqcZ3zvFBTcVVkji1u2QH4rOMP3PPxAIMkB8ONkdHTco1DmbE6BfDHArDqUYx\n"
+                + "qJUlPGlMqrKb3fCFiT3eXehwR7nlzQIDAQABo4GiMIGfMAkGA1UdEwQCMAAwLAYJ\n"
+                + "YIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1Ud\n"
+                + "DgQWBBSfFHe/Pzq2yjiCQkgWLNrQy16H2DAfBgNVHSMEGDAWgBR7mtqPkJlOUtKs\n"
+                + "27MDUsqHpg0+DTAkBgNVHREEHTAbggkqLmJhci5jb22CDiou6Iqx5a2QLmNvLmpw\n"
+                + "MA0GCSqGSIb3DQEBBQUAA4IBAQBobWC+D5/lx6YhX64CwZ26XLjxaE0S415ajbBq\n"
+                + "DK7lz+Rg7zOE3GsTAMi+ldUYnhyz0wDiXB8UwKXl0SDToB2Z4GOgqQjAqoMmrP0u\n"
+                + "WB6Y6dpkfd1qDRUzI120zPYgSdsXjHW9q2H77iV238hqIU7qCvEz+lfqqWEY504z\n"
+                + "hYNlknbUnR525ItosEVwXFBJTkZ3Yw8gg02c19yi8TAh5Li3Ad8XQmmSJMWBV4XK\n"
+                + "qFr0AIZKBlg6NZZFf/0dP9zcKhzSriW27bY0XfzA6GSiRDXrDjgXq6baRT6YwgIg\n"
+                + "pgJsDbJtZfHnV1nd3M6zOtQPm1TIQpNmMMMd/DPrGcUQerD3\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        // try the foo.com variations
+        assertTrue(verifier.verify(certs, "foo.com", session));
+        assertTrue(verifier.verify(certs, "www.foo.com", session));
+        assertTrue(verifier.verify(certs, "\u82b1\u5b50.foo.com", session));
+        assertFalse(verifier.verify(certs, "a.b.foo.com", session));
+        // these checks test alternative subjects. The test data contains an
+        // alternative subject starting with a japanese kanji character. This is
+        // not supported by Android because the underlying implementation from
+        // harmony follows the definition from rfc 1034 page 10 for alternative
+        // subject names. This causes the code to drop all alternative subjects.
+        // assertFalse(verifier.verify("bar.com", session));
+        // assertTrue(verifier.verify("www.bar.com", session));
+        // assertTrue(verifier.verify("\u82b1\u5b50.bar.com", session));
+        // assertTrue(verifier.verify("a.b.bar.com", session));
+    }
+
+    @Test public void subjectAltUsesLocalDomainAndIp() throws Exception {
+        // cat cert.cnf
+        // [req]
+        // distinguished_name=distinguished_name
+        // req_extensions=req_extensions
+        // x509_extensions=x509_extensions
+        // [distinguished_name]
+        // [req_extensions]
+        // [x509_extensions]
+        // subjectAltName=DNS:localhost.localdomain,DNS:localhost,IP:127.0.0.1
+        //
+        // $ openssl req -x509 -nodes -days 36500 -subj '/CN=localhost' -config ./cert.cnf \
+        //     -newkey rsa:512 -out cert.pem
+        X509Certificate certificate = certificate(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIBWDCCAQKgAwIBAgIJANS1EtICX2AZMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV\n"
+                + "BAMTCWxvY2FsaG9zdDAgFw0xMjAxMDIxOTA4NThaGA8yMTExMTIwOTE5MDg1OFow\n"
+                + "FDESMBAGA1UEAxMJbG9jYWxob3N0MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAPpt\n"
+                + "atK8r4/hf4hSIs0os/BSlQLbRBaK9AfBReM4QdAklcQqe6CHsStKfI8pp0zs7Ptg\n"
+                + "PmMdpbttL0O7mUboBC8CAwEAAaM1MDMwMQYDVR0RBCowKIIVbG9jYWxob3N0Lmxv\n"
+                + "Y2FsZG9tYWlugglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQEFBQADQQD0ntfL\n"
+                + "DCzOCv9Ma6Lv5o5jcYWVxvBSTsnt22hsJpWD1K7iY9lbkLwl0ivn73pG2evsAn9G\n"
+                + "X8YKH52fnHsCrhSD\n"
+                + "-----END CERTIFICATE-----");
+
+        assertEquals(new X500Principal("CN=localhost"), certificate.getSubjectX500Principal());
+        FakeSSLSession session = new FakeSSLSession(certificate);
+
+        X509Certificate[] certs = {};
+
+        assertTrue(verifier.verify(certs, "localhost", session));
+        assertTrue(verifier.verify(certs, "localhost.localdomain", session));
+        assertFalse(verifier.verify(certs, "local.host", session));
+
+        assertTrue(verifier.verify(certs, "127.0.0.1", session));
+        assertFalse(verifier.verify(certs, "127.0.0.2", session));
+    }
+
+    @Test public void wildcardsCannotMatchIpAddresses() throws Exception {
+        // openssl req -x509 -nodes -days 36500 -subj '/CN=*.0.0.1' -newkey rsa:512 -out cert.pem
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIBkjCCATygAwIBAgIJAMdemqOwd/BEMA0GCSqGSIb3DQEBBQUAMBIxEDAOBgNV\n"
+                + "BAMUByouMC4wLjEwIBcNMTAxMjIwMTY0NDI1WhgPMjExMDExMjYxNjQ0MjVaMBIx\n"
+                + "EDAOBgNVBAMUByouMC4wLjEwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAqY8c9Qrt\n"
+                + "YPWCvb7lclI+aDHM6fgbJcHsS9Zg8nUOh5dWrS7AgeA25wyaokFl4plBbbHQe2j+\n"
+                + "cCjsRiJIcQo9HwIDAQABo3MwcTAdBgNVHQ4EFgQUJ436TZPJvwCBKklZZqIvt1Yt\n"
+                + "JjEwQgYDVR0jBDswOYAUJ436TZPJvwCBKklZZqIvt1YtJjGhFqQUMBIxEDAOBgNV\n"
+                + "BAMUByouMC4wLjGCCQDHXpqjsHfwRDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB\n"
+                + "BQUAA0EAk9i88xdjWoewqvE+iMC9tD2obMchgFDaHH0ogxxiRaIKeEly3g0uGxIt\n"
+                + "fl2WRY8hb4x+zRrwsFaLEpdEvqcjOQ==\n"
+                + "-----END CERTIFICATE-----");
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "127.0.0.1", session));
+    }
+
+    /**
+     * Earlier implementations of Android's hostname verifier required that
+     * wildcard names wouldn't match "*.com" or similar. This was a nonstandard
+     * check that we've since dropped. It is the CA's responsibility to not hand
+     * out certificates that match so broadly.
+     */
+    @Test public void wildcardsDoesNotNeedTwoDots() throws Exception {
+        // openssl req -x509 -nodes -days 36500 -subj '/CN=*.com' -newkey rsa:512 -out cert.pem
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIBjDCCATagAwIBAgIJAOVulXCSu6HuMA0GCSqGSIb3DQEBBQUAMBAxDjAMBgNV\n"
+                + "BAMUBSouY29tMCAXDTEwMTIyMDE2NDkzOFoYDzIxMTAxMTI2MTY0OTM4WjAQMQ4w\n"
+                + "DAYDVQQDFAUqLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDJd8xqni+h7Iaz\n"
+                + "ypItivs9kPuiJUqVz+SuJ1C05SFc3PmlRCvwSIfhyD67fHcbMdl+A/LrIjhhKZJe\n"
+                + "1joO0+pFAgMBAAGjcTBvMB0GA1UdDgQWBBS4Iuzf5w8JdCp+EtBfdFNudf6+YzBA\n"
+                + "BgNVHSMEOTA3gBS4Iuzf5w8JdCp+EtBfdFNudf6+Y6EUpBIwEDEOMAwGA1UEAxQF\n"
+                + "Ki5jb22CCQDlbpVwkruh7jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA0EA\n"
+                + "U6LFxmZr31lFyis2/T68PpjAppc0DpNQuA2m/Y7oTHBDi55Fw6HVHCw3lucuWZ5d\n"
+                + "qUYo4ES548JdpQtcLrW2sA==\n"
+                + "-----END CERTIFICATE-----");
+        // Android-changed: Ignore common name in hostname verification. http://b/70278814
+        // assertTrue(verifier.verify("google.com", session));
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "google.com", session));
+    }
+
+    @Test public void subjectAltName() throws Exception {
+        // $ cat ./cert.cnf
+        // [req]
+        // distinguished_name=distinguished_name
+        // req_extensions=req_extensions
+        // x509_extensions=x509_extensions
+        // [distinguished_name]
+        // [req_extensions]
+        // [x509_extensions]
+        // subjectAltName=DNS:bar.com,DNS:baz.com
+        //
+        // $ openssl req -x509 -nodes -days 36500 -subj '/CN=foo.com' -config ./cert.cnf \
+        //     -newkey rsa:512 -out cert.pem
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIBPTCB6KADAgECAgkA7zoHaaqNGHQwDQYJKoZIhvcNAQEFBQAwEjEQMA4GA1UE\n"
+                + "AxMHZm9vLmNvbTAgFw0xMDEyMjAxODM5MzZaGA8yMTEwMTEyNjE4MzkzNlowEjEQ\n"
+                + "MA4GA1UEAxMHZm9vLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQC+gmoSxF+8\n"
+                + "hbV+rgRQqHIJd50216OWQJbU3BvdlPbca779NYO4+UZWTFdBM8BdQqs3H4B5Agvp\n"
+                + "y7HeSff1F7XRAgMBAAGjHzAdMBsGA1UdEQQUMBKCB2Jhci5jb22CB2Jhei5jb20w\n"
+                + "DQYJKoZIhvcNAQEFBQADQQBXpZZPOY2Dy1lGG81JTr8L4or9jpKacD7n51eS8iqI\n"
+                + "oTznPNuXHU5bFN0AAGX2ij47f/EahqTpo5RdS95P4sVm\n"
+                + "-----END CERTIFICATE-----");
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "foo.com", session));
+        assertTrue(verifier.verify(certs, "bar.com", session));
+        assertTrue(verifier.verify(certs, "baz.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+        assertFalse(verifier.verify(certs, "quux.com", session));
+    }
+
+    @Test public void subjectAltNameWithWildcard() throws Exception {
+        // $ cat ./cert.cnf
+        // [req]
+        // distinguished_name=distinguished_name
+        // req_extensions=req_extensions
+        // x509_extensions=x509_extensions
+        // [distinguished_name]
+        // [req_extensions]
+        // [x509_extensions]
+        // subjectAltName=DNS:bar.com,DNS:*.baz.com
+        //
+        // $ openssl req -x509 -nodes -days 36500 -subj '/CN=foo.com' -config ./cert.cnf \
+        //     -newkey rsa:512 -out cert.pem
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIBPzCB6qADAgECAgkAnv/7Jv5r7pMwDQYJKoZIhvcNAQEFBQAwEjEQMA4GA1UE\n"
+                + "AxMHZm9vLmNvbTAgFw0xMDEyMjAxODQ2MDFaGA8yMTEwMTEyNjE4NDYwMVowEjEQ\n"
+                + "MA4GA1UEAxMHZm9vLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDAz2YXnyog\n"
+                + "YdYLSFr/OEgSumtwqtZKJTB4wqTW/eKbBCEzxnyUMxWZIqUGu353PzwfOuWp2re3\n"
+                + "nvVV+QDYQlh9AgMBAAGjITAfMB0GA1UdEQQWMBSCB2Jhci5jb22CCSouYmF6LmNv\n"
+                + "bTANBgkqhkiG9w0BAQUFAANBAB8yrSl8zqy07i0SNYx2B/FnvQY734pxioaqFWfO\n"
+                + "Bqo1ZZl/9aPHEWIwBrxYNVB0SGu/kkbt/vxqOjzzrkXukmI=\n"
+                + "-----END CERTIFICATE-----");
+        X509Certificate[] certs = {};
+        assertFalse(verifier.verify(certs, "foo.com", session));
+        assertTrue(verifier.verify(certs, "bar.com", session));
+        assertTrue(verifier.verify(certs, "a.baz.com", session));
+        assertFalse(verifier.verify(certs, "baz.com", session));
+        assertFalse(verifier.verify(certs, "a.foo.com", session));
+        assertFalse(verifier.verify(certs, "a.bar.com", session));
+        assertFalse(verifier.verify(certs, "quux.com", session));
+    }
+
+    // BEGIN Android-added: Verify behaviour with top level wildcard SAN. http://b/144694112
+    @Test
+    public void subjectAltNameWithToplevelWildcard() throws Exception {
+        // Default OkHostnameVerifier instance should allow SANs which
+        // have wildcards for top-level domains.  The strict instance should not.
+        //
+        // Certificate generated using:-
+        //     openssl req -x509 -nodes -days 36500 -subj "/CN=Google Inc" \
+        //         -addext "subjectAltName=DNS:*.com" -newkey rsa:512
+        SSLSession session = session(""
+                + "-----BEGIN CERTIFICATE-----\n"
+                + "MIIBlTCCAT+gAwIBAgIUe1RB6C61ZW/SEQpKiywSEJOEOUMwDQYJKoZIhvcNAQEL\n"
+                + "BQAwFTETMBEGA1UEAwwKR29vZ2xlIEluYzAgFw0xOTExMjExMjE1NTBaGA8yMTE5\n"
+                + "MTAyODEyMTU1MFowFTETMBEGA1UEAwwKR29vZ2xlIEluYzBcMA0GCSqGSIb3DQEB\n"
+                + "AQUAA0sAMEgCQQCu24jT8hktpvnmcde4dqC6e7G5F4cNNLUFnTi3Ay9BzPH1r7sN\n"
+                + "v2lHTIQLKSlvjxa48mpeRBlOjDQigv7c+rfRAgMBAAGjZTBjMB0GA1UdDgQWBBQd\n"
+                + "myvYKfluxb0+kNEJoh1ZER2wUTAfBgNVHSMEGDAWgBQdmyvYKfluxb0+kNEJoh1Z\n"
+                + "ER2wUTAPBgNVHRMBAf8EBTADAQH/MBAGA1UdEQQJMAeCBSouY29tMA0GCSqGSIb3\n"
+                + "DQEBCwUAA0EAK710g2hQpXSmpbOQH4dHG61fkVDtM/kR/4/R61vDDqVkgOuyHqXl\n"
+                + "GUZFKHMeOZ8peQLT8b+5ik6pIO7Vu2pF6w==\n"
+                + "-----END CERTIFICATE-----\n");
+        X509Certificate[] certs = {};
+        assertTrue(OkHostnameVerifier.INSTANCE.verify(certs, "google.com", session));
+        assertFalse(OkHostnameVerifier.strictInstance().verify(certs, "google.com", session));
+    }
+    // END Android-added: Verify behaviour with top level wildcard SAN. http://b/144694112
+
+    // Android-changed: OkHostnameVerifier.verifyAsIpAddress not accessible on platform builds
+    @Test
+    @Ignore
+    public void verifyAsIpAddress() {
+        // IPv4
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("127.0.0.1"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("1.2.3.4"));
+
+        // IPv6
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("::1"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("2001:db8::1"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("::192.168.0.1"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("::ffff:192.168.0.1"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("FEDC:BA98:7654:3210:FEDC:BA98:7654:3210"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("1080:0:0:0:8:800:200C:417A"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("1080::8:800:200C:417A"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("FF01::101"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("0:0:0:0:0:0:13.1.68.3"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("0:0:0:0:0:FFFF:129.144.52.38"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("::13.1.68.3"));
+        assertTrue(OkHostnameVerifier.verifyAsIpAddress("::FFFF:129.144.52.38"));
+
+        // Hostnames
+        assertFalse(OkHostnameVerifier.verifyAsIpAddress("go"));
+        assertFalse(OkHostnameVerifier.verifyAsIpAddress("localhost"));
+        assertFalse(OkHostnameVerifier.verifyAsIpAddress("squareup.com"));
+        assertFalse(OkHostnameVerifier.verifyAsIpAddress("www.nintendo.co.jp"));
+    }
+
+    private X509Certificate certificate(String certificate) throws Exception {
+        return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(
+                new ByteArrayInputStream(certificate.getBytes(UTF_8)));
+    }
+
+    private SSLSession session(String certificate) throws Exception {
+        return new FakeSSLSession(certificate(certificate));
+    }
+}
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/MacTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/MacTest.java
new file mode 100644
index 0000000..577162b
--- /dev/null
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/MacTest.java
@@ -0,0 +1,403 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2021 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 com.android.org.conscrypt.TestUtils.decodeHex;
+import static com.android.org.conscrypt.TestUtils.encodeHex;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.Provider;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import javax.crypto.Mac;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import tests.util.ServiceTester;
+
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+@RunWith(JUnit4.class)
+public class MacTest {
+    private final List<String[]> testVectors = readTestVectors();
+
+    // Column indices in test vector CSV file
+    private static final int ALGORITHM_INDEX = 0;
+    private static final int KEY_INDEX = 1;
+    private static final int MESSAGE_INDEX = 2;
+    private static final int MAC_INDEX = 3;
+
+    // Number of splits to use when testing multiple buffers
+    private static final int NUM_SPLITS = 4;
+
+    private final Random random = new Random(System.currentTimeMillis());
+
+    private final Provider conscryptProvider = TestUtils.getConscryptProvider();
+
+    @BeforeClass
+    public static void setUp() {
+        TestUtils.assumeAllowsUnsignedCrypto();
+    }
+
+    @Test
+    public void knownAnswerTest() throws Exception {
+        for (String[] entry : testVectors) {
+            String algorithm = entry[ALGORITHM_INDEX];
+            String key = entry[KEY_INDEX];
+            String msg = entry[MESSAGE_INDEX];
+            String expected = entry[MAC_INDEX];
+
+            byte[] keyBytes = decodeHex(key);
+            byte[] msgBytes = decodeHex(msg);
+            byte[] expectedBytes = decodeHex(expected);
+            SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "RawBytes");
+
+            String baseFailMsg = String.format("Mac=%s\nKey=%s\nMsg=%s\nExpected=%s",
+                    algorithm, key, msg, expected);
+
+            // Calculate using Mac.update(byte[])
+            byte[] macBytes = generateMacUsingUpdate(algorithm, secretKey, msgBytes);
+            assertArrayEquals(failMessage("Using update()", baseFailMsg, macBytes),
+                    expectedBytes, macBytes);
+
+            // Calculate using Mac.final(byte[])
+            macBytes = generateMacUsingFinal(algorithm, secretKey, msgBytes);
+            assertArrayEquals(failMessage("Using final()", baseFailMsg, macBytes),
+                    expectedBytes, macBytes);
+
+            // Calculate using Mac.update(ByteBuffer) with a single non-direct ByteBuffer
+            ByteBuffer nondirectBuffer = ByteBuffer.wrap(msgBytes);
+            macBytes = generateMac(algorithm, secretKey, nondirectBuffer);
+            assertArrayEquals(failMessage("Non-direct ByteBuffer", baseFailMsg, macBytes),
+                    expectedBytes, macBytes);
+
+            // Calculate using Mac.update(ByteBuffer) with a single direct ByteBuffer
+            ByteBuffer directBuffer = ByteBuffer.allocateDirect(msgBytes.length);
+            directBuffer.put(msgBytes);
+            directBuffer.flip();
+            macBytes = generateMac(algorithm, secretKey, directBuffer);
+            assertArrayEquals(failMessage("Direct ByteBuffer", baseFailMsg, macBytes),
+                    expectedBytes, macBytes);
+
+            // Calculate using Mac.update(ByteBuffer) with a multiple non-direct ByteBuffers
+            nondirectBuffer.flip();
+            macBytes = generateMac(algorithm, secretKey, split(nondirectBuffer));
+            assertArrayEquals(failMessage("Multiple non-direct ByteBuffers", baseFailMsg, macBytes),
+                    expectedBytes, macBytes);
+
+            // Calculate using Mac.update(ByteBuffer) with a multiple direct ByteBuffers
+            directBuffer.flip();
+            macBytes = generateMac(algorithm, secretKey, split(directBuffer));
+            assertArrayEquals(failMessage("Multiple direct ByteBuffers", baseFailMsg, macBytes),
+                    expectedBytes, macBytes);
+
+            // Calculated using a pre-loved Mac
+            macBytes = generateReusingMac(algorithm, keyBytes, msgBytes);
+            assertArrayEquals(failMessage("Re-use Mac", baseFailMsg, macBytes),
+                    expectedBytes, macBytes);
+        }
+    }
+
+    @Test
+    public void serviceCreation() {
+        newMacServiceTester()
+                // Android KeyStore can only be initialised with its own private keys - tested
+                // elsewhere.
+                .skipProvider("AndroidKeyStore")
+                .skipProvider("AndroidKeyStoreBCWorkaround")
+                .run(new ServiceTester.Test() {
+                    @Override
+                    public void test(final Provider provider, final String algorithm)
+                            throws Exception {
+                        SecretKeySpec key = findAnyKey(algorithm);
+
+                        Mac mac = Mac.getInstance(algorithm);
+                        assertEquals(algorithm, mac.getAlgorithm());
+
+                        mac = Mac.getInstance(algorithm, provider);
+                        assertEquals(algorithm, mac.getAlgorithm());
+                        assertEquals(provider, mac.getProvider());
+                        if (key != null) {
+                            // TODO(prb) Ensure we have at least one test vector for every
+                            // MAC in Conscrypt and Android.
+                            mac.init(key);
+                            assertEquals(provider, mac.getProvider());
+                        }
+
+                        mac = Mac.getInstance(algorithm, provider.getName());
+                        assertEquals(algorithm, mac.getAlgorithm());
+                        assertEquals(provider, mac.getProvider());
+                        if (key != null) {
+                            mac.init(key);
+                            assertEquals(provider, mac.getProvider());
+                        }
+                    }
+                });
+    }
+
+    @Test
+    public void invalidKeyThrows() {
+        newMacServiceTester()
+                // BC actually accepts RSA public keys for these algorithms for some reason.
+                .skipCombination("BC", "PBEWITHHMACSHA")
+                .skipCombination("BC", "PBEWITHHMACSHA1")
+                .run(new ServiceTester.Test() {
+                    @Override
+                    public void test(final Provider provider, final String algorithm)
+                            throws Exception {
+                        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+                        generator.initialize(2048);
+                        KeyPair keyPair = generator.generateKeyPair();
+
+                        try {
+                            Mac mac = Mac.getInstance(algorithm, provider);
+                            mac.init(keyPair.getPublic(), null);
+                            fail();
+                        } catch (InvalidKeyException e) {
+                            // Expected
+                        }
+                    }
+                });
+    }
+
+    @Test
+    public void uninitializedMacThrows() {
+        newMacServiceTester().run(new ServiceTester.Test() {
+            @Override
+            public void test(final Provider provider, final String algorithm) throws Exception {
+                byte[] message = "Message".getBytes(StandardCharsets.UTF_8);
+
+                try {
+                    Mac mac = Mac.getInstance(algorithm, provider);
+                    mac.update(message);
+                    fail();
+                } catch (IllegalStateException e) {
+                    // Expected
+                }
+                try {
+                    Mac mac = Mac.getInstance(algorithm, provider);
+                    mac.doFinal(message);
+                    fail();
+                } catch (IllegalStateException e) {
+                    // Expected
+                }
+                try {
+                    Mac mac = Mac.getInstance(algorithm, provider);
+                    mac.doFinal();
+                    fail();
+                } catch (IllegalStateException e) {
+                    // Expected
+                }
+            }
+        });
+    }
+
+    private ServiceTester newMacServiceTester() {
+        return ServiceTester
+                .test("Mac")
+                // On Android 10 and 11 BC advertises these Macs but they are deprecated so throw
+                // on initialization.
+                .skipCombination("BC", "HMACMD5")
+                .skipCombination("BC", "HMACSHA1")
+                .skipCombination("BC", "HMACSHA224")
+                .skipCombination("BC", "HMACSHA256")
+                .skipCombination("BC", "HMACSHA384")
+                .skipCombination("BC", "HMACSHA512")
+                .skipCombination("BC", "PBEWITHHMACSHA224")
+                .skipCombination("BC", "PBEWITHHMACSHA256")
+                .skipCombination("BC", "PBEWITHHMACSHA384")
+                .skipCombination("BC", "PBEWITHHMACSHA512");
+    }
+
+    private static class DummyParameterSpec implements AlgorithmParameterSpec { }
+
+    @Test
+    public void algorithmParameters() {
+        ServiceTester
+                .test("Mac")
+                // Android KeyStore can only be initialised with its own private keys - tested
+                // elsewhere.
+                .skipProvider("AndroidKeyStore")
+                .skipProvider("AndroidKeyStoreBCWorkaround")
+                .run(new ServiceTester.Test() {
+                    @Override
+                    public void test(final Provider provider, final String algorithm)
+                            throws Exception {
+                        SecretKeySpec key = findAnyKey(algorithm);
+                        if (key != null) {
+                            Mac mac = Mac.getInstance(algorithm, provider);
+                            // Equivalent to mac.init(key) - allowed
+                            mac.init(key, null);
+
+                            try {
+                                mac = Mac.getInstance(algorithm, provider);
+                                mac.init(key, new DummyParameterSpec());
+                                fail();
+                            } catch (InvalidAlgorithmParameterException exception) {
+                                // Expected
+                            }
+                        }
+                    }
+                });
+    }
+
+    private SecretKeySpec findAnyKey(String algorithm) {
+        for (String[] entry : testVectors) {
+            if (entry[ALGORITHM_INDEX].equals(algorithm)) {
+                return new SecretKeySpec(decodeHex(entry[KEY_INDEX]), "RawBytes");
+            }
+        }
+        return null;
+    }
+
+    @Test
+    public void anyAlgorithmParametersThrows() throws Exception {
+        Set<String> seen = new HashSet<>();
+        for (String[] entry : testVectors) {
+            String algorithm = entry[ALGORITHM_INDEX];
+            if (!seen.contains(algorithm)) {
+                seen.add(algorithm);
+                byte[] keyBytes = decodeHex(entry[KEY_INDEX]);
+                SecretKeySpec key = new SecretKeySpec(keyBytes, "RawBytes");
+                Mac mac = Mac.getInstance(algorithm);
+                try {
+                    mac.init(key, new IvParameterSpec(keyBytes));
+                    fail(algorithm);
+                } catch (InvalidAlgorithmParameterException exception) {
+                    // Expected
+                }
+            }
+        }
+    }
+
+    private String failMessage(String test, String base, byte[] mac) {
+        return String.format("Test %s\n%s\nActual=  %s", test, base, encodeHex(mac));
+    }
+
+    // Splits a ByteBuffer into an array of NUM_SPLITS ByteBuffers containing the same data.
+    // If input.remaining < NUM_SPLITS then some buffers will be empty, which is fine.
+    private ByteBuffer[] split(ByteBuffer input) {
+        ByteBuffer[] buffers = new ByteBuffer[NUM_SPLITS];
+        int targetSize = (input.remaining() / NUM_SPLITS) + 1;
+        ByteBuffer buffer;
+        for (int i = 0; i < NUM_SPLITS; i++) {
+            int size = Math.min(targetSize, input.remaining());
+            buffer = input.isDirect() ? ByteBuffer.allocateDirect(size) : ByteBuffer.allocate(size);
+            buffers[i] = buffer;
+
+            int savedLimit = input.limit();
+            input.limit(input.position() + size);
+            buffer.put(input);
+            buffer.flip();
+            input.limit(savedLimit);
+        }
+        assertEquals(0, input.remaining());
+        return buffers;
+    }
+
+    private byte[] generateMacUsingUpdate(String algorithm, SecretKeySpec key, byte[] message)
+            throws Exception {
+        Mac mac = getConscryptMac(algorithm, key);
+        mac.update(message);
+        return mac.doFinal();
+    }
+
+    private byte[] generateMacUsingFinal(String algorithm, SecretKeySpec key, byte[] message)
+            throws Exception {
+        Mac mac = getConscryptMac(algorithm, key);
+        return mac.doFinal(message);
+    }
+
+    private byte[] generateMac(String algorithm, SecretKeySpec key, ByteBuffer buffer)
+            throws Exception {
+        return generateMac(algorithm, key, new ByteBuffer[] { buffer });
+    }
+
+    private byte[] generateMac(String algorithm, SecretKeySpec key, ByteBuffer[] buffers)
+            throws Exception {
+        Mac mac = getConscryptMac(algorithm, key);
+        for (ByteBuffer buffer : buffers) {
+            mac.update(buffer);
+        }
+        return mac.doFinal();
+    }
+
+    private byte[] generateReusingMac(String algorithm, byte[] keyBytes, byte[] message)
+            throws Exception {
+        Mac mac = getConscryptMac(algorithm);
+
+        // Mutate the original message and key and calculate a MAC from them
+        byte[] otherKeyBytes = new byte[keyBytes.length];
+        random.nextBytes(otherKeyBytes);
+        SecretKeySpec otherKey = new SecretKeySpec(otherKeyBytes, "RawBytes");
+        byte[] otherMessage = new byte[message.length];
+        random.nextBytes(otherMessage);
+        mac.init(otherKey);
+        mac.doFinal(otherMessage);
+
+        // Then re-use the same Mac with the original key and message
+        SecretKeySpec key = new SecretKeySpec(keyBytes, "RawBytes");
+        mac.reset();
+        mac.init(key);
+        mac.update(message);
+        return mac.doFinal();
+    }
+
+    private Mac getConscryptMac(String algorithm) throws Exception {
+        return getConscryptMac(algorithm, null);
+    }
+
+    private Mac getConscryptMac(String algorithm, SecretKeySpec key) throws Exception {
+        Mac mac = Mac.getInstance(algorithm, conscryptProvider);
+        assertNotNull(mac);
+        if (key != null) {
+            // Provider is not actually chosen until init
+            mac.init(key);
+            assertSame(conscryptProvider, mac.getProvider());
+        }
+        return mac;
+    }
+
+    private List<String[]> readTestVectors() {
+        try {
+            return TestUtils.readCsvResource("crypto/macs.csv");
+
+        } catch (IOException e) {
+            throw new AssertionError("Unable to load MAC test vectors", e);
+        }
+    }
+}
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/TrustManagerImplTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/TrustManagerImplTest.java
index f08a46a..ea58efb 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/TrustManagerImplTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/TrustManagerImplTest.java
@@ -22,8 +22,8 @@
 import static org.junit.Assert.fail;
 
 import com.android.org.conscrypt.java.security.TestKeyStore;
+import com.android.org.conscrypt.javax.net.ssl.TestHostnameVerifier;
 import java.io.IOException;
-import java.lang.reflect.Method;
 import java.security.KeyStore;
 import java.security.Principal;
 import java.security.cert.Certificate;
@@ -32,8 +32,6 @@
 import java.util.Arrays;
 import java.util.List;
 import javax.net.ssl.HandshakeCompletedListener;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLParameters;
 import javax.net.ssl.SSLPeerUnverifiedException;
 import javax.net.ssl.SSLSession;
@@ -141,11 +139,7 @@
         String goodHostname = TestKeyStore.CERT_HOSTNAME;
         String badHostname = "definitelywrong.nopenopenope";
 
-        // The default hostname verifier on OpenJDK rejects all hostnames, so use our own
-        javax.net.ssl.HostnameVerifier oldDefault = HttpsURLConnection.getDefaultHostnameVerifier();
         try {
-            HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier());
-
             SSLParameters params = new SSLParameters();
 
             // Without endpoint identification this should pass despite the mismatched hostname
@@ -171,13 +165,13 @@
 
             // Override the global default hostname verifier with a Conscrypt-specific one that
             // always passes.  Both scenarios should pass.
-            HostnameVerifier alwaysTrue = new HostnameVerifier() {
+            Conscrypt.setHostnameVerifier(tmi, new ConscryptHostnameVerifier() {
                 @Override
-                public boolean verify(String hostname, SSLSession session) {
+                public boolean verify(
+                        X509Certificate[] certificates, String s, SSLSession sslSession) {
                     return true;
                 }
-            };
-            HttpsURLConnection.setDefaultHostnameVerifier(alwaysTrue);
+            });
 
             certs = tmi.getTrustedChainForServer(chain, "RSA",
                     new FakeSSLSocket(new FakeSSLSession(badHostname, chain), params));
@@ -189,7 +183,8 @@
 
             // Now set an instance-specific verifier on the trust manager.  The bad hostname should
             // fail again.
-            Conscrypt.setHostnameVerifier(tmi, wrapVerifier(new TestHostnameVerifier()));
+            Conscrypt.setHostnameVerifier(
+                    tmi, Conscrypt.wrapHostnameVerifier(new TestHostnameVerifier()));
 
             try {
                 tmi.getTrustedChainForServer(chain, "RSA",
@@ -205,43 +200,21 @@
             // Remove the instance-specific verifier, and both should pass again.
             Conscrypt.setHostnameVerifier(tmi, null);
 
-            certs = tmi.getTrustedChainForServer(chain, "RSA",
-                    new FakeSSLSocket(new FakeSSLSession(badHostname, chain), params));
-            assertEquals(Arrays.asList(chain), certs);
+            try {
+                tmi.getTrustedChainForServer(chain, "RSA",
+                        new FakeSSLSocket(new FakeSSLSession(badHostname, chain), params));
+                fail();
+            } catch (CertificateException expected) {
+            }
 
             certs = tmi.getTrustedChainForServer(chain, "RSA",
                     new FakeSSLSocket(new FakeSSLSession(goodHostname, chain), params));
             assertEquals(Arrays.asList(chain), certs);
         } finally {
             Conscrypt.setDefaultHostnameVerifier(null);
-            HttpsURLConnection.setDefaultHostnameVerifier(oldDefault);
         }
     }
 
-    /*
-     * Wrap a HostnameVerifier in a ConscryptHostnameVerifier.
-     * In the Android platform ConscryptHostnameVerifier is a private API and the interface
-     * definition changed between Android 11 and Android 12.
-     * If an Android 12 Conscrypt module is present then there will also be a (non-public)
-     * method to wrap it with the correct interface.
-     * If an earlier module is present then the interface is the same as in the CTS 11 codebase
-     * and so we can just wrap it directly with an anonymous class.
-     * See also b/195615915
-     */
-    private ConscryptHostnameVerifier wrapVerifier(final HostnameVerifier verifier)
-            throws Exception {
-        Method wrapMethod = TestUtils.findWrapVerifierMethod();
-        if (wrapMethod != null) {
-            return (ConscryptHostnameVerifier) wrapMethod.invoke(null, verifier);
-        }
-        return new ConscryptHostnameVerifier() {
-            @Override
-            public boolean verify(String hostname, SSLSession session) {
-                return verifier.verify(hostname, session);
-            }
-        };
-    }
-
     private X509TrustManager trustManager(X509Certificate ca) throws Exception {
         KeyStore keyStore = TestKeyStore.createKeyStore();
         keyStore.setCertificateEntry("alias", ca);
@@ -508,8 +481,4 @@
             throw new UnsupportedOperationException();
         }
     }
-
-    private static class TestHostnameVerifier
-            extends com.android.org.conscrypt.javax.net.ssl.TestHostnameVerifier
-            implements ConscryptHostnameVerifier {}
 }
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestEC.java b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestEC.java
index bee284a..de48861 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestEC.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestEC.java
@@ -27,6 +27,7 @@
 import java.util.List;
 import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule;
 import org.junit.ClassRule;
+import org.junit.Test;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -36,40 +37,38 @@
  * @hide This class is not part of the Android public SDK API
  */
 @RunWith(JUnit4.class)
-public class KeyFactoryTestEC extends
-    AbstractKeyFactoryTest<ECPublicKeySpec, ECPrivateKeySpec> {
+public class KeyFactoryTestEC extends AbstractKeyFactoryTest<ECPublicKeySpec, ECPrivateKeySpec> {
+    // BEGIN Android-Added: Allow access to deprecated BC algorithms.
+    // Allow access to deprecated BC algorithms in this test, so we can ensure they
+    // continue to work
+    @ClassRule
+    public static TestRule enableDeprecatedBCAlgorithmsRule =
+            EnableDeprecatedBouncyCastleAlgorithmsRule.getInstance();
+    // END Android-Added: Allow access to deprecated BC algorithms.
 
-  // BEGIN Android-Added: Allow access to deprecated BC algorithms.
-  // Allow access to deprecated BC algorithms in this test, so we can ensure they
-  // continue to work
-  @ClassRule
-  public static TestRule enableDeprecatedBCAlgorithmsRule =
-          EnableDeprecatedBouncyCastleAlgorithmsRule.getInstance();
-  // END Android-Added: Allow access to deprecated BC algorithms.
+    public KeyFactoryTestEC() {
+        super("EC", ECPublicKeySpec.class, ECPrivateKeySpec.class);
+    }
 
-  public KeyFactoryTestEC() {
-    super("EC", ECPublicKeySpec.class, ECPrivateKeySpec.class);
-  }
+    @Override
+    public ServiceTester customizeTester(ServiceTester tester) {
+        // BC's EC keys always use explicit params, even though it's a bad idea, and we don't
+        // support those, so don't test BC keys
+        return tester.skipProvider("BC");
+    }
 
-  @Override
-  public ServiceTester customizeTester(ServiceTester tester) {
-    // BC's EC keys always use explicit params, even though it's a bad idea, and we don't support
-    // those, so don't test BC keys
-    return tester.skipProvider("BC");
-  }
+    @Override
+    protected void check(KeyPair keyPair) throws Exception {
+        new SignatureHelper("SHA256withECDSA").test(keyPair);
+    }
 
-  @Override
-  protected void check(KeyPair keyPair) throws Exception {
-    new SignatureHelper("SHA256withECDSA").test(keyPair);
-  }
-
-  @Override
-  protected List<KeyPair> getKeys() throws NoSuchAlgorithmException, InvalidKeySpecException {
-      return Arrays.asList(
-              new KeyPair(DefaultKeys.getPublicKey("EC"), DefaultKeys.getPrivateKey("EC")),
-              new KeyPair(new TestPublicKey(DefaultKeys.getPublicKey("EC")),
-                      new TestPrivateKey(DefaultKeys.getPrivateKey("EC"))),
-              new KeyPair(new TestECPublicKey((ECPublicKey) DefaultKeys.getPublicKey("EC")),
-                      new TestECPrivateKey((ECPrivateKey) DefaultKeys.getPrivateKey("EC"))));
-  }
+    @Override
+    protected List<KeyPair> getKeys() throws NoSuchAlgorithmException, InvalidKeySpecException {
+        return Arrays.asList(
+                new KeyPair(DefaultKeys.getPublicKey("EC"), DefaultKeys.getPrivateKey("EC")),
+                new KeyPair(new TestPublicKey(DefaultKeys.getPublicKey("EC")),
+                        new TestPrivateKey(DefaultKeys.getPrivateKey("EC"))),
+                new KeyPair(new TestECPublicKey((ECPublicKey) DefaultKeys.getPublicKey("EC")),
+                        new TestECPrivateKey((ECPrivateKey) DefaultKeys.getPrivateKey("EC"))));
+    }
 }
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 1ad01a7..6b0567e 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
@@ -20,7 +20,6 @@
 
 import java.security.KeyFactory;
 import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.Provider;
 import java.security.PublicKey;
@@ -44,9 +43,7 @@
  * @hide This class is not part of the Android public SDK API
  */
 @RunWith(JUnit4.class)
-public class KeyFactoryTestRSA extends
-        AbstractKeyFactoryTest<RSAPublicKeySpec, RSAPrivateKeySpec> {
-
+public class KeyFactoryTestRSA extends AbstractKeyFactoryTest<RSAPublicKeySpec, RSAPrivateKeySpec> {
     // BEGIN Android-Added: Allow access to deprecated BC algorithms.
     // Allow access to deprecated BC algorithms in this test, so we can ensure they
     // continue to work
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 074dd38..15b1628 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
@@ -19,9 +19,6 @@
 import java.security.KeyPair;
 import java.security.spec.RSAPrivateCrtKeySpec;
 import java.security.spec.RSAPublicKeySpec;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import tests.util.ServiceTester;
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestRSACustom.java b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestRSACustom.java
index 570929f..05dd7ad 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestRSACustom.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestRSACustom.java
@@ -18,16 +18,11 @@
 
 import java.security.KeyPair;
 import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.RSAPrivateKeySpec;
 import java.security.spec.RSAPublicKeySpec;
 import java.util.Arrays;
 import java.util.List;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import tests.util.ServiceTester;
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestXDH.java b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestXDH.java
new file mode 100644
index 0000000..31aeda9
--- /dev/null
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyFactoryTestXDH.java
@@ -0,0 +1,65 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2019 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.java.security;
+
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import tests.util.ServiceTester;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+@RunWith(JUnit4.class)
+public class KeyFactoryTestXDH extends
+    AbstractKeyFactoryTest<X509EncodedKeySpec, PKCS8EncodedKeySpec> {
+
+  public KeyFactoryTestXDH() {
+    super("XDH", X509EncodedKeySpec.class, PKCS8EncodedKeySpec.class);
+  }
+
+  @Override
+  protected void check(KeyPair keyPair) throws Exception {
+    new KeyAgreementHelper("XDH").test(keyPair);
+  }
+
+  @Override
+  protected ServiceTester customizeTester(ServiceTester tester) {
+    // TODO: fix this test when Conscrypt's XDH keys can inherit from XECPublicKey and XECPrivateKey
+    return tester.skipProvider("SunEC");
+  }
+
+  @Override
+  protected List<KeyPair> getKeys() throws NoSuchAlgorithmException, InvalidKeySpecException {
+    return Arrays.asList(
+        new KeyPair(
+            DefaultKeys.getPublicKey("XDH"),
+            DefaultKeys.getPrivateKey("XDH")
+        ),
+        new KeyPair(
+            new TestPublicKey(DefaultKeys.getPublicKey("XDH")),
+            new TestPrivateKey(DefaultKeys.getPrivateKey("XDH"))
+        )
+    );
+  }
+}
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyPairGeneratorTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyPairGeneratorTest.java
index 7ac65b6..9d746dc 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyPairGeneratorTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyPairGeneratorTest.java
@@ -319,6 +319,19 @@
                     // so ignore it.
                     continue;
                 }
+                if ("XDH".equals(k.getAlgorithm()) && "SunEC".equalsIgnoreCase(p.getName())
+                        && "11".equals(System.getProperty("java.specification.version"))) {
+                    // SunEC in OpenJDK 11 has a bug where the format specified in RFC 8410
+                    // Section 7. It uses a single OCTET STRING to represent the key instead
+                    // of an OCTET STRING inside of an OCTET STRING as defined in the RFC:
+                    // ("For the keys defined in this document, the private key is always an
+                    //   opaque byte sequence.  The ASN.1 type CurvePrivateKey is defined in
+                    //   this document to hold the byte sequence.  Thus, when encoding a
+                    //   OneAsymmetricKey object, the private key is wrapped in a
+                    //   CurvePrivateKey object and wrapped by the OCTET STRING of the
+                    //   "privateKey" field.")
+                    continue;
+                }
 
                 if ("PKCS#8".equals(k.getFormat())) {
                     PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(encoded);
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyPairGeneratorTestRSA.java b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyPairGeneratorTestRSA.java
index 2e9d074..f2f2539 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyPairGeneratorTestRSA.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyPairGeneratorTestRSA.java
@@ -25,10 +25,7 @@
 @RunWith(JUnit4.class)
 public class KeyPairGeneratorTestRSA extends AbstractKeyPairGeneratorTest {
 
-    @SuppressWarnings("unchecked")
     public KeyPairGeneratorTestRSA() {
         super("RSA", new CipherAsymmetricCryptHelper("RSA"));
     }
-
 }
-
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyPairGeneratorTestXDH.java b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyPairGeneratorTestXDH.java
new file mode 100644
index 0000000..3249c66
--- /dev/null
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/KeyPairGeneratorTestXDH.java
@@ -0,0 +1,36 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2009 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.java.security;
+
+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 KeyPairGeneratorTestXDH extends AbstractKeyPairGeneratorTest {
+
+    public KeyPairGeneratorTestXDH() {
+        super("XDH", new KeyAgreementHelper("XDH"));
+    }
+
+    @Override
+    protected int getKeySize() {
+        return 255;
+    }
+}
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/SignatureTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/SignatureTest.java
index e3c3122..1599bfc 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/SignatureTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/SignatureTest.java
@@ -116,6 +116,10 @@
                 // https://bugs.openjdk.java.net/browse/JDK-8044554), but skip verifying it all
                 // the same.
                 .skipProvider("SunPKCS11-NSS")
+                // We don't have code to generate key pairs for these yet.
+                .skipAlgorithm("Ed448")
+                .skipAlgorithm("Ed25519")
+                .skipAlgorithm("EdDSA")
                 .run(new ServiceTester.Test() {
                     @Override
                     public void test(Provider provider, String algorithm) throws Exception {
@@ -2939,13 +2943,54 @@
      * randomized, so this won't be the exact signature you'll get out of
      * another signing operation unless you use a fixed RNG.
      */
-    public static final byte[] SHA1withDSA_Vector2Signature = new byte[] {
-        (byte) 0x30, (byte) 0x2d, (byte) 0x02, (byte) 0x15, (byte) 0x00, (byte) 0x88, (byte) 0xef, (byte) 0xac,
-        (byte) 0x2b, (byte) 0x8b, (byte) 0xe2, (byte) 0x61, (byte) 0xc6, (byte) 0x2b, (byte) 0xea, (byte) 0xd5,
-        (byte) 0x96, (byte) 0xbc, (byte) 0xb0, (byte) 0xa1, (byte) 0x30, (byte) 0x0c, (byte) 0x1f, (byte) 0xed,
-        (byte) 0x11, (byte) 0x02, (byte) 0x14, (byte) 0x15, (byte) 0xc4, (byte) 0xfc, (byte) 0x82, (byte) 0x6f,
-        (byte) 0x17, (byte) 0xdc, (byte) 0x87, (byte) 0x82, (byte) 0x75, (byte) 0x23, (byte) 0xd4, (byte) 0x58,
-        (byte) 0xdc, (byte) 0x73, (byte) 0x3d, (byte) 0xf3, (byte) 0x51, (byte) 0xc0, (byte) 0x57,
+    private static final byte[] SHA1withDSA_Vector2Signature = new byte[] {
+            (byte) 0x30,
+            (byte) 0x2d,
+            (byte) 0x02,
+            (byte) 0x15,
+            (byte) 0x00,
+            (byte) 0x88,
+            (byte) 0xef,
+            (byte) 0xac,
+            (byte) 0x2b,
+            (byte) 0x8b,
+            (byte) 0xe2,
+            (byte) 0x61,
+            (byte) 0xc6,
+            (byte) 0x2b,
+            (byte) 0xea,
+            (byte) 0xd5,
+            (byte) 0x96,
+            (byte) 0xbc,
+            (byte) 0xb0,
+            (byte) 0xa1,
+            (byte) 0x30,
+            (byte) 0x0c,
+            (byte) 0x1f,
+            (byte) 0xed,
+            (byte) 0x11,
+            (byte) 0x02,
+            (byte) 0x14,
+            (byte) 0x15,
+            (byte) 0xc4,
+            (byte) 0xfc,
+            (byte) 0x82,
+            (byte) 0x6f,
+            (byte) 0x17,
+            (byte) 0xdc,
+            (byte) 0x87,
+            (byte) 0x82,
+            (byte) 0x75,
+            (byte) 0x23,
+            (byte) 0xd4,
+            (byte) 0x58,
+            (byte) 0xdc,
+            (byte) 0x73,
+            (byte) 0x3d,
+            (byte) 0xf3,
+            (byte) 0x51,
+            (byte) 0xc0,
+            (byte) 0x57,
     };
 
     /**
@@ -2953,28 +2998,30 @@
      * randomized, so this won't be the exact signature you'll get out of
      * another signing operation unless you use a fixed RNG.
      */
-    public static final byte[] SHA224withDSA_Vector2Signature = new byte[] {
-        (byte) 0x30, (byte) 0x2D, (byte) 0x02, (byte) 0x15, (byte) 0x00, (byte) 0xAD, (byte) 0xE5, (byte) 0x6D,
-        (byte) 0xF5, (byte) 0x11, (byte) 0x8D, (byte) 0x2E, (byte) 0x62, (byte) 0x5D, (byte) 0x98, (byte) 0x8A,
-        (byte) 0xC4, (byte) 0x88, (byte) 0x7E, (byte) 0xE6, (byte) 0xA3, (byte) 0x44, (byte) 0x99, (byte) 0xEF,
-        (byte) 0x49, (byte) 0x02, (byte) 0x14, (byte) 0x15, (byte) 0x3E, (byte) 0x32, (byte) 0xD6, (byte) 0xF9,
-        (byte) 0x79, (byte) 0x2C, (byte) 0x60, (byte) 0x6E, (byte) 0xF9, (byte) 0xA9, (byte) 0x78, (byte) 0xE7,
-        (byte) 0x4B, (byte) 0x87, (byte) 0x08, (byte) 0x96, (byte) 0x60, (byte) 0xDE, (byte) 0xB5
-    };
+    private static final byte[] SHA224withDSA_Vector2Signature =
+            new byte[] {(byte) 0x30, (byte) 0x2D, (byte) 0x02, (byte) 0x15, (byte) 0x00,
+                    (byte) 0xAD, (byte) 0xE5, (byte) 0x6D, (byte) 0xF5, (byte) 0x11, (byte) 0x8D,
+                    (byte) 0x2E, (byte) 0x62, (byte) 0x5D, (byte) 0x98, (byte) 0x8A, (byte) 0xC4,
+                    (byte) 0x88, (byte) 0x7E, (byte) 0xE6, (byte) 0xA3, (byte) 0x44, (byte) 0x99,
+                    (byte) 0xEF, (byte) 0x49, (byte) 0x02, (byte) 0x14, (byte) 0x15, (byte) 0x3E,
+                    (byte) 0x32, (byte) 0xD6, (byte) 0xF9, (byte) 0x79, (byte) 0x2C, (byte) 0x60,
+                    (byte) 0x6E, (byte) 0xF9, (byte) 0xA9, (byte) 0x78, (byte) 0xE7, (byte) 0x4B,
+                    (byte) 0x87, (byte) 0x08, (byte) 0x96, (byte) 0x60, (byte) 0xDE, (byte) 0xB5};
 
     /**
      * A possible signature using SHA256withDSA of Vector2Data. Note that DSS is
      * randomized, so this won't be the exact signature you'll get out of
      * another signing operation unless you use a fixed RNG.
      */
-    public static final byte[] SHA256withDSA_Vector2Signature = new byte[] {
-        (byte) 0x30, (byte) 0x2D, (byte) 0x02, (byte) 0x14, (byte) 0x0A, (byte) 0xB1, (byte) 0x74, (byte) 0x45,
-        (byte) 0xE1, (byte) 0x63, (byte) 0x43, (byte) 0x68, (byte) 0x65, (byte) 0xBC, (byte) 0xCA, (byte) 0x45,
-        (byte) 0x27, (byte) 0x11, (byte) 0x4D, (byte) 0x52, (byte) 0xFB, (byte) 0x22, (byte) 0x93, (byte) 0xDD,
-        (byte) 0x02, (byte) 0x15, (byte) 0x00, (byte) 0x98, (byte) 0x32, (byte) 0x1A, (byte) 0x16, (byte) 0x77,
-        (byte) 0x49, (byte) 0xA7, (byte) 0x78, (byte) 0xFD, (byte) 0xE0, (byte) 0xF7, (byte) 0x71, (byte) 0xD4,
-        (byte) 0x80, (byte) 0x50, (byte) 0xA7, (byte) 0xDD, (byte) 0x94, (byte) 0xD1, (byte) 0x6C
-    };
+    private static final byte[] SHA256withDSA_Vector2Signature =
+            new byte[] {(byte) 0x30, (byte) 0x2D, (byte) 0x02, (byte) 0x14, (byte) 0x0A,
+                    (byte) 0xB1, (byte) 0x74, (byte) 0x45, (byte) 0xE1, (byte) 0x63, (byte) 0x43,
+                    (byte) 0x68, (byte) 0x65, (byte) 0xBC, (byte) 0xCA, (byte) 0x45, (byte) 0x27,
+                    (byte) 0x11, (byte) 0x4D, (byte) 0x52, (byte) 0xFB, (byte) 0x22, (byte) 0x93,
+                    (byte) 0xDD, (byte) 0x02, (byte) 0x15, (byte) 0x00, (byte) 0x98, (byte) 0x32,
+                    (byte) 0x1A, (byte) 0x16, (byte) 0x77, (byte) 0x49, (byte) 0xA7, (byte) 0x78,
+                    (byte) 0xFD, (byte) 0xE0, (byte) 0xF7, (byte) 0x71, (byte) 0xD4, (byte) 0x80,
+                    (byte) 0x50, (byte) 0xA7, (byte) 0xDD, (byte) 0x94, (byte) 0xD1, (byte) 0x6C};
 
     @Test
     public void testSign_SHA1withDSA_Key_Success() throws Exception {
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/cert/CertificateFactoryTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/cert/CertificateFactoryTest.java
index 1cb4537..9146ca9 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/cert/CertificateFactoryTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/cert/CertificateFactoryTest.java
@@ -19,6 +19,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -53,6 +54,7 @@
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Date;
 import java.util.GregorianCalendar;
 import java.util.Iterator;
@@ -209,6 +211,48 @@
         + "zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6z5nRUP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqA"
         + "ibAxWEEHXw==";
 
+    // Generated with openssl crl2pkcs7 -nocrl -certfile cert.pem
+    private static final String VALID_CERTIFICATE_PKCS7_PEM = "-----BEGIN PKCS7-----\n"
+            + "MIIDUgYJKoZIhvcNAQcCoIIDQzCCAz8CAQExADALBgkqhkiG9w0BBwGgggMlMIID\n"
+            + "ITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBMMQsw\n"
+            + "CQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRk\n"
+            + "LjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0xMTEy\n"
+            + "MTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw\n"
+            + "FAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcwFQYD\n"
+            + "VQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA\n"
+            + "6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jNgtXj\n"
+            + "9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L05vu\n"
+            + "uWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAMBgNV\n"
+            + "HRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3RlLmNv\n"
+            + "bS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUFBwMC\n"
+            + "BglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRwOi8v\n"
+            + "b2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0ZS5j\n"
+            + "b20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUFAAOB\n"
+            + "gQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5u2ON\n"
+            + "gJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6z5nR\n"
+            + "UP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHX6EAMQA=\n"
+            + "-----END PKCS7-----\n";
+
+    private static final String VALID_CERTIFICATE_PKCS7_DER_BASE64 =
+            "MIIDUgYJKoZIhvcNAQcCoIIDQzCCAz8CAQExADALBgkqhkiG9w0BBwGgggMlMIID"
+            + "ITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBMMQsw"
+            + "CQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRk"
+            + "LjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0xMTEy"
+            + "MTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYw"
+            + "FAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcwFQYD"
+            + "VQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA"
+            + "6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jNgtXj"
+            + "9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L05vu"
+            + "uWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAMBgNV"
+            + "HRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3RlLmNv"
+            + "bS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUFBwMC"
+            + "BglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRwOi8v"
+            + "b2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0ZS5j"
+            + "b20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUFAAOB"
+            + "gQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5u2ON"
+            + "gJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6z5nR"
+            + "UP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHX6EAMQA=";
+
     private static final String VALID_CRL_PEM =
         "-----BEGIN X509 CRL-----\n"
             + "MIIBUTCBuwIBATANBgkqhkiG9w0BAQsFADBVMQswCQYDVQQGEwJHQjEkMCIGA1UE\n"
@@ -243,6 +287,30 @@
             + "CCHWAw8WN9XSJ4nGxdRiacG/5vEIx00ICUGCeGcnqWsSnFtagDtvry2c4MMexbSP"
             + "nDN0LLg=";
 
+    // Generated with openssl crl2pkcs7 -in crl.pem
+    private static final String VALID_CRL_PKCS7_PEM = "-----BEGIN PKCS7-----\n"
+            + "MIIBggYJKoZIhvcNAQcCoIIBczCCAW8CAQExADALBgkqhkiG9w0BBwGgAKGCAVUw\n"
+            + "ggFRMIG7AgEBMA0GCSqGSIb3DQEBCwUAMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQK\n"
+            + "ExtDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAw\n"
+            + "DgYDVQQHEwdFcncgV2VuFw0xOTA4MDcxMDI3MTBaFw0xOTA5MDYxMDI3MTBaMCIw\n"
+            + "IAIBBxcNMTkwODA3MTAyNjU0WjAMMAoGA1UdFQQDCgEBoA4wDDAKBgNVHRQEAwIB\n"
+            + "AjANBgkqhkiG9w0BAQsFAAOBgQDMX8MuIi9kNfgWlKM0KfApFuWeEktnU00EAfFx\n"
+            + "Ft8Vjemyhu9sYY6PHMJBb/TeCSeAblWtJ91U4syZAOsDGkqp5ioUOPQcB6da6BsI\n"
+            + "IdYDDxY31dInicbF1GJpwb/m8QjHTQgJQYJ4ZyepaxKcW1qAO2+vLZzgwx7FtI+c\n"
+            + "M3QsuDEA\n"
+            + "-----END PKCS7-----\n";
+
+    private static final String VALID_CRL_PKCS7_DER_BASE64 =
+            "MIIBggYJKoZIhvcNAQcCoIIBczCCAW8CAQExADALBgkqhkiG9w0BBwGgAKGCAVUw"
+            + "ggFRMIG7AgEBMA0GCSqGSIb3DQEBCwUAMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQK"
+            + "ExtDZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAw"
+            + "DgYDVQQHEwdFcncgV2VuFw0xOTA4MDcxMDI3MTBaFw0xOTA5MDYxMDI3MTBaMCIw"
+            + "IAIBBxcNMTkwODA3MTAyNjU0WjAMMAoGA1UdFQQDCgEBoA4wDDAKBgNVHRQEAwIB"
+            + "AjANBgkqhkiG9w0BAQsFAAOBgQDMX8MuIi9kNfgWlKM0KfApFuWeEktnU00EAfFx"
+            + "Ft8Vjemyhu9sYY6PHMJBb/TeCSeAblWtJ91U4syZAOsDGkqp5ioUOPQcB6da6BsI"
+            + "IdYDDxY31dInicbF1GJpwb/m8QjHTQgJQYJ4ZyepaxKcW1qAO2+vLZzgwx7FtI+c"
+            + "M3QsuDEA";
+
     private static final String INVALID_CRL_PEM =
         "-----BEGIN X509 CRL-----\n"
             + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"
@@ -281,6 +349,15 @@
             + "AAAAAAAA\n"
             + "-----END X509 CRL-----\n";
 
+    // PKCS#7 file containing a small certificate. This input is small enough to use a two-byte
+    // length prefix.
+    private static final String SMALL_PKCS7_DER_BASE64 =
+            "MIHrBgkqhkiG9w0BBwKggd0wgdoCAQExADALBgkqhkiG9w0BBwGggcEwgb4wcgIB"
+            + "ADAFBgMrZXAwDDEKMAgGA1UEAxMBQTAeFw0xNDA0MjMyMzIxNTdaFw0xNDA1MjMy"
+            + "MzIxNTdaMAwxCjAIBgNVBAMTAUEwKjAFBgMrZXADIQDXWpgBgrEKt9VL/tPJZAc6"
+            + "DuFy89qmIyWvAhpo9wdRGjAFBgMrZXADQQBuCzqji8VP9xU8mHEMjXGChX7YP5J6"
+            + "64UyVKHKH9Z1u4wEbB8dJ3ScaWSLr+VHVKUhsrvcdCelnXRrrSD7xWALoQAxAA==";
+
     @Test
     public void test_generateCertificate() throws Exception {
         ServiceTester.test("CertificateFactory")
@@ -302,22 +379,59 @@
     }
 
     private void test_generateCertificate(CertificateFactory cf) throws Exception {
+        Certificate cert;
         {
             byte[] valid = VALID_CERTIFICATE_PEM.getBytes(Charset.defaultCharset());
             Certificate c = cf.generateCertificate(new ByteArrayInputStream(valid));
             assertNotNull(c);
+            cert = c;
         }
 
         {
             byte[] valid = VALID_CERTIFICATE_PEM_CRLF.getBytes(Charset.defaultCharset());
             Certificate c = cf.generateCertificate(new ByteArrayInputStream(valid));
             assertNotNull(c);
+            assertEquals(c, cert);
         }
 
         {
             byte[] valid = TestUtils.decodeBase64(VALID_CERTIFICATE_DER_BASE64);
             Certificate c = cf.generateCertificate(new ByteArrayInputStream(valid));
             assertNotNull(c);
+            assertEquals(c, cert);
+        }
+
+        // The RI only supports PKCS#7 blobs with generateCertificates, not
+        // generateCertificate. See https://github.com/google/conscrypt/issues/987
+        if (!StandardNames.IS_RI) {
+            byte[] valid = TestUtils.decodeBase64(VALID_CERTIFICATE_PKCS7_DER_BASE64);
+            Certificate c = cf.generateCertificate(new ByteArrayInputStream(valid));
+            assertNotNull(c);
+            assertEquals(c, cert);
+        }
+
+        {
+            byte[] valid = VALID_CERTIFICATE_PKCS7_PEM.getBytes(Charset.defaultCharset());
+            Collection<? extends Certificate> cs =
+                    cf.generateCertificates(new ByteArrayInputStream(valid));
+            assertEquals(1, cs.size());
+            assertEquals(cs.iterator().next(), cert);
+        }
+
+        {
+            byte[] valid = TestUtils.decodeBase64(VALID_CERTIFICATE_PKCS7_DER_BASE64);
+            Collection<? extends Certificate> cs =
+                    cf.generateCertificates(new ByteArrayInputStream(valid));
+            assertEquals(1, cs.size());
+            assertEquals(cs.iterator().next(), cert);
+        }
+
+        {
+            byte[] valid = TestUtils.decodeBase64(SMALL_PKCS7_DER_BASE64);
+            Collection<? extends Certificate> cs =
+                    cf.generateCertificates(new ByteArrayInputStream(valid));
+            assertEquals(1, cs.size());
+            assertNotNull(cs.iterator().next());
         }
 
         try {
@@ -650,7 +764,7 @@
             assertEquals(providerName, Arrays.toString(pathFromList.getEncoded(encoding)),
                     Arrays.toString(encoded));
         }
-        assertFalse(providerName, Arrays.toString(encoded).equals(Arrays.toString(encodedCopy)));
+        assertNotEquals(providerName, Arrays.toString(encoded), Arrays.toString(encodedCopy));
 
         encodedCopy[0] ^= (byte) 0xFF;
         assertEquals(providerName, Arrays.toString(encoded), Arrays.toString(encodedCopy));
@@ -688,7 +802,7 @@
         Object output = ois.readObject();
         assertTrue(providerName, output instanceof CertPath);
 
-        assertEquals(providerName, actualPath, (CertPath) output);
+        assertEquals(providerName, actualPath, output);
     }
 
     /**
@@ -789,12 +903,33 @@
         assertNotNull(c);
 
         valid = VALID_CRL_PEM_CRLF.getBytes(Charset.defaultCharset());
-        c = cf.generateCRL(new ByteArrayInputStream(valid));
-        assertNotNull(c);
+        CRL c2 = cf.generateCRL(new ByteArrayInputStream(valid));
+        assertNotNull(c2);
+        assertEquals(c, c2);
 
         valid = TestUtils.decodeBase64(VALID_CRL_DER_BASE64);
-        c = cf.generateCRL(new ByteArrayInputStream(valid));
+        c2 = cf.generateCRL(new ByteArrayInputStream(valid));
         assertNotNull(c);
+        assertEquals(c, c2);
+
+        // The RI only supports PKCS#7 with generateCRLs, not generateCRL.
+        // See https://github.com/google/conscrypt/issues/987
+        if (!StandardNames.IS_RI) {
+            valid = TestUtils.decodeBase64(VALID_CRL_PKCS7_DER_BASE64);
+            c2 = cf.generateCRL(new ByteArrayInputStream(valid));
+            assertNotNull(c);
+            assertEquals(c, c2);
+        }
+
+        valid = TestUtils.decodeBase64(VALID_CRL_PKCS7_DER_BASE64);
+        Collection<? extends CRL> crls = cf.generateCRLs(new ByteArrayInputStream(valid));
+        assertEquals(1, crls.size());
+        assertEquals(c, crls.iterator().next());
+
+        valid = VALID_CRL_PKCS7_PEM.getBytes(Charset.defaultCharset());
+        crls = cf.generateCRLs(new ByteArrayInputStream(valid));
+        assertEquals(1, crls.size());
+        assertEquals(c, crls.iterator().next());
 
         try {
             byte[] invalid = INVALID_CRL_PEM.getBytes(Charset.defaultCharset());
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/cert/X509CRLTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/cert/X509CRLTest.java
index 8ce395b..ac3ee2b 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/cert/X509CRLTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/cert/X509CRLTest.java
@@ -17,12 +17,14 @@
 
 package com.android.org.conscrypt.java.security.cert;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.org.conscrypt.TestUtils;
 import java.io.ByteArrayInputStream;
 import java.nio.charset.StandardCharsets;
 import java.security.Provider;
@@ -106,6 +108,23 @@
             + "nDN0LLg=\n"
             + "-----END X509 CRL-----\n";
 
+    private static final String CRL_TBS_BASE64 =
+            "MIG7AgEBMA0GCSqGSIb3DQEBCwUAMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtD"
+            + "ZXJ0aWZpY2F0ZSBUcmFuc3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYD"
+            + "VQQHEwdFcncgV2VuFw0xOTA4MDcxMDI3MTBaFw0xOTA5MDYxMDI3MTBaMCIwIAIB"
+            + "BxcNMTkwODA3MTAyNjU0WjAMMAoGA1UdFQQDCgEBoA4wDDAKBgNVHRQEAwIBAg==";
+
+    private static final String UNKNOWN_SIGNATURE_OID = "-----BEGIN X509 CRL-----\n"
+            + "MIIBVzCBvgIBATAQBgwqhkiG9xIEAYS3CQIFADBVMQswCQYDVQQGEwJHQjEkMCIG\n"
+            + "A1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVXYWxl\n"
+            + "czEQMA4GA1UEBxMHRXJ3IFdlbhcNMTkwODA3MTAyNzEwWhcNMTkwOTA2MTAyNzEw\n"
+            + "WjAiMCACAQcXDTE5MDgwNzEwMjY1NFowDDAKBgNVHRUEAwoBAaAOMAwwCgYDVR0U\n"
+            + "BAMCAQIwEAYMKoZIhvcSBAGEtwkCBQADgYEAzF/DLiIvZDX4FpSjNCnwKRblnhJL\n"
+            + "Z1NNBAHxcRbfFY3psobvbGGOjxzCQW/03gkngG5VrSfdVOLMmQDrAxpKqeYqFDj0\n"
+            + "HAenWugbCCHWAw8WN9XSJ4nGxdRiacG/5vEIx00ICUGCeGcnqWsSnFtagDtvry2c\n"
+            + "4MMexbSPnDN0LLg=\n"
+            + "-----END X509 CRL-----\n";
+
     @Test
     public void testCrl() throws Exception {
         ServiceTester.test("CertificateFactory")
@@ -130,6 +149,9 @@
                     } catch (SignatureException expected) {
                     }
 
+                    byte[] expectedTBSCertList = TestUtils.decodeBase64(CRL_TBS_BASE64);
+                    assertArrayEquals(expectedTBSCertList, crl.getTBSCertList());
+
                     assertTrue(crl.isRevoked(revoked));
                     X509CRLEntry entry = crl.getRevokedCertificate(revoked);
                     assertEquals(CRLReason.KEY_COMPROMISE, entry.getRevocationReason());
@@ -144,4 +166,19 @@
                 }
             });
     }
+
+    @Test
+    public void testUnknownSigAlgOID() throws Exception {
+        ServiceTester.test("CertificateFactory")
+                .withAlgorithm("X509")
+                .run(new ServiceTester.Test() {
+                    @Override
+                    public void test(Provider p, String algorithm) throws Exception {
+                        CertificateFactory cf = CertificateFactory.getInstance("X509", p);
+                        X509CRL crl = (X509CRL) cf.generateCRL(new ByteArrayInputStream(
+                                UNKNOWN_SIGNATURE_OID.getBytes(StandardCharsets.US_ASCII)));
+                        assertEquals("1.2.840.113554.4.1.72585.2", crl.getSigAlgOID());
+                    }
+                });
+    }
 }
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/cert/X509CertificateTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/cert/X509CertificateTest.java
index 56a8a68..473b176 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/cert/X509CertificateTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/java/security/cert/X509CertificateTest.java
@@ -17,24 +17,38 @@
 
 package com.android.org.conscrypt.java.security.cert;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
+import com.android.org.conscrypt.TestUtils;
 import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
 import java.nio.charset.Charset;
 import java.security.InvalidKeyException;
 import java.security.Provider;
-import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+import javax.security.auth.x500.X500Principal;
 import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import tests.util.Pair;
 import tests.util.ServiceTester;
 
 /**
@@ -139,6 +153,247 @@
             + "Qhy0YgIgYWr0qSCLqxUQv3oQHMUpSmfHtP0Pwvb3DbbH6lY7TkI=\n"
             + "-----END CERTIFICATE-----\n";
 
+    /**
+     * This cert is signed with OID 1.2.840.113554.4.1.72585.2 instead of a
+     * standard one.
+     */
+    private static final String UNKNOWN_SIGNATURE_OID = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIB2TCCAXugAwIBAgIJANlMBNpJfb/rMA4GDCqGSIb3EgQBhLcJAjBFMQswCQYD\n"
+            + "VQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQg\n"
+            + "V2lkZ2l0cyBQdHkgTHRkMB4XDTE0MDQyMzIzMjE1N1oXDTE0MDUyMzIzMjE1N1ow\n"
+            + "RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu\n"
+            + "dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA\n"
+            + "BOYraeK/ZZ+Xvi8eDZSKTNWXa7epHg1G+92pqR6d3LpaAefWl6gKGPnDxKMeVuJ8\n"
+            + "g0jbFhoc9R1+8ZQtS89yIsGjUDBOMB0GA1UdDgQWBBSrhNKsq5Xwgk4WeAdVV1/k\n"
+            + "Jo2C0TAfBgNVHSMEGDAWgBSrhNKsq5Xwgk4WeAdVV1/kJo2C0TAMBgNVHRMEBTAD\n"
+            + "AQH/MA4GDCqGSIb3EgQBhLcJAgNIADBFAiEA8qA1XlE6NsOCeZvuJ1CFjnAGdJVX\n"
+            + "0il0APS+FYddxAcCIHweeRRqIYPwenRoeV8UmZpotPHLnhVe5h8yUmFedckU\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /**
+     * This is an X.509v1 certificatea, so most fields are missing. It exists to test accessors
+     * correctly handle the lack of fields. It was constructed by hand, so the signature itself is
+     * invalid.
+     */
+    private static final String X509V1_CERT = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBGjCBwgIJANlMBNpJfb/rMAkGByqGSM49BAEwFjEUMBIGA1UEAwwLVGVzdCBJ\n"
+            + "c3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjAXMRUwEwYDVQQD\n"
+            + "DAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATmK2niv2Wf\n"
+            + "l74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI2xYaHPUd\n"
+            + "fvGULUvPciLBMAkGByqGSM49BAEDSAAwRQIhAPKgNV5ROjbDgnmb7idQhY5wBnSV\n"
+            + "V9IpdAD0vhWHXcQHAiB8HnkUaiGD8Hp0aHlfFJmaaLTxy54VXuYfMlJhXnXJFA==\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /**
+     * This is a certificate with many extensions filled it. It exists to test accessors correctly
+     * report fields. It was constructed by hand, so the signature itself is invalid. Add more
+     * fields as necessary with https://github.com/google/der-ascii.
+     */
+    private static final String MANY_EXTENSIONS = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIEADCCAuigAwIBAgIJALW2IrlaBKUhMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV\n"
+            + "BAMMC1Rlc3QgSXNzdWVyMB4XDTE2MDcwOTA0MzgwOVoXDTE2MDgwODA0MzgwOVow\n"
+            + "FzEVMBMGA1UEAwwMVGVzdCBTdWJqZWN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n"
+            + "MIIBCgKCAQEAugvahBkSAUF1fC49vb1bvlPrcl80kop1iLpiuYoz4Qptwy57+EWs\n"
+            + "sZBcHprZ5BkWf6PeGZ7F5AX1PyJbGHZLqvMCvViP6pd4MFox/igESISEHEixoiXC\n"
+            + "zepBrhtp5UQSjHD4D4hKtgdMgVxX+LRtwgW3mnu/vBu7rzpr/DS8io99p3lqZ1Ak\n"
+            + "y+aNlcMj6MYy8U+YFEevb/V0lRY9oqwmW7BHnXikm/vi6sjIS350U8zb/mRzYeIs\n"
+            + "2R65LUduTL50+UMgat9ocewI2dv8aO9Dph+8NdGtg8LFYyTTHcUxJoMr1PTOgnmE\n"
+            + "T19WJH4PrFwk7ZE1QJQQ1L4iKmPeQistuQIDAQABgQIEoIICA1CjggFGMIIBQjAP\n"
+            + "BgNVHRMECDAGAQH/AgEKMCEGA1UdJQQaMBgGCCsGAQUFBwMBBgwqhkiG9xIEAYS3\n"
+            + "CQIwfwYDVR0RBHgwdoETc3ViamVjdEBleGFtcGxlLmNvbYITc3ViamVjdC5leGFt\n"
+            + "cGxlLmNvbaQZMBcxFTATBgNVBAMMDFRlc3QgU3ViamVjdIYbaHR0cHM6Ly9leGFt\n"
+            + "cGxlLmNvbS9zdWJqZWN0hwR/AAABiAwqhkiG9xIEAYS3CQIwewYDVR0SBHQwcoES\n"
+            + "aXNzdWVyQGV4YW1wbGUuY29tghJpc3N1ZXIuZXhhbXBsZS5jb22kGDAWMRQwEgYD\n"
+            + "VQQDDAtUZXN0IElzc3VlcoYaaHR0cHM6Ly9leGFtcGxlLmNvbS9pc3N1ZXKHBH8A\n"
+            + "AAGIDCqGSIb3EgQBhLcJAjAOBgNVHQ8BAf8EBAMCBaAwDQYJKoZIhvcNAQELBQAD\n"
+            + "ggEBAD7Jg68SArYWlcoHfZAB90Pmyrt5H6D8LRi+W2Ri1fBNxREELnezWJ2scjl4\n"
+            + "UMcsKYp4Pi950gVN+62IgrImcCNvtb5I1Cfy/MNNur9ffas6X334D0hYVIQTePyF\n"
+            + "k3umI+2mJQrtZZyMPIKSY/sYGQHhGGX6wGK+GO/og0PQk/Vu6D+GU2XRnDV0YZg1\n"
+            + "lsAsHd21XryK6fDmNkEMwbIWrts4xc7scRrGHWy+iMf6/7p/Ak/SIicM4XSwmlQ8\n"
+            + "pPxAZPr+E2LoVd9pMpWUwpW2UbtO5wsGTrY5sO45tFNN/y+jtUheB1C2ijObG/tX\n"
+            + "ELaiyCdM+S/waeuv0MXtI4xnn1A=\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /**
+     * This is a certificate whose basicConstraints extension marks it as a CA, with no pathlen
+     * constraint.
+     */
+    private static final String BASIC_CONSTRAINTS_NO_PATHLEN = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBMzCB2qADAgECAgkA2UwE2kl9v+swCgYIKoZIzj0EAwIwFjEUMBIGA1UEAwwL\n"
+            + "VGVzdCBJc3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjAXMRUw\n"
+            + "EwYDVQQDDAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATm\n"
+            + "K2niv2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI\n"
+            + "2xYaHPUdfvGULUvPciLBoxAwDjAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMCA0gA\n"
+            + "MEUCIQDyoDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13EBwIgfB55FGohg/B6\n"
+            + "dGh5XxSZmmi08cueFV7mHzJSYV51yRQ=\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /**
+     * This is a certificate whose basicConstraints extension marks it as a CA with a pathlen
+     * constraint of zero.
+     */
+    private static final String BASIC_CONSTRAINTS_PATHLEN_0 = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBNjCB3aADAgECAgkA2UwE2kl9v+swCgYIKoZIzj0EAwIwFjEUMBIGA1UEAwwL\n"
+            + "VGVzdCBJc3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjAXMRUw\n"
+            + "EwYDVQQDDAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATm\n"
+            + "K2niv2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI\n"
+            + "2xYaHPUdfvGULUvPciLBoxMwETAPBgNVHRMECDAGAQH/AgEAMAoGCCqGSM49BAMC\n"
+            + "A0gAMEUCIQDyoDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13EBwIgfB55FGoh\n"
+            + "g/B6dGh5XxSZmmi08cueFV7mHzJSYV51yRQ=\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /**
+     * This is a certificate whose basicConstraints extension marks it as a leaf certificate.
+     */
+    private static final String BASIC_CONSTRAINTS_LEAF = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBMDCB16ADAgECAgkA2UwE2kl9v+swCgYIKoZIzj0EAwIwFjEUMBIGA1UEAwwL\n"
+            + "VGVzdCBJc3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjAXMRUw\n"
+            + "EwYDVQQDDAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATm\n"
+            + "K2niv2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI\n"
+            + "2xYaHPUdfvGULUvPciLBow0wCzAJBgNVHRMEAjAAMAoGCCqGSM49BAMCA0gAMEUC\n"
+            + "IQDyoDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13EBwIgfB55FGohg/B6dGh5\n"
+            + "XxSZmmi08cueFV7mHzJSYV51yRQ=\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /**
+     * This is a certificate with a pathlen constraint of 10, but there is an unrelated invalid
+     * subjectAltNames extension.
+     */
+    private static final String BASIC_CONSTRAINTS_PATHLEN_10_BAD_SAN =
+            "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBRjCB7aADAgECAgkA2UwE2kl9v+swCgYIKoZIzj0EAwIwFjEUMBIGA1UEAwwL\n"
+            + "VGVzdCBJc3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjAXMRUw\n"
+            + "EwYDVQQDDAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATm\n"
+            + "K2niv2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI\n"
+            + "2xYaHPUdfvGULUvPciLBoyMwITAPBgNVHRMECDAGAQH/AgEKMA4GA1UdEQQHSU5W\n"
+            + "QUxJRDAKBggqhkjOPQQDAgNIADBFAiEA8qA1XlE6NsOCeZvuJ1CFjnAGdJVX0il0\n"
+            + "APS+FYddxAcCIHweeRRqIYPwenRoeV8UmZpotPHLnhVe5h8yUmFedckU\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /**
+     * This is a certificate whose keyUsage extension has more than nine bits. The getKeyUsage()
+     * method internally rounds up to nine bits, so this tests what happens when it does not need to
+     * round.
+     */
+    private static final String LARGE_KEY_USAGE = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBNjCB3aADAgECAgkA2UwE2kl9v+swCgYIKoZIzj0EAwIwFjEUMBIGA1UEAwwL\n"
+            + "VGVzdCBJc3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3WjAXMRUw\n"
+            + "EwYDVQQDDAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATm\n"
+            + "K2niv2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8SjHlbifINI\n"
+            + "2xYaHPUdfvGULUvPciLBoxMwETAPBgNVHQ8BAf8EBQMDBaAAMAoGCCqGSM49BAMC\n"
+            + "A0gAMEUCIQDyoDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13EBwIgfB55FGoh\n"
+            + "g/B6dGh5XxSZmmi08cueFV7mHzJSYV51yRQ=\n"
+            + "-----END CERTIFICATE-----\n";
+
+    /*
+     * OpenSSLX509Certificate needs to compensate for OpenSSL's AlgorithmIdentifier representation
+     * by re-encoding the parameter field. Test this behaves correctly against a variety of
+     * different parameter types.
+     */
+    private static final String SIGALG_NO_PARAMETER = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBKTCBzKADAgECAgkA2UwE2kl9v+swDgYMKoZIhvcSBAGEtwkCMBYxFDASBgNV\n"
+            + "BAMMC1Rlc3QgSXNzdWVyMB4XDTE0MDQyMzIzMjE1N1oXDTE0MDUyMzIzMjE1N1ow\n"
+            + "FzEVMBMGA1UEAwwMVGVzdCBTdWJqZWN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD\n"
+            + "QgAE5itp4r9ln5e+Lx4NlIpM1Zdrt6keDUb73ampHp3culoB59aXqAoY+cPEox5W\n"
+            + "4nyDSNsWGhz1HX7xlC1Lz3IiwTAOBgwqhkiG9xIEAYS3CQIDSAAwRQIhAPKgNV5R\n"
+            + "OjbDgnmb7idQhY5wBnSVV9IpdAD0vhWHXcQHAiB8HnkUaiGD8Hp0aHlfFJmaaLTx\n"
+            + "y54VXuYfMlJhXnXJFA==\n"
+            + "-----END CERTIFICATE-----\n";
+    private static final String SIGALG_NULL_PARAMETER = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBLTCBzqADAgECAgkA2UwE2kl9v+swEAYMKoZIhvcSBAGEtwkCBQAwFjEUMBIG\n"
+            + "A1UEAwwLVGVzdCBJc3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3\n"
+            + "WjAXMRUwEwYDVQQDDAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMB\n"
+            + "BwNCAATmK2niv2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8Sj\n"
+            + "HlbifINI2xYaHPUdfvGULUvPciLBMBAGDCqGSIb3EgQBhLcJAgUAA0gAMEUCIQDy\n"
+            + "oDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13EBwIgfB55FGohg/B6dGh5XxSZ\n"
+            + "mmi08cueFV7mHzJSYV51yRQ=\n"
+            + "-----END CERTIFICATE-----\n";
+    private static final String SIGALG_STRING_PARAMETER = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBNzCB06ADAgECAgkA2UwE2kl9v+swFQYMKoZIhvcSBAGEtwkCDAVwYXJhbTAW\n"
+            + "MRQwEgYDVQQDDAtUZXN0IElzc3VlcjAeFw0xNDA0MjMyMzIxNTdaFw0xNDA1MjMy\n"
+            + "MzIxNTdaMBcxFTATBgNVBAMMDFRlc3QgU3ViamVjdDBZMBMGByqGSM49AgEGCCqG\n"
+            + "SM49AwEHA0IABOYraeK/ZZ+Xvi8eDZSKTNWXa7epHg1G+92pqR6d3LpaAefWl6gK\n"
+            + "GPnDxKMeVuJ8g0jbFhoc9R1+8ZQtS89yIsEwFQYMKoZIhvcSBAGEtwkCDAVwYXJh\n"
+            + "bQNIADBFAiEA8qA1XlE6NsOCeZvuJ1CFjnAGdJVX0il0APS+FYddxAcCIHweeRRq\n"
+            + "IYPwenRoeV8UmZpotPHLnhVe5h8yUmFedckU\n"
+            + "-----END CERTIFICATE-----\n";
+    private static final String SIGALG_BOOLEAN_PARAMETER = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBLzCBz6ADAgECAgkA2UwE2kl9v+swEQYMKoZIhvcSBAGEtwkCAQH/MBYxFDAS\n"
+            + "BgNVBAMMC1Rlc3QgSXNzdWVyMB4XDTE0MDQyMzIzMjE1N1oXDTE0MDUyMzIzMjE1\n"
+            + "N1owFzEVMBMGA1UEAwwMVGVzdCBTdWJqZWN0MFkwEwYHKoZIzj0CAQYIKoZIzj0D\n"
+            + "AQcDQgAE5itp4r9ln5e+Lx4NlIpM1Zdrt6keDUb73ampHp3culoB59aXqAoY+cPE\n"
+            + "ox5W4nyDSNsWGhz1HX7xlC1Lz3IiwTARBgwqhkiG9xIEAYS3CQIBAf8DSAAwRQIh\n"
+            + "APKgNV5ROjbDgnmb7idQhY5wBnSVV9IpdAD0vhWHXcQHAiB8HnkUaiGD8Hp0aHlf\n"
+            + "FJmaaLTxy54VXuYfMlJhXnXJFA==\n"
+            + "-----END CERTIFICATE-----\n";
+    private static final String SIGALG_SEQUENCE_PARAMETER = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIBLTCBzqADAgECAgkA2UwE2kl9v+swEAYMKoZIhvcSBAGEtwkCMAAwFjEUMBIG\n"
+            + "A1UEAwwLVGVzdCBJc3N1ZXIwHhcNMTQwNDIzMjMyMTU3WhcNMTQwNTIzMjMyMTU3\n"
+            + "WjAXMRUwEwYDVQQDDAxUZXN0IFN1YmplY3QwWTATBgcqhkjOPQIBBggqhkjOPQMB\n"
+            + "BwNCAATmK2niv2Wfl74vHg2UikzVl2u3qR4NRvvdqakendy6WgHn1peoChj5w8Sj\n"
+            + "HlbifINI2xYaHPUdfvGULUvPciLBMBAGDCqGSIb3EgQBhLcJAjAAA0gAMEUCIQDy\n"
+            + "oDVeUTo2w4J5m+4nUIWOcAZ0lVfSKXQA9L4Vh13EBwIgfB55FGohg/B6dGh5XxSZ\n"
+            + "mmi08cueFV7mHzJSYV51yRQ=\n"
+            + "-----END CERTIFICATE-----\n";
+
+    private static Date dateFromUTC(int year, int month, int day, int hour, int minute, int second)
+            throws Exception {
+        Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+        c.set(year, month, day, hour, minute, second);
+        c.set(Calendar.MILLISECOND, 0);
+        return c.getTime();
+    }
+
+    private static X509Certificate certificateFromPEM(Provider p, String pem)
+            throws CertificateException {
+        CertificateFactory cf = CertificateFactory.getInstance("X509", p);
+        return (X509Certificate) cf.generateCertificate(
+                new ByteArrayInputStream(pem.getBytes(Charset.forName("US-ASCII"))));
+    }
+
+    private static List<Pair<Integer, String>> normalizeGeneralNames(Collection<List<?>> names) {
+        // Extract a more convenient type than Java's Collection<List<?>>.
+        List<Pair<Integer, String>> result = new ArrayList<Pair<Integer, String>>();
+        for (List<?> tuple : names) {
+            assertEquals(2, tuple.size());
+            int type = ((Integer) tuple.get(0)).intValue();
+            // TODO(davidben): Most name types are expected to have a String value, but some use
+            // byte[]. Update this logic when testing those name types. See
+            // X509Certificate.getSubjectAlternativeNames().
+            String value = (String) tuple.get(1);
+            result.add(Pair.of(type, value));
+        }
+        // Although there is a natural order (the order in the certificate), Java's API returns a
+        // Collection, so there is no guarantee of the provider using a particular order. Normalize
+        // the order before comparing.
+        Collections.sort(result, new Comparator<Pair<Integer, String>>() {
+            @Override
+            public int compare(Pair<Integer, String> a, Pair<Integer, String> b) {
+                int cmp = a.getFirst().compareTo(b.getFirst());
+                if (cmp != 0) {
+                    return cmp;
+                }
+                return a.getSecond().compareTo(b.getSecond());
+            }
+        });
+        return result;
+    }
+
+    private static void assertGeneralNamesEqual(
+            Collection<List<?>> expected, Collection<List<?>> actual) throws Exception {
+        assertEquals(normalizeGeneralNames(expected), normalizeGeneralNames(actual));
+    }
+
+    // Error Prone flags Date.equals(), but Instant and LocalDateTime are not available in Java 7.
+    // We could compare Date.getTime(), but this trips another warning in Error Prone. We do not use
+    // Date subclasses, so stick with Date.equals for now.
+    //
+    // https://errorprone.info/bugpattern/UndefinedEquals
+    @SuppressWarnings("UndefinedEquals")
+    private static void assertDatesEqual(Date expected, Date actual) throws Exception {
+        assertEquals(expected, actual);
+    }
+
     // See issue #539.
     @Test
     public void testMismatchedAlgorithm() throws Exception {
@@ -147,10 +402,8 @@
             .run(new ServiceTester.Test() {
                 @Override
                 public void test(Provider p, String algorithm) throws Exception {
-                    CertificateFactory cf = CertificateFactory.getInstance("X509", p);
                     try {
-                        Certificate c = cf.generateCertificate(new ByteArrayInputStream(
-                            MISMATCHED_ALGORITHM_CERT.getBytes(Charset.forName("US-ASCII"))));
+                        X509Certificate c = certificateFromPEM(p, MISMATCHED_ALGORITHM_CERT);
                         c.verify(c.getPublicKey());
                         fail();
                     } catch (CertificateException expected) {
@@ -173,9 +426,7 @@
                 @Override
                 public void test(Provider p, String algorithm) throws Exception {
                     try {
-                        CertificateFactory cf = CertificateFactory.getInstance("X509", p);
-                        Certificate c = cf.generateCertificate(new ByteArrayInputStream(
-                            EC_EXPLICIT_KEY_CERT.getBytes(Charset.forName("US-ASCII"))));
+                        X509Certificate c = certificateFromPEM(p, EC_EXPLICIT_KEY_CERT);
                         c.verify(c.getPublicKey());
                         fail();
                     } catch (InvalidKeyException expected) {
@@ -194,12 +445,224 @@
             .run(new ServiceTester.Test() {
                 @Override
                 public void test(Provider p, String algorithm) throws Exception {
-                    CertificateFactory cf = CertificateFactory.getInstance("X509", p);
-                    Certificate c = cf.generateCertificate(new ByteArrayInputStream(
-                        VALID_CERT.getBytes(Charset.forName("US-ASCII"))));
-                    assertEquals("SHA256WITHRSA",
-                        ((X509Certificate) c).getSigAlgName().toUpperCase());
+                    X509Certificate c = certificateFromPEM(p, VALID_CERT);
+                    assertEquals("SHA256WITHRSA", c.getSigAlgName().toUpperCase());
                 }
             });
     }
+
+    @Test
+    public void testUnknownSigAlgOID() throws Exception {
+        ServiceTester.test("CertificateFactory")
+                .withAlgorithm("X509")
+                .run(new ServiceTester.Test() {
+                    @Override
+                    public void test(Provider p, String algorithm) throws Exception {
+                        X509Certificate c = certificateFromPEM(p, UNKNOWN_SIGNATURE_OID);
+                        assertEquals("1.2.840.113554.4.1.72585.2", c.getSigAlgOID());
+                    }
+                });
+    }
+
+    @Test
+    public void testV1Cert() throws Exception {
+        ServiceTester tester = ServiceTester.test("CertificateFactory").withAlgorithm("X509");
+        tester.run(new ServiceTester.Test() {
+            @Override
+            public void test(Provider p, String algorithm) throws Exception {
+                X509Certificate c = certificateFromPEM(p, X509V1_CERT);
+
+                // Check basic certificate properties.
+                assertEquals(1, c.getVersion());
+                assertEquals(new BigInteger("d94c04da497dbfeb", 16), c.getSerialNumber());
+                assertDatesEqual(
+                        dateFromUTC(2014, Calendar.APRIL, 23, 23, 21, 57), c.getNotBefore());
+                assertDatesEqual(dateFromUTC(2014, Calendar.MAY, 23, 23, 21, 57), c.getNotAfter());
+                assertEquals(new X500Principal("CN=Test Issuer"), c.getIssuerX500Principal());
+                assertEquals(new X500Principal("CN=Test Subject"), c.getSubjectX500Principal());
+                assertEquals("1.2.840.10045.4.1", c.getSigAlgOID());
+                String signatureHex = "3045022100f2a0355e513a36c382799bee27"
+                        + "50858e7006749557d2297400f4be15875dc4"
+                        + "0702207c1e79146a2183f07a7468795f1499"
+                        + "9a68b4f1cb9e155ee61f3252615e75c914";
+                assertArrayEquals(TestUtils.decodeHex(signatureHex), c.getSignature());
+
+                // ECDSA signature AlgorithmIdentifiers omit parameters.
+                assertNull(c.getSigAlgParams());
+
+                // The certificate does not have UIDs.
+                assertNull(c.getIssuerUniqueID());
+                assertNull(c.getSubjectUniqueID());
+
+                // The certificate does not have any extensions.
+                assertEquals(-1, c.getBasicConstraints());
+                assertNull(c.getExtendedKeyUsage());
+                assertNull(c.getIssuerAlternativeNames());
+                assertNull(c.getKeyUsage());
+                assertNull(c.getSubjectAlternativeNames());
+            }
+        });
+    }
+
+    @Test
+    public void testManyExtensions() throws Exception {
+        ServiceTester tester = ServiceTester.test("CertificateFactory").withAlgorithm("X509");
+        tester.run(new ServiceTester.Test() {
+            @Override
+            public void test(Provider p, String algorithm) throws Exception {
+                X509Certificate c = certificateFromPEM(p, MANY_EXTENSIONS);
+
+                assertEquals(3, c.getVersion());
+                assertEquals(new BigInteger("b5b622b95a04a521", 16), c.getSerialNumber());
+                assertDatesEqual(dateFromUTC(2016, Calendar.JULY, 9, 4, 38, 9), c.getNotBefore());
+                assertDatesEqual(dateFromUTC(2016, Calendar.AUGUST, 8, 4, 38, 9), c.getNotAfter());
+                assertEquals(new X500Principal("CN=Test Issuer"), c.getIssuerX500Principal());
+                assertEquals(new X500Principal("CN=Test Subject"), c.getSubjectX500Principal());
+                assertEquals("1.2.840.113549.1.1.11", c.getSigAlgOID());
+                String signatureHex = "3ec983af1202b61695ca077d9001f743e6ca"
+                        + "bb791fa0fc2d18be5b6462d5f04dc511042e"
+                        + "77b3589dac72397850c72c298a783e2f79d2"
+                        + "054dfbad8882b22670236fb5be48d427f2fc"
+                        + "c34dbabf5f7dab3a5f7df80f485854841378"
+                        + "fc85937ba623eda6250aed659c8c3c829263"
+                        + "fb181901e11865fac062be18efe88343d093"
+                        + "f56ee83f865365d19c357461983596c02c1d"
+                        + "ddb55ebc8ae9f0e636410cc1b216aedb38c5"
+                        + "ceec711ac61d6cbe88c7faffba7f024fd222"
+                        + "270ce174b09a543ca4fc4064fafe1362e855"
+                        + "df69329594c295b651bb4ee70b064eb639b0"
+                        + "ee39b4534dff2fa3b5485e0750b68a339b1b"
+                        + "fb5710b6a2c8274cf92ff069ebafd0c5ed23"
+                        + "8c679f50";
+                assertArrayEquals(TestUtils.decodeHex(signatureHex), c.getSignature());
+
+                // Although documented to only return null when there are no parameters, the SUN
+                // provider also returns null when the algorithm uses an explicit parameter with a
+                // value of ASN.1 NULL.
+                if (c.getSigAlgParams() != null) {
+                    assertArrayEquals(TestUtils.decodeHex("0500"), c.getSigAlgParams());
+                }
+
+                assertArrayEquals(new boolean[] {true, false, true, false}, c.getIssuerUniqueID());
+                assertArrayEquals(
+                        new boolean[] {false, true, false, true, false}, c.getSubjectUniqueID());
+                assertEquals(10, c.getBasicConstraints());
+                assertEquals(Arrays.asList("1.3.6.1.5.5.7.3.1", "1.2.840.113554.4.1.72585.2"),
+                        c.getExtendedKeyUsage());
+
+                // TODO(davidben): Test the other name types.
+                assertGeneralNamesEqual(
+                        Arrays.<List<?>>asList(Arrays.asList(1, "issuer@example.com"),
+                                Arrays.asList(2, "issuer.example.com"),
+                                Arrays.asList(4, "CN=Test Issuer"),
+                                Arrays.asList(6, "https://example.com/issuer"),
+                                // TODO(https://github.com/google/conscrypt/issues/938): Fix IPv6
+                                // handling and include it in this test.
+                                Arrays.asList(7, "127.0.0.1"),
+                                Arrays.asList(8, "1.2.840.113554.4.1.72585.2")),
+                        c.getIssuerAlternativeNames());
+                assertGeneralNamesEqual(
+                        Arrays.<List<?>>asList(Arrays.asList(1, "subject@example.com"),
+                                Arrays.asList(2, "subject.example.com"),
+                                Arrays.asList(4, "CN=Test Subject"),
+                                Arrays.asList(6, "https://example.com/subject"),
+                                // TODO(https://github.com/google/conscrypt/issues/938): Fix IPv6
+                                // handling and include it in this test.
+                                Arrays.asList(7, "127.0.0.1"),
+                                Arrays.asList(8, "1.2.840.113554.4.1.72585.2")),
+                        c.getSubjectAlternativeNames());
+
+                // Although the BIT STRING in the certificate only has three bits, getKeyUsage()
+                // rounds up to at least 9 bits.
+                assertArrayEquals(
+                        new boolean[] {true, false, true, false, false, false, false, false, false},
+                        c.getKeyUsage());
+            }
+        });
+    }
+
+    @Test
+    public void testBasicConstraints() throws Exception {
+        ServiceTester tester = ServiceTester.test("CertificateFactory").withAlgorithm("X509");
+        tester.run(new ServiceTester.Test() {
+            @Override
+            public void test(Provider p, String algorithm) throws Exception {
+                // Test some additional edge cases in getBasicConstraints() beyond that
+                // testManyExtensions() and testV1Cert() covered.
+
+                // If there is no pathLen constraint but the certificate is a CA,
+                // getBasicConstraints() returns Integer.MAX_VALUE.
+                X509Certificate c = certificateFromPEM(p, BASIC_CONSTRAINTS_NO_PATHLEN);
+                assertEquals(Integer.MAX_VALUE, c.getBasicConstraints());
+
+                // If there is a pathLen constraint of zero, getBasicConstraints() returns it.
+                c = certificateFromPEM(p, BASIC_CONSTRAINTS_PATHLEN_0);
+                assertEquals(0, c.getBasicConstraints());
+
+                // If there is basicConstraints extension indicating a leaf certficate,
+                // getBasicConstraints() returns -1. The accessor does not distinguish between no
+                // basicConstraints extension and a leaf one.
+                c = certificateFromPEM(p, BASIC_CONSTRAINTS_LEAF);
+                assertEquals(-1, c.getBasicConstraints());
+
+                // If some unrelated extension has a syntax error, and that syntax error does not
+                // fail when constructing the certificate, it should not interfere with
+                // getBasicConstraints().
+                try {
+                    c = certificateFromPEM(p, BASIC_CONSTRAINTS_PATHLEN_10_BAD_SAN);
+                } catch (CertificateParsingException e) {
+                    // The certificate has a syntax error, so it would also be valid for the
+                    // provider to reject the certificate at construction. X.509 is an extensible
+                    // format, so different implementations may notice errors at different points.
+                    c = null;
+                }
+                if (c != null) {
+                    assertEquals(10, c.getBasicConstraints());
+                }
+            }
+        });
+    }
+
+    @Test
+    public void testLargeKeyUsage() throws Exception {
+        ServiceTester tester = ServiceTester.test("CertificateFactory").withAlgorithm("X509");
+        tester.run(new ServiceTester.Test() {
+            @Override
+            public void test(Provider p, String algorithm) throws Exception {
+                X509Certificate c = certificateFromPEM(p, LARGE_KEY_USAGE);
+                assertArrayEquals(new boolean[] {true, false, true, false, false, false, false,
+                                          false, false, false, false},
+                        c.getKeyUsage());
+            }
+        });
+    }
+
+    @Test
+    public void testSigAlgParams() throws Exception {
+        ServiceTester tester = ServiceTester.test("CertificateFactory").withAlgorithm("X509");
+        tester.run(new ServiceTester.Test() {
+            @Override
+            public void test(Provider p, String algorithm) throws Exception {
+                X509Certificate c = certificateFromPEM(p, SIGALG_NO_PARAMETER);
+                assertNull(c.getSigAlgParams());
+
+                c = certificateFromPEM(p, SIGALG_NULL_PARAMETER);
+                // Although documented to only return null when there are no parameters, the SUN
+                // provider also returns null when the algorithm uses an explicit parameter with a
+                // value of ASN.1 NULL.
+                if (c.getSigAlgParams() != null) {
+                    assertArrayEquals(TestUtils.decodeHex("0500"), c.getSigAlgParams());
+                }
+
+                c = certificateFromPEM(p, SIGALG_STRING_PARAMETER);
+                assertArrayEquals(TestUtils.decodeHex("0c05706172616d"), c.getSigAlgParams());
+
+                c = certificateFromPEM(p, SIGALG_BOOLEAN_PARAMETER);
+                assertArrayEquals(TestUtils.decodeHex("0101ff"), c.getSigAlgParams());
+
+                c = certificateFromPEM(p, SIGALG_SEQUENCE_PARAMETER);
+                assertArrayEquals(TestUtils.decodeHex("3000"), c.getSigAlgParams());
+            }
+        });
+    }
 }
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/CipherBasicsTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/CipherBasicsTest.java
index 0ae78f4..2c09f04 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/CipherBasicsTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/CipherBasicsTest.java
@@ -17,13 +17,12 @@
 
 package com.android.org.conscrypt.javax.crypto;
 
+import static com.android.org.conscrypt.TestUtils.decodeHex;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
+import com.android.org.conscrypt.TestUtils;
+import java.nio.ByteBuffer;
 import java.security.AlgorithmParameters;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
@@ -32,7 +31,6 @@
 import java.security.Provider;
 import java.security.Security;
 import java.security.spec.AlgorithmParameterSpec;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -41,7 +39,6 @@
 import javax.crypto.spec.GCMParameterSpec;
 import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
-import com.android.org.conscrypt.TestUtils;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -55,27 +52,26 @@
  */
 @RunWith(JUnit4.class)
 public final class CipherBasicsTest {
-
-    private static final Map<String, String> BASIC_CIPHER_TO_TEST_DATA = new HashMap<String, String>();
+    private static final Map<String, String> BASIC_CIPHER_TO_TEST_DATA = new HashMap<>();
     static {
-        BASIC_CIPHER_TO_TEST_DATA.put("AES/ECB/NoPadding", "/crypto/aes-ecb.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("AES/CBC/NoPadding", "/crypto/aes-cbc.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("AES/CFB8/NoPadding", "/crypto/aes-cfb8.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("AES/CFB128/NoPadding", "/crypto/aes-cfb128.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("AES/OFB/NoPadding", "/crypto/aes-ofb.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/ECB/NoPadding", "/crypto/desede-ecb.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/CBC/NoPadding", "/crypto/desede-cbc.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/CFB8/NoPadding", "/crypto/desede-cfb8.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/CFB64/NoPadding", "/crypto/desede-cfb64.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/OFB/NoPadding", "/crypto/desede-ofb.csv");
-        BASIC_CIPHER_TO_TEST_DATA.put("ChaCha20", "/crypto/chacha20.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("AES/ECB/NoPadding", "crypto/aes-ecb.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("AES/CBC/NoPadding", "crypto/aes-cbc.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("AES/CFB8/NoPadding", "crypto/aes-cfb8.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("AES/CFB128/NoPadding", "crypto/aes-cfb128.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("AES/OFB/NoPadding", "crypto/aes-ofb.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/ECB/NoPadding", "crypto/desede-ecb.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/CBC/NoPadding", "crypto/desede-cbc.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/CFB8/NoPadding", "crypto/desede-cfb8.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/CFB64/NoPadding", "crypto/desede-cfb64.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("DESEDE/OFB/NoPadding", "crypto/desede-ofb.csv");
+        BASIC_CIPHER_TO_TEST_DATA.put("ChaCha20", "crypto/chacha20.csv");
     }
 
-    private static final Map<String, String> AEAD_CIPHER_TO_TEST_DATA = new HashMap<String, String>();
+    private static final Map<String, String> AEAD_CIPHER_TO_TEST_DATA = new HashMap<>();
     static {
-        AEAD_CIPHER_TO_TEST_DATA.put("AES/GCM/NoPadding", "/crypto/aes-gcm.csv");
-        AEAD_CIPHER_TO_TEST_DATA.put("AES/GCM-SIV/NoPadding", "/crypto/aes-gcm-siv.csv");
-        AEAD_CIPHER_TO_TEST_DATA.put("ChaCha20/Poly1305/NoPadding", "/crypto/chacha20-poly1305.csv");
+        AEAD_CIPHER_TO_TEST_DATA.put("AES/GCM/NoPadding", "crypto/aes-gcm.csv");
+        AEAD_CIPHER_TO_TEST_DATA.put("AES/GCM-SIV/NoPadding", "crypto/aes-gcm-siv.csv");
+        AEAD_CIPHER_TO_TEST_DATA.put("ChaCha20/Poly1305/NoPadding", "crypto/chacha20-poly1305.csv");
     }
 
     private static final int KEY_INDEX = 0;
@@ -119,13 +115,13 @@
                     continue;
                 }
 
-                List<String[]> data = readCsvResource(entry.getValue());
+                List<String[]> data = TestUtils.readCsvResource(entry.getValue());
                 for (String[] line : data) {
-                    Key key = new SecretKeySpec(toBytes(line[KEY_INDEX]),
-                            getBaseAlgorithm(transformation));
-                    byte[] iv = toBytes(line[IV_INDEX]);
-                    byte[] plaintext = toBytes(line[PLAINTEXT_INDEX]);
-                    byte[] ciphertext = toBytes(line[CIPHERTEXT_INDEX]);
+                    Key key = new SecretKeySpec(
+                            decodeHex(line[KEY_INDEX]), getBaseAlgorithm(transformation));
+                    byte[] iv = decodeHex(line[IV_INDEX]);
+                    byte[] plaintext = decodeHex(line[PLAINTEXT_INDEX]);
+                    byte[] ciphertext = decodeHex(line[CIPHERTEXT_INDEX]);
 
                     // Initialize the IV, if there is one
                     AlgorithmParameters params;
@@ -142,20 +138,20 @@
                                         + ", algorithm " + transformation
                                         + " reported the wrong output size",
                                 ciphertext.length, cipher.getOutputSize(plaintext.length));
-                        assertTrue("Provider " + p.getName()
-                                        + ", algorithm " + transformation
-                                        + " failed on encryption, data is " + Arrays.toString(line),
-                                Arrays.equals(ciphertext, cipher.doFinal(plaintext)));
+                        assertArrayEquals("Provider " + p.getName() + ", algorithm "
+                                        + transformation + " failed on encryption, data is "
+                                        + Arrays.toString(line),
+                                ciphertext, cipher.doFinal(plaintext));
 
                         cipher.init(Cipher.DECRYPT_MODE, key, params);
                         assertEquals("Provider " + p.getName()
                                         + ", algorithm " + transformation
                                         + " reported the wrong output size",
                                 plaintext.length, cipher.getOutputSize(ciphertext.length));
-                        assertTrue("Provider " + p.getName()
-                                        + ", algorithm " + transformation
-                                        + " failed on decryption, data is " + Arrays.toString(line),
-                                Arrays.equals(plaintext, cipher.doFinal(ciphertext)));
+                        assertArrayEquals("Provider " + p.getName() + ", algorithm "
+                                        + transformation + " failed on decryption, data is "
+                                        + Arrays.toString(line),
+                                plaintext, cipher.doFinal(ciphertext));
                     } catch (InvalidKeyException e) {
                         // Some providers may not support raw SecretKeySpec keys, that's allowed
                     }
@@ -164,6 +160,35 @@
         }
     }
 
+    public void arrayBasedAssessment(Cipher cipher, byte[] aad, byte[] tag, byte[] plaintext,
+            byte[] ciphertext, Key key, AlgorithmParameterSpec params, String transformation,
+            Provider p, String[] line) throws Exception {
+        cipher.init(Cipher.ENCRYPT_MODE, key, params);
+        if (aad.length > 0) {
+            cipher.updateAAD(aad);
+        }
+        byte[] combinedOutput = new byte[ciphertext.length + tag.length];
+        assertEquals("Provider " + p.getName() + ", algorithm " + transformation
+                        + " reported the wrong output size",
+                combinedOutput.length, cipher.getOutputSize(plaintext.length));
+        System.arraycopy(ciphertext, 0, combinedOutput, 0, ciphertext.length);
+        System.arraycopy(tag, 0, combinedOutput, ciphertext.length, tag.length);
+        assertArrayEquals("Provider " + p.getName() + ", algorithm " + transformation
+                        + " failed on encryption, data is " + Arrays.toString(line),
+                combinedOutput, cipher.doFinal(plaintext));
+
+        cipher.init(Cipher.DECRYPT_MODE, key, params);
+        if (aad.length > 0) {
+            cipher.updateAAD(aad);
+        }
+        assertEquals("Provider " + p.getName() + ", algorithm " + transformation
+                        + " reported the wrong output size",
+                plaintext.length, cipher.getOutputSize(combinedOutput.length));
+        assertArrayEquals("Provider " + p.getName() + ", algorithm " + transformation
+                        + " failed on decryption, data is " + Arrays.toString(line),
+                plaintext, cipher.doFinal(combinedOutput));
+    }
+
     @Test
     public void testAeadEncryption() throws Exception {
         TestUtils.assumeAEADAvailable();
@@ -171,6 +196,13 @@
             for (Map.Entry<String, String> entry : AEAD_CIPHER_TO_TEST_DATA.entrySet()) {
                 String transformation = entry.getKey();
 
+                // On Android 10 and below, BC can return AES/GCM/NoPadding when asked for
+                // AES/GCM-SIV/NoPadding. Android will never actually ship AES/GCM-SIV/NoPadding
+                // in BC, so skip that combination.
+                if (p.getName().equals("BC") && transformation.equals("AES/GCM-SIV/NoPadding")) {
+                    continue;
+                }
+
                 Cipher cipher;
                 try {
                     cipher = Cipher.getInstance(transformation, p);
@@ -179,15 +211,15 @@
                     continue;
                 }
 
-                List<String[]> data = readCsvResource(entry.getValue());
+                List<String[]> data = TestUtils.readCsvResource(entry.getValue());
                 for (String[] line : data) {
-                    Key key = new SecretKeySpec(toBytes(line[KEY_INDEX]),
-                            getBaseAlgorithm(transformation));
-                    byte[] iv = toBytes(line[IV_INDEX]);
-                    byte[] plaintext = toBytes(line[PLAINTEXT_INDEX]);
-                    byte[] ciphertext = toBytes(line[CIPHERTEXT_INDEX]);
-                    byte[] tag = toBytes(line[TAG_INDEX]);
-                    byte[] aad = toBytes(line[AAD_INDEX]);
+                    Key key = new SecretKeySpec(
+                            decodeHex(line[KEY_INDEX]), getBaseAlgorithm(transformation));
+                    byte[] iv = decodeHex(line[IV_INDEX]);
+                    byte[] plaintext = decodeHex(line[PLAINTEXT_INDEX]);
+                    byte[] ciphertext = decodeHex(line[CIPHERTEXT_INDEX]);
+                    byte[] tag = decodeHex(line[TAG_INDEX]);
+                    byte[] aad = decodeHex(line[AAD_INDEX]);
 
                     // Some ChaCha20 tests include truncated tags, which the Java API doesn't
                     // support.  Skip those tests.
@@ -203,34 +235,18 @@
                     }
 
                     try {
-                        cipher.init(Cipher.ENCRYPT_MODE, key, params);
-                        if (aad.length > 0) {
-                            cipher.updateAAD(aad);
-                        }
-                        byte[] combinedOutput = new byte[ciphertext.length + tag.length];
-                        assertEquals("Provider " + p.getName()
-                                        + ", algorithm " + transformation
-                                        + " reported the wrong output size",
-                                combinedOutput.length, cipher.getOutputSize(plaintext.length));
-                        System.arraycopy(ciphertext, 0, combinedOutput, 0, ciphertext.length);
-                        System.arraycopy(tag, 0, combinedOutput, ciphertext.length, tag.length);
-                        assertTrue("Provider " + p.getName()
-                                        + ", algorithm " + transformation
-                                        + " failed on encryption, data is " + Arrays.toString(line),
-                                Arrays.equals(combinedOutput, cipher.doFinal(plaintext)));
-
-                        cipher.init(Cipher.DECRYPT_MODE, key, params);
-                        if (aad.length > 0) {
-                            cipher.updateAAD(aad);
-                        }
-                        assertEquals("Provider " + p.getName()
-                                        + ", algorithm " + transformation
-                                        + " reported the wrong output size",
-                                plaintext.length, cipher.getOutputSize(combinedOutput.length));
-                        assertTrue("Provider " + p.getName()
-                                        + ", algorithm " + transformation
-                                        + " failed on decryption, data is " + Arrays.toString(line),
-                                Arrays.equals(plaintext, cipher.doFinal(combinedOutput)));
+                        arrayBasedAssessment(cipher, aad, tag, plaintext, ciphertext, key, params,
+                                transformation, p, line);
+                        bufferBasedAssessment(cipher, aad, tag, plaintext, ciphertext, key, params,
+                                transformation, p, false, false);
+                        bufferBasedAssessment(cipher, aad, tag, plaintext, ciphertext, key, params,
+                                transformation, p, true, true);
+                        bufferBasedAssessment(cipher, aad, tag, plaintext, ciphertext, key, params,
+                                transformation, p, true, false);
+                        bufferBasedAssessment(cipher, aad, tag, plaintext, ciphertext, key, params,
+                                transformation, p, false, true);
+                        sharedBufferBasedAssessment(cipher, aad, tag, plaintext, ciphertext, key,
+                                params, transformation, p);
                     } catch (InvalidKeyException e) {
                         // Some providers may not support raw SecretKeySpec keys, that's allowed
                     } catch (InvalidAlgorithmParameterException e) {
@@ -242,25 +258,127 @@
         }
     }
 
-    private static List<String[]> readCsvResource(String resourceName) throws IOException {
-        InputStream stream = CipherBasicsTest.class.getResourceAsStream(resourceName);
-        List<String[]> lines = new ArrayList<String[]>();
-        BufferedReader reader = null;
-        try {
-            reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
-            String line;
-            while ((line = reader.readLine()) != null) {
-                if (line.isEmpty() || line.startsWith("#")) {
-                    continue;
-                }
-                lines.add(line.split(",", -1));
-            }
-        } finally {
-            if (reader != null) {
-                reader.close();
-            }
+    public void sharedBufferBasedAssessment(Cipher cipher, byte[] aad, byte[] tag,
+            byte[] _plaintext, byte[] _ciphertext, Key key, AlgorithmParameterSpec params,
+            String transformation, Provider p) throws Exception {
+        cipher.init(Cipher.ENCRYPT_MODE, key, params);
+        if (aad.length > 0) {
+            cipher.updateAAD(aad);
         }
-        return lines;
+        byte[] _combinedOutput = new byte[_ciphertext.length + tag.length];
+        byte[] _commonBacking = new byte[_plaintext.length + _combinedOutput.length];
+
+        assertEquals("Provider " + p.getName() + ", algorithm " + transformation
+                        + " reported the wrong output size",
+                _combinedOutput.length, cipher.getOutputSize(_plaintext.length));
+        System.arraycopy(_ciphertext, 0, _combinedOutput, 0, _ciphertext.length);
+        System.arraycopy(tag, 0, _combinedOutput, _ciphertext.length, tag.length);
+        System.arraycopy(_plaintext, 0, _commonBacking, 0, _plaintext.length);
+        System.arraycopy(
+                _combinedOutput, 0, _commonBacking, _plaintext.length, _combinedOutput.length);
+        ByteBuffer combinedOutput = ByteBuffer.wrap(_commonBacking);
+        ByteBuffer plaintext = combinedOutput.slice();
+        plaintext.limit(_plaintext.length);
+        combinedOutput.position(_plaintext.length);
+        // both byte buffers have been created from common backed array and have correct respecting
+        // positions and limits
+
+        combinedOutput.position(combinedOutput.limit());
+        ByteBuffer outputbuffer = ByteBuffer.allocate(cipher.getOutputSize(plaintext.remaining()));
+
+        cipher.doFinal(plaintext, outputbuffer);
+        assertEquals("Cipher doFinal did not encrypt correctly", combinedOutput, outputbuffer);
+        assertEquals(" input was not shifted", plaintext.position(), plaintext.limit());
+
+        cipher.init(Cipher.DECRYPT_MODE, key, params);
+        if (aad.length > 0) {
+            cipher.updateAAD(aad);
+        }
+        assertEquals("Provider " + p.getName() + ", algorithm " + transformation
+                        + " reported the wrong output size",
+                _plaintext.length, cipher.getOutputSize(_combinedOutput.length));
+        combinedOutput.position(_plaintext.length);
+
+        outputbuffer = ByteBuffer.allocate(cipher.getOutputSize(combinedOutput.remaining()));
+
+        combinedOutput.position(_plaintext.length);
+        plaintext.position(plaintext.limit());
+        cipher.doFinal(combinedOutput, outputbuffer);
+        assertEquals("Cipher doFinal did not decrypt correctly", plaintext, outputbuffer);
+        assertEquals(" input was not shifted", combinedOutput.position(), combinedOutput.limit());
+    }
+
+    public void bufferBasedAssessment(Cipher cipher, byte[] aad, byte[] tag, byte[] _plaintext,
+            byte[] _ciphertext, Key key, AlgorithmParameterSpec params, String transformation,
+            Provider p, boolean inBoolDirect, boolean outBoolDirect) throws Exception {
+        cipher.init(Cipher.ENCRYPT_MODE, key, params);
+        if (aad.length > 0) {
+            cipher.updateAAD(aad);
+        }
+        byte[] _combinedOutput = new byte[_ciphertext.length + tag.length];
+        ByteBuffer plaintext = ByteBuffer.wrap(_plaintext);
+        if (inBoolDirect) {
+            ByteBuffer plaintext_ = plaintext;
+            int incap = plaintext_.remaining();
+            plaintext = ByteBuffer.allocateDirect(incap);
+            plaintext.mark();
+            plaintext.put(plaintext_);
+            plaintext.reset();
+        }
+
+        assertEquals("Provider " + p.getName() + ", algorithm " + transformation
+                        + " reported the wrong output size",
+                _combinedOutput.length, cipher.getOutputSize(_plaintext.length));
+        System.arraycopy(_ciphertext, 0, _combinedOutput, 0, _ciphertext.length);
+        System.arraycopy(tag, 0, _combinedOutput, _ciphertext.length, tag.length);
+
+        ByteBuffer combinedOutput = ByteBuffer.wrap(_combinedOutput);
+        if (outBoolDirect) {
+            ByteBuffer combinedOutput_ = combinedOutput;
+            int outcap = combinedOutput_.remaining();
+            combinedOutput = ByteBuffer.allocateDirect(outcap);
+            combinedOutput.mark();
+            combinedOutput.put(combinedOutput_);
+        }
+        combinedOutput.position(combinedOutput.limit());
+        ByteBuffer outputbuffer;
+        if (outBoolDirect) {
+            outputbuffer = ByteBuffer.allocateDirect(cipher.getOutputSize(plaintext.remaining()));
+        } else {
+            outputbuffer = ByteBuffer.allocate(cipher.getOutputSize(plaintext.remaining()));
+        }
+
+        cipher.doFinal(plaintext, outputbuffer);
+        assertEquals("Cipher doFinal did not encrypt correctly", combinedOutput, outputbuffer);
+        assertEquals(" input was not shifted", plaintext.position(), plaintext.limit());
+
+        cipher.init(Cipher.DECRYPT_MODE, key, params);
+        if (aad.length > 0) {
+            cipher.updateAAD(aad);
+        }
+        assertEquals("Provider " + p.getName() + ", algorithm " + transformation
+                        + " reported the wrong output size",
+                _plaintext.length, cipher.getOutputSize(_combinedOutput.length));
+        combinedOutput = ByteBuffer.wrap(_combinedOutput);
+        if (inBoolDirect) {
+            ByteBuffer combinedOutput_ = combinedOutput;
+            int incap = combinedOutput_.remaining();
+            combinedOutput = ByteBuffer.allocateDirect(incap);
+            combinedOutput.mark();
+            combinedOutput.put(combinedOutput_);
+            combinedOutput.reset();
+        }
+        if (outBoolDirect) {
+            outputbuffer =
+                    ByteBuffer.allocateDirect(cipher.getOutputSize(combinedOutput.remaining()));
+        } else {
+            outputbuffer = ByteBuffer.allocate(cipher.getOutputSize(combinedOutput.remaining()));
+        }
+        combinedOutput.position(0);
+        plaintext.position(plaintext.limit());
+        cipher.doFinal(combinedOutput, outputbuffer);
+        assertEquals("Cipher doFinal did not decrypt correctly", plaintext, outputbuffer);
+        assertEquals(" input was not shifted", combinedOutput.position(), combinedOutput.limit());
     }
 
     /**
@@ -274,7 +392,89 @@
         return transformation;
     }
 
-    private static byte[] toBytes(String hex) {
-        return TestUtils.decodeHex(hex, /* allowSingleChar= */ true);
+    /**
+     * Encryption with ByteBuffers should be copy-safe even if the buffers have different starting
+     * offsets and/or do not make the backing array visible.
+     *
+     * <p>Note that bugs in this often require a sizeable input to reproduce; the default
+     * implementation of engineUpdate(ByteBuffer, ByteBuffer) copies through 4KB bounce buffers, so
+     * we need to use something larger to see any problems - 8KB is what we use here.
+     *
+     * @see https://bugs.openjdk.java.net/browse/JDK-8181386
+     */
+    @Test
+    public void testByteBufferShiftedAlias() throws Exception {
+        byte[] ptVector = new byte[8192];
+
+        for (int i = 0; i < 3; i++) {
+            // outputOffset = offset relative to start of input.
+            for (int outputOffset = -1; outputOffset <= 1; outputOffset++) {
+                SecretKeySpec key = new SecretKeySpec(new byte[16], "AES");
+                GCMParameterSpec parameters = new GCMParameterSpec(128, new byte[12]);
+                Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+                cipher.init(Cipher.ENCRYPT_MODE, key, parameters);
+
+                ByteBuffer output, input, inputRO;
+
+                // We'll try three scenarios: Ordinary array backed buffers, array backed buffers
+                // where one is read-only, and direct byte buffers.
+                String mode;
+                // offsets relative to start of buffer
+                int inputOffsetInBuffer = 1;
+                int outputOffsetInBuffer = inputOffsetInBuffer + outputOffset;
+                int sliceLength = cipher.getOutputSize(ptVector.length);
+                int bufferSize = sliceLength + Math.max(inputOffsetInBuffer, outputOffsetInBuffer);
+
+                mode = "direct buffers";
+                ByteBuffer buf = ByteBuffer.allocateDirect(bufferSize);
+                output = buf.duplicate();
+                output.position(outputOffsetInBuffer);
+                output.limit(sliceLength + outputOffsetInBuffer);
+                output = output.slice();
+
+                input = buf.duplicate();
+                input.position(inputOffsetInBuffer);
+                input.limit(sliceLength + inputOffsetInBuffer);
+                input = input.slice();
+
+                inputRO = input.duplicate();
+
+                // Now that we have our overlapping 'input' and 'output' buffers, we can write our
+                // plaintext into the input buffer.
+                input.put(ptVector);
+                input.flip();
+                // Make sure the RO input buffer has the same limit in case the plaintext is shorter
+                // than sliceLength (which it generally will be for anything other than ECB or CTR
+                // mode)
+                inputRO.limit(input.limit());
+
+                try {
+                    int ctSize = cipher.doFinal(inputRO, output);
+
+                    // Now flip the buffers around and undo everything
+                    byte[] tmp = new byte[ctSize];
+                    output.flip();
+                    output.get(tmp);
+
+                    output.clear();
+                    input.clear();
+                    inputRO.clear();
+
+                    input.put(tmp);
+                    input.flip();
+                    inputRO.limit(input.limit());
+
+                    cipher.init(Cipher.DECRYPT_MODE, key, parameters);
+                    cipher.doFinal(inputRO, output);
+
+                    output.flip();
+                    assertEquals(ByteBuffer.wrap(ptVector), output);
+                } catch (Throwable t) {
+                    throw new AssertionError("Overlapping buffers test failed with buffer type: "
+                                    + mode + " and output offset " + outputOffset,
+                            t);
+                }
+            }
+        }
     }
 }
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 198e751..0a762d2 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
@@ -75,6 +75,7 @@
 import javax.crypto.spec.PSource;
 import javax.crypto.spec.SecretKeySpec;
 import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule;
+import libcore.test.annotation.NonCts;
 import org.bouncycastle.asn1.x509.KeyUsage;
 import org.junit.Assume;
 import org.junit.BeforeClass;
@@ -382,18 +383,14 @@
         setExpectedBlockSize("AES/OFB/PKCS7PADDING", 16);
         setExpectedBlockSize("AES/OFB/NOPADDING", 16);
         setExpectedBlockSize("AES_128/CBC/PKCS5PADDING", 16);
-        setExpectedBlockSize("AES_128/CBC/PKCS7PADDING", 16);
         setExpectedBlockSize("AES_128/CBC/NOPADDING", 16);
         setExpectedBlockSize("AES_128/ECB/PKCS5PADDING", 16);
-        setExpectedBlockSize("AES_128/ECB/PKCS7PADDING", 16);
         setExpectedBlockSize("AES_128/ECB/NOPADDING", 16);
         setExpectedBlockSize("AES_128/GCM/NOPADDING", 16);
         setExpectedBlockSize("AES_128/GCM-SIV/NOPADDING", 16);
         setExpectedBlockSize("AES_256/CBC/PKCS5PADDING", 16);
-        setExpectedBlockSize("AES_256/CBC/PKCS7PADDING", 16);
         setExpectedBlockSize("AES_256/CBC/NOPADDING", 16);
         setExpectedBlockSize("AES_256/ECB/PKCS5PADDING", 16);
-        setExpectedBlockSize("AES_256/ECB/PKCS7PADDING", 16);
         setExpectedBlockSize("AES_256/ECB/NOPADDING", 16);
         setExpectedBlockSize("AES_256/GCM/NOPADDING", 16);
         setExpectedBlockSize("AES_256/GCM-SIV/NOPADDING", 16);
@@ -414,7 +411,6 @@
         }
 
         setExpectedBlockSize("ARC4", 0);
-        setExpectedBlockSize("ARCFOUR", 0);
         setExpectedBlockSize("CHACHA20", 0);
         setExpectedBlockSize("CHACHA20/POLY1305/NOPADDING", 0);
         setExpectedBlockSize("PBEWITHSHAAND40BITRC4", 0);
@@ -428,27 +424,9 @@
 
         setExpectedBlockSize("DESEDE", 8);
         setExpectedBlockSize("DESEDE/CBC/PKCS5PADDING", 8);
-        setExpectedBlockSize("DESEDE/CBC/PKCS7PADDING", 8);
         setExpectedBlockSize("DESEDE/CBC/NOPADDING", 8);
-        setExpectedBlockSize("DESEDE/CFB/PKCS5PADDING", 8);
-        setExpectedBlockSize("DESEDE/CFB/PKCS7PADDING", 8);
-        setExpectedBlockSize("DESEDE/CFB/NOPADDING", 8);
-        setExpectedBlockSize("DESEDE/CTR/PKCS5PADDING", 8);
-        setExpectedBlockSize("DESEDE/CTR/PKCS7PADDING", 8);
-        setExpectedBlockSize("DESEDE/CTR/NOPADDING", 8);
-        setExpectedBlockSize("DESEDE/CTS/PKCS5PADDING", 8);
-        setExpectedBlockSize("DESEDE/CTS/PKCS7PADDING", 8);
-        setExpectedBlockSize("DESEDE/CTS/NOPADDING", 8);
-        setExpectedBlockSize("DESEDE/ECB/PKCS5PADDING", 8);
-        setExpectedBlockSize("DESEDE/ECB/PKCS7PADDING", 8);
-        setExpectedBlockSize("DESEDE/ECB/NOPADDING", 8);
-        setExpectedBlockSize("DESEDE/OFB/PKCS5PADDING", 8);
-        setExpectedBlockSize("DESEDE/OFB/PKCS7PADDING", 8);
-        setExpectedBlockSize("DESEDE/OFB/NOPADDING", 8);
         setExpectedBlockSize("PBEWITHSHAAND2-KEYTRIPLEDES-CBC", 8);
         setExpectedBlockSize("PBEWITHSHAAND3-KEYTRIPLEDES-CBC", 8);
-        setExpectedBlockSize("PBEWITHMD5ANDTRIPLEDES", 8);
-        setExpectedBlockSize("PBEWITHSHA1ANDDESEDE", 8);
 
 
         if (StandardNames.IS_RI) {
@@ -2156,414 +2134,405 @@
     /*
      * echo -n 'This is a test of OAEP' | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_Plaintext = new byte[] {
-            (byte) 0x54, (byte) 0x68, (byte) 0x69, (byte) 0x73, (byte) 0x20, (byte) 0x69,
-            (byte) 0x73, (byte) 0x20, (byte) 0x61, (byte) 0x20, (byte) 0x74, (byte) 0x65,
-            (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x6f, (byte) 0x66, (byte) 0x20,
-            (byte) 0x4f, (byte) 0x41, (byte) 0x45, (byte) 0x50
-    };
+    private static final byte[] RSA_Vector2_Plaintext =
+            new byte[] {(byte) 0x54, (byte) 0x68, (byte) 0x69, (byte) 0x73, (byte) 0x20,
+                    (byte) 0x69, (byte) 0x73, (byte) 0x20, (byte) 0x61, (byte) 0x20, (byte) 0x74,
+                    (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x6f, (byte) 0x66,
+                    (byte) 0x20, (byte) 0x4f, (byte) 0x41, (byte) 0x45, (byte) 0x50};
 
     /*
      * echo -n 'This is a test of OAEP' | openssl pkeyutl -encrypt -inkey rsakey.pem \
      * -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha1 -pkeyopt rsa_mgf1_md:sha1 \
      * | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA1_MGF1_SHA1 = new byte[] {
-            (byte) 0x53, (byte) 0x71, (byte) 0x84, (byte) 0x2e, (byte) 0x01, (byte) 0x74,
-            (byte) 0x82, (byte) 0xb3, (byte) 0x01, (byte) 0xac, (byte) 0x2b, (byte) 0xbd,
-            (byte) 0x40, (byte) 0xa7, (byte) 0x5b, (byte) 0x60, (byte) 0xf1, (byte) 0xde,
-            (byte) 0x54, (byte) 0x1d, (byte) 0x94, (byte) 0xc1, (byte) 0x10, (byte) 0x31,
-            (byte) 0x6f, (byte) 0xa3, (byte) 0xd8, (byte) 0x41, (byte) 0x2e, (byte) 0x82,
-            (byte) 0xad, (byte) 0x07, (byte) 0x6f, (byte) 0x25, (byte) 0x6c, (byte) 0xb5,
-            (byte) 0xef, (byte) 0xc6, (byte) 0xa6, (byte) 0xfb, (byte) 0xb1, (byte) 0x9d,
-            (byte) 0x75, (byte) 0x67, (byte) 0xb0, (byte) 0x97, (byte) 0x21, (byte) 0x3c,
-            (byte) 0x17, (byte) 0x04, (byte) 0xdc, (byte) 0x4e, (byte) 0x7e, (byte) 0x3f,
-            (byte) 0x5c, (byte) 0x13, (byte) 0x5e, (byte) 0x15, (byte) 0x0f, (byte) 0xe2,
-            (byte) 0xa7, (byte) 0x62, (byte) 0x6a, (byte) 0x08, (byte) 0xb1, (byte) 0xbc,
-            (byte) 0x2f, (byte) 0xcb, (byte) 0xb5, (byte) 0x96, (byte) 0x2d, (byte) 0xec,
-            (byte) 0x71, (byte) 0x4d, (byte) 0x59, (byte) 0x6e, (byte) 0x27, (byte) 0x85,
-            (byte) 0x87, (byte) 0x9b, (byte) 0xcc, (byte) 0x40, (byte) 0x32, (byte) 0x09,
-            (byte) 0x06, (byte) 0xe6, (byte) 0x7d, (byte) 0xdf, (byte) 0xeb, (byte) 0x2f,
-            (byte) 0xa8, (byte) 0x1c, (byte) 0x53, (byte) 0xdb, (byte) 0xa7, (byte) 0x48,
-            (byte) 0xf5, (byte) 0xbf, (byte) 0x2f, (byte) 0xbb, (byte) 0xee, (byte) 0xc7,
-            (byte) 0x55, (byte) 0x5e, (byte) 0xc4, (byte) 0x1c, (byte) 0x84, (byte) 0xed,
-            (byte) 0x97, (byte) 0x7e, (byte) 0xce, (byte) 0xa5, (byte) 0x69, (byte) 0x73,
-            (byte) 0xb3, (byte) 0xe0, (byte) 0x8c, (byte) 0x2a, (byte) 0xf2, (byte) 0xc7,
-            (byte) 0x65, (byte) 0xff, (byte) 0x10, (byte) 0xed, (byte) 0x25, (byte) 0xf0,
-            (byte) 0xf8, (byte) 0xda, (byte) 0x2f, (byte) 0x7f, (byte) 0xe0, (byte) 0x69,
-            (byte) 0xed, (byte) 0xb1, (byte) 0x0e, (byte) 0xcb, (byte) 0x43, (byte) 0xe4,
-            (byte) 0x31, (byte) 0xe6, (byte) 0x52, (byte) 0xfd, (byte) 0xa7, (byte) 0xe5,
-            (byte) 0x21, (byte) 0xd0, (byte) 0x67, (byte) 0x0a, (byte) 0xc1, (byte) 0xa1,
-            (byte) 0xb9, (byte) 0x04, (byte) 0xdb, (byte) 0x98, (byte) 0x4f, (byte) 0xf9,
-            (byte) 0x5c, (byte) 0x60, (byte) 0x4d, (byte) 0xac, (byte) 0x7a, (byte) 0x69,
-            (byte) 0xbd, (byte) 0x63, (byte) 0x0d, (byte) 0xb2, (byte) 0x01, (byte) 0x83,
-            (byte) 0xd7, (byte) 0x22, (byte) 0x5d, (byte) 0xed, (byte) 0xbd, (byte) 0x32,
-            (byte) 0x98, (byte) 0xd1, (byte) 0x4a, (byte) 0x2e, (byte) 0xb7, (byte) 0xb1,
-            (byte) 0x6d, (byte) 0x8a, (byte) 0x8f, (byte) 0xef, (byte) 0xc3, (byte) 0x89,
-            (byte) 0xdf, (byte) 0xa5, (byte) 0xac, (byte) 0xfb, (byte) 0x38, (byte) 0x61,
-            (byte) 0x32, (byte) 0xc5, (byte) 0x19, (byte) 0x83, (byte) 0x1f, (byte) 0x9c,
-            (byte) 0x45, (byte) 0x58, (byte) 0xdd, (byte) 0xa3, (byte) 0x57, (byte) 0xe4,
-            (byte) 0x91, (byte) 0xd2, (byte) 0x11, (byte) 0xf8, (byte) 0x96, (byte) 0x36,
-            (byte) 0x67, (byte) 0x99, (byte) 0x2b, (byte) 0x62, (byte) 0x21, (byte) 0xe3,
-            (byte) 0xa8, (byte) 0x5e, (byte) 0xa4, (byte) 0x2e, (byte) 0x0c, (byte) 0x29,
-            (byte) 0xf9, (byte) 0xcd, (byte) 0xfa, (byte) 0xbe, (byte) 0x3f, (byte) 0xd8,
-            (byte) 0xec, (byte) 0x6b, (byte) 0x32, (byte) 0xb3, (byte) 0x40, (byte) 0x4f,
-            (byte) 0x48, (byte) 0xe3, (byte) 0x14, (byte) 0x87, (byte) 0xa7, (byte) 0x5c,
-            (byte) 0xba, (byte) 0xdf, (byte) 0x0e, (byte) 0x64, (byte) 0xdc, (byte) 0xe2,
-            (byte) 0x51, (byte) 0xf4, (byte) 0x41, (byte) 0x25, (byte) 0x23, (byte) 0xc8,
-            (byte) 0x50, (byte) 0x1e, (byte) 0x9e, (byte) 0xb0
-    };
+    private static final byte[] RSA_Vector2_OAEP_SHA1_MGF1_SHA1 =
+            new byte[] {(byte) 0x53, (byte) 0x71, (byte) 0x84, (byte) 0x2e, (byte) 0x01,
+                    (byte) 0x74, (byte) 0x82, (byte) 0xb3, (byte) 0x01, (byte) 0xac, (byte) 0x2b,
+                    (byte) 0xbd, (byte) 0x40, (byte) 0xa7, (byte) 0x5b, (byte) 0x60, (byte) 0xf1,
+                    (byte) 0xde, (byte) 0x54, (byte) 0x1d, (byte) 0x94, (byte) 0xc1, (byte) 0x10,
+                    (byte) 0x31, (byte) 0x6f, (byte) 0xa3, (byte) 0xd8, (byte) 0x41, (byte) 0x2e,
+                    (byte) 0x82, (byte) 0xad, (byte) 0x07, (byte) 0x6f, (byte) 0x25, (byte) 0x6c,
+                    (byte) 0xb5, (byte) 0xef, (byte) 0xc6, (byte) 0xa6, (byte) 0xfb, (byte) 0xb1,
+                    (byte) 0x9d, (byte) 0x75, (byte) 0x67, (byte) 0xb0, (byte) 0x97, (byte) 0x21,
+                    (byte) 0x3c, (byte) 0x17, (byte) 0x04, (byte) 0xdc, (byte) 0x4e, (byte) 0x7e,
+                    (byte) 0x3f, (byte) 0x5c, (byte) 0x13, (byte) 0x5e, (byte) 0x15, (byte) 0x0f,
+                    (byte) 0xe2, (byte) 0xa7, (byte) 0x62, (byte) 0x6a, (byte) 0x08, (byte) 0xb1,
+                    (byte) 0xbc, (byte) 0x2f, (byte) 0xcb, (byte) 0xb5, (byte) 0x96, (byte) 0x2d,
+                    (byte) 0xec, (byte) 0x71, (byte) 0x4d, (byte) 0x59, (byte) 0x6e, (byte) 0x27,
+                    (byte) 0x85, (byte) 0x87, (byte) 0x9b, (byte) 0xcc, (byte) 0x40, (byte) 0x32,
+                    (byte) 0x09, (byte) 0x06, (byte) 0xe6, (byte) 0x7d, (byte) 0xdf, (byte) 0xeb,
+                    (byte) 0x2f, (byte) 0xa8, (byte) 0x1c, (byte) 0x53, (byte) 0xdb, (byte) 0xa7,
+                    (byte) 0x48, (byte) 0xf5, (byte) 0xbf, (byte) 0x2f, (byte) 0xbb, (byte) 0xee,
+                    (byte) 0xc7, (byte) 0x55, (byte) 0x5e, (byte) 0xc4, (byte) 0x1c, (byte) 0x84,
+                    (byte) 0xed, (byte) 0x97, (byte) 0x7e, (byte) 0xce, (byte) 0xa5, (byte) 0x69,
+                    (byte) 0x73, (byte) 0xb3, (byte) 0xe0, (byte) 0x8c, (byte) 0x2a, (byte) 0xf2,
+                    (byte) 0xc7, (byte) 0x65, (byte) 0xff, (byte) 0x10, (byte) 0xed, (byte) 0x25,
+                    (byte) 0xf0, (byte) 0xf8, (byte) 0xda, (byte) 0x2f, (byte) 0x7f, (byte) 0xe0,
+                    (byte) 0x69, (byte) 0xed, (byte) 0xb1, (byte) 0x0e, (byte) 0xcb, (byte) 0x43,
+                    (byte) 0xe4, (byte) 0x31, (byte) 0xe6, (byte) 0x52, (byte) 0xfd, (byte) 0xa7,
+                    (byte) 0xe5, (byte) 0x21, (byte) 0xd0, (byte) 0x67, (byte) 0x0a, (byte) 0xc1,
+                    (byte) 0xa1, (byte) 0xb9, (byte) 0x04, (byte) 0xdb, (byte) 0x98, (byte) 0x4f,
+                    (byte) 0xf9, (byte) 0x5c, (byte) 0x60, (byte) 0x4d, (byte) 0xac, (byte) 0x7a,
+                    (byte) 0x69, (byte) 0xbd, (byte) 0x63, (byte) 0x0d, (byte) 0xb2, (byte) 0x01,
+                    (byte) 0x83, (byte) 0xd7, (byte) 0x22, (byte) 0x5d, (byte) 0xed, (byte) 0xbd,
+                    (byte) 0x32, (byte) 0x98, (byte) 0xd1, (byte) 0x4a, (byte) 0x2e, (byte) 0xb7,
+                    (byte) 0xb1, (byte) 0x6d, (byte) 0x8a, (byte) 0x8f, (byte) 0xef, (byte) 0xc3,
+                    (byte) 0x89, (byte) 0xdf, (byte) 0xa5, (byte) 0xac, (byte) 0xfb, (byte) 0x38,
+                    (byte) 0x61, (byte) 0x32, (byte) 0xc5, (byte) 0x19, (byte) 0x83, (byte) 0x1f,
+                    (byte) 0x9c, (byte) 0x45, (byte) 0x58, (byte) 0xdd, (byte) 0xa3, (byte) 0x57,
+                    (byte) 0xe4, (byte) 0x91, (byte) 0xd2, (byte) 0x11, (byte) 0xf8, (byte) 0x96,
+                    (byte) 0x36, (byte) 0x67, (byte) 0x99, (byte) 0x2b, (byte) 0x62, (byte) 0x21,
+                    (byte) 0xe3, (byte) 0xa8, (byte) 0x5e, (byte) 0xa4, (byte) 0x2e, (byte) 0x0c,
+                    (byte) 0x29, (byte) 0xf9, (byte) 0xcd, (byte) 0xfa, (byte) 0xbe, (byte) 0x3f,
+                    (byte) 0xd8, (byte) 0xec, (byte) 0x6b, (byte) 0x32, (byte) 0xb3, (byte) 0x40,
+                    (byte) 0x4f, (byte) 0x48, (byte) 0xe3, (byte) 0x14, (byte) 0x87, (byte) 0xa7,
+                    (byte) 0x5c, (byte) 0xba, (byte) 0xdf, (byte) 0x0e, (byte) 0x64, (byte) 0xdc,
+                    (byte) 0xe2, (byte) 0x51, (byte) 0xf4, (byte) 0x41, (byte) 0x25, (byte) 0x23,
+                    (byte) 0xc8, (byte) 0x50, (byte) 0x1e, (byte) 0x9e, (byte) 0xb0};
 
     /*
      * echo -n 'This is a test of OAEP' | openssl pkeyutl -encrypt -inkey rsakey.pem -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha1 | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA256_MGF1_SHA1 = new byte[] {
-            (byte) 0x25, (byte) 0x9f, (byte) 0xc3, (byte) 0x69, (byte) 0xbc, (byte) 0x3f,
-            (byte) 0xe7, (byte) 0x9e, (byte) 0x76, (byte) 0xef, (byte) 0x6c, (byte) 0xd2,
-            (byte) 0x2b, (byte) 0x7b, (byte) 0xf0, (byte) 0xeb, (byte) 0xc2, (byte) 0x28,
-            (byte) 0x40, (byte) 0x4e, (byte) 0x9b, (byte) 0x2a, (byte) 0x4e, (byte) 0xa4,
-            (byte) 0x79, (byte) 0x66, (byte) 0xf1, (byte) 0x10, (byte) 0x96, (byte) 0x8c,
-            (byte) 0x58, (byte) 0x92, (byte) 0xb7, (byte) 0x70, (byte) 0xed, (byte) 0x3a,
-            (byte) 0xe0, (byte) 0x99, (byte) 0xd1, (byte) 0x80, (byte) 0x4b, (byte) 0x53,
-            (byte) 0x70, (byte) 0x9b, (byte) 0x51, (byte) 0xbf, (byte) 0xc1, (byte) 0x3a,
-            (byte) 0x70, (byte) 0xc5, (byte) 0x79, (byte) 0x21, (byte) 0x6e, (byte) 0xb3,
-            (byte) 0xf7, (byte) 0xa9, (byte) 0xe6, (byte) 0xcb, (byte) 0x70, (byte) 0xe4,
-            (byte) 0xf3, (byte) 0x4f, (byte) 0x45, (byte) 0xcf, (byte) 0xb7, (byte) 0x2b,
-            (byte) 0x38, (byte) 0xfd, (byte) 0x5d, (byte) 0x9a, (byte) 0x53, (byte) 0xc5,
-            (byte) 0x05, (byte) 0x74, (byte) 0x8d, (byte) 0x1d, (byte) 0x6e, (byte) 0x83,
-            (byte) 0xaa, (byte) 0x71, (byte) 0xc5, (byte) 0xe1, (byte) 0xa1, (byte) 0xa6,
-            (byte) 0xf3, (byte) 0xee, (byte) 0x5f, (byte) 0x9e, (byte) 0x4f, (byte) 0xe8,
-            (byte) 0x15, (byte) 0xd5, (byte) 0xa9, (byte) 0x1b, (byte) 0xa6, (byte) 0x41,
-            (byte) 0x2b, (byte) 0x18, (byte) 0x13, (byte) 0x20, (byte) 0x9f, (byte) 0x6b,
-            (byte) 0xf1, (byte) 0xd8, (byte) 0xf4, (byte) 0x87, (byte) 0xfa, (byte) 0x80,
-            (byte) 0xec, (byte) 0x0e, (byte) 0xa4, (byte) 0x4b, (byte) 0x24, (byte) 0x03,
-            (byte) 0x14, (byte) 0x25, (byte) 0xf2, (byte) 0x20, (byte) 0xfc, (byte) 0x52,
-            (byte) 0xf9, (byte) 0xd6, (byte) 0x7a, (byte) 0x4a, (byte) 0x45, (byte) 0x33,
-            (byte) 0xec, (byte) 0xde, (byte) 0x3c, (byte) 0x5b, (byte) 0xf2, (byte) 0xdc,
-            (byte) 0x8e, (byte) 0xc6, (byte) 0xb3, (byte) 0x26, (byte) 0xd3, (byte) 0x68,
-            (byte) 0xa7, (byte) 0xd8, (byte) 0x3a, (byte) 0xde, (byte) 0xa9, (byte) 0x25,
-            (byte) 0x1d, (byte) 0x42, (byte) 0x75, (byte) 0x66, (byte) 0x16, (byte) 0x29,
-            (byte) 0xad, (byte) 0x09, (byte) 0x74, (byte) 0x41, (byte) 0xbb, (byte) 0x45,
-            (byte) 0x39, (byte) 0x04, (byte) 0x7a, (byte) 0x93, (byte) 0xad, (byte) 0x1c,
-            (byte) 0xa6, (byte) 0x38, (byte) 0xf4, (byte) 0xac, (byte) 0xca, (byte) 0x5a,
-            (byte) 0xab, (byte) 0x92, (byte) 0x76, (byte) 0x26, (byte) 0x3c, (byte) 0xeb,
-            (byte) 0xda, (byte) 0xfc, (byte) 0x25, (byte) 0x93, (byte) 0x23, (byte) 0x01,
-            (byte) 0xe2, (byte) 0xac, (byte) 0x5e, (byte) 0x4c, (byte) 0xb7, (byte) 0xbc,
-            (byte) 0x5b, (byte) 0xaa, (byte) 0x14, (byte) 0xe9, (byte) 0xbf, (byte) 0x2d,
-            (byte) 0x3a, (byte) 0xdc, (byte) 0x2f, (byte) 0x6b, (byte) 0x4d, (byte) 0x0e,
-            (byte) 0x0a, (byte) 0x82, (byte) 0x3c, (byte) 0xd9, (byte) 0x32, (byte) 0xc1,
-            (byte) 0xc4, (byte) 0xa2, (byte) 0x46, (byte) 0x71, (byte) 0x10, (byte) 0x54,
-            (byte) 0x1a, (byte) 0xa6, (byte) 0xaa, (byte) 0x64, (byte) 0xe7, (byte) 0xc2,
-            (byte) 0xae, (byte) 0xbc, (byte) 0x3d, (byte) 0xa4, (byte) 0xa8, (byte) 0xd1,
-            (byte) 0xb7, (byte) 0x27, (byte) 0xef, (byte) 0x5f, (byte) 0xe7, (byte) 0xa7,
-            (byte) 0x5d, (byte) 0xa0, (byte) 0xcd, (byte) 0x57, (byte) 0xf1, (byte) 0xe0,
-            (byte) 0xd8, (byte) 0x42, (byte) 0x10, (byte) 0x77, (byte) 0xc3, (byte) 0xa7,
-            (byte) 0x1e, (byte) 0x0c, (byte) 0x37, (byte) 0x16, (byte) 0x11, (byte) 0x94,
-            (byte) 0x21, (byte) 0xf2, (byte) 0xca, (byte) 0x60, (byte) 0xce, (byte) 0xca,
-            (byte) 0x59, (byte) 0xf9, (byte) 0xe5, (byte) 0xe4
-    };
+    private static final byte[] RSA_Vector2_OAEP_SHA256_MGF1_SHA1 =
+            new byte[] {(byte) 0x25, (byte) 0x9f, (byte) 0xc3, (byte) 0x69, (byte) 0xbc,
+                    (byte) 0x3f, (byte) 0xe7, (byte) 0x9e, (byte) 0x76, (byte) 0xef, (byte) 0x6c,
+                    (byte) 0xd2, (byte) 0x2b, (byte) 0x7b, (byte) 0xf0, (byte) 0xeb, (byte) 0xc2,
+                    (byte) 0x28, (byte) 0x40, (byte) 0x4e, (byte) 0x9b, (byte) 0x2a, (byte) 0x4e,
+                    (byte) 0xa4, (byte) 0x79, (byte) 0x66, (byte) 0xf1, (byte) 0x10, (byte) 0x96,
+                    (byte) 0x8c, (byte) 0x58, (byte) 0x92, (byte) 0xb7, (byte) 0x70, (byte) 0xed,
+                    (byte) 0x3a, (byte) 0xe0, (byte) 0x99, (byte) 0xd1, (byte) 0x80, (byte) 0x4b,
+                    (byte) 0x53, (byte) 0x70, (byte) 0x9b, (byte) 0x51, (byte) 0xbf, (byte) 0xc1,
+                    (byte) 0x3a, (byte) 0x70, (byte) 0xc5, (byte) 0x79, (byte) 0x21, (byte) 0x6e,
+                    (byte) 0xb3, (byte) 0xf7, (byte) 0xa9, (byte) 0xe6, (byte) 0xcb, (byte) 0x70,
+                    (byte) 0xe4, (byte) 0xf3, (byte) 0x4f, (byte) 0x45, (byte) 0xcf, (byte) 0xb7,
+                    (byte) 0x2b, (byte) 0x38, (byte) 0xfd, (byte) 0x5d, (byte) 0x9a, (byte) 0x53,
+                    (byte) 0xc5, (byte) 0x05, (byte) 0x74, (byte) 0x8d, (byte) 0x1d, (byte) 0x6e,
+                    (byte) 0x83, (byte) 0xaa, (byte) 0x71, (byte) 0xc5, (byte) 0xe1, (byte) 0xa1,
+                    (byte) 0xa6, (byte) 0xf3, (byte) 0xee, (byte) 0x5f, (byte) 0x9e, (byte) 0x4f,
+                    (byte) 0xe8, (byte) 0x15, (byte) 0xd5, (byte) 0xa9, (byte) 0x1b, (byte) 0xa6,
+                    (byte) 0x41, (byte) 0x2b, (byte) 0x18, (byte) 0x13, (byte) 0x20, (byte) 0x9f,
+                    (byte) 0x6b, (byte) 0xf1, (byte) 0xd8, (byte) 0xf4, (byte) 0x87, (byte) 0xfa,
+                    (byte) 0x80, (byte) 0xec, (byte) 0x0e, (byte) 0xa4, (byte) 0x4b, (byte) 0x24,
+                    (byte) 0x03, (byte) 0x14, (byte) 0x25, (byte) 0xf2, (byte) 0x20, (byte) 0xfc,
+                    (byte) 0x52, (byte) 0xf9, (byte) 0xd6, (byte) 0x7a, (byte) 0x4a, (byte) 0x45,
+                    (byte) 0x33, (byte) 0xec, (byte) 0xde, (byte) 0x3c, (byte) 0x5b, (byte) 0xf2,
+                    (byte) 0xdc, (byte) 0x8e, (byte) 0xc6, (byte) 0xb3, (byte) 0x26, (byte) 0xd3,
+                    (byte) 0x68, (byte) 0xa7, (byte) 0xd8, (byte) 0x3a, (byte) 0xde, (byte) 0xa9,
+                    (byte) 0x25, (byte) 0x1d, (byte) 0x42, (byte) 0x75, (byte) 0x66, (byte) 0x16,
+                    (byte) 0x29, (byte) 0xad, (byte) 0x09, (byte) 0x74, (byte) 0x41, (byte) 0xbb,
+                    (byte) 0x45, (byte) 0x39, (byte) 0x04, (byte) 0x7a, (byte) 0x93, (byte) 0xad,
+                    (byte) 0x1c, (byte) 0xa6, (byte) 0x38, (byte) 0xf4, (byte) 0xac, (byte) 0xca,
+                    (byte) 0x5a, (byte) 0xab, (byte) 0x92, (byte) 0x76, (byte) 0x26, (byte) 0x3c,
+                    (byte) 0xeb, (byte) 0xda, (byte) 0xfc, (byte) 0x25, (byte) 0x93, (byte) 0x23,
+                    (byte) 0x01, (byte) 0xe2, (byte) 0xac, (byte) 0x5e, (byte) 0x4c, (byte) 0xb7,
+                    (byte) 0xbc, (byte) 0x5b, (byte) 0xaa, (byte) 0x14, (byte) 0xe9, (byte) 0xbf,
+                    (byte) 0x2d, (byte) 0x3a, (byte) 0xdc, (byte) 0x2f, (byte) 0x6b, (byte) 0x4d,
+                    (byte) 0x0e, (byte) 0x0a, (byte) 0x82, (byte) 0x3c, (byte) 0xd9, (byte) 0x32,
+                    (byte) 0xc1, (byte) 0xc4, (byte) 0xa2, (byte) 0x46, (byte) 0x71, (byte) 0x10,
+                    (byte) 0x54, (byte) 0x1a, (byte) 0xa6, (byte) 0xaa, (byte) 0x64, (byte) 0xe7,
+                    (byte) 0xc2, (byte) 0xae, (byte) 0xbc, (byte) 0x3d, (byte) 0xa4, (byte) 0xa8,
+                    (byte) 0xd1, (byte) 0xb7, (byte) 0x27, (byte) 0xef, (byte) 0x5f, (byte) 0xe7,
+                    (byte) 0xa7, (byte) 0x5d, (byte) 0xa0, (byte) 0xcd, (byte) 0x57, (byte) 0xf1,
+                    (byte) 0xe0, (byte) 0xd8, (byte) 0x42, (byte) 0x10, (byte) 0x77, (byte) 0xc3,
+                    (byte) 0xa7, (byte) 0x1e, (byte) 0x0c, (byte) 0x37, (byte) 0x16, (byte) 0x11,
+                    (byte) 0x94, (byte) 0x21, (byte) 0xf2, (byte) 0xca, (byte) 0x60, (byte) 0xce,
+                    (byte) 0xca, (byte) 0x59, (byte) 0xf9, (byte) 0xe5, (byte) 0xe4};
 
     /*
      * echo -n 'This is a test of OAEP' | openssl pkeyutl -encrypt -inkey /tmp/rsakey.txt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha1 -pkeyopt rsa_oaep_label:010203FFA00A | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA256_MGF1_SHA1_LABEL = new byte[] {
-            (byte) 0x80, (byte) 0xb1, (byte) 0xf2, (byte) 0xc2, (byte) 0x03, (byte) 0xc5,
-            (byte) 0xdf, (byte) 0xbd, (byte) 0xed, (byte) 0xfe, (byte) 0xe6, (byte) 0xff,
-            (byte) 0xd3, (byte) 0x38, (byte) 0x1e, (byte) 0x6d, (byte) 0xae, (byte) 0x47,
-            (byte) 0xfe, (byte) 0x19, (byte) 0xf9, (byte) 0x8c, (byte) 0xf1, (byte) 0x4d,
-            (byte) 0x18, (byte) 0x2b, (byte) 0x7e, (byte) 0x8e, (byte) 0x47, (byte) 0x39,
-            (byte) 0xa8, (byte) 0x04, (byte) 0xc4, (byte) 0x7d, (byte) 0x56, (byte) 0x03,
-            (byte) 0x15, (byte) 0x92, (byte) 0x18, (byte) 0xde, (byte) 0x56, (byte) 0xb3,
-            (byte) 0x01, (byte) 0x93, (byte) 0x16, (byte) 0xe3, (byte) 0xfa, (byte) 0xaa,
-            (byte) 0xf3, (byte) 0x73, (byte) 0x39, (byte) 0x26, (byte) 0xfb, (byte) 0xb0,
-            (byte) 0x18, (byte) 0x20, (byte) 0xdb, (byte) 0xa1, (byte) 0xbf, (byte) 0x31,
-            (byte) 0x22, (byte) 0xc8, (byte) 0x1d, (byte) 0xdb, (byte) 0xa0, (byte) 0x5a,
-            (byte) 0x22, (byte) 0xcd, (byte) 0x09, (byte) 0xb3, (byte) 0xcb, (byte) 0xa2,
-            (byte) 0x46, (byte) 0x14, (byte) 0x35, (byte) 0x66, (byte) 0xe8, (byte) 0xb8,
-            (byte) 0x07, (byte) 0x23, (byte) 0xc5, (byte) 0xae, (byte) 0xe6, (byte) 0xf1,
-            (byte) 0x7a, (byte) 0x8f, (byte) 0x5c, (byte) 0x44, (byte) 0x34, (byte) 0xbf,
-            (byte) 0xd6, (byte) 0xf8, (byte) 0x0c, (byte) 0xc7, (byte) 0x8d, (byte) 0xcd,
-            (byte) 0x23, (byte) 0x84, (byte) 0xbe, (byte) 0x9b, (byte) 0xbf, (byte) 0x9a,
-            (byte) 0x70, (byte) 0x0f, (byte) 0x18, (byte) 0xc0, (byte) 0x6f, (byte) 0x23,
-            (byte) 0x67, (byte) 0xf8, (byte) 0xbb, (byte) 0xce, (byte) 0xc2, (byte) 0x47,
-            (byte) 0x82, (byte) 0xa0, (byte) 0xa5, (byte) 0x60, (byte) 0xcd, (byte) 0x25,
-            (byte) 0xa5, (byte) 0x4b, (byte) 0xe4, (byte) 0x06, (byte) 0x7f, (byte) 0x46,
-            (byte) 0x62, (byte) 0x86, (byte) 0x94, (byte) 0xbc, (byte) 0x7f, (byte) 0xb0,
-            (byte) 0x2e, (byte) 0xc1, (byte) 0x8c, (byte) 0x6c, (byte) 0x58, (byte) 0x05,
-            (byte) 0x6f, (byte) 0x35, (byte) 0x76, (byte) 0xd3, (byte) 0xdf, (byte) 0xc0,
-            (byte) 0xdd, (byte) 0x66, (byte) 0xbe, (byte) 0xa1, (byte) 0x7e, (byte) 0x52,
-            (byte) 0xed, (byte) 0x81, (byte) 0x0e, (byte) 0x2d, (byte) 0x5b, (byte) 0x2b,
-            (byte) 0xe3, (byte) 0x52, (byte) 0x0e, (byte) 0x56, (byte) 0x9b, (byte) 0x05,
-            (byte) 0x72, (byte) 0xa8, (byte) 0xc8, (byte) 0x57, (byte) 0x22, (byte) 0x67,
-            (byte) 0x0e, (byte) 0x5f, (byte) 0x01, (byte) 0xf2, (byte) 0x69, (byte) 0x66,
-            (byte) 0x6a, (byte) 0x47, (byte) 0x4f, (byte) 0x78, (byte) 0xb3, (byte) 0x1e,
-            (byte) 0x7d, (byte) 0xce, (byte) 0xb3, (byte) 0x35, (byte) 0xdf, (byte) 0x23,
-            (byte) 0xac, (byte) 0xf8, (byte) 0x88, (byte) 0xa1, (byte) 0xde, (byte) 0x38,
-            (byte) 0x96, (byte) 0xfd, (byte) 0xa2, (byte) 0x5d, (byte) 0x09, (byte) 0x52,
-            (byte) 0x11, (byte) 0x2b, (byte) 0x21, (byte) 0xf0, (byte) 0x0d, (byte) 0x4c,
-            (byte) 0x15, (byte) 0xc3, (byte) 0x88, (byte) 0x2b, (byte) 0xf6, (byte) 0x2b,
-            (byte) 0xe3, (byte) 0xfd, (byte) 0x52, (byte) 0xf0, (byte) 0x09, (byte) 0x5c,
-            (byte) 0x4f, (byte) 0x5b, (byte) 0x8b, (byte) 0x84, (byte) 0x71, (byte) 0x72,
-            (byte) 0x8d, (byte) 0xaa, (byte) 0x6c, (byte) 0x55, (byte) 0xba, (byte) 0xe7,
-            (byte) 0x9c, (byte) 0xba, (byte) 0xbf, (byte) 0xf4, (byte) 0x09, (byte) 0x0a,
-            (byte) 0x60, (byte) 0xec, (byte) 0x53, (byte) 0xa4, (byte) 0x01, (byte) 0xa5,
-            (byte) 0xf2, (byte) 0x58, (byte) 0xab, (byte) 0x95, (byte) 0x68, (byte) 0x79,
-            (byte) 0x0b, (byte) 0xc3, (byte) 0xc4, (byte) 0x00, (byte) 0x68, (byte) 0x19,
-            (byte) 0xca, (byte) 0x07, (byte) 0x0d, (byte) 0x32
-    };
+    private static final byte[] RSA_Vector2_OAEP_SHA256_MGF1_SHA1_LABEL =
+            new byte[] {(byte) 0x80, (byte) 0xb1, (byte) 0xf2, (byte) 0xc2, (byte) 0x03,
+                    (byte) 0xc5, (byte) 0xdf, (byte) 0xbd, (byte) 0xed, (byte) 0xfe, (byte) 0xe6,
+                    (byte) 0xff, (byte) 0xd3, (byte) 0x38, (byte) 0x1e, (byte) 0x6d, (byte) 0xae,
+                    (byte) 0x47, (byte) 0xfe, (byte) 0x19, (byte) 0xf9, (byte) 0x8c, (byte) 0xf1,
+                    (byte) 0x4d, (byte) 0x18, (byte) 0x2b, (byte) 0x7e, (byte) 0x8e, (byte) 0x47,
+                    (byte) 0x39, (byte) 0xa8, (byte) 0x04, (byte) 0xc4, (byte) 0x7d, (byte) 0x56,
+                    (byte) 0x03, (byte) 0x15, (byte) 0x92, (byte) 0x18, (byte) 0xde, (byte) 0x56,
+                    (byte) 0xb3, (byte) 0x01, (byte) 0x93, (byte) 0x16, (byte) 0xe3, (byte) 0xfa,
+                    (byte) 0xaa, (byte) 0xf3, (byte) 0x73, (byte) 0x39, (byte) 0x26, (byte) 0xfb,
+                    (byte) 0xb0, (byte) 0x18, (byte) 0x20, (byte) 0xdb, (byte) 0xa1, (byte) 0xbf,
+                    (byte) 0x31, (byte) 0x22, (byte) 0xc8, (byte) 0x1d, (byte) 0xdb, (byte) 0xa0,
+                    (byte) 0x5a, (byte) 0x22, (byte) 0xcd, (byte) 0x09, (byte) 0xb3, (byte) 0xcb,
+                    (byte) 0xa2, (byte) 0x46, (byte) 0x14, (byte) 0x35, (byte) 0x66, (byte) 0xe8,
+                    (byte) 0xb8, (byte) 0x07, (byte) 0x23, (byte) 0xc5, (byte) 0xae, (byte) 0xe6,
+                    (byte) 0xf1, (byte) 0x7a, (byte) 0x8f, (byte) 0x5c, (byte) 0x44, (byte) 0x34,
+                    (byte) 0xbf, (byte) 0xd6, (byte) 0xf8, (byte) 0x0c, (byte) 0xc7, (byte) 0x8d,
+                    (byte) 0xcd, (byte) 0x23, (byte) 0x84, (byte) 0xbe, (byte) 0x9b, (byte) 0xbf,
+                    (byte) 0x9a, (byte) 0x70, (byte) 0x0f, (byte) 0x18, (byte) 0xc0, (byte) 0x6f,
+                    (byte) 0x23, (byte) 0x67, (byte) 0xf8, (byte) 0xbb, (byte) 0xce, (byte) 0xc2,
+                    (byte) 0x47, (byte) 0x82, (byte) 0xa0, (byte) 0xa5, (byte) 0x60, (byte) 0xcd,
+                    (byte) 0x25, (byte) 0xa5, (byte) 0x4b, (byte) 0xe4, (byte) 0x06, (byte) 0x7f,
+                    (byte) 0x46, (byte) 0x62, (byte) 0x86, (byte) 0x94, (byte) 0xbc, (byte) 0x7f,
+                    (byte) 0xb0, (byte) 0x2e, (byte) 0xc1, (byte) 0x8c, (byte) 0x6c, (byte) 0x58,
+                    (byte) 0x05, (byte) 0x6f, (byte) 0x35, (byte) 0x76, (byte) 0xd3, (byte) 0xdf,
+                    (byte) 0xc0, (byte) 0xdd, (byte) 0x66, (byte) 0xbe, (byte) 0xa1, (byte) 0x7e,
+                    (byte) 0x52, (byte) 0xed, (byte) 0x81, (byte) 0x0e, (byte) 0x2d, (byte) 0x5b,
+                    (byte) 0x2b, (byte) 0xe3, (byte) 0x52, (byte) 0x0e, (byte) 0x56, (byte) 0x9b,
+                    (byte) 0x05, (byte) 0x72, (byte) 0xa8, (byte) 0xc8, (byte) 0x57, (byte) 0x22,
+                    (byte) 0x67, (byte) 0x0e, (byte) 0x5f, (byte) 0x01, (byte) 0xf2, (byte) 0x69,
+                    (byte) 0x66, (byte) 0x6a, (byte) 0x47, (byte) 0x4f, (byte) 0x78, (byte) 0xb3,
+                    (byte) 0x1e, (byte) 0x7d, (byte) 0xce, (byte) 0xb3, (byte) 0x35, (byte) 0xdf,
+                    (byte) 0x23, (byte) 0xac, (byte) 0xf8, (byte) 0x88, (byte) 0xa1, (byte) 0xde,
+                    (byte) 0x38, (byte) 0x96, (byte) 0xfd, (byte) 0xa2, (byte) 0x5d, (byte) 0x09,
+                    (byte) 0x52, (byte) 0x11, (byte) 0x2b, (byte) 0x21, (byte) 0xf0, (byte) 0x0d,
+                    (byte) 0x4c, (byte) 0x15, (byte) 0xc3, (byte) 0x88, (byte) 0x2b, (byte) 0xf6,
+                    (byte) 0x2b, (byte) 0xe3, (byte) 0xfd, (byte) 0x52, (byte) 0xf0, (byte) 0x09,
+                    (byte) 0x5c, (byte) 0x4f, (byte) 0x5b, (byte) 0x8b, (byte) 0x84, (byte) 0x71,
+                    (byte) 0x72, (byte) 0x8d, (byte) 0xaa, (byte) 0x6c, (byte) 0x55, (byte) 0xba,
+                    (byte) 0xe7, (byte) 0x9c, (byte) 0xba, (byte) 0xbf, (byte) 0xf4, (byte) 0x09,
+                    (byte) 0x0a, (byte) 0x60, (byte) 0xec, (byte) 0x53, (byte) 0xa4, (byte) 0x01,
+                    (byte) 0xa5, (byte) 0xf2, (byte) 0x58, (byte) 0xab, (byte) 0x95, (byte) 0x68,
+                    (byte) 0x79, (byte) 0x0b, (byte) 0xc3, (byte) 0xc4, (byte) 0x00, (byte) 0x68,
+                    (byte) 0x19, (byte) 0xca, (byte) 0x07, (byte) 0x0d, (byte) 0x32};
 
     /*
      * echo -n 'This is a test of OAEP' | openssl pkeyutl -encrypt -inkey rsakey.pem \
      * -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha224 -pkeyopt rsa_mgf1_md:sha224 \
      * | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA224_MGF1_SHA224 = new byte[] {
-            (byte) 0xae, (byte) 0xdd, (byte) 0xe6, (byte) 0xab, (byte) 0x00, (byte) 0xd6,
-            (byte) 0x1e, (byte) 0x7e, (byte) 0x85, (byte) 0x63, (byte) 0xab, (byte) 0x51,
-            (byte) 0x79, (byte) 0x92, (byte) 0xf1, (byte) 0xb9, (byte) 0x4f, (byte) 0x23,
-            (byte) 0xae, (byte) 0xf7, (byte) 0x1b, (byte) 0x5f, (byte) 0x10, (byte) 0x5b,
-            (byte) 0xa5, (byte) 0x15, (byte) 0x87, (byte) 0xa3, (byte) 0xbb, (byte) 0x26,
-            (byte) 0xfe, (byte) 0x7f, (byte) 0xc0, (byte) 0xa3, (byte) 0x67, (byte) 0x95,
-            (byte) 0xda, (byte) 0xc4, (byte) 0x6f, (byte) 0x6e, (byte) 0x08, (byte) 0x23,
-            (byte) 0x28, (byte) 0x0b, (byte) 0xdd, (byte) 0x29, (byte) 0x29, (byte) 0xdc,
-            (byte) 0xb0, (byte) 0x35, (byte) 0x16, (byte) 0x2e, (byte) 0x0f, (byte) 0xb9,
-            (byte) 0x1d, (byte) 0x90, (byte) 0x27, (byte) 0x68, (byte) 0xc7, (byte) 0x92,
-            (byte) 0x52, (byte) 0x8a, (byte) 0x1d, (byte) 0x48, (byte) 0x6a, (byte) 0x7d,
-            (byte) 0x0b, (byte) 0xf6, (byte) 0x35, (byte) 0xca, (byte) 0xe1, (byte) 0x57,
-            (byte) 0xdd, (byte) 0x36, (byte) 0x3b, (byte) 0x51, (byte) 0x45, (byte) 0x77,
-            (byte) 0x28, (byte) 0x4f, (byte) 0x98, (byte) 0xc0, (byte) 0xe0, (byte) 0xa7,
-            (byte) 0x51, (byte) 0x98, (byte) 0x84, (byte) 0x7a, (byte) 0x29, (byte) 0x05,
-            (byte) 0x9f, (byte) 0x60, (byte) 0x66, (byte) 0xf6, (byte) 0x83, (byte) 0xcd,
-            (byte) 0x03, (byte) 0x3e, (byte) 0x82, (byte) 0x0f, (byte) 0x57, (byte) 0x4b,
-            (byte) 0x27, (byte) 0x14, (byte) 0xf6, (byte) 0xc8, (byte) 0x5b, (byte) 0xed,
-            (byte) 0xc3, (byte) 0x77, (byte) 0x6f, (byte) 0xec, (byte) 0x0e, (byte) 0xae,
-            (byte) 0x59, (byte) 0xbe, (byte) 0x68, (byte) 0x76, (byte) 0x16, (byte) 0x17,
-            (byte) 0x77, (byte) 0xe2, (byte) 0xbd, (byte) 0xe0, (byte) 0x5a, (byte) 0x14,
-            (byte) 0xd9, (byte) 0xf4, (byte) 0x3f, (byte) 0x50, (byte) 0x31, (byte) 0xf0,
-            (byte) 0x0c, (byte) 0x82, (byte) 0x6c, (byte) 0xcc, (byte) 0x81, (byte) 0x84,
-            (byte) 0x3e, (byte) 0x63, (byte) 0x93, (byte) 0xe7, (byte) 0x12, (byte) 0x2d,
-            (byte) 0xc9, (byte) 0xa3, (byte) 0xe3, (byte) 0xce, (byte) 0xfd, (byte) 0xc7,
-            (byte) 0xe1, (byte) 0xef, (byte) 0xa4, (byte) 0x16, (byte) 0x5c, (byte) 0x60,
-            (byte) 0xb1, (byte) 0x80, (byte) 0x31, (byte) 0x15, (byte) 0x5c, (byte) 0x35,
-            (byte) 0x25, (byte) 0x0b, (byte) 0x89, (byte) 0xe4, (byte) 0x56, (byte) 0x74,
-            (byte) 0x8b, (byte) 0xaf, (byte) 0x8e, (byte) 0xe9, (byte) 0xe2, (byte) 0x37,
-            (byte) 0x17, (byte) 0xe6, (byte) 0x7b, (byte) 0x78, (byte) 0xd8, (byte) 0x2c,
-            (byte) 0x27, (byte) 0x52, (byte) 0x21, (byte) 0x96, (byte) 0xa0, (byte) 0x92,
-            (byte) 0x95, (byte) 0x64, (byte) 0xc3, (byte) 0x7f, (byte) 0x45, (byte) 0xfc,
-            (byte) 0x3d, (byte) 0x48, (byte) 0x4a, (byte) 0xd5, (byte) 0xa4, (byte) 0x0a,
-            (byte) 0x57, (byte) 0x07, (byte) 0x57, (byte) 0x95, (byte) 0x9f, (byte) 0x2f,
-            (byte) 0x75, (byte) 0x32, (byte) 0x2a, (byte) 0x4d, (byte) 0x64, (byte) 0xbd,
-            (byte) 0xb1, (byte) 0xe0, (byte) 0x46, (byte) 0x4f, (byte) 0xe8, (byte) 0x6c,
-            (byte) 0x4b, (byte) 0x77, (byte) 0xcc, (byte) 0x36, (byte) 0x87, (byte) 0x05,
-            (byte) 0x56, (byte) 0x9a, (byte) 0xe4, (byte) 0x2c, (byte) 0x43, (byte) 0xfd,
-            (byte) 0x34, (byte) 0x97, (byte) 0xf8, (byte) 0xd7, (byte) 0x91, (byte) 0xff,
-            (byte) 0x56, (byte) 0x86, (byte) 0x17, (byte) 0x49, (byte) 0x0a, (byte) 0x52,
-            (byte) 0xfb, (byte) 0xe5, (byte) 0x49, (byte) 0xdf, (byte) 0xc1, (byte) 0x28,
-            (byte) 0x9d, (byte) 0x85, (byte) 0x66, (byte) 0x9d, (byte) 0x1d, (byte) 0xa4,
-            (byte) 0x7e, (byte) 0x9a, (byte) 0x5b, (byte) 0x30
-    };
+    private static final byte[] RSA_Vector2_OAEP_SHA224_MGF1_SHA224 =
+            new byte[] {(byte) 0xae, (byte) 0xdd, (byte) 0xe6, (byte) 0xab, (byte) 0x00,
+                    (byte) 0xd6, (byte) 0x1e, (byte) 0x7e, (byte) 0x85, (byte) 0x63, (byte) 0xab,
+                    (byte) 0x51, (byte) 0x79, (byte) 0x92, (byte) 0xf1, (byte) 0xb9, (byte) 0x4f,
+                    (byte) 0x23, (byte) 0xae, (byte) 0xf7, (byte) 0x1b, (byte) 0x5f, (byte) 0x10,
+                    (byte) 0x5b, (byte) 0xa5, (byte) 0x15, (byte) 0x87, (byte) 0xa3, (byte) 0xbb,
+                    (byte) 0x26, (byte) 0xfe, (byte) 0x7f, (byte) 0xc0, (byte) 0xa3, (byte) 0x67,
+                    (byte) 0x95, (byte) 0xda, (byte) 0xc4, (byte) 0x6f, (byte) 0x6e, (byte) 0x08,
+                    (byte) 0x23, (byte) 0x28, (byte) 0x0b, (byte) 0xdd, (byte) 0x29, (byte) 0x29,
+                    (byte) 0xdc, (byte) 0xb0, (byte) 0x35, (byte) 0x16, (byte) 0x2e, (byte) 0x0f,
+                    (byte) 0xb9, (byte) 0x1d, (byte) 0x90, (byte) 0x27, (byte) 0x68, (byte) 0xc7,
+                    (byte) 0x92, (byte) 0x52, (byte) 0x8a, (byte) 0x1d, (byte) 0x48, (byte) 0x6a,
+                    (byte) 0x7d, (byte) 0x0b, (byte) 0xf6, (byte) 0x35, (byte) 0xca, (byte) 0xe1,
+                    (byte) 0x57, (byte) 0xdd, (byte) 0x36, (byte) 0x3b, (byte) 0x51, (byte) 0x45,
+                    (byte) 0x77, (byte) 0x28, (byte) 0x4f, (byte) 0x98, (byte) 0xc0, (byte) 0xe0,
+                    (byte) 0xa7, (byte) 0x51, (byte) 0x98, (byte) 0x84, (byte) 0x7a, (byte) 0x29,
+                    (byte) 0x05, (byte) 0x9f, (byte) 0x60, (byte) 0x66, (byte) 0xf6, (byte) 0x83,
+                    (byte) 0xcd, (byte) 0x03, (byte) 0x3e, (byte) 0x82, (byte) 0x0f, (byte) 0x57,
+                    (byte) 0x4b, (byte) 0x27, (byte) 0x14, (byte) 0xf6, (byte) 0xc8, (byte) 0x5b,
+                    (byte) 0xed, (byte) 0xc3, (byte) 0x77, (byte) 0x6f, (byte) 0xec, (byte) 0x0e,
+                    (byte) 0xae, (byte) 0x59, (byte) 0xbe, (byte) 0x68, (byte) 0x76, (byte) 0x16,
+                    (byte) 0x17, (byte) 0x77, (byte) 0xe2, (byte) 0xbd, (byte) 0xe0, (byte) 0x5a,
+                    (byte) 0x14, (byte) 0xd9, (byte) 0xf4, (byte) 0x3f, (byte) 0x50, (byte) 0x31,
+                    (byte) 0xf0, (byte) 0x0c, (byte) 0x82, (byte) 0x6c, (byte) 0xcc, (byte) 0x81,
+                    (byte) 0x84, (byte) 0x3e, (byte) 0x63, (byte) 0x93, (byte) 0xe7, (byte) 0x12,
+                    (byte) 0x2d, (byte) 0xc9, (byte) 0xa3, (byte) 0xe3, (byte) 0xce, (byte) 0xfd,
+                    (byte) 0xc7, (byte) 0xe1, (byte) 0xef, (byte) 0xa4, (byte) 0x16, (byte) 0x5c,
+                    (byte) 0x60, (byte) 0xb1, (byte) 0x80, (byte) 0x31, (byte) 0x15, (byte) 0x5c,
+                    (byte) 0x35, (byte) 0x25, (byte) 0x0b, (byte) 0x89, (byte) 0xe4, (byte) 0x56,
+                    (byte) 0x74, (byte) 0x8b, (byte) 0xaf, (byte) 0x8e, (byte) 0xe9, (byte) 0xe2,
+                    (byte) 0x37, (byte) 0x17, (byte) 0xe6, (byte) 0x7b, (byte) 0x78, (byte) 0xd8,
+                    (byte) 0x2c, (byte) 0x27, (byte) 0x52, (byte) 0x21, (byte) 0x96, (byte) 0xa0,
+                    (byte) 0x92, (byte) 0x95, (byte) 0x64, (byte) 0xc3, (byte) 0x7f, (byte) 0x45,
+                    (byte) 0xfc, (byte) 0x3d, (byte) 0x48, (byte) 0x4a, (byte) 0xd5, (byte) 0xa4,
+                    (byte) 0x0a, (byte) 0x57, (byte) 0x07, (byte) 0x57, (byte) 0x95, (byte) 0x9f,
+                    (byte) 0x2f, (byte) 0x75, (byte) 0x32, (byte) 0x2a, (byte) 0x4d, (byte) 0x64,
+                    (byte) 0xbd, (byte) 0xb1, (byte) 0xe0, (byte) 0x46, (byte) 0x4f, (byte) 0xe8,
+                    (byte) 0x6c, (byte) 0x4b, (byte) 0x77, (byte) 0xcc, (byte) 0x36, (byte) 0x87,
+                    (byte) 0x05, (byte) 0x56, (byte) 0x9a, (byte) 0xe4, (byte) 0x2c, (byte) 0x43,
+                    (byte) 0xfd, (byte) 0x34, (byte) 0x97, (byte) 0xf8, (byte) 0xd7, (byte) 0x91,
+                    (byte) 0xff, (byte) 0x56, (byte) 0x86, (byte) 0x17, (byte) 0x49, (byte) 0x0a,
+                    (byte) 0x52, (byte) 0xfb, (byte) 0xe5, (byte) 0x49, (byte) 0xdf, (byte) 0xc1,
+                    (byte) 0x28, (byte) 0x9d, (byte) 0x85, (byte) 0x66, (byte) 0x9d, (byte) 0x1d,
+                    (byte) 0xa4, (byte) 0x7e, (byte) 0x9a, (byte) 0x5b, (byte) 0x30};
 
     /*
      * echo -n 'This is a test of OAEP' | openssl pkeyutl -encrypt -inkey /tmp/rsakey.txt \
      * -pkeyopt rsa_padding_mode:oaep -pkey rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha256 \
      * | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA256_MGF1_SHA256 = new byte[] {
-            (byte) 0x6a, (byte) 0x2b, (byte) 0xb2, (byte) 0xa3, (byte) 0x26, (byte) 0xa6,
-            (byte) 0x7a, (byte) 0x4a, (byte) 0x1f, (byte) 0xe5, (byte) 0xc8, (byte) 0x94,
-            (byte) 0x11, (byte) 0x1a, (byte) 0x92, (byte) 0x07, (byte) 0x0a, (byte) 0xf4,
-            (byte) 0x07, (byte) 0x0b, (byte) 0xd6, (byte) 0x37, (byte) 0xa5, (byte) 0x5d,
-            (byte) 0x16, (byte) 0x0a, (byte) 0x7d, (byte) 0x13, (byte) 0x27, (byte) 0x32,
-            (byte) 0x5a, (byte) 0xc3, (byte) 0x0d, (byte) 0x7a, (byte) 0x54, (byte) 0xfe,
-            (byte) 0x02, (byte) 0x28, (byte) 0xc6, (byte) 0x8e, (byte) 0x32, (byte) 0x7b,
-            (byte) 0x0a, (byte) 0x52, (byte) 0xf8, (byte) 0xe6, (byte) 0xab, (byte) 0x16,
-            (byte) 0x77, (byte) 0x7c, (byte) 0x53, (byte) 0xcd, (byte) 0xb0, (byte) 0xb6,
-            (byte) 0x90, (byte) 0xce, (byte) 0x7b, (byte) 0xa5, (byte) 0xdb, (byte) 0xab,
-            (byte) 0xfd, (byte) 0xf5, (byte) 0xbb, (byte) 0x49, (byte) 0x63, (byte) 0xb7,
-            (byte) 0xa8, (byte) 0x3e, (byte) 0x53, (byte) 0xf1, (byte) 0x00, (byte) 0x4d,
-            (byte) 0x72, (byte) 0x15, (byte) 0x34, (byte) 0xa8, (byte) 0x5b, (byte) 0x00,
-            (byte) 0x01, (byte) 0x75, (byte) 0xdc, (byte) 0xb6, (byte) 0xd1, (byte) 0xdf,
-            (byte) 0xcb, (byte) 0x93, (byte) 0xf3, (byte) 0x31, (byte) 0x04, (byte) 0x7e,
-            (byte) 0x48, (byte) 0x3e, (byte) 0xc9, (byte) 0xaf, (byte) 0xd7, (byte) 0xbd,
-            (byte) 0x9e, (byte) 0x73, (byte) 0x01, (byte) 0x79, (byte) 0xf8, (byte) 0xdc,
-            (byte) 0x46, (byte) 0x31, (byte) 0x55, (byte) 0x83, (byte) 0x21, (byte) 0xd1,
-            (byte) 0x19, (byte) 0x0b, (byte) 0x57, (byte) 0xf1, (byte) 0x06, (byte) 0xb9,
-            (byte) 0x32, (byte) 0x0e, (byte) 0x9d, (byte) 0x38, (byte) 0x53, (byte) 0x94,
-            (byte) 0x96, (byte) 0xd4, (byte) 0x6d, (byte) 0x18, (byte) 0xe2, (byte) 0xe3,
-            (byte) 0xcd, (byte) 0xfa, (byte) 0xfe, (byte) 0xb3, (byte) 0xe3, (byte) 0x27,
-            (byte) 0xd7, (byte) 0x45, (byte) 0xe8, (byte) 0x46, (byte) 0x6b, (byte) 0x06,
-            (byte) 0x0f, (byte) 0x5e, (byte) 0x24, (byte) 0x02, (byte) 0xef, (byte) 0xa2,
-            (byte) 0x69, (byte) 0xe6, (byte) 0x15, (byte) 0xb3, (byte) 0x8f, (byte) 0x71,
-            (byte) 0x97, (byte) 0x39, (byte) 0xfb, (byte) 0x32, (byte) 0xe0, (byte) 0xe5,
-            (byte) 0xac, (byte) 0x46, (byte) 0xb4, (byte) 0xe7, (byte) 0x3d, (byte) 0x89,
-            (byte) 0xba, (byte) 0xd9, (byte) 0x4c, (byte) 0x25, (byte) 0x97, (byte) 0xef,
-            (byte) 0xe6, (byte) 0x17, (byte) 0x23, (byte) 0x4e, (byte) 0xc8, (byte) 0xdb,
-            (byte) 0x18, (byte) 0x9b, (byte) 0xba, (byte) 0xb5, (byte) 0x7e, (byte) 0x19,
-            (byte) 0x4d, (byte) 0x95, (byte) 0x7d, (byte) 0x60, (byte) 0x1b, (byte) 0xa7,
-            (byte) 0x06, (byte) 0x1e, (byte) 0x99, (byte) 0x4a, (byte) 0xf2, (byte) 0x82,
-            (byte) 0x71, (byte) 0x62, (byte) 0x41, (byte) 0xa4, (byte) 0xa7, (byte) 0xdb,
-            (byte) 0x88, (byte) 0xb0, (byte) 0x4a, (byte) 0xc7, (byte) 0x3b, (byte) 0xce,
-            (byte) 0x91, (byte) 0x4f, (byte) 0xc7, (byte) 0xca, (byte) 0x6f, (byte) 0x89,
-            (byte) 0xac, (byte) 0x1a, (byte) 0x36, (byte) 0x84, (byte) 0x0c, (byte) 0x97,
-            (byte) 0xa0, (byte) 0x1a, (byte) 0x08, (byte) 0x6f, (byte) 0x70, (byte) 0xf3,
-            (byte) 0x94, (byte) 0xa0, (byte) 0x0f, (byte) 0x44, (byte) 0xdd, (byte) 0x86,
-            (byte) 0x9d, (byte) 0x2c, (byte) 0xac, (byte) 0x43, (byte) 0xed, (byte) 0xb8,
-            (byte) 0xa1, (byte) 0x66, (byte) 0xf3, (byte) 0xd3, (byte) 0x5c, (byte) 0xe5,
-            (byte) 0xe2, (byte) 0x4c, (byte) 0x7e, (byte) 0xda, (byte) 0x20, (byte) 0xbd,
-            (byte) 0x5a, (byte) 0x75, (byte) 0x12, (byte) 0x31, (byte) 0x23, (byte) 0x02,
-            (byte) 0xb5, (byte) 0x1f, (byte) 0x38, (byte) 0x98
-    };
+    private static final byte[] RSA_Vector2_OAEP_SHA256_MGF1_SHA256 =
+            new byte[] {(byte) 0x6a, (byte) 0x2b, (byte) 0xb2, (byte) 0xa3, (byte) 0x26,
+                    (byte) 0xa6, (byte) 0x7a, (byte) 0x4a, (byte) 0x1f, (byte) 0xe5, (byte) 0xc8,
+                    (byte) 0x94, (byte) 0x11, (byte) 0x1a, (byte) 0x92, (byte) 0x07, (byte) 0x0a,
+                    (byte) 0xf4, (byte) 0x07, (byte) 0x0b, (byte) 0xd6, (byte) 0x37, (byte) 0xa5,
+                    (byte) 0x5d, (byte) 0x16, (byte) 0x0a, (byte) 0x7d, (byte) 0x13, (byte) 0x27,
+                    (byte) 0x32, (byte) 0x5a, (byte) 0xc3, (byte) 0x0d, (byte) 0x7a, (byte) 0x54,
+                    (byte) 0xfe, (byte) 0x02, (byte) 0x28, (byte) 0xc6, (byte) 0x8e, (byte) 0x32,
+                    (byte) 0x7b, (byte) 0x0a, (byte) 0x52, (byte) 0xf8, (byte) 0xe6, (byte) 0xab,
+                    (byte) 0x16, (byte) 0x77, (byte) 0x7c, (byte) 0x53, (byte) 0xcd, (byte) 0xb0,
+                    (byte) 0xb6, (byte) 0x90, (byte) 0xce, (byte) 0x7b, (byte) 0xa5, (byte) 0xdb,
+                    (byte) 0xab, (byte) 0xfd, (byte) 0xf5, (byte) 0xbb, (byte) 0x49, (byte) 0x63,
+                    (byte) 0xb7, (byte) 0xa8, (byte) 0x3e, (byte) 0x53, (byte) 0xf1, (byte) 0x00,
+                    (byte) 0x4d, (byte) 0x72, (byte) 0x15, (byte) 0x34, (byte) 0xa8, (byte) 0x5b,
+                    (byte) 0x00, (byte) 0x01, (byte) 0x75, (byte) 0xdc, (byte) 0xb6, (byte) 0xd1,
+                    (byte) 0xdf, (byte) 0xcb, (byte) 0x93, (byte) 0xf3, (byte) 0x31, (byte) 0x04,
+                    (byte) 0x7e, (byte) 0x48, (byte) 0x3e, (byte) 0xc9, (byte) 0xaf, (byte) 0xd7,
+                    (byte) 0xbd, (byte) 0x9e, (byte) 0x73, (byte) 0x01, (byte) 0x79, (byte) 0xf8,
+                    (byte) 0xdc, (byte) 0x46, (byte) 0x31, (byte) 0x55, (byte) 0x83, (byte) 0x21,
+                    (byte) 0xd1, (byte) 0x19, (byte) 0x0b, (byte) 0x57, (byte) 0xf1, (byte) 0x06,
+                    (byte) 0xb9, (byte) 0x32, (byte) 0x0e, (byte) 0x9d, (byte) 0x38, (byte) 0x53,
+                    (byte) 0x94, (byte) 0x96, (byte) 0xd4, (byte) 0x6d, (byte) 0x18, (byte) 0xe2,
+                    (byte) 0xe3, (byte) 0xcd, (byte) 0xfa, (byte) 0xfe, (byte) 0xb3, (byte) 0xe3,
+                    (byte) 0x27, (byte) 0xd7, (byte) 0x45, (byte) 0xe8, (byte) 0x46, (byte) 0x6b,
+                    (byte) 0x06, (byte) 0x0f, (byte) 0x5e, (byte) 0x24, (byte) 0x02, (byte) 0xef,
+                    (byte) 0xa2, (byte) 0x69, (byte) 0xe6, (byte) 0x15, (byte) 0xb3, (byte) 0x8f,
+                    (byte) 0x71, (byte) 0x97, (byte) 0x39, (byte) 0xfb, (byte) 0x32, (byte) 0xe0,
+                    (byte) 0xe5, (byte) 0xac, (byte) 0x46, (byte) 0xb4, (byte) 0xe7, (byte) 0x3d,
+                    (byte) 0x89, (byte) 0xba, (byte) 0xd9, (byte) 0x4c, (byte) 0x25, (byte) 0x97,
+                    (byte) 0xef, (byte) 0xe6, (byte) 0x17, (byte) 0x23, (byte) 0x4e, (byte) 0xc8,
+                    (byte) 0xdb, (byte) 0x18, (byte) 0x9b, (byte) 0xba, (byte) 0xb5, (byte) 0x7e,
+                    (byte) 0x19, (byte) 0x4d, (byte) 0x95, (byte) 0x7d, (byte) 0x60, (byte) 0x1b,
+                    (byte) 0xa7, (byte) 0x06, (byte) 0x1e, (byte) 0x99, (byte) 0x4a, (byte) 0xf2,
+                    (byte) 0x82, (byte) 0x71, (byte) 0x62, (byte) 0x41, (byte) 0xa4, (byte) 0xa7,
+                    (byte) 0xdb, (byte) 0x88, (byte) 0xb0, (byte) 0x4a, (byte) 0xc7, (byte) 0x3b,
+                    (byte) 0xce, (byte) 0x91, (byte) 0x4f, (byte) 0xc7, (byte) 0xca, (byte) 0x6f,
+                    (byte) 0x89, (byte) 0xac, (byte) 0x1a, (byte) 0x36, (byte) 0x84, (byte) 0x0c,
+                    (byte) 0x97, (byte) 0xa0, (byte) 0x1a, (byte) 0x08, (byte) 0x6f, (byte) 0x70,
+                    (byte) 0xf3, (byte) 0x94, (byte) 0xa0, (byte) 0x0f, (byte) 0x44, (byte) 0xdd,
+                    (byte) 0x86, (byte) 0x9d, (byte) 0x2c, (byte) 0xac, (byte) 0x43, (byte) 0xed,
+                    (byte) 0xb8, (byte) 0xa1, (byte) 0x66, (byte) 0xf3, (byte) 0xd3, (byte) 0x5c,
+                    (byte) 0xe5, (byte) 0xe2, (byte) 0x4c, (byte) 0x7e, (byte) 0xda, (byte) 0x20,
+                    (byte) 0xbd, (byte) 0x5a, (byte) 0x75, (byte) 0x12, (byte) 0x31, (byte) 0x23,
+                    (byte) 0x02, (byte) 0xb5, (byte) 0x1f, (byte) 0x38, (byte) 0x98};
 
     /*
      * echo -n 'This is a test of OAEP' | openssl pkeyutl -encrypt -inkey /tmp/rsakey.txt \
      * -pkeyopt rsa_padding_mode:oaep -pkey rsa_oaep_md:sha384 -pkeyopt rsa_mgf1_md:sha384 \
      * | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA384_MGF1_SHA384 = new byte[] {
-            (byte) 0xa1, (byte) 0xb3, (byte) 0x3b, (byte) 0x34, (byte) 0x69, (byte) 0x9e,
-            (byte) 0xd8, (byte) 0xa0, (byte) 0x37, (byte) 0x2c, (byte) 0xeb, (byte) 0xef,
-            (byte) 0xf2, (byte) 0xaf, (byte) 0xfa, (byte) 0x63, (byte) 0x5d, (byte) 0x88,
-            (byte) 0xac, (byte) 0x51, (byte) 0xd4, (byte) 0x7f, (byte) 0x85, (byte) 0xf0,
-            (byte) 0x5e, (byte) 0xb4, (byte) 0x81, (byte) 0x7c, (byte) 0x82, (byte) 0x4f,
-            (byte) 0x92, (byte) 0xf7, (byte) 0x77, (byte) 0x48, (byte) 0x4c, (byte) 0xb1,
-            (byte) 0x42, (byte) 0xb3, (byte) 0x0e, (byte) 0x94, (byte) 0xc8, (byte) 0x5a,
-            (byte) 0xae, (byte) 0xed, (byte) 0x8d, (byte) 0x51, (byte) 0x72, (byte) 0x6b,
-            (byte) 0xa9, (byte) 0xd4, (byte) 0x1e, (byte) 0xbe, (byte) 0x38, (byte) 0x2c,
-            (byte) 0xd0, (byte) 0x43, (byte) 0xae, (byte) 0xb4, (byte) 0x30, (byte) 0xa9,
-            (byte) 0x93, (byte) 0x47, (byte) 0xb5, (byte) 0x9d, (byte) 0x03, (byte) 0x92,
-            (byte) 0x25, (byte) 0x74, (byte) 0xed, (byte) 0xfa, (byte) 0xfe, (byte) 0xf1,
-            (byte) 0xba, (byte) 0x04, (byte) 0x3a, (byte) 0x4d, (byte) 0x6d, (byte) 0x9a,
-            (byte) 0x0d, (byte) 0x95, (byte) 0x02, (byte) 0xb0, (byte) 0xac, (byte) 0x77,
-            (byte) 0x11, (byte) 0x44, (byte) 0xeb, (byte) 0xd2, (byte) 0x02, (byte) 0x90,
-            (byte) 0xea, (byte) 0x2f, (byte) 0x68, (byte) 0x2a, (byte) 0x69, (byte) 0xcf,
-            (byte) 0x45, (byte) 0x34, (byte) 0xff, (byte) 0x00, (byte) 0xc6, (byte) 0x3c,
-            (byte) 0x0b, (byte) 0x2c, (byte) 0x5f, (byte) 0x8c, (byte) 0x2c, (byte) 0xbf,
-            (byte) 0xc2, (byte) 0x4b, (byte) 0x16, (byte) 0x07, (byte) 0x84, (byte) 0x74,
-            (byte) 0xf0, (byte) 0x7a, (byte) 0x01, (byte) 0x7e, (byte) 0x74, (byte) 0x01,
-            (byte) 0x88, (byte) 0xce, (byte) 0xda, (byte) 0xe4, (byte) 0x21, (byte) 0x89,
-            (byte) 0xfc, (byte) 0xac, (byte) 0x68, (byte) 0xdb, (byte) 0xfc, (byte) 0x5f,
-            (byte) 0x3f, (byte) 0x00, (byte) 0xd9, (byte) 0x32, (byte) 0x1d, (byte) 0xa5,
-            (byte) 0xec, (byte) 0x72, (byte) 0x46, (byte) 0x23, (byte) 0xe5, (byte) 0x7f,
-            (byte) 0x49, (byte) 0x0e, (byte) 0x3e, (byte) 0xf2, (byte) 0x2b, (byte) 0x16,
-            (byte) 0x52, (byte) 0x9f, (byte) 0x9d, (byte) 0x0c, (byte) 0xfe, (byte) 0xab,
-            (byte) 0xdd, (byte) 0x77, (byte) 0x77, (byte) 0x94, (byte) 0xa4, (byte) 0x92,
-            (byte) 0xa2, (byte) 0x41, (byte) 0x0d, (byte) 0x4b, (byte) 0x57, (byte) 0x80,
-            (byte) 0xd6, (byte) 0x74, (byte) 0x63, (byte) 0xd5, (byte) 0xbf, (byte) 0x5c,
-            (byte) 0xa0, (byte) 0xda, (byte) 0x3c, (byte) 0xe6, (byte) 0xbf, (byte) 0xa4,
-            (byte) 0xc3, (byte) 0xfb, (byte) 0x46, (byte) 0x3b, (byte) 0x73, (byte) 0x30,
-            (byte) 0x4b, (byte) 0x57, (byte) 0x27, (byte) 0x0c, (byte) 0x81, (byte) 0xde,
-            (byte) 0x8a, (byte) 0x01, (byte) 0xe5, (byte) 0x7e, (byte) 0xe0, (byte) 0x16,
-            (byte) 0x11, (byte) 0x24, (byte) 0x34, (byte) 0x22, (byte) 0x01, (byte) 0x9f,
-            (byte) 0xe6, (byte) 0xa9, (byte) 0xfb, (byte) 0xad, (byte) 0x55, (byte) 0x17,
-            (byte) 0x2a, (byte) 0x92, (byte) 0x87, (byte) 0xf3, (byte) 0x72, (byte) 0xc9,
-            (byte) 0x3d, (byte) 0xc9, (byte) 0x2e, (byte) 0x32, (byte) 0x8e, (byte) 0xbb,
-            (byte) 0xdc, (byte) 0x1b, (byte) 0xa7, (byte) 0x7b, (byte) 0x73, (byte) 0xd7,
-            (byte) 0xf4, (byte) 0xad, (byte) 0xa9, (byte) 0x3a, (byte) 0xf7, (byte) 0xa8,
-            (byte) 0x82, (byte) 0x92, (byte) 0x40, (byte) 0xd4, (byte) 0x51, (byte) 0x87,
-            (byte) 0xe1, (byte) 0xb7, (byte) 0x4f, (byte) 0x91, (byte) 0x75, (byte) 0x5b,
-            (byte) 0x03, (byte) 0x9d, (byte) 0xa1, (byte) 0xd4, (byte) 0x00, (byte) 0x05,
-            (byte) 0x79, (byte) 0x42, (byte) 0x93, (byte) 0x76
-    };
+    private static final byte[] RSA_Vector2_OAEP_SHA384_MGF1_SHA384 =
+            new byte[] {(byte) 0xa1, (byte) 0xb3, (byte) 0x3b, (byte) 0x34, (byte) 0x69,
+                    (byte) 0x9e, (byte) 0xd8, (byte) 0xa0, (byte) 0x37, (byte) 0x2c, (byte) 0xeb,
+                    (byte) 0xef, (byte) 0xf2, (byte) 0xaf, (byte) 0xfa, (byte) 0x63, (byte) 0x5d,
+                    (byte) 0x88, (byte) 0xac, (byte) 0x51, (byte) 0xd4, (byte) 0x7f, (byte) 0x85,
+                    (byte) 0xf0, (byte) 0x5e, (byte) 0xb4, (byte) 0x81, (byte) 0x7c, (byte) 0x82,
+                    (byte) 0x4f, (byte) 0x92, (byte) 0xf7, (byte) 0x77, (byte) 0x48, (byte) 0x4c,
+                    (byte) 0xb1, (byte) 0x42, (byte) 0xb3, (byte) 0x0e, (byte) 0x94, (byte) 0xc8,
+                    (byte) 0x5a, (byte) 0xae, (byte) 0xed, (byte) 0x8d, (byte) 0x51, (byte) 0x72,
+                    (byte) 0x6b, (byte) 0xa9, (byte) 0xd4, (byte) 0x1e, (byte) 0xbe, (byte) 0x38,
+                    (byte) 0x2c, (byte) 0xd0, (byte) 0x43, (byte) 0xae, (byte) 0xb4, (byte) 0x30,
+                    (byte) 0xa9, (byte) 0x93, (byte) 0x47, (byte) 0xb5, (byte) 0x9d, (byte) 0x03,
+                    (byte) 0x92, (byte) 0x25, (byte) 0x74, (byte) 0xed, (byte) 0xfa, (byte) 0xfe,
+                    (byte) 0xf1, (byte) 0xba, (byte) 0x04, (byte) 0x3a, (byte) 0x4d, (byte) 0x6d,
+                    (byte) 0x9a, (byte) 0x0d, (byte) 0x95, (byte) 0x02, (byte) 0xb0, (byte) 0xac,
+                    (byte) 0x77, (byte) 0x11, (byte) 0x44, (byte) 0xeb, (byte) 0xd2, (byte) 0x02,
+                    (byte) 0x90, (byte) 0xea, (byte) 0x2f, (byte) 0x68, (byte) 0x2a, (byte) 0x69,
+                    (byte) 0xcf, (byte) 0x45, (byte) 0x34, (byte) 0xff, (byte) 0x00, (byte) 0xc6,
+                    (byte) 0x3c, (byte) 0x0b, (byte) 0x2c, (byte) 0x5f, (byte) 0x8c, (byte) 0x2c,
+                    (byte) 0xbf, (byte) 0xc2, (byte) 0x4b, (byte) 0x16, (byte) 0x07, (byte) 0x84,
+                    (byte) 0x74, (byte) 0xf0, (byte) 0x7a, (byte) 0x01, (byte) 0x7e, (byte) 0x74,
+                    (byte) 0x01, (byte) 0x88, (byte) 0xce, (byte) 0xda, (byte) 0xe4, (byte) 0x21,
+                    (byte) 0x89, (byte) 0xfc, (byte) 0xac, (byte) 0x68, (byte) 0xdb, (byte) 0xfc,
+                    (byte) 0x5f, (byte) 0x3f, (byte) 0x00, (byte) 0xd9, (byte) 0x32, (byte) 0x1d,
+                    (byte) 0xa5, (byte) 0xec, (byte) 0x72, (byte) 0x46, (byte) 0x23, (byte) 0xe5,
+                    (byte) 0x7f, (byte) 0x49, (byte) 0x0e, (byte) 0x3e, (byte) 0xf2, (byte) 0x2b,
+                    (byte) 0x16, (byte) 0x52, (byte) 0x9f, (byte) 0x9d, (byte) 0x0c, (byte) 0xfe,
+                    (byte) 0xab, (byte) 0xdd, (byte) 0x77, (byte) 0x77, (byte) 0x94, (byte) 0xa4,
+                    (byte) 0x92, (byte) 0xa2, (byte) 0x41, (byte) 0x0d, (byte) 0x4b, (byte) 0x57,
+                    (byte) 0x80, (byte) 0xd6, (byte) 0x74, (byte) 0x63, (byte) 0xd5, (byte) 0xbf,
+                    (byte) 0x5c, (byte) 0xa0, (byte) 0xda, (byte) 0x3c, (byte) 0xe6, (byte) 0xbf,
+                    (byte) 0xa4, (byte) 0xc3, (byte) 0xfb, (byte) 0x46, (byte) 0x3b, (byte) 0x73,
+                    (byte) 0x30, (byte) 0x4b, (byte) 0x57, (byte) 0x27, (byte) 0x0c, (byte) 0x81,
+                    (byte) 0xde, (byte) 0x8a, (byte) 0x01, (byte) 0xe5, (byte) 0x7e, (byte) 0xe0,
+                    (byte) 0x16, (byte) 0x11, (byte) 0x24, (byte) 0x34, (byte) 0x22, (byte) 0x01,
+                    (byte) 0x9f, (byte) 0xe6, (byte) 0xa9, (byte) 0xfb, (byte) 0xad, (byte) 0x55,
+                    (byte) 0x17, (byte) 0x2a, (byte) 0x92, (byte) 0x87, (byte) 0xf3, (byte) 0x72,
+                    (byte) 0xc9, (byte) 0x3d, (byte) 0xc9, (byte) 0x2e, (byte) 0x32, (byte) 0x8e,
+                    (byte) 0xbb, (byte) 0xdc, (byte) 0x1b, (byte) 0xa7, (byte) 0x7b, (byte) 0x73,
+                    (byte) 0xd7, (byte) 0xf4, (byte) 0xad, (byte) 0xa9, (byte) 0x3a, (byte) 0xf7,
+                    (byte) 0xa8, (byte) 0x82, (byte) 0x92, (byte) 0x40, (byte) 0xd4, (byte) 0x51,
+                    (byte) 0x87, (byte) 0xe1, (byte) 0xb7, (byte) 0x4f, (byte) 0x91, (byte) 0x75,
+                    (byte) 0x5b, (byte) 0x03, (byte) 0x9d, (byte) 0xa1, (byte) 0xd4, (byte) 0x00,
+                    (byte) 0x05, (byte) 0x79, (byte) 0x42, (byte) 0x93, (byte) 0x76};
 
     /*
      * echo -n 'This is a test of OAEP' | openssl pkeyutl -encrypt -inkey /tmp/rsakey.txt \
      * -pkeyopt rsa_padding_mode:oaep -pkey rsa_oaep_md:sha512 -pkeyopt rsa_mgf1_md:sha512 \
      * | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA512_MGF1_SHA512 = new byte[] {
-            (byte) 0x75, (byte) 0x0f, (byte) 0xf9, (byte) 0x21, (byte) 0xca, (byte) 0xcc,
-            (byte) 0x0e, (byte) 0x13, (byte) 0x9e, (byte) 0x38, (byte) 0xa4, (byte) 0xa7,
-            (byte) 0xee, (byte) 0x61, (byte) 0x6d, (byte) 0x56, (byte) 0xea, (byte) 0x36,
-            (byte) 0xeb, (byte) 0xec, (byte) 0xfa, (byte) 0x1a, (byte) 0xeb, (byte) 0x0c,
-            (byte) 0xb2, (byte) 0x58, (byte) 0x9d, (byte) 0xde, (byte) 0x47, (byte) 0x27,
-            (byte) 0x2d, (byte) 0xbd, (byte) 0x8b, (byte) 0xa7, (byte) 0xf1, (byte) 0x8b,
-            (byte) 0xba, (byte) 0x4c, (byte) 0xab, (byte) 0x39, (byte) 0x6a, (byte) 0x82,
-            (byte) 0x0d, (byte) 0xaf, (byte) 0x4c, (byte) 0xde, (byte) 0xdb, (byte) 0x5e,
-            (byte) 0xdb, (byte) 0x08, (byte) 0x98, (byte) 0x06, (byte) 0xc5, (byte) 0x99,
-            (byte) 0xb6, (byte) 0x6d, (byte) 0xbc, (byte) 0x5b, (byte) 0xf9, (byte) 0xe4,
-            (byte) 0x97, (byte) 0x0b, (byte) 0xba, (byte) 0xe3, (byte) 0x17, (byte) 0xa9,
-            (byte) 0x3c, (byte) 0x4b, (byte) 0x21, (byte) 0xd8, (byte) 0x29, (byte) 0xf8,
-            (byte) 0xa7, (byte) 0x1c, (byte) 0x15, (byte) 0xd7, (byte) 0xf6, (byte) 0xfc,
-            (byte) 0x53, (byte) 0x64, (byte) 0x97, (byte) 0x9e, (byte) 0x22, (byte) 0xb1,
-            (byte) 0x93, (byte) 0x26, (byte) 0x80, (byte) 0xdc, (byte) 0xaa, (byte) 0x1b,
-            (byte) 0xae, (byte) 0x69, (byte) 0x0f, (byte) 0x74, (byte) 0x3d, (byte) 0x61,
-            (byte) 0x80, (byte) 0x68, (byte) 0xb8, (byte) 0xaf, (byte) 0x63, (byte) 0x72,
-            (byte) 0x37, (byte) 0x4f, (byte) 0xf3, (byte) 0x29, (byte) 0x4a, (byte) 0x75,
-            (byte) 0x4f, (byte) 0x29, (byte) 0x40, (byte) 0x01, (byte) 0xd3, (byte) 0xc6,
-            (byte) 0x56, (byte) 0x1a, (byte) 0xaf, (byte) 0xc3, (byte) 0xb3, (byte) 0xd2,
-            (byte) 0xb9, (byte) 0x91, (byte) 0x35, (byte) 0x1b, (byte) 0x89, (byte) 0x4c,
-            (byte) 0x61, (byte) 0xa2, (byte) 0x8e, (byte) 0x6f, (byte) 0x12, (byte) 0x4a,
-            (byte) 0x10, (byte) 0xc2, (byte) 0xcc, (byte) 0xab, (byte) 0x51, (byte) 0xec,
-            (byte) 0x1b, (byte) 0xb5, (byte) 0xfe, (byte) 0x20, (byte) 0x16, (byte) 0xb2,
-            (byte) 0xc5, (byte) 0x0f, (byte) 0xe1, (byte) 0x6a, (byte) 0xb4, (byte) 0x6c,
-            (byte) 0x27, (byte) 0xd9, (byte) 0x42, (byte) 0xb9, (byte) 0xb6, (byte) 0x55,
-            (byte) 0xa8, (byte) 0xbc, (byte) 0x1c, (byte) 0x32, (byte) 0x54, (byte) 0x84,
-            (byte) 0xec, (byte) 0x1e, (byte) 0x95, (byte) 0xd8, (byte) 0xae, (byte) 0xca,
-            (byte) 0xc1, (byte) 0xad, (byte) 0x4c, (byte) 0x65, (byte) 0xd6, (byte) 0xc2,
-            (byte) 0x19, (byte) 0x66, (byte) 0xad, (byte) 0x9f, (byte) 0x55, (byte) 0x15,
-            (byte) 0xe1, (byte) 0x5d, (byte) 0x8f, (byte) 0xab, (byte) 0x18, (byte) 0x68,
-            (byte) 0x42, (byte) 0x7c, (byte) 0x48, (byte) 0xb7, (byte) 0x2c, (byte) 0xfd,
-            (byte) 0x1a, (byte) 0x07, (byte) 0xa1, (byte) 0x6a, (byte) 0xfb, (byte) 0x81,
-            (byte) 0xc6, (byte) 0x93, (byte) 0xbf, (byte) 0xa3, (byte) 0x5d, (byte) 0xfd,
-            (byte) 0xce, (byte) 0xf3, (byte) 0x17, (byte) 0x26, (byte) 0xf0, (byte) 0xda,
-            (byte) 0x0e, (byte) 0xd1, (byte) 0x86, (byte) 0x9d, (byte) 0x61, (byte) 0xd1,
-            (byte) 0x8a, (byte) 0xdb, (byte) 0x36, (byte) 0x39, (byte) 0x1c, (byte) 0xd4,
-            (byte) 0x99, (byte) 0x53, (byte) 0x30, (byte) 0x5a, (byte) 0x01, (byte) 0xf4,
-            (byte) 0xa0, (byte) 0xca, (byte) 0x94, (byte) 0x72, (byte) 0x3d, (byte) 0xe3,
-            (byte) 0x50, (byte) 0x95, (byte) 0xcb, (byte) 0xa9, (byte) 0x37, (byte) 0xeb,
-            (byte) 0x66, (byte) 0x21, (byte) 0x20, (byte) 0x2e, (byte) 0xf2, (byte) 0xfd,
-            (byte) 0xfa, (byte) 0x54, (byte) 0xbf, (byte) 0x17, (byte) 0x23, (byte) 0xbb,
-            (byte) 0x9e, (byte) 0x77, (byte) 0xe0, (byte) 0xaa
-    };
+    private static final byte[] RSA_Vector2_OAEP_SHA512_MGF1_SHA512 =
+            new byte[] {(byte) 0x75, (byte) 0x0f, (byte) 0xf9, (byte) 0x21, (byte) 0xca,
+                    (byte) 0xcc, (byte) 0x0e, (byte) 0x13, (byte) 0x9e, (byte) 0x38, (byte) 0xa4,
+                    (byte) 0xa7, (byte) 0xee, (byte) 0x61, (byte) 0x6d, (byte) 0x56, (byte) 0xea,
+                    (byte) 0x36, (byte) 0xeb, (byte) 0xec, (byte) 0xfa, (byte) 0x1a, (byte) 0xeb,
+                    (byte) 0x0c, (byte) 0xb2, (byte) 0x58, (byte) 0x9d, (byte) 0xde, (byte) 0x47,
+                    (byte) 0x27, (byte) 0x2d, (byte) 0xbd, (byte) 0x8b, (byte) 0xa7, (byte) 0xf1,
+                    (byte) 0x8b, (byte) 0xba, (byte) 0x4c, (byte) 0xab, (byte) 0x39, (byte) 0x6a,
+                    (byte) 0x82, (byte) 0x0d, (byte) 0xaf, (byte) 0x4c, (byte) 0xde, (byte) 0xdb,
+                    (byte) 0x5e, (byte) 0xdb, (byte) 0x08, (byte) 0x98, (byte) 0x06, (byte) 0xc5,
+                    (byte) 0x99, (byte) 0xb6, (byte) 0x6d, (byte) 0xbc, (byte) 0x5b, (byte) 0xf9,
+                    (byte) 0xe4, (byte) 0x97, (byte) 0x0b, (byte) 0xba, (byte) 0xe3, (byte) 0x17,
+                    (byte) 0xa9, (byte) 0x3c, (byte) 0x4b, (byte) 0x21, (byte) 0xd8, (byte) 0x29,
+                    (byte) 0xf8, (byte) 0xa7, (byte) 0x1c, (byte) 0x15, (byte) 0xd7, (byte) 0xf6,
+                    (byte) 0xfc, (byte) 0x53, (byte) 0x64, (byte) 0x97, (byte) 0x9e, (byte) 0x22,
+                    (byte) 0xb1, (byte) 0x93, (byte) 0x26, (byte) 0x80, (byte) 0xdc, (byte) 0xaa,
+                    (byte) 0x1b, (byte) 0xae, (byte) 0x69, (byte) 0x0f, (byte) 0x74, (byte) 0x3d,
+                    (byte) 0x61, (byte) 0x80, (byte) 0x68, (byte) 0xb8, (byte) 0xaf, (byte) 0x63,
+                    (byte) 0x72, (byte) 0x37, (byte) 0x4f, (byte) 0xf3, (byte) 0x29, (byte) 0x4a,
+                    (byte) 0x75, (byte) 0x4f, (byte) 0x29, (byte) 0x40, (byte) 0x01, (byte) 0xd3,
+                    (byte) 0xc6, (byte) 0x56, (byte) 0x1a, (byte) 0xaf, (byte) 0xc3, (byte) 0xb3,
+                    (byte) 0xd2, (byte) 0xb9, (byte) 0x91, (byte) 0x35, (byte) 0x1b, (byte) 0x89,
+                    (byte) 0x4c, (byte) 0x61, (byte) 0xa2, (byte) 0x8e, (byte) 0x6f, (byte) 0x12,
+                    (byte) 0x4a, (byte) 0x10, (byte) 0xc2, (byte) 0xcc, (byte) 0xab, (byte) 0x51,
+                    (byte) 0xec, (byte) 0x1b, (byte) 0xb5, (byte) 0xfe, (byte) 0x20, (byte) 0x16,
+                    (byte) 0xb2, (byte) 0xc5, (byte) 0x0f, (byte) 0xe1, (byte) 0x6a, (byte) 0xb4,
+                    (byte) 0x6c, (byte) 0x27, (byte) 0xd9, (byte) 0x42, (byte) 0xb9, (byte) 0xb6,
+                    (byte) 0x55, (byte) 0xa8, (byte) 0xbc, (byte) 0x1c, (byte) 0x32, (byte) 0x54,
+                    (byte) 0x84, (byte) 0xec, (byte) 0x1e, (byte) 0x95, (byte) 0xd8, (byte) 0xae,
+                    (byte) 0xca, (byte) 0xc1, (byte) 0xad, (byte) 0x4c, (byte) 0x65, (byte) 0xd6,
+                    (byte) 0xc2, (byte) 0x19, (byte) 0x66, (byte) 0xad, (byte) 0x9f, (byte) 0x55,
+                    (byte) 0x15, (byte) 0xe1, (byte) 0x5d, (byte) 0x8f, (byte) 0xab, (byte) 0x18,
+                    (byte) 0x68, (byte) 0x42, (byte) 0x7c, (byte) 0x48, (byte) 0xb7, (byte) 0x2c,
+                    (byte) 0xfd, (byte) 0x1a, (byte) 0x07, (byte) 0xa1, (byte) 0x6a, (byte) 0xfb,
+                    (byte) 0x81, (byte) 0xc6, (byte) 0x93, (byte) 0xbf, (byte) 0xa3, (byte) 0x5d,
+                    (byte) 0xfd, (byte) 0xce, (byte) 0xf3, (byte) 0x17, (byte) 0x26, (byte) 0xf0,
+                    (byte) 0xda, (byte) 0x0e, (byte) 0xd1, (byte) 0x86, (byte) 0x9d, (byte) 0x61,
+                    (byte) 0xd1, (byte) 0x8a, (byte) 0xdb, (byte) 0x36, (byte) 0x39, (byte) 0x1c,
+                    (byte) 0xd4, (byte) 0x99, (byte) 0x53, (byte) 0x30, (byte) 0x5a, (byte) 0x01,
+                    (byte) 0xf4, (byte) 0xa0, (byte) 0xca, (byte) 0x94, (byte) 0x72, (byte) 0x3d,
+                    (byte) 0xe3, (byte) 0x50, (byte) 0x95, (byte) 0xcb, (byte) 0xa9, (byte) 0x37,
+                    (byte) 0xeb, (byte) 0x66, (byte) 0x21, (byte) 0x20, (byte) 0x2e, (byte) 0xf2,
+                    (byte) 0xfd, (byte) 0xfa, (byte) 0x54, (byte) 0xbf, (byte) 0x17, (byte) 0x23,
+                    (byte) 0xbb, (byte) 0x9e, (byte) 0x77, (byte) 0xe0, (byte) 0xaa};
 
     /*
      * echo -n 'This is a test of OAEP' | openssl pkeyutl -encrypt -inkey /tmp/rsakey.txt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha512 -pkeyopt rsa_mgf1_md:sha512 -pkeyopt rsa_oaep_label:010203FFA00A | xxd -p -i | sed 's/0x/(byte) 0x/g'
      */
-    public static final byte[] RSA_Vector2_OAEP_SHA512_MGF1_SHA512_LABEL = new byte[] {
-            (byte) 0x31, (byte) 0x3b, (byte) 0x23, (byte) 0xcf, (byte) 0x40, (byte) 0xfe,
-            (byte) 0x15, (byte) 0x94, (byte) 0xd6, (byte) 0x81, (byte) 0x21, (byte) 0x69,
-            (byte) 0x8e, (byte) 0x58, (byte) 0xd5, (byte) 0x0f, (byte) 0xa8, (byte) 0x72,
-            (byte) 0x94, (byte) 0x13, (byte) 0xfe, (byte) 0xf9, (byte) 0xa1, (byte) 0x47,
-            (byte) 0x49, (byte) 0x91, (byte) 0xcb, (byte) 0x66, (byte) 0xe6, (byte) 0x5d,
-            (byte) 0x02, (byte) 0xad, (byte) 0xd4, (byte) 0x2f, (byte) 0x4f, (byte) 0xab,
-            (byte) 0xb7, (byte) 0x9e, (byte) 0xc0, (byte) 0xf0, (byte) 0x3d, (byte) 0x66,
-            (byte) 0x0e, (byte) 0x20, (byte) 0x82, (byte) 0x7f, (byte) 0x22, (byte) 0x8f,
-            (byte) 0x81, (byte) 0xba, (byte) 0x47, (byte) 0xc7, (byte) 0xaf, (byte) 0xb6,
-            (byte) 0x0e, (byte) 0x78, (byte) 0xe3, (byte) 0x30, (byte) 0xd7, (byte) 0x6c,
-            (byte) 0x81, (byte) 0xc2, (byte) 0x05, (byte) 0x7e, (byte) 0xe9, (byte) 0xac,
-            (byte) 0x8d, (byte) 0x45, (byte) 0x25, (byte) 0xe8, (byte) 0x26, (byte) 0x39,
-            (byte) 0x88, (byte) 0x64, (byte) 0x2e, (byte) 0xc6, (byte) 0xed, (byte) 0xd4,
-            (byte) 0xad, (byte) 0x94, (byte) 0xc8, (byte) 0x4e, (byte) 0x4a, (byte) 0x71,
-            (byte) 0x1e, (byte) 0x11, (byte) 0x14, (byte) 0x03, (byte) 0x56, (byte) 0x02,
-            (byte) 0x28, (byte) 0x32, (byte) 0x8f, (byte) 0xe2, (byte) 0x16, (byte) 0x4a,
-            (byte) 0x62, (byte) 0xa6, (byte) 0x9a, (byte) 0x8d, (byte) 0xf8, (byte) 0x33,
-            (byte) 0x35, (byte) 0xa2, (byte) 0xc7, (byte) 0x70, (byte) 0xcc, (byte) 0x26,
-            (byte) 0x1e, (byte) 0x4d, (byte) 0x9c, (byte) 0x4e, (byte) 0x2b, (byte) 0xe8,
-            (byte) 0xfd, (byte) 0x07, (byte) 0x33, (byte) 0x15, (byte) 0x53, (byte) 0x11,
-            (byte) 0x5c, (byte) 0x6f, (byte) 0x5d, (byte) 0x23, (byte) 0x7b, (byte) 0x3f,
-            (byte) 0x73, (byte) 0xff, (byte) 0xf4, (byte) 0xbe, (byte) 0x1f, (byte) 0xe6,
-            (byte) 0x5a, (byte) 0xb8, (byte) 0x2b, (byte) 0xd2, (byte) 0xbe, (byte) 0xa0,
-            (byte) 0x91, (byte) 0x5d, (byte) 0xca, (byte) 0x89, (byte) 0xb3, (byte) 0xce,
-            (byte) 0x0a, (byte) 0x2b, (byte) 0xce, (byte) 0xb9, (byte) 0xbe, (byte) 0x5d,
-            (byte) 0xb2, (byte) 0xc2, (byte) 0xd6, (byte) 0xa9, (byte) 0xbc, (byte) 0x37,
-            (byte) 0xed, (byte) 0x9a, (byte) 0xba, (byte) 0x35, (byte) 0xf8, (byte) 0x6e,
-            (byte) 0x63, (byte) 0x76, (byte) 0xd1, (byte) 0x12, (byte) 0xf5, (byte) 0x89,
-            (byte) 0xf0, (byte) 0x13, (byte) 0x86, (byte) 0xe7, (byte) 0x1b, (byte) 0x94,
-            (byte) 0xcb, (byte) 0xc8, (byte) 0x5c, (byte) 0x4c, (byte) 0x1b, (byte) 0x8a,
-            (byte) 0x2d, (byte) 0x6b, (byte) 0x24, (byte) 0x1a, (byte) 0x38, (byte) 0x14,
-            (byte) 0x77, (byte) 0x49, (byte) 0xe5, (byte) 0x08, (byte) 0x25, (byte) 0xe4,
-            (byte) 0xa6, (byte) 0xcf, (byte) 0x62, (byte) 0xfd, (byte) 0x66, (byte) 0x28,
-            (byte) 0xf0, (byte) 0x3a, (byte) 0x9c, (byte) 0x31, (byte) 0xef, (byte) 0x48,
-            (byte) 0x2a, (byte) 0xd3, (byte) 0x3e, (byte) 0x29, (byte) 0xfa, (byte) 0x18,
-            (byte) 0x8f, (byte) 0xd6, (byte) 0xaa, (byte) 0x1d, (byte) 0x10, (byte) 0xcd,
-            (byte) 0x35, (byte) 0x25, (byte) 0x92, (byte) 0x48, (byte) 0xa0, (byte) 0x2c,
-            (byte) 0xc1, (byte) 0x31, (byte) 0xeb, (byte) 0x47, (byte) 0x5b, (byte) 0x22,
-            (byte) 0x52, (byte) 0x7c, (byte) 0xf5, (byte) 0xec, (byte) 0x76, (byte) 0x90,
-            (byte) 0x94, (byte) 0x58, (byte) 0xd9, (byte) 0xd6, (byte) 0xe0, (byte) 0x0a,
-            (byte) 0x3f, (byte) 0x09, (byte) 0x98, (byte) 0x03, (byte) 0xc5, (byte) 0x07,
-            (byte) 0x8f, (byte) 0x89, (byte) 0x1e, (byte) 0x62, (byte) 0x2c, (byte) 0xea,
-            (byte) 0x17, (byte) 0x0a, (byte) 0x2e, (byte) 0x68
-    };
+    private static final byte[] RSA_Vector2_OAEP_SHA512_MGF1_SHA512_LABEL =
+            new byte[] {(byte) 0x31, (byte) 0x3b, (byte) 0x23, (byte) 0xcf, (byte) 0x40,
+                    (byte) 0xfe, (byte) 0x15, (byte) 0x94, (byte) 0xd6, (byte) 0x81, (byte) 0x21,
+                    (byte) 0x69, (byte) 0x8e, (byte) 0x58, (byte) 0xd5, (byte) 0x0f, (byte) 0xa8,
+                    (byte) 0x72, (byte) 0x94, (byte) 0x13, (byte) 0xfe, (byte) 0xf9, (byte) 0xa1,
+                    (byte) 0x47, (byte) 0x49, (byte) 0x91, (byte) 0xcb, (byte) 0x66, (byte) 0xe6,
+                    (byte) 0x5d, (byte) 0x02, (byte) 0xad, (byte) 0xd4, (byte) 0x2f, (byte) 0x4f,
+                    (byte) 0xab, (byte) 0xb7, (byte) 0x9e, (byte) 0xc0, (byte) 0xf0, (byte) 0x3d,
+                    (byte) 0x66, (byte) 0x0e, (byte) 0x20, (byte) 0x82, (byte) 0x7f, (byte) 0x22,
+                    (byte) 0x8f, (byte) 0x81, (byte) 0xba, (byte) 0x47, (byte) 0xc7, (byte) 0xaf,
+                    (byte) 0xb6, (byte) 0x0e, (byte) 0x78, (byte) 0xe3, (byte) 0x30, (byte) 0xd7,
+                    (byte) 0x6c, (byte) 0x81, (byte) 0xc2, (byte) 0x05, (byte) 0x7e, (byte) 0xe9,
+                    (byte) 0xac, (byte) 0x8d, (byte) 0x45, (byte) 0x25, (byte) 0xe8, (byte) 0x26,
+                    (byte) 0x39, (byte) 0x88, (byte) 0x64, (byte) 0x2e, (byte) 0xc6, (byte) 0xed,
+                    (byte) 0xd4, (byte) 0xad, (byte) 0x94, (byte) 0xc8, (byte) 0x4e, (byte) 0x4a,
+                    (byte) 0x71, (byte) 0x1e, (byte) 0x11, (byte) 0x14, (byte) 0x03, (byte) 0x56,
+                    (byte) 0x02, (byte) 0x28, (byte) 0x32, (byte) 0x8f, (byte) 0xe2, (byte) 0x16,
+                    (byte) 0x4a, (byte) 0x62, (byte) 0xa6, (byte) 0x9a, (byte) 0x8d, (byte) 0xf8,
+                    (byte) 0x33, (byte) 0x35, (byte) 0xa2, (byte) 0xc7, (byte) 0x70, (byte) 0xcc,
+                    (byte) 0x26, (byte) 0x1e, (byte) 0x4d, (byte) 0x9c, (byte) 0x4e, (byte) 0x2b,
+                    (byte) 0xe8, (byte) 0xfd, (byte) 0x07, (byte) 0x33, (byte) 0x15, (byte) 0x53,
+                    (byte) 0x11, (byte) 0x5c, (byte) 0x6f, (byte) 0x5d, (byte) 0x23, (byte) 0x7b,
+                    (byte) 0x3f, (byte) 0x73, (byte) 0xff, (byte) 0xf4, (byte) 0xbe, (byte) 0x1f,
+                    (byte) 0xe6, (byte) 0x5a, (byte) 0xb8, (byte) 0x2b, (byte) 0xd2, (byte) 0xbe,
+                    (byte) 0xa0, (byte) 0x91, (byte) 0x5d, (byte) 0xca, (byte) 0x89, (byte) 0xb3,
+                    (byte) 0xce, (byte) 0x0a, (byte) 0x2b, (byte) 0xce, (byte) 0xb9, (byte) 0xbe,
+                    (byte) 0x5d, (byte) 0xb2, (byte) 0xc2, (byte) 0xd6, (byte) 0xa9, (byte) 0xbc,
+                    (byte) 0x37, (byte) 0xed, (byte) 0x9a, (byte) 0xba, (byte) 0x35, (byte) 0xf8,
+                    (byte) 0x6e, (byte) 0x63, (byte) 0x76, (byte) 0xd1, (byte) 0x12, (byte) 0xf5,
+                    (byte) 0x89, (byte) 0xf0, (byte) 0x13, (byte) 0x86, (byte) 0xe7, (byte) 0x1b,
+                    (byte) 0x94, (byte) 0xcb, (byte) 0xc8, (byte) 0x5c, (byte) 0x4c, (byte) 0x1b,
+                    (byte) 0x8a, (byte) 0x2d, (byte) 0x6b, (byte) 0x24, (byte) 0x1a, (byte) 0x38,
+                    (byte) 0x14, (byte) 0x77, (byte) 0x49, (byte) 0xe5, (byte) 0x08, (byte) 0x25,
+                    (byte) 0xe4, (byte) 0xa6, (byte) 0xcf, (byte) 0x62, (byte) 0xfd, (byte) 0x66,
+                    (byte) 0x28, (byte) 0xf0, (byte) 0x3a, (byte) 0x9c, (byte) 0x31, (byte) 0xef,
+                    (byte) 0x48, (byte) 0x2a, (byte) 0xd3, (byte) 0x3e, (byte) 0x29, (byte) 0xfa,
+                    (byte) 0x18, (byte) 0x8f, (byte) 0xd6, (byte) 0xaa, (byte) 0x1d, (byte) 0x10,
+                    (byte) 0xcd, (byte) 0x35, (byte) 0x25, (byte) 0x92, (byte) 0x48, (byte) 0xa0,
+                    (byte) 0x2c, (byte) 0xc1, (byte) 0x31, (byte) 0xeb, (byte) 0x47, (byte) 0x5b,
+                    (byte) 0x22, (byte) 0x52, (byte) 0x7c, (byte) 0xf5, (byte) 0xec, (byte) 0x76,
+                    (byte) 0x90, (byte) 0x94, (byte) 0x58, (byte) 0xd9, (byte) 0xd6, (byte) 0xe0,
+                    (byte) 0x0a, (byte) 0x3f, (byte) 0x09, (byte) 0x98, (byte) 0x03, (byte) 0xc5,
+                    (byte) 0x07, (byte) 0x8f, (byte) 0x89, (byte) 0x1e, (byte) 0x62, (byte) 0x2c,
+                    (byte) 0xea, (byte) 0x17, (byte) 0x0a, (byte) 0x2e, (byte) 0x68};
 
     @Test
     public void testRSA_ECB_NoPadding_Private_OnlyDoFinal_Success() throws Exception {
@@ -4673,6 +4642,8 @@
      * TODO(27995180): consider whether we keep this compatibility. Consider whether we only allow
      * if an IV is passed in the parameters.
      */
+    @NonCts(bug = 287231726, reason = "The test asserts buggy or non-breaking "
+            + "behaviors, but the behavior has been fixed in the future ART module version.")
     @Test
     public void test_PBKDF2WITHHMACSHA1_SKFactory_and_PBEAESCBC_Cipher_noIV() throws Exception {
         Assume.assumeNotNull(Security.getProvider("BC"));
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java
index 1cad3d5..a357ecb 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java
@@ -465,6 +465,19 @@
         if (providers == null) {
             return new Provider[0];
         }
+
+        // Do not test AndroidKeyStore as KeyAgreement provider. It only handles Android
+        // Keystore-backed keys. It's OKish not to test AndroidKeyStore here because it's tested by
+        // cts/tests/test/keystore.
+        List<Provider> filteredProvidersList = new ArrayList<Provider>(providers.length);
+        for (Provider provider : providers) {
+            if ("AndroidKeyStore".equals(provider.getName())) {
+                continue;
+            }
+            filteredProvidersList.add(provider);
+        }
+        providers = filteredProvidersList.toArray(new Provider[filteredProvidersList.size()]);
+
         // Sort providers by name to guarantee deterministic order in which providers are used in
         // the tests.
         return sortByName(providers);
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java
new file mode 100644
index 0000000..ade5447
--- /dev/null
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java
@@ -0,0 +1,104 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.org.conscrypt.javax.crypto;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import javax.crypto.KeyAgreement;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+
+/**
+ * Tests for all registered X25519 and X448 {@link KeyAgreement} providers.
+ * @hide This class is not part of the Android public SDK API
+ */
+@RunWith(JUnit4.class)
+public class XDHKeyAgreementTest {
+    private static final byte[] RFC_7748_X25519_OUR_PRIV_KEY = new byte[] {
+            (byte) 0x30, (byte) 0x2e, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x30, (byte) 0x05, (byte) 0x06,
+            (byte) 0x03, (byte) 0x2b, (byte) 0x65, (byte) 0x6e, (byte) 0x04, (byte) 0x22, (byte) 0x04, (byte) 0x20,
+            (byte) 0xa5, (byte) 0x46, (byte) 0xe3, (byte) 0x6b, (byte) 0xf0, (byte) 0x52, (byte) 0x7c, (byte) 0x9d,
+            (byte) 0x3b, (byte) 0x16, (byte) 0x15, (byte) 0x4b, (byte) 0x82, (byte) 0x46, (byte) 0x5e, (byte) 0xdd,
+            (byte) 0x62, (byte) 0x14, (byte) 0x4c, (byte) 0x0a, (byte) 0xc1, (byte) 0xfc, (byte) 0x5a, (byte) 0x18,
+            (byte) 0x50, (byte) 0x6a, (byte) 0x22, (byte) 0x44, (byte) 0xba, (byte) 0x44, (byte) 0x9a, (byte) 0xc4,
+    };
+
+    // Broken key for testing with JDK 11. Instead of wrapping OCTET STRING with OCTET STRING.
+    private static final byte[] RFC_7748_X25519_OUR_PRIV_KEY_BROKEN = new byte[] {
+            (byte) 0x30, (byte) 0x2c, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x30, (byte) 0x05, (byte) 0x06,
+            (byte) 0x03, (byte) 0x2b, (byte) 0x65, (byte) 0x6e, (byte) 0x04, (byte) 0x20,
+            (byte) 0xa5, (byte) 0x46, (byte) 0xe3, (byte) 0x6b, (byte) 0xf0, (byte) 0x52, (byte) 0x7c, (byte) 0x9d,
+            (byte) 0x3b, (byte) 0x16, (byte) 0x15, (byte) 0x4b, (byte) 0x82, (byte) 0x46, (byte) 0x5e, (byte) 0xdd,
+            (byte) 0x62, (byte) 0x14, (byte) 0x4c, (byte) 0x0a, (byte) 0xc1, (byte) 0xfc, (byte) 0x5a, (byte) 0x18,
+            (byte) 0x50, (byte) 0x6a, (byte) 0x22, (byte) 0x44, (byte) 0xba, (byte) 0x44, (byte) 0x9a, (byte) 0xc4,
+    };
+
+    private static final byte[] RFC_7748_X25519_THEIR_PUB_KEY = new byte[] {
+            (byte) 0x30, (byte) 0x2a, (byte) 0x30, (byte) 0x05, (byte) 0x06, (byte) 0x03, (byte) 0x2b, (byte) 0x65,
+            (byte) 0x6e, (byte) 0x03, (byte) 0x21, (byte) 0x00, (byte) 0xe6, (byte) 0xdb, (byte) 0x68, (byte) 0x67,
+            (byte) 0x58, (byte) 0x30, (byte) 0x30, (byte) 0xdb, (byte) 0x35, (byte) 0x94, (byte) 0xc1, (byte) 0xa4,
+            (byte) 0x24, (byte) 0xb1, (byte) 0x5f, (byte) 0x7c, (byte) 0x72, (byte) 0x66, (byte) 0x24, (byte) 0xec,
+            (byte) 0x26, (byte) 0xb3, (byte) 0x35, (byte) 0x3b, (byte) 0x10, (byte) 0xa9, (byte) 0x03, (byte) 0xa6,
+            (byte) 0xd0, (byte) 0xab, (byte) 0x1c, (byte) 0x4c,
+    };
+
+    private static final byte[] RFC_7748_X25519_SECRET = new byte[] {
+            (byte) 0xc3, (byte) 0xda, (byte) 0x55, (byte) 0x37, (byte) 0x9d, (byte) 0xe9, (byte) 0xc6, (byte) 0x90,
+            (byte) 0x8e, (byte) 0x94, (byte) 0xea, (byte) 0x4d, (byte) 0xf2, (byte) 0x8d, (byte) 0x08, (byte) 0x4f,
+            (byte) 0x32, (byte) 0xec, (byte) 0xcf, (byte) 0x03, (byte) 0x49, (byte) 0x1c, (byte) 0x71, (byte) 0xf7,
+            (byte) 0x54, (byte) 0xb4, (byte) 0x07, (byte) 0x55, (byte) 0x77, (byte) 0xa2, (byte) 0x85, (byte) 0x52,
+    };
+
+    private PrivateKey rfc7748X25519PrivateKey;
+    private PublicKey rfc7748X25519PublicKey;
+
+    private void setupKeys(Provider p) throws Exception {
+        KeyFactory kf = KeyFactory.getInstance("XDH", p);
+
+        byte[] privateKey;
+        if ("SunEC".equalsIgnoreCase(p.getName())
+                && "11".equals(System.getProperty("java.specification.version"))) {
+            // SunEC in OpenJDK 11 has a bug where the format specified in RFC 8410
+            // Section 7. It uses a single OCTET STRING to represent the key instead
+            // of an OCTET STRING inside of an OCTET STRING as defined in the RFC:
+            // ("For the keys defined in this document, the private key is always an
+            //   opaque byte sequence.  The ASN.1 type CurvePrivateKey is defined in
+            //   this document to hold the byte sequence.  Thus, when encoding a
+            //   OneAsymmetricKey object, the private key is wrapped in a
+            //   CurvePrivateKey object and wrapped by the OCTET STRING of the
+            //   "privateKey" field.")
+            privateKey = RFC_7748_X25519_OUR_PRIV_KEY_BROKEN;
+        } else {
+            privateKey = RFC_7748_X25519_OUR_PRIV_KEY;
+        }
+
+        rfc7748X25519PrivateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(privateKey));
+        rfc7748X25519PublicKey = kf.generatePublic(new X509EncodedKeySpec(RFC_7748_X25519_THEIR_PUB_KEY));
+    }
+
+    @Test
+    public void test_XDHKeyAgreement() throws Exception {
+        for (Provider p : Security.getProviders("KeyAgreement.XDH")) {
+            setupKeys(p);
+
+            KeyAgreement ka = KeyAgreement.getInstance("XDH", p);
+
+            test_x25519_keyAgreement_rfc7748_kat_success(ka);
+        }
+    }
+
+    private void test_x25519_keyAgreement_rfc7748_kat_success(KeyAgreement ka) throws Exception {
+        ka.init(rfc7748X25519PrivateKey);
+        ka.doPhase(rfc7748X25519PublicKey, true);
+
+        assertArrayEquals(RFC_7748_X25519_SECRET, ka.generateSecret());
+    }
+}
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLContextTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLContextTest.java
index 5122f70..972928b 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLContextTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLContextTest.java
@@ -17,6 +17,7 @@
 
 package com.android.org.conscrypt.javax.net.ssl;
 
+import static com.android.org.conscrypt.TestUtils.isWindows;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
@@ -25,6 +26,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.org.conscrypt.TestUtils;
+import com.android.org.conscrypt.java.security.StandardNames;
 import java.io.IOException;
 import java.security.AccessController;
 import java.security.InvalidAlgorithmParameterException;
@@ -38,6 +41,7 @@
 import java.security.UnrecoverableKeyException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
@@ -60,8 +64,6 @@
 import javax.net.ssl.TrustManagerFactorySpi;
 import javax.net.ssl.X509KeyManager;
 import junit.framework.AssertionFailedError;
-import com.android.org.conscrypt.TestUtils;
-import com.android.org.conscrypt.java.security.StandardNames;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -677,14 +679,21 @@
     }
 
     private static void assertContentsInOrder(List<String> expected, String... actual) {
+        List<String> actualList = Arrays.asList(actual);
         if (expected.size() != actual.length) {
             fail("Unexpected length. Expected len <" + expected.size() + ">, actual len <"
-                    + actual.length + ">, expected <" + expected + ">, actual <"
-                    + Arrays.asList(actual) + ">");
+                    + actual.length + ">, expected <" + expected + ">, actual <" + actualList
+                    + ">");
         }
-        if (!expected.equals(Arrays.asList(actual))) {
-            fail("Unexpected element(s). Expected <" + expected + ">, actual <"
-                    + Arrays.asList(actual) + ">");
+
+        if (isWindows()) {
+            // TODO(prbprbprb): CpuFeatures.isAESHardwareAccelerated is not reliable on windows
+            Collections.sort(actualList);
+            Collections.sort(expected);
+        }
+
+        if (!expected.equals(actualList)) {
+            fail("Unexpected element(s). Expected <" + expected + ">, actual <" + actualList + ">");
         }
     }
 
@@ -724,7 +733,7 @@
         }
 
         if (version[0] == 1) {
-            assert version[1] >= 6;
+            assertTrue(version[1] >= 6);
             return version[1];
         } else {
             return version[0];
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLEngineTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLEngineTest.java
index 7996c32..f3b5148 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLEngineTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLEngineTest.java
@@ -18,6 +18,7 @@
 package com.android.org.conscrypt.javax.net.ssl;
 
 import static com.android.org.conscrypt.TestUtils.UTF_8;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -26,11 +27,13 @@
 import static org.junit.Assert.fail;
 
 import com.android.org.conscrypt.TestUtils;
+import com.android.org.conscrypt.TestUtils.BufferType;
 import com.android.org.conscrypt.java.security.StandardNames;
 import com.android.org.conscrypt.java.security.TestKeyStore;
 import java.io.IOException;
 import java.net.Socket;
 import java.nio.ByteBuffer;
+import java.nio.ReadOnlyBufferException;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
@@ -786,7 +789,8 @@
             final boolean serverClientMode, final boolean[] finished) throws Exception {
         TestSSLContext c;
         if (!clientClientMode && serverClientMode) {
-            c = TestSSLContext.create(TestKeyStore.getServer(), TestKeyStore.getClient());
+            c = TestSSLContext.create(/* client= */ TestKeyStore.getServer(),
+                    /* server= */ TestKeyStore.getClient());
         } else {
             c = TestSSLContext.create();
         }
@@ -922,6 +926,187 @@
         c.close();
     }
 
+    @Test
+    public void wrapPreconditions() throws Exception {
+        ByteBuffer buffer = ByteBuffer.allocate(10);
+        ByteBuffer[] buffers = new ByteBuffer[] {buffer, buffer, buffer};
+        ByteBuffer[] badBuffers = new ByteBuffer[] {buffer, buffer, null, buffer};
+
+        // Client/server mode not set => IllegalStateException
+        try {
+            newUnconnectedEngine().wrap(buffer, buffer);
+            fail();
+        } catch (IllegalStateException e) {
+            // Expected
+        }
+
+        try {
+            newUnconnectedEngine().wrap(buffers, buffer);
+            fail();
+        } catch (IllegalStateException e) {
+            // Expected
+        }
+
+        try {
+            newUnconnectedEngine().wrap(buffers, 0, 1, buffer);
+            fail();
+        } catch (IllegalStateException e) {
+            // Expected
+        }
+
+        // Read-only destination => ReadOnlyBufferException
+        try {
+            newConnectedEngine().wrap(buffer, buffer.asReadOnlyBuffer());
+            fail();
+        } catch (ReadOnlyBufferException e) {
+            // Expected
+        }
+
+        try {
+            newConnectedEngine().wrap(buffers, buffer.asReadOnlyBuffer());
+            fail();
+        } catch (ReadOnlyBufferException e) {
+            // Expected
+        }
+
+        try {
+            newConnectedEngine().wrap(buffers, 0, 1, buffer.asReadOnlyBuffer());
+            fail();
+        } catch (ReadOnlyBufferException e) {
+            // Expected
+        }
+
+        // Null destination => IllegalArgumentException
+        try {
+            newConnectedEngine().wrap(buffer, null);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            newConnectedEngine().wrap(buffers, null);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            newConnectedEngine().wrap(buffers, 0, 1, null);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        // Null source => IllegalArgumentException
+        try {
+            newConnectedEngine().wrap((ByteBuffer) null, buffer);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            newConnectedEngine().wrap((ByteBuffer[]) null, buffer);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            newConnectedEngine().wrap(null, 0, 1, buffer);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        // Null entries in buffer array => IllegalArgumentException
+        try {
+            newConnectedEngine().wrap(badBuffers, buffer);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            newConnectedEngine().wrap(badBuffers, 0, badBuffers.length, buffer);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        // Bad offset or length => IndexOutOfBoundsException
+        try {
+            newConnectedEngine().wrap(buffers, 0, 7, buffer);
+            fail();
+        } catch (IndexOutOfBoundsException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void bufferArrayOffsets() throws Exception {
+        TestSSLEnginePair pair = TestSSLEnginePair.create();
+        ByteBuffer tlsBuffer = ByteBuffer.allocate(600);
+        int bufferSize = 100;
+
+        for (BufferType bufferType : BufferType.values()) {
+            ByteBuffer[] sourceBuffers = bufferType.newRandomBuffers(
+                    bufferSize, bufferSize, bufferSize, bufferSize, bufferSize);
+            for (int offset = 0; offset < sourceBuffers.length; offset++) {
+                for (int length = 1; length < sourceBuffers.length - offset; length++) {
+                    // Reset source buffers
+                    for (ByteBuffer buffer : sourceBuffers) {
+                        if (buffer.remaining() == 0) {
+                            buffer.flip();
+                        }
+                        assertEquals(bufferSize, buffer.remaining());
+                    }
+                    // Make an array copy of what we expect to send
+                    byte[] sourceBytes = copyDataFromBuffers(sourceBuffers, offset, length);
+                    byte[] destinationBytes = new byte[sourceBytes.length];
+                    ByteBuffer destination = ByteBuffer.wrap(destinationBytes);
+                    // Send and compare
+                    tlsBuffer.clear();
+                    pair.client.wrap(sourceBuffers, offset, length, tlsBuffer);
+                    tlsBuffer.flip();
+                    pair.server.unwrap(tlsBuffer, destination);
+                    assertArrayEquals(sourceBytes, destinationBytes);
+                }
+            }
+        }
+    }
+
+    private byte[] copyDataFromBuffers(ByteBuffer[] buffers, int offset, int length) {
+        // NB avoids using Arrays.copyOfRange() to prevent any common bugs with
+        // ConscryptEngine.wrap().
+        int size = 0;
+        for (int i = offset; i < offset + length; i++) {
+            size += buffers[i].remaining();
+        }
+        byte[] data = new byte[size];
+        int dataOffset = 0;
+        for (int i = offset; i < offset + length; i++) {
+            ByteBuffer buffer = buffers[i];
+            int remaining = buffer.remaining();
+            buffer.get(data, dataOffset, remaining);
+            buffer.flip();
+            dataOffset += remaining;
+        }
+        return data;
+    }
+
+    private SSLEngine newUnconnectedEngine() {
+        TestSSLContext context = TestSSLContext.create();
+        return context.clientContext.createSSLEngine();
+    }
+
+    private SSLEngine newConnectedEngine() throws Exception {
+        TestSSLEnginePair pair = TestSSLEnginePair.create();
+        assertConnected(pair);
+        return pair.client;
+    }
+
     private void assertConnected(TestSSLEnginePair e) {
         assertConnected(e.client, e.server);
     }
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java
index b75860e..24621c5 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java
@@ -17,9 +17,9 @@
 
 package com.android.org.conscrypt.javax.net.ssl;
 
-import static java.util.Collections.singleton;
 import static com.android.org.conscrypt.TestUtils.UTF_8;
 import static com.android.org.conscrypt.TestUtils.assumeJava8;
+import static java.util.Collections.singleton;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -30,6 +30,18 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.org.conscrypt.Conscrypt;
+import com.android.org.conscrypt.TestUtils;
+import com.android.org.conscrypt.java.security.TestKeyStore;
+import com.android.org.conscrypt.testing.FailingSniMatcher;
+import com.android.org.conscrypt.tlswire.TlsTester;
+import com.android.org.conscrypt.tlswire.handshake.AlpnHelloExtension;
+import com.android.org.conscrypt.tlswire.handshake.ClientHello;
+import com.android.org.conscrypt.tlswire.handshake.HandshakeMessage;
+import com.android.org.conscrypt.tlswire.handshake.HelloExtension;
+import com.android.org.conscrypt.tlswire.handshake.ServerNameHelloExtension;
+import com.android.org.conscrypt.tlswire.record.TlsProtocols;
+import com.android.org.conscrypt.tlswire.record.TlsRecord;
 import java.io.ByteArrayInputStream;
 import java.io.DataInputStream;
 import java.nio.ByteBuffer;
@@ -38,6 +50,7 @@
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Random;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
@@ -62,18 +75,6 @@
 import javax.net.ssl.SSLSession;
 import javax.net.ssl.X509ExtendedKeyManager;
 import javax.net.ssl.X509TrustManager;
-import com.android.org.conscrypt.Conscrypt;
-import com.android.org.conscrypt.TestUtils;
-import com.android.org.conscrypt.java.security.TestKeyStore;
-import com.android.org.conscrypt.testing.FailingSniMatcher;
-import com.android.org.conscrypt.tlswire.TlsTester;
-import com.android.org.conscrypt.tlswire.handshake.AlpnHelloExtension;
-import com.android.org.conscrypt.tlswire.handshake.ClientHello;
-import com.android.org.conscrypt.tlswire.handshake.HandshakeMessage;
-import com.android.org.conscrypt.tlswire.handshake.HelloExtension;
-import com.android.org.conscrypt.tlswire.handshake.ServerNameHelloExtension;
-import com.android.org.conscrypt.tlswire.record.TlsProtocols;
-import com.android.org.conscrypt.tlswire.record.TlsRecord;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -757,7 +758,7 @@
                     });
             fail();
         } catch (SSLHandshakeException e) {
-            assertEquals(e.getMessage(), "SNI match failed: any.host");
+            assertEquals("SNI match failed: any.host", e.getMessage());
         }
     }
 
@@ -808,6 +809,102 @@
         assertTrue(serverAliasCalled.get());
     }
 
+    // Splits a ByteArray into an array of ByteBuffers each no bigger than the specified size.
+    private ByteBuffer[] splitDataIntoBuffers(byte[] sourceData, int size) {
+        int nbuf = ((sourceData.length - 1) / size) + 1;
+        ByteBuffer[] buffers = new ByteBuffer[nbuf];
+        int buffer = 0;
+        for (int offset = 0; offset < sourceData.length; offset += size, buffer++) {
+            buffers[buffer] = ByteBuffer.allocate(size);
+            int remaining = sourceData.length - offset;
+            buffers[buffer].put(sourceData, offset, Math.min(remaining, size));
+            buffers[buffer].flip();
+        }
+        return buffers;
+    }
+
+    // Sends dataSize bytes of application data, split into an array of ByteBuffers
+    // of size bufferSize and verifies it arrives intact. If offset is non-zero then
+    // additional invalid buffers will be added to the start and end of the buffer array
+    // in order to test the offset and length arguments of wrap().
+    private void sendAppDataInMultipleBuffers(
+            SSLEngine src, SSLEngine dst, int dataSize, int bufferSize) throws SSLException {
+        // Generate random data and split into multiple.
+        byte[] sourceData = new byte[dataSize];
+        Random random = new Random(System.currentTimeMillis());
+        random.nextBytes(sourceData);
+        ByteBuffer[] sourceBuffers = splitDataIntoBuffers(sourceData, bufferSize);
+        int length = sourceBuffers.length;
+
+        // Ensure there is no pending outbound data or encrypted data and handshaking is complete.
+        ByteBuffer tlsBuffer = ByteBuffer.allocateDirect(src.getSession().getPacketBufferSize());
+        SSLEngineResult result = src.wrap(EMPTY_BUFFER, tlsBuffer);
+        assertEquals(Status.OK, result.getStatus());
+        assertEquals(HandshakeStatus.NOT_HANDSHAKING, result.getHandshakeStatus());
+        assertEquals(0, result.bytesConsumed());
+        assertEquals(0, result.bytesProduced());
+
+        // Ensure there is no pending inbound data
+        result = dst.unwrap(EMPTY_BUFFER, tlsBuffer);
+        assertEquals(Status.BUFFER_UNDERFLOW, result.getStatus());
+        assertEquals(0, result.bytesConsumed());
+        assertEquals(0, result.bytesProduced());
+
+        // Send the data.  wrap() should consume as many source buffers as needed but
+        // never generate more than one full packet buffer of TLS data, and so unwrap()
+        // should only be needed once per loop iteration.
+        int consumed = 0, produced = 0;
+        ByteBuffer destBuffer = ByteBuffer.allocate(dataSize);
+        while (consumed < dataSize) {
+            String message =
+                    String.format("sendData: dataSize=%d, bufSize=%d", dataSize, bufferSize);
+
+            tlsBuffer.clear();
+            result = src.wrap(sourceBuffers, 0, length, tlsBuffer);
+            assertEquals(message, Status.OK, result.getStatus());
+            consumed += result.bytesConsumed();
+
+            tlsBuffer.flip();
+            result = dst.unwrap(tlsBuffer, destBuffer);
+            assertEquals(message, Status.OK, result.getStatus());
+            produced += result.bytesProduced();
+        }
+        assertEquals(dataSize, consumed);
+        assertEquals(dataSize, produced);
+
+        // Compare source and destination data.
+        destBuffer.flip();
+        // destBuffer is non-direct so will always have a backing array
+        assertArrayEquals(sourceData, destBuffer.array());
+    }
+
+    /**
+     * Tests the multiple {@link ByteBuffer} cases of {@code wrap())} by sending blocks of
+     * application data split into arrays of ByteBuffers of different sizes.
+     *
+     * The main intention is to check that regardless of how the data is structured in
+     * buffers, each call to wrap() always generates a single TLS record that is smaller
+     * than the maximum allowed size, see https://github.com/google/conscrypt/issues/929
+     */
+    @Test
+    public void multipleBuffersOfDifferentSizes() throws Exception {
+        TestSSLEnginePair pair = TestSSLEnginePair.create();
+        SSLSession session = pair.client.getSession();
+        int appBufSize = session.getApplicationBufferSize();
+
+        int[] dataSizes = new int[] {12, 512, 555, 1500, 8192, appBufSize, 5 * appBufSize};
+        int[] bufferSizes = new int[] {
+                53, 512, 8192, appBufSize, appBufSize - 53, appBufSize + 53, 5 * appBufSize};
+        for (int dataSize : dataSizes) {
+            for (int bufSize : bufferSizes) {
+                sendAppDataInMultipleBuffers(pair.client, pair.server, dataSize, bufSize);
+                sendAppDataInMultipleBuffers(pair.server, pair.client, dataSize, bufSize);
+                sendAppDataInMultipleBuffers(pair.client, pair.server, dataSize, bufSize);
+                sendAppDataInMultipleBuffers(pair.server, pair.client, dataSize, bufSize);
+            }
+        }
+    }
+
     private TestKeyStore addServerCertListener(final Runnable callback) {
         TestKeyStore store = TestKeyStore.getServer().copy();
         X509ExtendedKeyManager tm = new ForwardingX509ExtendedKeyManager(
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSessionContextTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSessionContextTest.java
index 9b1fa5c..19a6bee 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSessionContextTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSessionContextTest.java
@@ -73,6 +73,7 @@
     }
 
     @Test
+    @SuppressWarnings("JdkObsolete") // Public API SSLSessionContext.getIds() uses Enumeration
     public void test_SSLSessionContext_getIds() {
         TestSSLContext c = newTestContext();
         assertSSLSessionContextSize(0, c);
@@ -99,6 +100,7 @@
     }
 
     @Test
+    @SuppressWarnings("JdkObsolete") // Public API SSLSessionContext.getIds() uses Enumeration
     public void test_SSLSessionContext_getSession() {
         TestSSLContext c = newTestContext();
         try {
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSessionTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSessionTest.java
index 670ee52..f80dea4 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSessionTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSessionTest.java
@@ -202,14 +202,22 @@
             fail();
         } catch (SSLPeerUnverifiedException expected) {
             // Ignored.
+        } catch (UnsupportedOperationException e) {
+            if (!StandardNames.IS_15_OR_UP) {
+                fail("Should only throw UnsupportedOperationException on OpenJDK 15 or up");
+            }
         }
         assertNotNull(s.client.getPeerCertificates());
-        TestKeyStore.assertChainLength(s.client.getPeerCertificateChain());
+        TestKeyStore.assertChainLength(s.client.getPeerCertificates());
         try {
             assertNull(s.server.getPeerCertificateChain());
             fail();
         } catch (SSLPeerUnverifiedException expected) {
             // Ignored.
+        } catch (UnsupportedOperationException e) {
+            if (!StandardNames.IS_15_OR_UP) {
+                fail("Should only throw UnsupportedOperationException on OpenJDK 15 or up");
+            }
         }
         s.close();
     }
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSocketTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSocketTest.java
index 77b0179..5a556fd 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSocketTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSocketTest.java
@@ -73,12 +73,12 @@
 import javax.net.ssl.X509ExtendedTrustManager;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import tests.net.DelegatingSSLSocketFactory;
 import tests.util.ForEachRunner;
-import tests.util.ForEachRunner.Callback;
 import tests.util.Pair;
 
 /**
@@ -426,6 +426,7 @@
      * lower span of contiguous protocols is used in practice.
      */
     @Test
+    @Ignore("Needs TLS 1.0 or 1.1 which are scheduled for deprecation")
     public void test_SSLSocket_noncontiguousProtocols_useLower() throws Exception {
         TestSSLContext c = TestSSLContext.create();
         SSLContext clientContext = c.clientContext;
@@ -459,6 +460,7 @@
      * for both client and server isn't supported by the other.
      */
     @Test
+    @Ignore("Needs TLS 1.0 or 1.1 which are scheduled for deprecation")
     public void test_SSLSocket_noncontiguousProtocols_canNegotiate() throws Exception {
         TestSSLContext c = TestSSLContext.create();
         SSLContext clientContext = c.clientContext;
@@ -879,7 +881,7 @@
 
     @Test
     public void test_SSLSocket_ClientHello_cipherSuites() throws Exception {
-        ForEachRunner.runNamed(new Callback<SSLSocketFactory>() {
+        ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() {
             @Override
             public void run(SSLSocketFactory sslSocketFactory) throws Exception {
                 ClientHello clientHello = TlsTester
@@ -910,7 +912,7 @@
 
     @Test
     public void test_SSLSocket_ClientHello_supportedCurves() throws Exception {
-        ForEachRunner.runNamed(new Callback<SSLSocketFactory>() {
+        ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() {
             @Override
             public void run(SSLSocketFactory sslSocketFactory) throws Exception {
                 ClientHello clientHello = TlsTester
@@ -936,7 +938,7 @@
 
     @Test
     public void test_SSLSocket_ClientHello_clientProtocolVersion() throws Exception {
-        ForEachRunner.runNamed(new Callback<SSLSocketFactory>() {
+        ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() {
             @Override
             public void run(SSLSocketFactory sslSocketFactory) throws Exception {
                 ClientHello clientHello = TlsTester
@@ -948,7 +950,7 @@
 
     @Test
     public void test_SSLSocket_ClientHello_compressionMethods() throws Exception {
-        ForEachRunner.runNamed(new Callback<SSLSocketFactory>() {
+        ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() {
             @Override
             public void run(SSLSocketFactory sslSocketFactory) throws Exception {
                 ClientHello clientHello = TlsTester
@@ -1013,6 +1015,7 @@
 
     // Confirms that communication without the TLS_FALLBACK_SCSV cipher works as it always did.
     @Test
+    @Ignore("Needs TLS 1.0 or 1.1 which are scheduled for deprecation")
     public void test_SSLSocket_sendsNoTlsFallbackScsv_Fallback_Success() throws Exception {
         TestSSLContext context = TestSSLContext.create();
         // TLS_FALLBACK_SCSV is only applicable to TLS <= 1.2
@@ -1053,6 +1056,7 @@
     }
 
     @Test
+    @Ignore("Needs TLS 1.0 or 1.1 which are scheduled for deprecation")
     public void test_SSLSocket_sendsTlsFallbackScsv_InappropriateFallback_Failure()
             throws Exception {
         TestSSLContext context = TestSSLContext.create();
@@ -1106,6 +1110,7 @@
     }
 
     @Test
+    @Ignore("Needs TLS 1.0 or 1.1 which are scheduled for deprecation")
     public void test_SSLSocket_tlsFallback_byVersion() throws Exception {
         String[] supportedProtocols =
                 SSLContext.getDefault().getDefaultSSLParameters().getProtocols();
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java
index ad109ba..3b2f5f1 100644
--- a/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/javax/net/ssl/SSLSocketVersionCompatibilityTest.java
@@ -18,6 +18,10 @@
 package com.android.org.conscrypt.javax.net.ssl;
 
 import static com.android.org.conscrypt.TestUtils.UTF_8;
+import static com.android.org.conscrypt.TestUtils.isLinux;
+import static com.android.org.conscrypt.TestUtils.isOsx;
+import static com.android.org.conscrypt.TestUtils.isWindows;
+import static com.android.org.conscrypt.TestUtils.osName;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -111,7 +115,6 @@
 import org.junit.runners.Parameterized;
 import tests.net.DelegatingSSLSocketFactory;
 import tests.util.ForEachRunner;
-import tests.util.ForEachRunner.Callback;
 import tests.util.Pair;
 
 /**
@@ -364,6 +367,7 @@
         client.addHandshakeCompletedListener(new HandshakeCompletedListener() {
             @Override
             public void handshakeCompleted(HandshakeCompletedEvent event) {
+                SSLSocket socket = null;
                 try {
                     SSLSession session = event.getSession();
                     String cipherSuite = event.getCipherSuite();
@@ -373,7 +377,7 @@
                         event.getPeerCertificateChain();
                     Principal peerPrincipal = event.getPeerPrincipal();
                     Principal localPrincipal = event.getLocalPrincipal();
-                    SSLSocket socket = event.getSocket();
+                    socket = event.getSocket();
                     assertNotNull(session);
                     byte[] id = session.getId();
                     assertNotNull(id);
@@ -410,16 +414,19 @@
                     assertNotNull(socket);
                     assertSame(client, socket);
                     assertNull(socket.getHandshakeSession());
+                } catch (RuntimeException e) {
+                    throw e;
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                } finally {
                     synchronized (handshakeCompletedListenerCalled) {
                         handshakeCompletedListenerCalled[0] = true;
                         handshakeCompletedListenerCalled.notify();
                     }
                     handshakeCompletedListenerCalled[0] = true;
-                    socket.removeHandshakeCompletedListener(this);
-                } catch (RuntimeException e) {
-                    throw e;
-                } catch (Exception e) {
-                    throw new RuntimeException(e);
+                    if (socket != null) {
+                        socket.removeHandshakeCompletedListener(this);
+                    }
                 }
             }
         });
@@ -1469,9 +1476,15 @@
      * thread will interrupt another thread blocked writing on the same
      * socket.
      *
+     * Currently disabled: If the victim thread is not actually blocked in a write
+     * call then ConscryptEngineSocket can corrupt the output due to unsynchronized
+     * concurrent access to the socket's output stream and cause flakes: b/161347005
+     * TODO(prb): Re-enable after underlying bug resolved
+     *
      * See also b/147323301 where close() triggered an infinite loop instead.
      */
     @Test
+    @Ignore
     public void test_SSLSocket_interrupt_write_withAutoclose() throws Exception {
         final TestSSLContext c = new TestSSLContext.Builder()
                                          .clientProtocol(clientVersion)
@@ -1482,6 +1495,8 @@
                 underlying, c.host.getHostName(), c.port, true);
         final byte[] data = new byte[1024 * 64];
 
+        // TODO(b/161347005): Re-enable once engine-based socket interruption works correctly.
+        assumeFalse(isConscryptEngineSocket(wrapping));
         Future<Void> clientFuture = runAsync(new Callable<Void>() {
             @Override
             public Void call() throws Exception {
@@ -1495,13 +1510,6 @@
                     fail();
                 } catch (SocketException expected) {
                     assertTrue(expected.getMessage().contains("closed"));
-                } catch (SSLException e) {
-                    // TODO(b/159199048): Workaround for known TreeHugger
-                    // cf_x86 presubmit failure which doesn't occur on non-TreeHugger
-                    // cf_x86 or real devices.
-                    if (!e.getMessage().contains("Engine bytesProduced")) {
-                        throw e;
-                    }
                 }
                 return null;
             }
@@ -1554,7 +1562,7 @@
 
     @Test
     public void test_SSLSocket_ClientHello_SNI() throws Exception {
-        ForEachRunner.runNamed(new Callback<SSLSocketFactory>() {
+        ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() {
             @Override
             public void run(SSLSocketFactory sslSocketFactory) throws Exception {
                 ClientHello clientHello = TlsTester
@@ -1572,8 +1580,8 @@
     @Test
     public void test_SSLSocket_ClientHello_ALPN() throws Exception {
         final String[] protocolList = new String[] { "h2", "http/1.1" };
-        
-        ForEachRunner.runNamed(new Callback<SSLSocketFactory>() {
+
+        ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() {
             @Override
             public void run(SSLSocketFactory sslSocketFactory) throws Exception {
                 ClientHello clientHello = TlsTester.captureTlsHandshakeClientHello(executor,
@@ -2083,23 +2091,6 @@
         return "ConscryptEngineSocket".equals(clazz.getSimpleName());
     }
 
-    private static String osName() {
-        return System.getProperty("os.name").toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", "");
-    }
-
-    private static boolean isLinux() {
-        return osName().startsWith("linux");
-    }
-
-    private static boolean isWindows() {
-        return osName().startsWith("windows");
-    }
-
-    private static boolean isOsx() {
-        String name = osName();
-        return name.startsWith("macosx") || name.startsWith("osx");
-    }
-
     private <T> Future<T> runAsync(Callable<T> callable) {
         return executor.submit(callable);
     }
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/metrics/CipherSuiteTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/metrics/CipherSuiteTest.java
new file mode 100644
index 0000000..63283d1
--- /dev/null
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/metrics/CipherSuiteTest.java
@@ -0,0 +1,23 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.org.conscrypt.metrics;
+
+import static org.junit.Assert.assertSame;
+
+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 CipherSuiteTest {
+
+    @Test
+    public void consistency() {
+        for (CipherSuite cipherSuite : CipherSuite.values()) {
+            assertSame(cipherSuite, CipherSuite.forName(cipherSuite.name()));
+        }
+        assertSame(CipherSuite.UNKNOWN_CIPHER_SUITE, CipherSuite.forName("random junk"));
+    }
+}
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/metrics/OptionalMethodTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/metrics/OptionalMethodTest.java
new file mode 100644
index 0000000..4675042
--- /dev/null
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/metrics/OptionalMethodTest.java
@@ -0,0 +1,66 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.org.conscrypt.metrics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+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 OptionalMethodTest {
+
+    @Test
+    public void workingMethod() {
+        OptionalMethod substring =
+                new OptionalMethod(String.class, "substring", int.class, int.class);
+        assertNotNull(substring);
+
+        assertEquals("put", substring.invoke("input", 2, 5));
+    }
+
+    @Test
+    public void nullClass() {
+        OptionalMethod substring =
+                new OptionalMethod(null, "substring", int.class, int.class);
+        assertNotNull(substring);
+
+        assertNull(substring.invoke("input", 2, 5));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullMethodName() {
+        new OptionalMethod(String.class, null, int.class, int.class);
+    }
+
+    @Test
+    public void nullArgumentClasses() {
+        OptionalMethod substring = new OptionalMethod(String.class, "substring", int.class, null);
+        assertNotNull(substring);
+
+        assertNull(substring.invoke("input", 2, 5));
+    }
+
+    @Test
+    public void noSuchMethodName() {
+        OptionalMethod subwrong =
+                new OptionalMethod(null, "subwrong", int.class, int.class);
+        assertNotNull(subwrong);
+
+        assertNull(subwrong.invoke("input", 2, 5));
+    }
+
+    @Test
+    public void noSuchMethodArgs() {
+        OptionalMethod subwrong =
+                new OptionalMethod(null, "substring", long.class, byte[].class);
+        assertNotNull(subwrong);
+
+        assertNull(subwrong.invoke("input", 2, 5));
+    }
+}
diff --git a/repackaged/common/src/test/java/com/android/org/conscrypt/metrics/ProtocolTest.java b/repackaged/common/src/test/java/com/android/org/conscrypt/metrics/ProtocolTest.java
new file mode 100644
index 0000000..002cadf
--- /dev/null
+++ b/repackaged/common/src/test/java/com/android/org/conscrypt/metrics/ProtocolTest.java
@@ -0,0 +1,24 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+package com.android.org.conscrypt.metrics;
+
+import static org.junit.Assert.assertSame;
+
+import org.junit.Ignore;
+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 ProtocolTest {
+    @Test
+    @Ignore("Test intended for MTS only.")
+    public void consistency() {
+        for (Protocol protocol : Protocol.values()) {
+            String name = protocol.name().replace('_', '.');
+            assertSame(protocol, Protocol.forName(name));
+        }
+    }
+}
diff --git a/repackaged/openjdk/src/main/java/com/android/org/conscrypt/Java8PlatformUtil.java b/repackaged/openjdk/src/main/java/com/android/org/conscrypt/Java8PlatformUtil.java
index 9cdb922..c5b8304 100644
--- a/repackaged/openjdk/src/main/java/com/android/org/conscrypt/Java8PlatformUtil.java
+++ b/repackaged/openjdk/src/main/java/com/android/org/conscrypt/Java8PlatformUtil.java
@@ -43,15 +43,6 @@
         }
     }
 
-    static void getSSLParameters(
-            SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket) {
-        getSSLParameters(params, impl);
-        if (impl.getUseSni() && AddressUtils.isValidSniHostname(socket.getHostname())) {
-            params.setServerNames(Collections.singletonList(
-                    (SNIServerName) new SNIHostName(socket.getHostname())));
-        }
-    }
-
     static void setSSLParameters(
             SSLParameters params, SSLParametersImpl impl, ConscryptEngine engine) {
         setSSLParameters(params, impl);
@@ -61,6 +52,16 @@
             engine.setHostname(sniHost);
         }
     }
+
+    static void getSSLParameters(
+            SSLParameters params, SSLParametersImpl impl, AbstractConscryptSocket socket) {
+        getSSLParameters(params, impl);
+        if (impl.getUseSni() && AddressUtils.isValidSniHostname(socket.getHostname())) {
+            params.setServerNames(Collections.singletonList(
+                    (SNIServerName) new SNIHostName(socket.getHostname())));
+        }
+    }
+
     static void getSSLParameters(
             SSLParameters params, SSLParametersImpl impl, ConscryptEngine engine) {
         getSSLParameters(params, impl);
diff --git a/repackaged/openjdk/src/main/java/com/android/org/conscrypt/Platform.java b/repackaged/openjdk/src/main/java/com/android/org/conscrypt/Platform.java
index 13166d8..d71d2f8 100644
--- a/repackaged/openjdk/src/main/java/com/android/org/conscrypt/Platform.java
+++ b/repackaged/openjdk/src/main/java/com/android/org/conscrypt/Platform.java
@@ -100,6 +100,7 @@
             getCurveNameMethod = ECParameterSpec.class.getDeclaredMethod("getCurveName");
             getCurveNameMethod.setAccessible(true);
         } catch (Exception ignored) {
+            // Method not available, leave it as null, which is checked before use
         }
         GET_CURVE_NAME_METHOD = getCurveNameMethod;
     }
@@ -194,9 +195,19 @@
         }
 
         try {
-            Field f_impl = Socket.class.getDeclaredField("impl");
-            f_impl.setAccessible(true);
-            Object socketImpl = f_impl.get(s);
+            Method m_getImpl = Socket.class.getDeclaredMethod("getImpl");
+            m_getImpl.setAccessible(true);
+            Object socketImpl = m_getImpl.invoke(s);
+            try {
+                Class<?> c_delegatingSocketImpl = Class.forName("java.net.DelegatingSocketImpl");
+                if (c_delegatingSocketImpl.isAssignableFrom(socketImpl.getClass())) {
+                    Method m_delegate = c_delegatingSocketImpl.getDeclaredMethod("delegate");
+                    m_delegate.setAccessible(true);
+                    socketImpl = m_delegate.invoke(socketImpl);
+                }
+            } catch (Exception ignored) {
+                // Ignored
+            }
             Field f_fd = SocketImpl.class.getDeclaredField("fd");
             f_fd.setAccessible(true);
             return (FileDescriptor) f_fd.get(socketImpl);
@@ -581,10 +592,8 @@
             return originalHostName;
         } catch (InvocationTargetException e) {
             throw new RuntimeException("Failed to get originalHostName", e);
-        } catch (ClassNotFoundException ignore) {
+        } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException ignore) {
             // passthrough and return addr.getHostAddress()
-        } catch (IllegalAccessException ignore) {
-        } catch (NoSuchMethodException ignore) {
         }
 
         return addr.getHostAddress();
@@ -658,9 +667,10 @@
         KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
         try {
             ks.load(null, null);
-        } catch (CertificateException ignored) {
         } catch (NoSuchAlgorithmException ignored) {
-        } catch (IOException ignored) {
+            // TODO(prb): Should this be re-thrown? It happens if "the algorithm used to check
+            // the integrity of the KeyStore cannot be found".
+        } catch (IOException | CertificateException ignored) {
             // We're not loading anything, so ignore it
         }
         // Find the highest-priority non-Conscrypt provider that provides a PKIX
@@ -708,7 +718,7 @@
         return null;
     }
 
-    static CertBlacklist newDefaultBlacklist() {
+    static CertBlocklist newDefaultBlocklist() {
         return null;
     }
 
@@ -786,4 +796,21 @@
             });
         }
     }
+
+    public static ConscryptHostnameVerifier getDefaultHostnameVerifier() {
+        return OkHostnameVerifier.strictInstance();
+    }
+
+    @SuppressWarnings("unused")
+    static long getMillisSinceBoot() {
+        return 0;
+    }
+
+    @SuppressWarnings("unused")
+    static void countTlsHandshake(
+            boolean success, String protocol, String cipherSuite, long duration) {}
+
+    public static boolean isJavaxCertificateSupported() {
+        return JAVA_VERSION < 15;
+    }
 }
diff --git a/repackaged/openjdk/src/test/java/com/android/org/conscrypt/ConscryptSocketTest.java b/repackaged/openjdk/src/test/java/com/android/org/conscrypt/ConscryptSocketTest.java
index 4af2a49..2d169ab 100644
--- a/repackaged/openjdk/src/test/java/com/android/org/conscrypt/ConscryptSocketTest.java
+++ b/repackaged/openjdk/src/test/java/com/android/org/conscrypt/ConscryptSocketTest.java
@@ -20,24 +20,29 @@
 import static com.android.org.conscrypt.TestUtils.openTestFile;
 import static com.android.org.conscrypt.TestUtils.readTestFile;
 import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.Mockito.when;
 
 import java.io.IOException;
 import java.lang.reflect.Field;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
 import java.net.ServerSocket;
 import java.net.Socket;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
 import java.security.KeyManagementException;
 import java.security.KeyStore;
 import java.security.PrivateKey;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
+import java.util.Random;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -84,12 +89,6 @@
                     OpenSSLContextImpl context, ServerSocket server, SSLSocketFactory factory) {
                 return null;
             }
-
-            @Override
-            Socket newServerSocket(OpenSSLContextImpl context, ServerSocket server,
-                    SSLSocketFactory factory) throws IOException {
-                return server.accept();
-            }
         },
         PLAIN {
             @Override
@@ -97,11 +96,14 @@
                     SSLSocketFactory factory) throws IOException {
                 return new Socket(server.getInetAddress(), server.getLocalPort());
             }
-
+        },
+        CHANNEL {
             @Override
-            Socket newServerSocket(OpenSSLContextImpl context, ServerSocket server,
+            Socket newClientSocket(OpenSSLContextImpl context, ServerSocket server,
                     SSLSocketFactory factory) throws IOException {
-                return server.accept();
+                SocketChannel channel = SocketChannel.open();
+                channel.connect(server.getLocalSocketAddress());
+                return channel.socket();
             }
         },
         SSL {
@@ -126,8 +128,11 @@
 
         abstract Socket newClientSocket(OpenSSLContextImpl context, ServerSocket server,
                 SSLSocketFactory factory) throws IOException;
-        abstract Socket newServerSocket(OpenSSLContextImpl context, ServerSocket server,
-                SSLSocketFactory factory) throws IOException;
+
+        Socket newServerSocket(OpenSSLContextImpl context, ServerSocket server,
+                SSLSocketFactory factory) throws IOException {
+            return server.accept();
+        }
     }
 
     /**
@@ -197,15 +202,47 @@
         }
     }
 
-    @Parameters(name = "{0} wrapping {1}")
+    /**
+     * @hide This class is not part of the Android public SDK API
+     */
+    public enum ServerSocketType {
+        PLAIN {
+            @Override
+            public ServerSocket newServerSocket() throws IOException {
+                return new ServerSocket(0, 50, TestUtils.getLoopbackAddress());
+            }
+        },
+        CHANNEL {
+            @Override
+            public ServerSocket newServerSocket() throws IOException {
+                ServerSocketChannel channel = ServerSocketChannel.open();
+                InetAddress localAddress = TestUtils.getLoopbackAddress();
+                channel.socket().bind(new InetSocketAddress(localAddress.getHostName(), 0), 50);
+                return channel.socket();
+            }
+        };
+        public abstract ServerSocket newServerSocket() throws IOException;
+    }
+
+    @Parameters(name = "{0} wrapping {1} connecting to {2}")
     public static Object[][] data() {
         return new Object[][] {
-            {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.NONE},
-            {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.PLAIN},
-            // Not supported: {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.SSL},
-            {SocketType.ENGINE, UnderlyingSocketType.NONE},
-            {SocketType.ENGINE, UnderlyingSocketType.PLAIN},
-            {SocketType.ENGINE, UnderlyingSocketType.SSL}};
+                {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.NONE, ServerSocketType.PLAIN},
+                {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.NONE, ServerSocketType.CHANNEL},
+                {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.PLAIN, ServerSocketType.PLAIN},
+                {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.PLAIN, ServerSocketType.CHANNEL},
+                {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.CHANNEL, ServerSocketType.PLAIN},
+                {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.CHANNEL,
+                        ServerSocketType.CHANNEL},
+                // Not supported: {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.SSL},
+                {SocketType.ENGINE, UnderlyingSocketType.NONE, ServerSocketType.PLAIN},
+                {SocketType.ENGINE, UnderlyingSocketType.NONE, ServerSocketType.CHANNEL},
+                {SocketType.ENGINE, UnderlyingSocketType.PLAIN, ServerSocketType.PLAIN},
+                {SocketType.ENGINE, UnderlyingSocketType.PLAIN, ServerSocketType.CHANNEL},
+                {SocketType.ENGINE, UnderlyingSocketType.CHANNEL, ServerSocketType.PLAIN},
+                {SocketType.ENGINE, UnderlyingSocketType.CHANNEL, ServerSocketType.CHANNEL},
+                {SocketType.ENGINE, UnderlyingSocketType.SSL, ServerSocketType.PLAIN},
+                {SocketType.ENGINE, UnderlyingSocketType.SSL, ServerSocketType.CHANNEL}};
     }
 
     @Parameter
@@ -214,6 +251,8 @@
     @Parameter(1)
     public UnderlyingSocketType underlyingSocketType;
 
+    @Parameter(2) public ServerSocketType serverSocketType;
+
     private X509Certificate ca;
     private X509Certificate cert;
     private X509Certificate certEmbedded;
@@ -221,6 +260,7 @@
 
     private Field contextSSLParameters;
     private ExecutorService executor;
+    private final Random random = new Random(System.currentTimeMillis());
 
     @Before
     public void setUp() throws Exception {
@@ -394,7 +434,7 @@
         }
 
         void doHandshake() throws Exception {
-            ServerSocket listener = newServerSocket();
+            ServerSocket listener = serverSocketType.newServerSocket();
             Future<AbstractConscryptSocket> clientFuture = handshake(listener, clientHooks);
             Future<AbstractConscryptSocket> serverFuture = handshake(listener, serverHooks);
 
@@ -570,7 +610,7 @@
     @Test
     @SuppressWarnings("deprecation")
     public void setAlpnProtocolWithNullShouldSucceed() throws Exception {
-        ServerSocket listening = newServerSocket();
+        ServerSocket listening = serverSocketType.newServerSocket();
         OpenSSLSocketImpl clientSocket = null;
         try {
             Socket underlying = new Socket(listening.getInetAddress(), listening.getLocalPort());
@@ -591,7 +631,7 @@
     // http://b/27250522
     @Test
     public void test_setSoTimeout_doesNotCreateSocketImpl() throws Exception {
-        ServerSocket listening = newServerSocket();
+        ServerSocket listening = serverSocketType.newServerSocket();
         try {
             Socket underlying = new Socket(listening.getInetAddress(), listening.getLocalPort());
             Socket socket = socketType.newClientSocket(
@@ -654,7 +694,44 @@
         assertEquals(alpnProtocol, Conscrypt.getApplicationProtocol(connection.client));
     }
 
-    private static ServerSocket newServerSocket() throws IOException {
-        return new ServerSocket(0, 50, TestUtils.getLoopbackAddress());
+    @Test
+    public void dataFlows() throws Exception {
+        final TestConnection connection =
+                new TestConnection(new X509Certificate[] {cert, ca}, certKey);
+        connection.doHandshakeSuccess();
+
+        // Basic data flow assurance.  Send random buffers in each direction, each less than 16K
+        // so should fit in a single TLS packet.  50% chance of sending in each direction on
+        // each iteration to randomize the flow.
+        for (int i = 0; i < 50; i++) {
+            if (random.nextBoolean()) {
+                sendData(connection.client, connection.server, randomBuffer());
+            }
+            if (random.nextBoolean()) {
+                sendData(connection.server, connection.client, randomBuffer());
+            }
+        }
+    }
+
+    private void sendData(SSLSocket source, final SSLSocket destination, byte[] data)
+            throws Exception {
+        final byte[] received = new byte[data.length];
+
+        Future<Integer> readFuture = executor.submit(new Callable<Integer>() {
+            @Override
+            public Integer call() throws Exception {
+                return destination.getInputStream().read(received);
+            }
+        });
+
+        source.getOutputStream().write(data);
+        assertEquals(data.length, (int) readFuture.get());
+        assertArrayEquals(data, received);
+    }
+
+    private byte[] randomBuffer() {
+        byte[] buffer = new byte[random.nextInt(16 * 1024)];
+        random.nextBytes(buffer);
+        return buffer;
     }
 }
diff --git a/repackaged/openjdk/src/test/java/com/android/org/conscrypt/NativeCryptoTest.java b/repackaged/openjdk/src/test/java/com/android/org/conscrypt/NativeCryptoTest.java
index 1569348..ea274a1 100644
--- a/repackaged/openjdk/src/test/java/com/android/org/conscrypt/NativeCryptoTest.java
+++ b/repackaged/openjdk/src/test/java/com/android/org/conscrypt/NativeCryptoTest.java
@@ -161,6 +161,7 @@
     /**
      * Lazily create shared test certificates.
      */
+    @SuppressWarnings("JdkObsolete") // Public API KeyStore.aliases() uses Enumeration
     private static synchronized void initCerts() {
         if (SERVER_PRIVATE_KEY != null) {
             return;
diff --git a/repackaged/openjdk/src/test/java/com/android/org/conscrypt/OpenSSLX509CertificateTest.java b/repackaged/openjdk/src/test/java/com/android/org/conscrypt/OpenSSLX509CertificateTest.java
index 07439e0..ab89f45 100644
--- a/repackaged/openjdk/src/test/java/com/android/org/conscrypt/OpenSSLX509CertificateTest.java
+++ b/repackaged/openjdk/src/test/java/com/android/org/conscrypt/OpenSSLX509CertificateTest.java
@@ -19,6 +19,7 @@
 
 import static com.android.org.conscrypt.TestUtils.openTestFile;
 
+import com.android.org.conscrypt.OpenSSLX509CertificateFactory.ParsingException;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.FileNotFoundException;
@@ -26,10 +27,11 @@
 import java.io.ObjectOutputStream;
 import java.io.ObjectStreamClass;
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
 import junit.framework.TestCase;
-import com.android.org.conscrypt.OpenSSLX509CertificateFactory.ParsingException;
 
 /**
  * @hide This class is not part of the Android public SDK API
@@ -47,10 +49,29 @@
 
             // Mark the field as non-final on JVM that need it.
             try {
-                Field modifiersField = Field.class.getDeclaredField("modifiers");
-                modifiersField.setAccessible(true);
-                modifiersField.setInt(targetUID, targetUID.getModifiers() & ~Modifier.FINAL);
-            } catch (NoSuchFieldException ignored) {
+                Field modifiersField = null;
+                try {
+                    modifiersField = Field.class.getDeclaredField("modifiers");
+                } catch (NoSuchFieldException e) {
+                    try {
+                        Method getDeclaredFields0 =
+                                Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
+                        getDeclaredFields0.setAccessible(true);
+                        Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
+                        for (Field field : fields) {
+                            if ("modifiers".equals(field.getName())) {
+                                modifiersField = field;
+                                break;
+                            }
+                        }
+                    } catch (NoSuchMethodException | InvocationTargetException ignored) {
+                    }
+                }
+                if (modifiersField != null) {
+                    modifiersField.setAccessible(true);
+                    modifiersField.setInt(targetUID, targetUID.getModifiers() & ~Modifier.FINAL);
+                }
+            } catch (Exception ignored) {
             }
 
             targetUID.set(null, clDesc.getSerialVersionUID());
@@ -109,32 +130,32 @@
                 certPoisoned.getTBSCertificate(),
                 cert.getTBSCertificate()));
 
-        assertTrue(Arrays.equals(
-                certPoisoned.withDeletedExtension(CT_POISON_EXTENSION).getTBSCertificate(),
-                cert.getTBSCertificate()));
+        assertTrue(
+                Arrays.equals(certPoisoned.getTBSCertificateWithoutExtension(CT_POISON_EXTENSION),
+                        cert.getTBSCertificate()));
     }
 
     public void test_deletingExtensionMakesCopy() throws Exception {
-        /* Calling withDeletedExtension should not modify the original certificate, only make a copy.
+        /* Calling getTBSCertificateWithoutExtension should not modify the original certificate.
          * Make sure the extension is still present in the original object.
          */
         OpenSSLX509Certificate certPoisoned = loadTestCertificate("cert-ct-poisoned.pem");
         assertTrue(certPoisoned.getCriticalExtensionOIDs().contains(CT_POISON_EXTENSION));
 
-        OpenSSLX509Certificate certWithoutExtension = certPoisoned.withDeletedExtension(CT_POISON_EXTENSION);
-
+        certPoisoned.getTBSCertificateWithoutExtension(CT_POISON_EXTENSION);
         assertTrue(certPoisoned.getCriticalExtensionOIDs().contains(CT_POISON_EXTENSION));
-        assertFalse(certWithoutExtension.getCriticalExtensionOIDs().contains(CT_POISON_EXTENSION));
     }
 
     public void test_deletingMissingExtension() throws Exception {
-        /* withDeletedExtension should be safe to call on a certificate without the extension, and
-         * return an identical copy.
+        /* getTBSCertificateWithoutExtension should throw on a certificate without the extension.
          */
         OpenSSLX509Certificate cert = loadTestCertificate("cert.pem");
         assertFalse(cert.getCriticalExtensionOIDs().contains(CT_POISON_EXTENSION));
 
-        OpenSSLX509Certificate cert2 = cert.withDeletedExtension(CT_POISON_EXTENSION);
-        assertEquals(cert, cert2);
+        try {
+            cert.getTBSCertificateWithoutExtension(CT_POISON_EXTENSION);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
     }
 }
diff --git a/repackaged/openjdk/src/test/java/com/android/org/conscrypt/ServerSessionContextTest.java b/repackaged/openjdk/src/test/java/com/android/org/conscrypt/ServerSessionContextTest.java
index c5986cc..2502615 100644
--- a/repackaged/openjdk/src/test/java/com/android/org/conscrypt/ServerSessionContextTest.java
+++ b/repackaged/openjdk/src/test/java/com/android/org/conscrypt/ServerSessionContextTest.java
@@ -38,6 +38,7 @@
     }
 
     @Override
+    @SuppressWarnings("JdkObsolete") // Public API SSLSessionContext.getIds() uses Enumeration
     int size(ServerSessionContext context) {
         int count = 0;
         Enumeration<byte[]> ids = context.getIds();
diff --git a/repackaged/platform/src/main/java/com/android/org/conscrypt/CertBlacklistImpl.java b/repackaged/platform/src/main/java/com/android/org/conscrypt/CertBlacklistImpl.java
deleted file mode 100644
index 2b9ab24..0000000
--- a/repackaged/platform/src/main/java/com/android/org/conscrypt/CertBlacklistImpl.java
+++ /dev/null
@@ -1,301 +0,0 @@
-/* GENERATED SOURCE. DO NOT MODIFY. */
-/*
- * Copyright (C) 2012 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 java.nio.charset.StandardCharsets.UTF_8;
-
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.math.BigInteger;
-import java.security.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.security.PublicKey;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * @hide This class is not part of the Android public SDK API
- */
-@Internal
-public final class CertBlacklistImpl implements CertBlacklist {
-    private static final Logger logger = Logger.getLogger(CertBlacklistImpl.class.getName());
-
-    private final Set<BigInteger> serialBlacklist;
-    private final Set<ByteString> pubkeyBlacklist;
-
-    /**
-     * public for testing only.
-     */
-    public CertBlacklistImpl(Set<BigInteger> serialBlacklist, Set<ByteString> pubkeyBlacklist) {
-        this.serialBlacklist = serialBlacklist;
-        this.pubkeyBlacklist = pubkeyBlacklist;
-    }
-
-    public static CertBlacklist getDefault() {
-        String androidData = System.getenv("ANDROID_DATA");
-        String blacklistRoot = androidData + "/misc/keychain/";
-        String defaultPubkeyBlacklistPath = blacklistRoot + "pubkey_blacklist.txt";
-        String defaultSerialBlacklistPath = blacklistRoot + "serial_blacklist.txt";
-
-        Set<ByteString> pubkeyBlacklist = readPublicKeyBlackList(defaultPubkeyBlacklistPath);
-        Set<BigInteger> serialBlacklist = readSerialBlackList(defaultSerialBlacklistPath);
-        return new CertBlacklistImpl(serialBlacklist, pubkeyBlacklist);
-    }
-
-    private static boolean isHex(String value) {
-        try {
-            new BigInteger(value, 16);
-            return true;
-        } catch (NumberFormatException e) {
-            logger.log(Level.WARNING, "Could not parse hex value " + value, e);
-            return false;
-        }
-    }
-
-    private static boolean isPubkeyHash(String value) {
-        if (value.length() != 40) {
-            logger.log(Level.WARNING, "Invalid pubkey hash length: " + value.length());
-            return false;
-        }
-        return isHex(value);
-    }
-
-    private static String readBlacklist(String path) {
-        try {
-            return readFileAsString(path);
-        } catch (FileNotFoundException ignored) {
-        } catch (IOException e) {
-            logger.log(Level.WARNING, "Could not read blacklist", e);
-        }
-        return "";
-    }
-
-    // From IoUtils.readFileAsString
-    private static String readFileAsString(String path) throws IOException {
-        return readFileAsBytes(path).toString("UTF-8");
-    }
-
-    // Based on IoUtils.readFileAsBytes
-    private static ByteArrayOutputStream readFileAsBytes(String path) throws IOException {
-        RandomAccessFile f = null;
-        try {
-            f = new RandomAccessFile(path, "r");
-            ByteArrayOutputStream bytes = new ByteArrayOutputStream((int) f.length());
-            byte[] buffer = new byte[8192];
-            while (true) {
-                int byteCount = f.read(buffer);
-                if (byteCount == -1) {
-                    return bytes;
-                }
-                bytes.write(buffer, 0, byteCount);
-            }
-        } finally {
-            closeQuietly(f);
-        }
-    }
-
-    // Base on IoUtils.closeQuietly
-    private static void closeQuietly(Closeable closeable) {
-        if (closeable != null) {
-            try {
-                closeable.close();
-            } catch (RuntimeException rethrown) {
-                throw rethrown;
-            } catch (Exception ignored) {
-            }
-        }
-    }
-
-    private static Set<BigInteger> readSerialBlackList(String path) {
-
-        /* Start out with a base set of known bad values.
-         *
-         * WARNING: Do not add short serials to this list!
-         *
-         * Since this currently doesn't compare the serial + issuer, you
-         * should only add serials that have enough entropy here. Short
-         * serials may inadvertently match a certificate that was issued
-         * not in compliance with the Baseline Requirements.
-         */
-        Set<BigInteger> bl = new HashSet<BigInteger>(Arrays.asList(
-            // From http://src.chromium.org/viewvc/chrome/trunk/src/net/base/x509_certificate.cc?revision=78748&view=markup
-            // Not a real certificate. For testing only.
-            new BigInteger("077a59bcd53459601ca6907267a6dd1c", 16),
-            new BigInteger("047ecbe9fca55f7bd09eae36e10cae1e", 16),
-            new BigInteger("d8f35f4eb7872b2dab0692e315382fb0", 16),
-            new BigInteger("b0b7133ed096f9b56fae91c874bd3ac0", 16),
-            new BigInteger("9239d5348f40d1695a745470e1f23f43", 16),
-            new BigInteger("e9028b9578e415dc1a710a2b88154447", 16),
-            new BigInteger("d7558fdaf5f1105bb213282b707729a3", 16),
-            new BigInteger("f5c86af36162f13a64f54f6dc9587c06", 16),
-            new BigInteger("392a434f0e07df1f8aa305de34e0c229", 16),
-            new BigInteger("3e75ced46b693021218830ae86a82a71", 16)
-        ));
-
-        // attempt to augment it with values taken from gservices
-        String serialBlacklist = readBlacklist(path);
-        if (!serialBlacklist.equals("")) {
-            for (String value : serialBlacklist.split(",", -1)) {
-                try {
-                    bl.add(new BigInteger(value, 16));
-                } catch (NumberFormatException e) {
-                    logger.log(Level.WARNING, "Tried to blacklist invalid serial number " + value, e);
-                }
-            }
-        }
-
-        // whether that succeeds or fails, send it on its merry way
-        return Collections.unmodifiableSet(bl);
-    }
-
-    private static Set<ByteString> readPublicKeyBlackList(String path) {
-        // start out with a base set of known bad values
-        Set<ByteString> bl = new HashSet<ByteString>(toByteStrings(
-                // Blacklist test cert for CTS. The cert and key can be found in
-                // src/test/resources/blacklist_test_ca.pem and
-                // src/test/resources/blacklist_test_ca_key.pem.
-                "bae78e6bed65a2bf60ddedde7fd91e825865e93d".getBytes(UTF_8),
-                // From
-                // http://src.chromium.org/viewvc/chrome/branches/782/src/net/base/x509_certificate.cc?r1=98750&r2=98749&pathrev=98750
-                // C=NL, O=DigiNotar, CN=DigiNotar Root CA/emailAddress=info@diginotar.nl
-                "410f36363258f30b347d12ce4863e433437806a8".getBytes(UTF_8),
-                // Subject: CN=DigiNotar Cyber CA
-                // Issuer: CN=GTE CyberTrust Global Root
-                "ba3e7bd38cd7e1e6b9cd4c219962e59d7a2f4e37".getBytes(UTF_8),
-                // Subject: CN=DigiNotar Services 1024 CA
-                // Issuer: CN=Entrust.net
-                "e23b8d105f87710a68d9248050ebefc627be4ca6".getBytes(UTF_8),
-                // Subject: CN=DigiNotar PKIoverheid CA Organisatie - G2
-                // Issuer: CN=Staat der Nederlanden Organisatie CA - G2
-                "7b2e16bc39bcd72b456e9f055d1de615b74945db".getBytes(UTF_8),
-                // Subject: CN=DigiNotar PKIoverheid CA Overheid en Bedrijven
-                // Issuer: CN=Staat der Nederlanden Overheid CA
-                "e8f91200c65cee16e039b9f883841661635f81c5".getBytes(UTF_8),
-                // From http://src.chromium.org/viewvc/chrome?view=rev&revision=108479
-                // Subject: O=Digicert Sdn. Bhd.
-                // Issuer: CN=GTE CyberTrust Global Root
-                "0129bcd5b448ae8d2496d1c3e19723919088e152".getBytes(UTF_8),
-                // Subject:
-                // CN=e-islem.kktcmerkezbankasi.org/emailAddress=ileti@kktcmerkezbankasi.org Issuer:
-                // CN=T\xC3\x9CRKTRUST Elektronik Sunucu Sertifikas\xC4\xB1 Hizmetleri
-                "5f3ab33d55007054bc5e3e5553cd8d8465d77c61".getBytes(UTF_8),
-                // Subject: CN=*.EGO.GOV.TR 93
-                // Issuer: CN=T\xC3\x9CRKTRUST Elektronik Sunucu Sertifikas\xC4\xB1 Hizmetleri
-                "783333c9687df63377efceddd82efa9101913e8e".getBytes(UTF_8),
-                // Subject: Subject: C=FR, O=DG Tr\xC3\xA9sor, CN=AC DG Tr\xC3\xA9sor SSL
-                // Issuer: C=FR, O=DGTPE, CN=AC DGTPE Signature Authentification
-                "3ecf4bbbe46096d514bb539bb913d77aa4ef31bf".getBytes(UTF_8)));
-
-        // attempt to augment it with values taken from gservices
-        String pubkeyBlacklist = readBlacklist(path);
-        if (!pubkeyBlacklist.equals("")) {
-            for (String value : pubkeyBlacklist.split(",", -1)) {
-                value = value.trim();
-                if (isPubkeyHash(value)) {
-                    bl.add(new ByteString(value.getBytes(UTF_8)));
-                } else {
-                    logger.log(Level.WARNING, "Tried to blacklist invalid pubkey " + value);
-                }
-            }
-        }
-
-        return bl;
-    }
-
-    @Override
-    public boolean isPublicKeyBlackListed(PublicKey publicKey) {
-        byte[] encoded = publicKey.getEncoded();
-        MessageDigest md;
-        try {
-            md = MessageDigest.getInstance("SHA1");
-        } catch (GeneralSecurityException e) {
-            logger.log(Level.SEVERE, "Unable to get SHA1 MessageDigest", e);
-            return false;
-        }
-        byte[] out = toHex(md.digest(encoded));
-        for (ByteString blacklisted : pubkeyBlacklist) {
-            if (Arrays.equals(blacklisted.bytes, out)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static final byte[] HEX_TABLE = { (byte) '0', (byte) '1', (byte) '2', (byte) '3',
-        (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a',
-        (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'};
-
-    private static byte[] toHex(byte[] in) {
-        byte[] out = new byte[in.length * 2];
-        int outIndex = 0;
-        for (int i = 0; i < in.length; i++) {
-            int value = in[i] & 0xff;
-            out[outIndex++] = HEX_TABLE[value >> 4];
-            out[outIndex++] = HEX_TABLE[value & 0xf];
-        }
-        return out;
-    }
-
-    @Override
-    public boolean isSerialNumberBlackListed(BigInteger serial) {
-        return serialBlacklist.contains(serial);
-    }
-
-    private static List<ByteString> toByteStrings(byte[]... allBytes) {
-        List<ByteString> byteStrings = new ArrayList<>(allBytes.length + 1);
-        for (byte[] bytes : allBytes) {
-            byteStrings.add(new ByteString(bytes));
-        }
-        return byteStrings;
-    }
-
-    private static class ByteString {
-        final byte[] bytes;
-
-        public ByteString(byte[] bytes) {
-            this.bytes = bytes;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o == this) {
-                return true;
-            }
-            if (!(o instanceof ByteString)) {
-                return false;
-            }
-
-            ByteString other = (ByteString) o;
-            return Arrays.equals(bytes, other.bytes);
-        }
-
-        @Override
-        public int hashCode() {
-            return Arrays.hashCode(bytes);
-        }
-    }
-}
diff --git a/platform/src/main/java/org/conscrypt/CertBlacklistImpl.java b/repackaged/platform/src/main/java/com/android/org/conscrypt/CertBlocklistImpl.java
similarity index 81%
copy from platform/src/main/java/org/conscrypt/CertBlacklistImpl.java
copy to repackaged/platform/src/main/java/com/android/org/conscrypt/CertBlocklistImpl.java
index c2cdd80..06de29a 100644
--- a/platform/src/main/java/org/conscrypt/CertBlacklistImpl.java
+++ b/repackaged/platform/src/main/java/com/android/org/conscrypt/CertBlocklistImpl.java
@@ -1,3 +1,4 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
 /*
  * Copyright (C) 2012 The Android Open Source Project
  *
@@ -14,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.conscrypt;
+package com.android.org.conscrypt;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
@@ -36,30 +37,33 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
 @Internal
-public final class CertBlacklistImpl implements CertBlacklist {
-    private static final Logger logger = Logger.getLogger(CertBlacklistImpl.class.getName());
+public final class CertBlocklistImpl implements CertBlocklist {
+    private static final Logger logger = Logger.getLogger(CertBlocklistImpl.class.getName());
 
-    private final Set<BigInteger> serialBlacklist;
-    private final Set<ByteString> pubkeyBlacklist;
+    private final Set<BigInteger> serialBlocklist;
+    private final Set<ByteString> pubkeyBlocklist;
 
     /**
      * public for testing only.
      */
-    public CertBlacklistImpl(Set<BigInteger> serialBlacklist, Set<ByteString> pubkeyBlacklist) {
-        this.serialBlacklist = serialBlacklist;
-        this.pubkeyBlacklist = pubkeyBlacklist;
+    public CertBlocklistImpl(Set<BigInteger> serialBlocklist, Set<ByteString> pubkeyBlocklist) {
+        this.serialBlocklist = serialBlocklist;
+        this.pubkeyBlocklist = pubkeyBlocklist;
     }
 
-    public static CertBlacklist getDefault() {
+    public static CertBlocklist getDefault() {
         String androidData = System.getenv("ANDROID_DATA");
-        String blacklistRoot = androidData + "/misc/keychain/";
-        String defaultPubkeyBlacklistPath = blacklistRoot + "pubkey_blacklist.txt";
-        String defaultSerialBlacklistPath = blacklistRoot + "serial_blacklist.txt";
+        String blocklistRoot = androidData + "/misc/keychain/";
+        String defaultPubkeyBlocklistPath = blocklistRoot + "pubkey_blacklist.txt";
+        String defaultSerialBlocklistPath = blocklistRoot + "serial_blacklist.txt";
 
-        Set<ByteString> pubkeyBlacklist = readPublicKeyBlackList(defaultPubkeyBlacklistPath);
-        Set<BigInteger> serialBlacklist = readSerialBlackList(defaultSerialBlacklistPath);
-        return new CertBlacklistImpl(serialBlacklist, pubkeyBlacklist);
+        Set<ByteString> pubkeyBlocklist = readPublicKeyBlockList(defaultPubkeyBlocklistPath);
+        Set<BigInteger> serialBlocklist = readSerialBlockList(defaultSerialBlocklistPath);
+        return new CertBlocklistImpl(serialBlocklist, pubkeyBlocklist);
     }
 
     private static boolean isHex(String value) {
@@ -80,12 +84,13 @@
         return isHex(value);
     }
 
-    private static String readBlacklist(String path) {
+    private static String readBlocklist(String path) {
         try {
             return readFileAsString(path);
         } catch (FileNotFoundException ignored) {
+            // Ignored
         } catch (IOException e) {
-            logger.log(Level.WARNING, "Could not read blacklist", e);
+            logger.log(Level.WARNING, "Could not read blocklist", e);
         }
         return "";
     }
@@ -122,11 +127,12 @@
             } catch (RuntimeException rethrown) {
                 throw rethrown;
             } catch (Exception ignored) {
+                // Ignored
             }
         }
     }
 
-    private static Set<BigInteger> readSerialBlackList(String path) {
+    private static Set<BigInteger> readSerialBlockList(String path) {
 
         /* Start out with a base set of known bad values.
          *
@@ -153,9 +159,9 @@
         ));
 
         // attempt to augment it with values taken from gservices
-        String serialBlacklist = readBlacklist(path);
-        if (!serialBlacklist.equals("")) {
-            for (String value : serialBlacklist.split(",", -1)) {
+        String serialBlocklist = readBlocklist(path);
+        if (!serialBlocklist.equals("")) {
+            for (String value : serialBlocklist.split(",", -1)) {
                 try {
                     bl.add(new BigInteger(value, 16));
                 } catch (NumberFormatException e) {
@@ -168,13 +174,13 @@
         return Collections.unmodifiableSet(bl);
     }
 
-    private static Set<ByteString> readPublicKeyBlackList(String path) {
+    private static Set<ByteString> readPublicKeyBlockList(String path) {
 
         // start out with a base set of known bad values
         Set<ByteString> bl = new HashSet<ByteString>(toByteStrings(
-            // Blacklist test cert for CTS. The cert and key can be found in
-            // src/test/resources/blacklist_test_ca.pem and
-            // src/test/resources/blacklist_test_ca_key.pem.
+            // Blocklist test cert for CTS. The cert and key can be found in
+            // src/test/resources/blocklist_test_ca.pem and
+            // src/test/resources/blocklist_test_ca_key.pem.
             "bae78e6bed65a2bf60ddedde7fd91e825865e93d".getBytes(UTF_8),
             // From http://src.chromium.org/viewvc/chrome/branches/782/src/net/base/x509_certificate.cc?r1=98750&r2=98749&pathrev=98750
             // C=NL, O=DigiNotar, CN=DigiNotar Root CA/emailAddress=info@diginotar.nl
@@ -207,14 +213,14 @@
         ));
 
         // attempt to augment it with values taken from gservices
-        String pubkeyBlacklist = readBlacklist(path);
-        if (!pubkeyBlacklist.equals("")) {
-            for (String value : pubkeyBlacklist.split(",", -1)) {
+        String pubkeyBlocklist = readBlocklist(path);
+        if (!pubkeyBlocklist.equals("")) {
+            for (String value : pubkeyBlocklist.split(",", -1)) {
                 value = value.trim();
                 if (isPubkeyHash(value)) {
                     bl.add(new ByteString(value.getBytes(UTF_8)));
                 } else {
-                    logger.log(Level.WARNING, "Tried to blacklist invalid pubkey " + value);
+                    logger.log(Level.WARNING, "Tried to blocklist invalid pubkey " + value);
                 }
             }
         }
@@ -223,7 +229,7 @@
     }
 
     @Override
-    public boolean isPublicKeyBlackListed(PublicKey publicKey) {
+    public boolean isPublicKeyBlockListed(PublicKey publicKey) {
         byte[] encoded = publicKey.getEncoded();
         MessageDigest md;
         try {
@@ -233,8 +239,8 @@
             return false;
         }
         byte[] out = toHex(md.digest(encoded));
-        for (ByteString blacklisted : pubkeyBlacklist) {
-            if (Arrays.equals(blacklisted.bytes, out)) {
+        for (ByteString blocklisted : pubkeyBlocklist) {
+            if (Arrays.equals(blocklisted.bytes, out)) {
                 return true;
             }
         }
@@ -257,8 +263,8 @@
     }
 
     @Override
-    public boolean isSerialNumberBlackListed(BigInteger serial) {
-        return serialBlacklist.contains(serial);
+    public boolean isSerialNumberBlockListed(BigInteger serial) {
+        return serialBlocklist.contains(serial);
     }
 
     private static List<ByteString> toByteStrings(byte[]... allBytes) {
diff --git a/repackaged/platform/src/main/java/com/android/org/conscrypt/Platform.java b/repackaged/platform/src/main/java/com/android/org/conscrypt/Platform.java
index 573bbb2..8f9b11e 100644
--- a/repackaged/platform/src/main/java/com/android/org/conscrypt/Platform.java
+++ b/repackaged/platform/src/main/java/com/android/org/conscrypt/Platform.java
@@ -27,10 +27,14 @@
 import com.android.org.conscrypt.ct.CTLogStoreImpl;
 import com.android.org.conscrypt.ct.CTPolicy;
 import com.android.org.conscrypt.ct.CTPolicyImpl;
+import com.android.org.conscrypt.metrics.CipherSuite;
+import com.android.org.conscrypt.metrics.ConscryptStatsLog;
+import com.android.org.conscrypt.metrics.Protocol;
 import dalvik.system.BlockGuard;
 import dalvik.system.CloseGuard;
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.lang.System;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -53,6 +57,7 @@
 import java.util.Collections;
 import java.util.List;
 import javax.crypto.spec.GCMParameterSpec;
+import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SNIHostName;
 import javax.net.ssl.SNIMatcher;
 import javax.net.ssl.SNIServerName;
@@ -504,8 +509,8 @@
         return new TrustedCertificateStore();
     }
 
-    static CertBlacklist newDefaultBlacklist() {
-        return CertBlacklistImpl.getDefault();
+    static CertBlocklist newDefaultBlocklist() {
+        return CertBlocklistImpl.getDefault();
     }
 
     static CTLogStore newDefaultLogStore() {
@@ -531,4 +536,30 @@
         }
         return false;
     }
+
+    public static ConscryptHostnameVerifier getDefaultHostnameVerifier() {
+        return Conscrypt.wrapHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier());
+    }
+
+    /**
+     * Returns milliseconds elapsed since boot, including time spent in sleep.
+     * @return long number of milliseconds elapsed since boot
+     */
+    static long getMillisSinceBoot() {
+        return System.currentTimeMillis();
+    }
+
+    static void countTlsHandshake(
+            boolean success, String protocol, String cipherSuite, long duration) {
+        Protocol proto = Protocol.forName(protocol);
+        CipherSuite suite = CipherSuite.forName(cipherSuite);
+        int dur = (int) duration;
+
+        ConscryptStatsLog.write(ConscryptStatsLog.TLS_HANDSHAKE_REPORTED, success, proto.getId(),
+                suite.getId(), dur);
+    }
+
+    public static boolean isJavaxCertificateSupported() {
+        return true;
+    }
 }
diff --git a/repackaged/platform/src/main/java/com/android/org/conscrypt/TrustedCertificateStore.java b/repackaged/platform/src/main/java/com/android/org/conscrypt/TrustedCertificateStore.java
index ba233be..16128b9 100644
--- a/repackaged/platform/src/main/java/com/android/org/conscrypt/TrustedCertificateStore.java
+++ b/repackaged/platform/src/main/java/com/android/org/conscrypt/TrustedCertificateStore.java
@@ -60,9 +60,8 @@
  * <p>In addition to supporting the {@code
  * TrustedCertificateKeyStoreSpi} implementation, {@code
  * TrustedCertificateStore} also provides the additional public
- * methods {@link #isTrustAnchor} and {@link #findIssuer} to allow
- * efficient lookup operations for CAs again based on the file naming
- * convention.
+ * method  {@link #findIssuer} to allow  efficient lookup operations
+ * for CAs again based on the file naming convention.
  *
  * <p>The KeyChainService users the {@link installCertificate} and
  * {@link #deleteCertificateEntry} to install user CAs as well as
@@ -82,17 +81,16 @@
  * user.
  * @hide This class is not part of the Android public SDK API
  */
-@libcore.api.CorePlatformApi
+@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
 @Internal
 public class TrustedCertificateStore implements ConscryptCertStore {
-
     private static final String PREFIX_SYSTEM = "system:";
     private static final String PREFIX_USER = "user:";
 
     public static final boolean isSystem(String alias) {
         return alias.startsWith(PREFIX_SYSTEM);
     }
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public static final boolean isUser(String alias) {
         return alias.startsWith(PREFIX_USER);
     }
@@ -119,7 +117,7 @@
         }
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public static void setDefaultUserDirectory(File root) {
         PreloadHolder.defaultCaCertsAddedDir = new File(root, "cacerts-added");
         PreloadHolder.defaultCaCertsDeletedDir = new File(root, "cacerts-removed");
@@ -131,7 +129,7 @@
 
     @android.compat.annotation
             .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi
+            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
             public TrustedCertificateStore() {
         this(PreloadHolder.defaultCaCertsSystemDir, PreloadHolder.defaultCaCertsAddedDir,
                 PreloadHolder.defaultCaCertsDeletedDir);
@@ -143,14 +141,13 @@
         this.deletedDir = deletedDir;
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public Certificate getCertificate(String alias) {
         return getCertificate(alias, false);
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public Certificate getCertificate(String alias, boolean includeDeletedSystem) {
-
         File file = fileForAlias(alias);
         if (file == null || (isUser(alias) && isTombstone(file))) {
             return null;
@@ -227,7 +224,8 @@
         return getCertificateFile(deletedDir, x).exists();
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+    @SuppressWarnings("JdkObsolete") // Used in public API TrustedCertificateKeyStoreSpi
     public Date getCreationDate(String alias) {
         // containsAlias check ensures the later fileForAlias result
         // was not a deleted system cert.
@@ -245,7 +243,7 @@
         return new Date(time);
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public Set<String> aliases() {
         Set<String> result = new HashSet<String>();
         addAliases(result, PREFIX_USER, addedDir);
@@ -253,7 +251,7 @@
         return result;
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public Set<String> userAliases() {
         Set<String> result = new HashSet<String>();
         addAliases(result, PREFIX_USER, addedDir);
@@ -273,7 +271,7 @@
         }
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public Set<String> allSystemAliases() {
         Set<String> result = new HashSet<String>();
         String[] files = systemDir.list();
@@ -289,7 +287,7 @@
         return result;
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public boolean containsAlias(String alias) {
         return containsAlias(alias, false);
     }
@@ -298,12 +296,12 @@
         return getCertificate(alias, includeDeletedSystem) != null;
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public String getCertificateAlias(Certificate c) {
         return getCertificateAlias(c, false);
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public String getCertificateAlias(Certificate c, boolean includeDeletedSystem) {
         if (c == null || !(c instanceof X509Certificate)) {
             return null;
@@ -327,7 +325,7 @@
      * Returns true to indicate that the certificate was added by the
      * user, false otherwise.
      */
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public boolean isUserAddedCertificate(X509Certificate cert) {
         return getCertificateFile(addedDir, cert).exists();
     }
@@ -340,7 +338,7 @@
      *
      * @VisibleForTesting
      */
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public File getCertificateFile(File dir, final X509Certificate x) {
         // compare X509Certificate.getEncoded values
         CertSelector selector = new CertSelector() {
@@ -361,7 +359,7 @@
      * with other differences (for example when switching signature
      * from md2WithRSAEncryption to SHA1withRSA)
      */
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     @Override
     public X509Certificate getTrustAnchor(final X509Certificate c) {
         // compare X509Certificate.getPublicKey values
@@ -393,7 +391,7 @@
      * TrustManagerImpl} to locate the CA certificate that signed the
      * provided {@code X509Certificate}.
      */
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public X509Certificate findIssuer(final X509Certificate c) {
         // match on verified issuer of Certificate
         CertSelector selector = new CertSelector() {
@@ -419,7 +417,7 @@
         return null;
     }
 
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     @Override
     public Set<X509Certificate> findAllIssuers(final X509Certificate c) {
         Set<X509Certificate> issuers = null;
@@ -502,7 +500,7 @@
      */
     @android.compat.annotation
             .UnsupportedAppUsage
-            @libcore.api.CorePlatformApi
+            @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
             public List<X509Certificate> getCertificateChain(X509Certificate leaf)
             throws CertificateException {
         final LinkedHashSet<OpenSSLX509Certificate> chain
@@ -600,7 +598,7 @@
      * silently ignores the certificate if it already exists in the
      * store.
      */
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public void installCertificate(X509Certificate cert) throws IOException, CertificateException {
         if (cert == null) {
             throw new NullPointerException("cert == null");
@@ -636,7 +634,7 @@
      * only. Instead, this is used by the {@code KeyChainService} to
      * delete CA certificates.
      */
-    @libcore.api.CorePlatformApi
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
     public void deleteCertificateEntry(String alias) throws IOException, CertificateException {
         if (alias == null) {
             return;
diff --git a/repackaged/platform/src/main/java/com/android/org/conscrypt/ct/CTLogStoreImpl.java b/repackaged/platform/src/main/java/com/android/org/conscrypt/ct/CTLogStoreImpl.java
index 37c84c8..d34b683 100644
--- a/repackaged/platform/src/main/java/com/android/org/conscrypt/ct/CTLogStoreImpl.java
+++ b/repackaged/platform/src/main/java/com/android/org/conscrypt/ct/CTLogStoreImpl.java
@@ -17,6 +17,8 @@
 
 package com.android.org.conscrypt.ct;
 
+import com.android.org.conscrypt.Internal;
+import com.android.org.conscrypt.InternalUtil;
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
@@ -24,6 +26,7 @@
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
@@ -33,15 +36,13 @@
 import java.util.HashSet;
 import java.util.Scanner;
 import java.util.Set;
-import com.android.org.conscrypt.Internal;
-import com.android.org.conscrypt.InternalUtil;
 
 /**
  * @hide This class is not part of the Android public SDK API
  */
 @Internal
 public class CTLogStoreImpl implements CTLogStore {
-    private static final Charset US_ASCII = Charset.forName("US-ASCII");
+    private static final Charset US_ASCII = StandardCharsets.US_ASCII;
 
     /**
      * Thrown when parsing of a log file fails.
@@ -75,12 +76,13 @@
         defaultSystemLogDir = new File(ANDROID_ROOT + "/etc/security/ct_known_logs/");
     }
 
-    private File userLogDir;
-    private File systemLogDir;
-    private CTLogInfo[] fallbackLogs;
+    private final File userLogDir;
+    private final File systemLogDir;
+    private final CTLogInfo[] fallbackLogs;
 
-    private HashMap<ByteBuffer, CTLogInfo> logCache = new HashMap<>();
-    private Set<ByteBuffer> missingLogCache = Collections.synchronizedSet(new HashSet<ByteBuffer>());
+    private final HashMap<ByteBuffer, CTLogInfo> logCache = new HashMap<>();
+    private final Set<ByteBuffer> missingLogCache =
+            Collections.synchronizedSet(new HashSet<ByteBuffer>());
 
     public CTLogStoreImpl() {
         this(defaultUserLogDir,
@@ -121,13 +123,17 @@
             return loadLog(new File(userLogDir, filename));
         } catch (InvalidLogFileException e) {
             return null;
-        } catch (FileNotFoundException e) {}
+        } catch (FileNotFoundException e) {
+            // Ignored
+        }
 
         try {
             return loadLog(new File(systemLogDir, filename));
         } catch (InvalidLogFileException e) {
             return null;
-        } catch (FileNotFoundException e) {}
+        } catch (FileNotFoundException e) {
+            // Ignored
+        }
 
         // If the updateable logs dont exist then use the fallback logs.
         if (!userLogDir.exists()) {
diff --git a/repackaged/platform/src/test/java/com/android/org/conscrypt/CertBlacklistTest.java b/repackaged/platform/src/test/java/com/android/org/conscrypt/CertBlacklistTest.java
deleted file mode 100644
index b45e2d3..0000000
--- a/repackaged/platform/src/test/java/com/android/org/conscrypt/CertBlacklistTest.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/* GENERATED SOURCE. DO NOT MODIFY. */
-/*
- * Copyright (C) 2016 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.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.InputStream;
-import java.security.KeyStore;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Collection;
-import javax.net.ssl.X509TrustManager;
-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 CertBlacklistTest {
-    private static final String BLACKLIST_CA = "test_blacklist_ca.pem";
-    private static final String BLACKLISTED_CHAIN = "blacklist_test_chain.pem";
-    private static final String BLACKLIST_FALLBACK_VALID_CA = "blacklist_test_valid_ca.pem";
-    private static final String BLACKLISTED_VALID_CHAIN = "blacklist_test_valid_chain.pem";
-
-    /**
-     * Ensure that the test blacklisted CA is actually blacklisted by default.
-     */
-    @Test
-    public void testBlacklistedPublicKey() throws Exception {
-        // This class was renamed in the Android 12 based Conscrypt module.
-        // If such a module is installed, simply skip the test as it is covered by MTS
-        TestUtils.assumeBeforeAndroid12Mainline();
-        X509Certificate blacklistedCa = loadCertificate(BLACKLIST_CA);
-        CertBlacklist blacklist = CertBlacklistImpl.getDefault();
-        assertTrue(blacklist.isPublicKeyBlackListed(blacklistedCa.getPublicKey()));
-    }
-
-    /**
-     * Check that the blacklisted CA is rejected even if it used as a root of trust
-     */
-    @Test
-    public void testBlacklistedCaUntrusted() throws Exception {
-        X509Certificate blacklistedCa = loadCertificate(BLACKLIST_CA);
-        assertUntrusted(new X509Certificate[] {blacklistedCa}, getTrustManager(blacklistedCa));
-    }
-
-    /**
-     * Check that a chain that is rooted in a blacklisted trusted CA is rejected.
-     */
-    @Test
-    public void testBlacklistedRootOfTrust() throws Exception {
-        // Chain is leaf -> blacklisted
-        X509Certificate[] chain = loadCertificates(BLACKLISTED_CHAIN);
-        X509Certificate blacklistedCa = loadCertificate(BLACKLIST_CA);
-        assertUntrusted(chain, getTrustManager(blacklistedCa));
-    }
-
-    /** Test that the path building correctly routes around a blacklisted cert where there are
-     * other valid paths available. This prevents breakage where a cert was cross signed by a
-     * blacklisted CA but is still valid due to also being cross signed by CAs that remain trusted.
-     * Path:
-     *
-     * leaf -> intermediate -> blacklisted_ca
-     *               \
-     *                -------> trusted_ca
-     */
-    @Test
-    public void testBlacklistedIntermediateFallback() throws Exception {
-        X509Certificate[] chain = loadCertificates(BLACKLISTED_VALID_CHAIN);
-        X509Certificate blacklistedCa = loadCertificate(BLACKLIST_CA);
-        X509Certificate validCa = loadCertificate(BLACKLIST_FALLBACK_VALID_CA);
-        assertTrusted(chain, getTrustManager(blacklistedCa, validCa));
-        // Check that without the trusted_ca the chain is invalid (since it only chains to a
-        // blacklisted ca)
-        assertUntrusted(chain, getTrustManager(blacklistedCa));
-    }
-
-    private static X509Certificate loadCertificate(String file) throws Exception {
-        return loadCertificates(file)[0];
-    }
-
-    private static X509Certificate[] loadCertificates(String file) throws Exception {
-        CertificateFactory factory = CertificateFactory.getInstance("X.509");
-        try (InputStream is = TestUtils.openTestFile(file)) {
-            Collection<? extends Certificate> collection = factory.generateCertificates(is);
-            is.close();
-            X509Certificate[] certs = new X509Certificate[collection.size()];
-            int i = 0;
-            for (Certificate cert : collection) {
-                certs[i++] = (X509Certificate) cert;
-            }
-            return certs;
-        }
-    }
-
-    private static TrustManagerImpl getTrustManager(X509Certificate... trustedCas)
-            throws Exception {
-        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
-        ks.load(null);
-        int i = 0;
-        for (X509Certificate ca : trustedCas) {
-            ks.setCertificateEntry(String.valueOf(i++), ca);
-        }
-        return new TrustManagerImpl(ks);
-    }
-
-    private static void assertTrusted(X509Certificate[] certs, X509TrustManager tm)
-            throws Exception {
-        tm.checkServerTrusted(certs, "RSA");
-    }
-
-    private static void assertUntrusted(X509Certificate[] certs, X509TrustManager tm) {
-        try {
-            tm.checkServerTrusted(certs, "RSA");
-            fail();
-        } catch (CertificateException expected) {
-        }
-    }
-}
diff --git a/repackaged/platform/src/test/java/com/android/org/conscrypt/CertBlocklistTest.java b/repackaged/platform/src/test/java/com/android/org/conscrypt/CertBlocklistTest.java
new file mode 100644
index 0000000..c970f22
--- /dev/null
+++ b/repackaged/platform/src/test/java/com/android/org/conscrypt/CertBlocklistTest.java
@@ -0,0 +1,127 @@
+/* GENERATED SOURCE. DO NOT MODIFY. */
+/*
+ * Copyright (C) 2016 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 java.io.InputStream;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import javax.net.ssl.X509TrustManager;
+import junit.framework.TestCase;
+
+/**
+ * @hide This class is not part of the Android public SDK API
+ */
+public class CertBlocklistTest extends TestCase {
+
+    private static final String BLOCKLIST_CA = "test_blocklist_ca.pem";
+    private static final String BLOCKLISTED_CHAIN = "blocklist_test_chain.pem";
+    private static final String BLOCKLIST_FALLBACK_VALID_CA = "blocklist_test_valid_ca.pem";
+    private static final String BLOCKLISTED_VALID_CHAIN = "blocklist_test_valid_chain.pem";
+
+    /**
+     * Ensure that the test blocklisted CA is actually blocklisted by default.
+     */
+    public void testBlocklistedPublicKey() throws Exception {
+        X509Certificate blocklistedCa = loadCertificate(BLOCKLIST_CA);
+        CertBlocklist blocklist = CertBlocklistImpl.getDefault();
+        assertTrue(blocklist.isPublicKeyBlockListed(blocklistedCa.getPublicKey()));
+    }
+
+    /**
+     * Check that the blocklisted CA is rejected even if it used as a root of trust
+     */
+    public void testBlocklistedCaUntrusted() throws Exception {
+        X509Certificate blocklistedCa = loadCertificate(BLOCKLIST_CA);
+        assertUntrusted(new X509Certificate[] {blocklistedCa}, getTrustManager(blocklistedCa));
+    }
+
+    /**
+     * Check that a chain that is rooted in a blocklisted trusted CA is rejected.
+     */
+    public void testBlocklistedRootOfTrust() throws Exception {
+        // Chain is leaf -> blocklisted
+        X509Certificate[] chain = loadCertificates(BLOCKLISTED_CHAIN);
+        X509Certificate blocklistedCa = loadCertificate(BLOCKLIST_CA);
+        assertUntrusted(chain, getTrustManager(blocklistedCa));
+    }
+
+    /** Test that the path building correctly routes around a blocklisted cert where there are
+     * other valid paths available. This prevents breakage where a cert was cross signed by a
+     * blocklisted CA but is still valid due to also being cross signed by CAs that remain trusted.
+     * Path:
+     *
+     * leaf -> intermediate -> blocklisted_ca
+     *               \
+     *                -------> trusted_ca
+     */
+    public void testBlocklistedIntermediateFallback() throws Exception {
+        X509Certificate[] chain = loadCertificates(BLOCKLISTED_VALID_CHAIN);
+        X509Certificate blocklistedCa = loadCertificate(BLOCKLIST_CA);
+        X509Certificate validCa = loadCertificate(BLOCKLIST_FALLBACK_VALID_CA);
+        assertTrusted(chain, getTrustManager(blocklistedCa, validCa));
+        // Check that without the trusted_ca the chain is invalid (since it only chains to a
+        // blocklisted ca)
+        assertUntrusted(chain, getTrustManager(blocklistedCa));
+    }
+
+    private static X509Certificate loadCertificate(String file) throws Exception {
+        return loadCertificates(file)[0];
+    }
+
+    private static X509Certificate[] loadCertificates(String file) throws Exception {
+        CertificateFactory factory = CertificateFactory.getInstance("X.509");
+        try (InputStream is = TestUtils.openTestFile(file)) {
+            Collection<? extends Certificate> collection = factory.generateCertificates(is);
+            is.close();
+            X509Certificate[] certs = new X509Certificate[collection.size()];
+            int i = 0;
+            for (Certificate cert : collection) {
+                certs[i++] = (X509Certificate) cert;
+            }
+            return certs;
+        }
+    }
+
+    private static TrustManagerImpl getTrustManager(X509Certificate... trustedCas)
+            throws Exception {
+        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+        ks.load(null);
+        int i = 0;
+        for (X509Certificate ca : trustedCas) {
+            ks.setCertificateEntry(String.valueOf(i++), ca);
+        }
+        return new TrustManagerImpl(ks);
+    }
+
+    private static void assertTrusted(X509Certificate[] certs, X509TrustManager tm)
+            throws Exception {
+        tm.checkServerTrusted(certs, "RSA");
+    }
+
+    private static void assertUntrusted(X509Certificate[] certs, X509TrustManager tm) {
+        try {
+            tm.checkServerTrusted(certs, "RSA");
+            fail();
+        } catch (CertificateException expected) {
+        }
+    }
+}
diff --git a/repackaged/platform/src/test/java/com/android/org/conscrypt/TrustedCertificateStoreTest.java b/repackaged/platform/src/test/java/com/android/org/conscrypt/TrustedCertificateStoreTest.java
index 5dbc8eb..40c136b 100644
--- a/repackaged/platform/src/test/java/com/android/org/conscrypt/TrustedCertificateStoreTest.java
+++ b/repackaged/platform/src/test/java/com/android/org/conscrypt/TrustedCertificateStoreTest.java
@@ -882,6 +882,7 @@
         assertEquals(x, store.findIssuer(x));
     }
 
+    @SuppressWarnings("JdkObsolete") // Date is used in public API TrustedCertificateKeyStoreSpi
     private void assertTrusted(X509Certificate x, String alias) {
         assertEquals(x, store.getCertificate(alias));
         assertEquals(file(alias).lastModified(), store.getCreationDate(alias).getTime());
diff --git a/repackaged/platform/src/test/java/com/android/org/conscrypt/metrics/MetricsTest.java b/repackaged/platform/src/test/java/com/android/org/conscrypt/metrics/MetricsTest.java
new file mode 100644
index 0000000..2f053fe
--- /dev/null
+++ b/repackaged/platform/src/test/java/com/android/org/conscrypt/metrics/MetricsTest.java
@@ -0,0 +1,85 @@
+/* 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.metrics;
+
+import static org.junit.Assert.assertEquals;
+
+import android.util.StatsEvent;
+import com.android.org.conscrypt.TestUtils;
+import org.junit.Ignore;
+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 MetricsTest {
+    public static final int TLS_HANDSHAKE_REPORTED = 317;
+
+    // Tests that ReflexiveEvent produces the same event as framework's.
+    @Test
+    @Ignore // Ignore on CTS 12 only: b/259508875
+    public void test_reflexiveEvent() throws Exception {
+        TestUtils.assumeStatsLogAvailable();
+
+        StatsEvent frameworkStatsEvent = StatsEvent.newBuilder()
+                                                 .setAtomId(TLS_HANDSHAKE_REPORTED)
+                                                 .writeBoolean(false)
+                                                 .writeInt(1) // protocol
+                                                 .writeInt(2) // cipher suite
+                                                 .writeInt(100) // duration
+                                                 .usePooledBuffer()
+                                                 .build();
+
+        ReflexiveStatsEvent reflexiveStatsEvent =
+                ReflexiveStatsEvent.buildEvent(TLS_HANDSHAKE_REPORTED, false, 1, 2, 100);
+        StatsEvent constructedEvent = (StatsEvent) reflexiveStatsEvent.getStatsEvent();
+
+        // TODO(nikitai): Figure out how to use hidden (@hide) getters from StatsEvent
+        // to eliminate the use of reflection
+        int fid = (Integer) frameworkStatsEvent.getClass()
+                          .getMethod("getAtomId")
+                          .invoke(frameworkStatsEvent);
+        int cid = (Integer) constructedEvent.getClass()
+                          .getMethod("getAtomId")
+                          .invoke(constructedEvent);
+        assertEquals(fid, cid);
+
+        int fnb = (Integer) frameworkStatsEvent.getClass()
+                          .getMethod("getNumBytes")
+                          .invoke(frameworkStatsEvent);
+        int cnb = (Integer) constructedEvent.getClass()
+                          .getMethod("getNumBytes")
+                          .invoke(constructedEvent);
+        assertEquals(fnb, cnb);
+
+        byte[] fbytes = (byte[]) frameworkStatsEvent.getClass()
+                                .getMethod("getBytes")
+                                .invoke(frameworkStatsEvent);
+        byte[] cbytes =
+                (byte[]) constructedEvent.getClass().getMethod("getBytes").invoke(constructedEvent);
+        for (int i = 0; i < fnb; i++) {
+            // skip encoded timestamp (bytes 1-8)
+            if (i < 1 || i > 8) {
+                assertEquals(fbytes[i], cbytes[i]);
+            }
+        }
+    }
+}
diff --git a/repackaged/testing/src/main/java/com/android/org/conscrypt/TestUtils.java b/repackaged/testing/src/main/java/com/android/org/conscrypt/TestUtils.java
index 11b757e..fedb2e4 100644
--- a/repackaged/testing/src/main/java/com/android/org/conscrypt/TestUtils.java
+++ b/repackaged/testing/src/main/java/com/android/org/conscrypt/TestUtils.java
@@ -24,9 +24,11 @@
 import com.android.org.conscrypt.java.security.StandardNames;
 import com.android.org.conscrypt.java.security.TestKeyStore;
 import com.android.org.conscrypt.testing.Streams;
+import java.io.BufferedReader;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.net.InetAddress;
@@ -48,11 +50,10 @@
 import java.util.Base64;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
+import java.util.Random;
 import java.util.Set;
-import java.util.function.IntFunction;
 import java.util.function.Predicate;
-
-import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLEngineResult;
@@ -83,6 +84,44 @@
 
     static final String TEST_CIPHER = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
 
+    /**
+     * @hide This class is not part of the Android public SDK API
+     */
+    public enum BufferType {
+        HEAP {
+            @Override
+            ByteBuffer newBuffer(int size) {
+                return ByteBuffer.allocate(size);
+            }
+        },
+        DIRECT {
+            @Override
+            ByteBuffer newBuffer(int size) {
+                return ByteBuffer.allocateDirect(size);
+            }
+        };
+        private static final Random random = new Random(System.currentTimeMillis());
+        abstract ByteBuffer newBuffer(int size);
+
+        public ByteBuffer[] newRandomBuffers(int... sizes) {
+            int numBuffers = sizes.length;
+            ByteBuffer[] result = new ByteBuffer[numBuffers];
+            for (int i = 0; i < numBuffers; i++) {
+                result[i] = newRandomBuffer(sizes[i]);
+            }
+            return result;
+        }
+
+        public ByteBuffer newRandomBuffer(int size) {
+            byte[] data = new byte[size];
+            random.nextBytes(data);
+            ByteBuffer buffer = newBuffer(size);
+            buffer.put(data);
+            buffer.flip();
+            return buffer;
+        }
+    }
+
     private TestUtils() {}
 
     private static Provider getNonConscryptTlsProvider() {
@@ -128,6 +167,10 @@
         assumeClassAvailable("javax.net.ssl.X509ExtendedTrustManager");
     }
 
+    public static void assumeStatsLogAvailable() {
+        assumeClassAvailable("android.util.StatsEvent");
+    }
+
     public static void assumeSetEndpointIdentificationAlgorithmAvailable() {
         boolean supported = false;
         try {
@@ -154,21 +197,10 @@
         }
     }
 
-    // Is a pre-Android 12 mainline module installed.  Detect based on a class that
-    // was renamed in the Android 12 codebase.
-    private static boolean isBeforeAndroid12Mainline() {
-        return isClassAvailable("com.android.org.conscrypt.CertBlacklistImpl");
-    }
-
     public static void assumeAndroid() {
         Assume.assumeTrue(isAndroid());
     }
 
-    public static void assumeBeforeAndroid12Mainline() {
-        Assume.assumeTrue(isAndroid() && isBeforeAndroid12Mainline());
-    }
-
-    // Assume a pre-Android 12 mainline module is installed.
     public static void assumeAllowsUnsignedCrypto() {
         // The Oracle JRE disallows loading crypto providers from unsigned jars
         Assume.assumeTrue(isAndroid()
@@ -186,18 +218,6 @@
         Assume.assumeTrue("SHA2 with DSA signatures not available", available);
     }
 
-    private static Method findMethod(Class<?> cls, String methodName, Class<?>... methodParams) {
-        try {
-            return cls.getDeclaredMethod(methodName, methodParams);
-        } catch (NoSuchMethodException e) {
-            return null;
-        }
-    }
-
-    public static Method findWrapVerifierMethod() {
-        return findMethod(Conscrypt.class, "wrapHostnameVerifier", HostnameVerifier.class);
-    }
-
     public static InetAddress getLoopbackAddress() {
         try {
             Method method = InetAddress.class.getMethod("getLoopbackAddress");
@@ -253,7 +273,7 @@
 
     public static PublicKey readPublicKeyPemFile(String name)
             throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
-        String keyData = new String(readTestFile(name), "US-ASCII");
+        String keyData = new String(readTestFile(name), StandardCharsets.US_ASCII);
         keyData = keyData.replace("-----BEGIN PUBLIC KEY-----", "");
         keyData = keyData.replace("-----END PUBLIC KEY-----", "");
         keyData = keyData.replace("\r", "");
@@ -262,6 +282,22 @@
                 new X509EncodedKeySpec(decodeBase64(keyData)));
     }
 
+    public static List<String[]> readCsvResource(String resourceName) throws IOException {
+        InputStream stream = openTestFile(resourceName);
+        List<String[]> lines = new ArrayList<>();
+        try (BufferedReader reader =
+                        new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (line.isEmpty() || line.startsWith("#")) {
+                    continue;
+                }
+                lines.add(line.split(",", -1));
+            }
+        }
+        return lines;
+    }
+
     /**
      * Looks up the conscrypt class for the given simple name (i.e. no package prefix).
      */
@@ -314,24 +350,6 @@
         }
     }
 
-    public static SSLSocketFactory getConscryptSocketFactory(boolean useEngineSocket) {
-        return setUseEngineSocket(getSocketFactory(getConscryptProvider()), useEngineSocket);
-    }
-
-    public static SSLServerSocketFactory getConscryptServerSocketFactory(boolean useEngineSocket) {
-        return setUseEngineSocket(getServerSocketFactory(getConscryptProvider()), useEngineSocket);
-    }
-
-    private static SSLSocketFactory getSocketFactory(Provider provider) {
-        SSLContext clientContext = initClientSslContext(newContext(provider));
-        return clientContext.getSocketFactory();
-    }
-
-    private static SSLServerSocketFactory getServerSocketFactory(Provider provider) {
-        SSLContext serverContext = initServerSslContext(newContext(provider));
-        return serverContext.getServerSocketFactory();
-    }
-
     static SSLContext newContext(Provider provider) {
         try {
             return SSLContext.getInstance("TLS", provider);
@@ -350,27 +368,19 @@
         SSLContext jdkContext = newClientSslContext(getJdkProvider());
         SSLContext conscryptContext = newClientSslContext(getConscryptProvider());
         // No point building a Set here due to small list sizes.
-        final List<String> conscryptProtocols = getSupportedProtocols(conscryptContext);
-        // TODO(prb): Certificate auth fails when connecting Conscrypt and JDK's TLS 1.3.
-        Predicate<String> predicate = new Predicate<String>() {
-            @Override
-            public boolean test(String string) {
-              return conscryptProtocols.contains(string) && !string.equals(PROTOCOL_TLS_V1_3);
-            }
-        };
+        List<String> conscryptProtocols = getSupportedProtocols(conscryptContext);
+        Predicate<String> predicate = p
+                -> conscryptProtocols.contains(p)
+                // TODO(prb): Certificate auth fails when connecting Conscrypt and JDK's TLS 1.3.
+                && !p.equals(PROTOCOL_TLS_V1_3);
         return getSupportedProtocols(jdkContext, predicate);
     }
 
     public static String[] getCommonCipherSuites() {
         SSLContext jdkContext = newClientSslContext(getJdkProvider());
         SSLContext conscryptContext = newClientSslContext(getConscryptProvider());
-        final Set<String> conscryptCiphers = new HashSet<>(getSupportedCiphers(conscryptContext));
-        Predicate<String> predicate = new Predicate<String>() {
-            @Override
-            public boolean test(String string) {
-              return isTlsCipherSuite(string) && conscryptCiphers.contains(string);
-            }
-        };
+        Set<String> conscryptCiphers = new HashSet<>(getSupportedCiphers(conscryptContext));
+        Predicate<String> predicate = c -> isTlsCipherSuite(c) && conscryptCiphers.contains(c);
         return getSupportedCiphers(jdkContext, predicate);
     }
 
@@ -379,15 +389,9 @@
     }
 
     public static String[] getSupportedCiphers(SSLContext ctx, Predicate<String> predicate) {
-        IntFunction<String[]> transform = new IntFunction<String[]>() {
-            @Override
-            public String[] apply(int value) {
-                return new String[value];
-            }
-        };
         return Arrays.stream(ctx.getDefaultSSLParameters().getCipherSuites())
                 .filter(predicate)
-                .toArray(transform);
+                .toArray(String[] ::new);
     }
 
     public static List<String> getSupportedProtocols(SSLContext ctx) {
@@ -395,15 +399,9 @@
     }
 
     public static String[] getSupportedProtocols(SSLContext ctx, Predicate<String> predicate) {
-        IntFunction<String[]> transform = new IntFunction<String[]>() {
-            @Override
-            public String[] apply(int value) {
-                return new String[value];
-            }
-        };
         return Arrays.stream(ctx.getDefaultSSLParameters().getProtocols())
                 .filter(predicate)
-                .toArray(transform);
+                .toArray(String[] ::new);
     }
 
     private static boolean isTlsCipherSuite(String cipher) {
@@ -483,7 +481,7 @@
     }
 
     /**
-     * Performs the intial TLS handshake between the two {@link SSLEngine} instances.
+     * Performs the initial TLS handshake between the two {@link SSLEngine} instances.
      */
     public static void doEngineHandshake(SSLEngine clientEngine, SSLEngine serverEngine,
         ByteBuffer clientAppBuffer, ByteBuffer clientPacketBuffer, ByteBuffer serverAppBuffer,
@@ -654,7 +652,6 @@
         return result;
     }
 
-
     private static int toDigit(char[] str, int offset) throws IllegalArgumentException {
         // NOTE: that this isn't really a code point in the traditional sense, since we're
         // just rejecting surrogate pairs outright.
@@ -672,6 +669,18 @@
                 " at offset " + offset);
     }
 
+    private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
+
+    public static String encodeHex(byte[] data) {
+        char[] hex = new char[data.length * 2];
+        for (int i = 0; i < data.length; i++) {
+            int value = data[i] & 0xff;
+            hex[2 * i] = HEX_CHARS[value >>> 4];
+            hex[2 * i + 1] = HEX_CHARS[value & 0x0f];
+        }
+        return new String(hex);
+    }
+
     private static final String BASE64_ALPHABET =
             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
@@ -757,4 +766,21 @@
     public static void assumeJava8() {
         Assume.assumeTrue("Require Java 8: " + javaVersion(), isJavaVersion(8));
     }
+
+    public static String osName() {
+        return System.getProperty("os.name").toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", "");
+    }
+
+    public static boolean isLinux() {
+        return osName().startsWith("linux");
+    }
+
+    public static boolean isWindows() {
+        return osName().startsWith("windows");
+    }
+
+    public static boolean isOsx() {
+        String name = osName();
+        return name.startsWith("macosx") || name.startsWith("osx");
+    }
 }
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 c66e210..8a751aa 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
@@ -38,8 +38,7 @@
     private final Class<PublicKeySpec> publicKeySpecClass;
     private final Class<PrivateKeySpec> privateKeySpecClass;
 
-    public AbstractKeyFactoryTest(String algorithmName,
-            Class<PublicKeySpec> publicKeySpecClass,
+    protected AbstractKeyFactoryTest(String algorithmName, Class<PublicKeySpec> publicKeySpecClass,
             Class<PrivateKeySpec> privateKeySpecClass) {
         this.algorithmName = algorithmName;
         this.publicKeySpecClass = publicKeySpecClass;
diff --git a/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/AbstractKeyPairGeneratorTest.java b/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/AbstractKeyPairGeneratorTest.java
index f9ea7cf..105bd48 100644
--- a/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/AbstractKeyPairGeneratorTest.java
+++ b/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/AbstractKeyPairGeneratorTest.java
@@ -44,9 +44,13 @@
         generator = KeyPairGenerator.getInstance(algorithmName);
     }
 
+    protected int getKeySize() {
+        return 1024;
+    }
+
     @Test
     public void testKeyPairGenerator() throws Exception {
-        generator.initialize(1024);
+        generator.initialize(getKeySize());
 
         KeyPair keyPair = generator.generateKeyPair();
 
diff --git a/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/CipherHelper.java b/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/CipherHelper.java
index 73f59f7..29eb63a 100644
--- a/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/CipherHelper.java
+++ b/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/CipherHelper.java
@@ -19,6 +19,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import java.nio.charset.StandardCharsets;
 import java.security.Key;
 import javax.crypto.Cipher;
 
@@ -32,7 +33,7 @@
     private final int mode1;
     private final int mode2;
 
-    public CipherHelper(String algorithmName, String plainData, int mode1, int mode2) {
+    protected CipherHelper(String algorithmName, String plainData, int mode1, int mode2) {
         this.algorithmName = algorithmName;
         this.plainData = plainData;
         this.mode1 = mode1;
@@ -42,11 +43,11 @@
     public void test(Key encryptKey, Key decryptKey) throws Exception {
         Cipher cipher = Cipher.getInstance(algorithmName);
         cipher.init(mode1, encryptKey);
-        byte[] encrypted = cipher.doFinal(plainData.getBytes("UTF-8"));
+        byte[] encrypted = cipher.doFinal(plainData.getBytes(StandardCharsets.UTF_8));
 
         cipher.init(mode2, decryptKey);
         byte[] decrypted = cipher.doFinal(encrypted);
-        String decryptedString = new String(decrypted, "UTF-8");
+        String decryptedString = new String(decrypted, StandardCharsets.UTF_8);
 
         assertEquals("transformed data does not match", plainData, decryptedString);
     }
diff --git a/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/CpuFeatures.java b/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/CpuFeatures.java
index f75fb94..553a714 100644
--- a/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/CpuFeatures.java
+++ b/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/CpuFeatures.java
@@ -63,9 +63,13 @@
                 EVP_has_aes_hardware.setAccessible(true);
                 return ((Integer) EVP_has_aes_hardware.invoke(null)) == 1;
             } catch (NoSuchMethodException ignored) {
+                // Ignored
             } catch (SecurityException ignored) {
+                // Ignored
             } catch (IllegalAccessException ignored) {
+                // Ignored
             } catch (IllegalArgumentException ignored) {
+                // Ignored
             } catch (InvocationTargetException e) {
                 throw new IllegalArgumentException(e);
             }
diff --git a/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/DefaultKeys.java b/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/DefaultKeys.java
index 66737c3..0ce676f 100644
--- a/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/DefaultKeys.java
+++ b/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/DefaultKeys.java
@@ -193,6 +193,104 @@
                 "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKoHwjEdyQBvyYUd/Oi+m05qO103dQdKBwj2qjz+f"
                 + "mC8y+cGAGwxMWgnc1xJYw767qY59R36o2TQlQHNI9d0CDA==");
 
+        private static final byte[] X25519_private = new byte[] {
+                (byte) 0x30,
+                (byte) 0x2e,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x00,
+                (byte) 0x30,
+                (byte) 0x05,
+                (byte) 0x06,
+                (byte) 0x03,
+                (byte) 0x2b,
+                (byte) 0x65,
+                (byte) 0x6e,
+                (byte) 0x04,
+                (byte) 0x22,
+                (byte) 0x04,
+                (byte) 0x20,
+                (byte) 0xa5,
+                (byte) 0x46,
+                (byte) 0xe3,
+                (byte) 0x6b,
+                (byte) 0xf0,
+                (byte) 0x52,
+                (byte) 0x7c,
+                (byte) 0x9d,
+                (byte) 0x3b,
+                (byte) 0x16,
+                (byte) 0x15,
+                (byte) 0x4b,
+                (byte) 0x82,
+                (byte) 0x46,
+                (byte) 0x5e,
+                (byte) 0xdd,
+                (byte) 0x62,
+                (byte) 0x14,
+                (byte) 0x4c,
+                (byte) 0x0a,
+                (byte) 0xc1,
+                (byte) 0xfc,
+                (byte) 0x5a,
+                (byte) 0x18,
+                (byte) 0x50,
+                (byte) 0x6a,
+                (byte) 0x22,
+                (byte) 0x44,
+                (byte) 0xba,
+                (byte) 0x44,
+                (byte) 0x9a,
+                (byte) 0xc4,
+        };
+
+        private static final byte[] X25519_public = new byte[] {
+                (byte) 0x30,
+                (byte) 0x2a,
+                (byte) 0x30,
+                (byte) 0x05,
+                (byte) 0x06,
+                (byte) 0x03,
+                (byte) 0x2b,
+                (byte) 0x65,
+                (byte) 0x6e,
+                (byte) 0x03,
+                (byte) 0x21,
+                (byte) 0x00,
+                (byte) 0xe6,
+                (byte) 0xdb,
+                (byte) 0x68,
+                (byte) 0x67,
+                (byte) 0x58,
+                (byte) 0x30,
+                (byte) 0x30,
+                (byte) 0xdb,
+                (byte) 0x35,
+                (byte) 0x94,
+                (byte) 0xc1,
+                (byte) 0xa4,
+                (byte) 0x24,
+                (byte) 0xb1,
+                (byte) 0x5f,
+                (byte) 0x7c,
+                (byte) 0x72,
+                (byte) 0x66,
+                (byte) 0x24,
+                (byte) 0xec,
+                (byte) 0x26,
+                (byte) 0xb3,
+                (byte) 0x35,
+                (byte) 0x3b,
+                (byte) 0x10,
+                (byte) 0xa9,
+                (byte) 0x03,
+                (byte) 0xa6,
+                (byte) 0xd0,
+                (byte) 0xab,
+                (byte) 0x1c,
+                (byte) 0x4c,
+        };
+
         private static final HashMap<String, KeySpec> keys = new HashMap<String, KeySpec>();
         static {
             keys.put("DH_public", new X509EncodedKeySpec(DH_public));
@@ -203,6 +301,8 @@
             keys.put("RSA_private", new PKCS8EncodedKeySpec(RSA_private));
             keys.put("EC_public", new X509EncodedKeySpec(EC_public));
             keys.put("EC_private", new PKCS8EncodedKeySpec(EC_private));
+            keys.put("XDH_public", new X509EncodedKeySpec(X25519_public));
+            keys.put("XDH_private", new PKCS8EncodedKeySpec(X25519_private));
         }
 
     public static PrivateKey getPrivateKey(String algorithmName) throws NoSuchAlgorithmException, InvalidKeySpecException
diff --git a/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/StandardNames.java b/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/StandardNames.java
index 2825677..e0cb275 100644
--- a/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/StandardNames.java
+++ b/repackaged/testing/src/main/java/com/android/org/conscrypt/java/security/StandardNames.java
@@ -66,6 +66,27 @@
 
     public static final String KEY_STORE_ALGORITHM = IS_RI ? "JKS" : "BKS";
 
+    public static final boolean IS_15_OR_UP = majorVersionFromJavaSpecificationVersion() >= 15;
+
+    private static int majorVersionFromJavaSpecificationVersion() {
+        return majorVersion(System.getProperty("java.specification.version", "1.6"));
+    }
+
+    private static int majorVersion(final String javaSpecVersion) {
+        final String[] components = javaSpecVersion.split("\\.", -1);
+        final int[] version = new int[components.length];
+        for (int i = 0; i < components.length; i++) {
+            version[i] = Integer.parseInt(components[i]);
+        }
+
+        if (version[0] == 1) {
+            assertTrue(version[1] >= 6);
+            return version[1];
+        } else {
+            return version[0];
+        }
+    }
+
     /**
      * RFC 5746's Signaling Cipher Suite Value to indicate a request for secure renegotiation
      */
diff --git a/repackaged/testing/src/main/java/com/android/org/conscrypt/javax/net/ssl/FakeSSLSession.java b/repackaged/testing/src/main/java/com/android/org/conscrypt/javax/net/ssl/FakeSSLSession.java
index ecd8b24..a373c72 100644
--- a/repackaged/testing/src/main/java/com/android/org/conscrypt/javax/net/ssl/FakeSSLSession.java
+++ b/repackaged/testing/src/main/java/com/android/org/conscrypt/javax/net/ssl/FakeSSLSession.java
@@ -20,6 +20,7 @@
 import java.nio.charset.Charset;
 import java.security.Principal;
 import java.security.cert.Certificate;
+import javax.net.ssl.SSLPeerUnverifiedException;
 import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSessionContext;
 
@@ -80,7 +81,7 @@
     }
 
     @Override
-    public Certificate[] getPeerCertificates() {
+    public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
         throw new UnsupportedOperationException();
     }
 
diff --git a/repackaged/testing/src/main/java/com/android/org/conscrypt/javax/net/ssl/TestHostnameVerifier.java b/repackaged/testing/src/main/java/com/android/org/conscrypt/javax/net/ssl/TestHostnameVerifier.java
index 1c62bac..b2d5552 100644
--- a/repackaged/testing/src/main/java/com/android/org/conscrypt/javax/net/ssl/TestHostnameVerifier.java
+++ b/repackaged/testing/src/main/java/com/android/org/conscrypt/javax/net/ssl/TestHostnameVerifier.java
@@ -37,7 +37,7 @@
     private static final int DNS_NAME_TYPE = 2;
 
     @SuppressWarnings("MixedMutabilityReturnType")
-    private List<String> getHostnames(X509Certificate cert) {
+    private static List<String> getHostnames(X509Certificate cert) {
         List<String> result = new ArrayList<String>();
         try {
             Collection<List<?>> altNamePairs = cert.getSubjectAlternativeNames();
diff --git a/repackaged/testing/src/main/java/tests/util/ServiceTester.java b/repackaged/testing/src/main/java/tests/util/ServiceTester.java
index 02bb5dd..90b188f 100644
--- a/repackaged/testing/src/main/java/tests/util/ServiceTester.java
+++ b/repackaged/testing/src/main/java/tests/util/ServiceTester.java
@@ -49,11 +49,13 @@
     void test(Provider p, String algorithm) throws Exception;
   }
 
+  private static final String SEPARATOR = "||";
   private final String service;
-  private Set<Provider> providers = new LinkedHashSet<>();
-  private Set<Provider> skipProviders = new HashSet<>();
-  private Set<String> algorithms = new LinkedHashSet<>();
-  private Set<String> skipAlgorithms = new HashSet<>();
+  private final Set<Provider> providers = new LinkedHashSet<>();
+  private final Set<Provider> skipProviders = new HashSet<>();
+  private final Set<String> algorithms = new LinkedHashSet<>();
+  private final Set<String> skipAlgorithms = new HashSet<>();
+  private final Set<String> skipCombinations = new HashSet<>();
 
   private ServiceTester(String service) {
     this.service = service;
@@ -133,6 +135,18 @@
   }
 
   /**
+   * Causes the given combination of provider and algorithm to be omitted from this instance's
+   * testing. If no tested provider provides the given algorithm, does nothing.
+   */
+  public ServiceTester skipCombination(String provider, String algorithm) {
+      Provider p = Security.getProvider(provider);
+      if (p != null) {
+          skipCombinations.add(makeCombination(provider, algorithm));
+      }
+      return this;
+  }
+
+  /**
    * Runs the given test against the configured combination of providers and algorithms.  Continues
    * running all combinations even if some fail.  If any of the test runs fail, this throws
    * an exception with the details of the failure(s).
@@ -147,14 +161,16 @@
     for (Provider p : providers) {
       if (algorithms.isEmpty()) {
         for (Provider.Service s : p.getServices()) {
-            if (s.getType().equals(service) && !skipAlgorithms.contains(s.getAlgorithm())) {
+            if (s.getType().equals(service) && !skipAlgorithms.contains(s.getAlgorithm())
+                    && !shouldSkipCombination(p.getName(), s.getAlgorithm())) {
                 doTest(test, p, s.getAlgorithm(), errors);
             }
         }
       } else {
           algorithms.removeAll(skipAlgorithms);
           for (String algorithm : algorithms) {
-              if (p.getService(service, algorithm) != null) {
+              if (p.getService(service, algorithm) != null
+                      && !shouldSkipCombination(p.getName(), algorithm)) {
                   doTest(test, p, algorithm, errors);
               }
           }
@@ -166,6 +182,14 @@
     }
   }
 
+  private String makeCombination(String provider, String algorithm) {
+      return provider + SEPARATOR + algorithm;
+  }
+
+  private boolean shouldSkipCombination(String provider, String algorithm) {
+      return skipCombinations.contains(makeCombination(provider, algorithm));
+  }
+
   private void doTest(Test test, Provider p, String algorithm, PrintStream errors) {
     try {
       test.test(p, algorithm);
diff --git a/srcgen/generate_android_src.sh b/srcgen/generate_android_src.sh
index 486c229..fc03064 100755
--- a/srcgen/generate_android_src.sh
+++ b/srcgen/generate_android_src.sh
@@ -29,7 +29,6 @@
     benchmark-base \
     common \
     openjdk \
-    common \
     platform \
     testing \
 "
diff --git a/srcgen/core-platform-api.txt b/srcgen/stable-core-platform-api.txt
similarity index 100%
rename from srcgen/core-platform-api.txt
rename to srcgen/stable-core-platform-api.txt
diff --git a/srcgen/unsupported-app-usage.json b/srcgen/unsupported-app-usage.json
index d4ba3bf..12935e9 100644
--- a/srcgen/unsupported-app-usage.json
+++ b/srcgen/unsupported-app-usage.json
@@ -4,62 +4,82 @@
   {
     "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#getAlpnSelectedProtocol()",
     "maxTargetSdk": "dalvik.annotation.compat.VersionCodes.Q",
-    "publicAlternatives": "Use {@link javax.net.ssl.SSLSocket#getApplicationProtocol()}."
+    "publicAlternatives": "Use {@code javax.net.ssl.SSLSocket#getApplicationProtocol()}."
   },
   {
     "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#getApplicationProtocols()",
     "maxTargetSdk": "dalvik.annotation.compat.VersionCodes.Q",
-    "publicAlternatives": "Use {@link javax.net.ssl.SSLParameters#getApplicationProtocols()}."
+    "publicAlternatives": "Use {@code javax.net.ssl.SSLParameters#getApplicationProtocols()}."
   },
   {
-    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#getChannelId()"
+    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#getChannelId()",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
-    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#getHostname()"
+    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#getHostname()",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
-    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#getHostnameOrIP()"
+    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#getHostnameOrIP()",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
-    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#getNpnSelectedProtocol()"
+    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#getNpnSelectedProtocol()",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
-    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#getSoWriteTimeout()"
+    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#getSoWriteTimeout()",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
     "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#setAlpnProtocols(byte[])",
     "maxTargetSdk": "dalvik.annotation.compat.VersionCodes.Q",
-    "publicAlternatives": "Use {@link javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}."
+    "publicAlternatives": "Use {@code javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}."
   },
   {
     "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#setAlpnProtocols(String[])",
     "maxTargetSdk": "dalvik.annotation.compat.VersionCodes.Q",
-    "publicAlternatives": "Use {@link javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}."
+    "publicAlternatives": "Use {@code javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}."
   },
   {
     "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#setApplicationProtocols(String[])",
     "maxTargetSdk": "dalvik.annotation.compat.VersionCodes.Q",
-    "publicAlternatives": "Use {@link javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}."
+    "publicAlternatives": "Use {@code javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}."
   },
   {
-    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#setChannelIdEnabled(boolean)"
+    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#setChannelIdEnabled(boolean)",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
-    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#setChannelIdPrivateKey(PrivateKey)"
+    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#setChannelIdPrivateKey(PrivateKey)",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
-    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#setHandshakeTimeout(int)"
+    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#setHandshakeTimeout(int)",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
     "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#setHostname(String)",
     "maxTargetSdk": "dalvik.annotation.compat.VersionCodes.Q",
-    "publicAlternatives": "Use {@link javax.net.ssl.SSLParameters#setServerNames}."
+    "publicAlternatives": "Use {@code javax.net.ssl.SSLParameters#setServerNames}."
   },
   {
-    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#setNpnProtocols(byte[])"
+    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#setNpnProtocols(byte[])",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
-    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#setSoWriteTimeout(int)"
+    "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#setSoWriteTimeout(int)",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
     "@location": "method:com.android.org.conscrypt.AbstractConscryptSocket#setUseSessionTickets(boolean)",
@@ -75,7 +95,7 @@
   {
     "@location": "method:com.android.org.conscrypt.ConscryptEngineSocket#setHostname(String)",
     "maxTargetSdk": "dalvik.annotation.compat.VersionCodes.Q",
-    "publicAlternatives": "Use {@link javax.net.ssl.SSLParameters#setServerNames}."
+    "publicAlternatives": "Use {@code javax.net.ssl.SSLParameters#setServerNames}."
   },
   {
     "@location": "method:com.android.org.conscrypt.ConscryptEngineSocket#setUseSessionTickets(boolean)",
@@ -85,7 +105,7 @@
   {
     "@location": "method:com.android.org.conscrypt.ConscryptFileDescriptorSocket#setHostname(String)",
     "maxTargetSdk": "dalvik.annotation.compat.VersionCodes.Q",
-    "publicAlternatives": "Use {@link javax.net.ssl.SSLParameters#setServerNames}."
+    "publicAlternatives": "Use {@code javax.net.ssl.SSLParameters#setServerNames}."
   },
   {
     "@location": "method:com.android.org.conscrypt.ConscryptFileDescriptorSocket#setUseSessionTickets(boolean)",
@@ -281,52 +301,68 @@
   {
     "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#getAlpnSelectedProtocol()",
     "maxTargetSdk": "dalvik.annotation.compat.VersionCodes.Q",
-    "publicAlternatives": "Use {@link javax.net.ssl.SSLSocket#getApplicationProtocol()}."
+    "publicAlternatives": "Use {@code javax.net.ssl.SSLSocket#getApplicationProtocol()}."
   },
   {
-    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#getChannelId()"
+    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#getChannelId()",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
-    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#getHostname()"
+    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#getHostname()",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
-    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#getHostnameOrIP()"
+    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#getHostnameOrIP()",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
     "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#getNpnSelectedProtocol()"
   },
   {
-    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#getSoWriteTimeout()"
+    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#getSoWriteTimeout()",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
     "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#setAlpnProtocols(byte[])",
     "maxTargetSdk": "dalvik.annotation.compat.VersionCodes.Q",
-    "publicAlternatives": "Use {@link javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}."
+    "publicAlternatives": "Use {@code javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}."
   },
   {
     "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#setAlpnProtocols(String[])",
     "maxTargetSdk": "dalvik.annotation.compat.VersionCodes.Q",
-    "publicAlternatives": "Use {@link javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}."
+    "publicAlternatives": "Use {@code javax.net.ssl.SSLParameters#setApplicationProtocols(java.lang.String[])}."
   },
   {
-    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#setChannelIdEnabled(boolean)"
+    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#setChannelIdEnabled(boolean)",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
-    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#setChannelIdPrivateKey(PrivateKey)"
+    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#setChannelIdPrivateKey(PrivateKey)",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
-    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#setHandshakeTimeout(int)"
+    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#setHandshakeTimeout(int)",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
     "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#setHostname(String)",
     "maxTargetSdk": "dalvik.annotation.compat.VersionCodes.Q",
-    "publicAlternatives": "Use {@link javax.net.ssl.SSLParameters#setServerNames}."
+    "publicAlternatives": "Use {@code javax.net.ssl.SSLParameters#setServerNames}."
   },
   {
     "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#setNpnProtocols(byte[])"
   },
   {
-    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#setSoWriteTimeout(int)"
+    "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#setSoWriteTimeout(int)",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
     "@location": "method:com.android.org.conscrypt.OpenSSLSocketImpl#setUseSessionTickets(boolean)",
@@ -346,10 +382,14 @@
     "@location": "method:com.android.org.conscrypt.SSLParametersImpl#getDefaultX509TrustManager()"
   },
   {
-    "@location": "method:com.android.org.conscrypt.SSLParametersImpl#getX509TrustManager()"
+    "@location": "method:com.android.org.conscrypt.SSLParametersImpl#getX509TrustManager()",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
-    "@location": "method:com.android.org.conscrypt.SSLParametersImpl#setEnabledProtocols(String[])"
+    "@location": "method:com.android.org.conscrypt.SSLParametersImpl#setEnabledProtocols(String[])",
+    "maxTargetSdk": 30,
+    "trackingBug": 170729553
   },
   {
     "@location": "field:com.android.org.conscrypt.SSLParametersImpl#x509TrustManager"
diff --git a/test-support/Android.bp b/test-support/Android.bp
index f8a025f..c122bc6 100644
--- a/test-support/Android.bp
+++ b/test-support/Android.bp
@@ -13,6 +13,15 @@
 // limitations under the License.
 
 // Conscrypt test support classes.
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "external_conscrypt_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["external_conscrypt_license"],
+}
+
 java_library {
     name: "conscrypt-support",
     visibility: [
diff --git a/testing/build.gradle b/testing/build.gradle
index 0d0d4cb..7585637 100644
--- a/testing/build.gradle
+++ b/testing/build.gradle
@@ -1,3 +1,7 @@
+plugins {
+    id 'com.github.johnrengelman.shadow' version '6.0.0'
+}
+
 description = 'Conscrypt: Testing'
 
 sourceSets {
@@ -9,10 +13,13 @@
 }
 
 dependencies {
-    // Only compile against this. Other modules will embed the generated code directly.
-    compileOnly project(':conscrypt-constants')
+    // Only compile against these. Other modules will embed the generated
+    // constants directly. The stubs libraries should not end up in the
+    // final build.
+    compileOnly project(':conscrypt-constants'),
+                project(':conscrypt-libcore-stub'),
+                project(':conscrypt-android-stub')
 
-    compile project(':conscrypt-libcore-stub'),
-            libraries.bouncycastle_apis,
+    compile libraries.bouncycastle_apis,
             libraries.bouncycastle_provider
 }
diff --git a/testing/src/main/java/org/conscrypt/TestUtils.java b/testing/src/main/java/org/conscrypt/TestUtils.java
index a434b15..b0ca971 100644
--- a/testing/src/main/java/org/conscrypt/TestUtils.java
+++ b/testing/src/main/java/org/conscrypt/TestUtils.java
@@ -20,9 +20,11 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.fail;
 
+import java.io.BufferedReader;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.net.InetAddress;
@@ -44,11 +46,11 @@
 import java.util.Base64;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
+import java.util.Random;
 import java.util.Set;
-import java.util.function.IntFunction;
 import java.util.function.Predicate;
 
-import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLEngineResult;
@@ -81,6 +83,41 @@
 
     static final String TEST_CIPHER = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
 
+    public enum BufferType {
+        HEAP {
+            @Override
+            ByteBuffer newBuffer(int size) {
+                return ByteBuffer.allocate(size);
+            }
+        },
+        DIRECT {
+            @Override
+            ByteBuffer newBuffer(int size) {
+                return ByteBuffer.allocateDirect(size);
+            }
+        };
+        private static final Random random = new Random(System.currentTimeMillis());
+        abstract ByteBuffer newBuffer(int size);
+
+        public ByteBuffer[] newRandomBuffers(int... sizes) {
+            int numBuffers = sizes.length;
+            ByteBuffer[] result = new ByteBuffer[numBuffers];
+            for (int i = 0; i < numBuffers; i++) {
+                result[i] = newRandomBuffer(sizes[i]);
+            }
+            return result;
+        }
+
+        public ByteBuffer newRandomBuffer(int size) {
+            byte[] data = new byte[size];
+            random.nextBytes(data);
+            ByteBuffer buffer = newBuffer(size);
+            buffer.put(data);
+            buffer.flip();
+            return buffer;
+        }
+    }
+
     private TestUtils() {}
 
     private static Provider getNonConscryptTlsProvider() {
@@ -126,6 +163,10 @@
         assumeClassAvailable("javax.net.ssl.X509ExtendedTrustManager");
     }
 
+    public static void assumeStatsLogAvailable() {
+        assumeClassAvailable("android.util.StatsEvent");
+    }
+
     public static void assumeSetEndpointIdentificationAlgorithmAvailable() {
         boolean supported = false;
         try {
@@ -152,21 +193,10 @@
         }
     }
 
-    // Is a pre-Android 12 mainline module installed.  Detect based on a class that
-    // was renamed in the Android 12 codebase.
-    private static boolean isBeforeAndroid12Mainline() {
-        return isClassAvailable("org.conscrypt.CertBlacklistImpl");
-    }
-
     public static void assumeAndroid() {
         Assume.assumeTrue(isAndroid());
     }
 
-    public static void assumeBeforeAndroid12Mainline() {
-        Assume.assumeTrue(isAndroid() && isBeforeAndroid12Mainline());
-    }
-
-    // Assume a pre-Android 12 mainline module is installed.
     public static void assumeAllowsUnsignedCrypto() {
         // The Oracle JRE disallows loading crypto providers from unsigned jars
         Assume.assumeTrue(isAndroid()
@@ -184,18 +214,6 @@
         Assume.assumeTrue("SHA2 with DSA signatures not available", available);
     }
 
-    private static Method findMethod(Class<?> cls, String methodName,  Class<?>... methodParams) {
-        try {
-            return cls.getDeclaredMethod(methodName, methodParams);
-        } catch (NoSuchMethodException e) {
-            return null;
-        }
-    }
-
-    public static Method findWrapVerifierMethod() {
-        return findMethod(Conscrypt.class, "wrapHostnameVerifier", HostnameVerifier.class);
-    }
-
     public static InetAddress getLoopbackAddress() {
         try {
             Method method = InetAddress.class.getMethod("getLoopbackAddress");
@@ -250,7 +268,7 @@
 
     public static PublicKey readPublicKeyPemFile(String name)
             throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
-        String keyData = new String(readTestFile(name), "US-ASCII");
+        String keyData = new String(readTestFile(name), StandardCharsets.US_ASCII);
         keyData = keyData.replace("-----BEGIN PUBLIC KEY-----", "");
         keyData = keyData.replace("-----END PUBLIC KEY-----", "");
         keyData = keyData.replace("\r", "");
@@ -259,6 +277,22 @@
                 new X509EncodedKeySpec(decodeBase64(keyData)));
     }
 
+    public static List<String[]> readCsvResource(String resourceName) throws IOException {
+        InputStream stream = openTestFile(resourceName);
+        List<String[]> lines = new ArrayList<>();
+        try (BufferedReader reader
+                     = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (line.isEmpty() || line.startsWith("#")) {
+                    continue;
+                }
+                lines.add(line.split(",", -1));
+            }
+        }
+        return lines;
+    }
+
     /**
      * Looks up the conscrypt class for the given simple name (i.e. no package prefix).
      */
@@ -311,24 +345,6 @@
         }
     }
 
-    public static SSLSocketFactory getConscryptSocketFactory(boolean useEngineSocket) {
-        return setUseEngineSocket(getSocketFactory(getConscryptProvider()), useEngineSocket);
-    }
-
-    public static SSLServerSocketFactory getConscryptServerSocketFactory(boolean useEngineSocket) {
-        return setUseEngineSocket(getServerSocketFactory(getConscryptProvider()), useEngineSocket);
-    }
-
-    private static SSLSocketFactory getSocketFactory(Provider provider) {
-        SSLContext clientContext = initClientSslContext(newContext(provider));
-        return clientContext.getSocketFactory();
-    }
-
-    private static SSLServerSocketFactory getServerSocketFactory(Provider provider) {
-        SSLContext serverContext = initServerSslContext(newContext(provider));
-        return serverContext.getServerSocketFactory();
-    }
-
     static SSLContext newContext(Provider provider) {
         try {
             return SSLContext.getInstance("TLS", provider);
@@ -347,27 +363,18 @@
         SSLContext jdkContext = newClientSslContext(getJdkProvider());
         SSLContext conscryptContext = newClientSslContext(getConscryptProvider());
         // No point building a Set here due to small list sizes.
-        final List<String> conscryptProtocols = getSupportedProtocols(conscryptContext);
-        // TODO(prb): Certificate auth fails when connecting Conscrypt and JDK's TLS 1.3.
-        Predicate<String> predicate = new Predicate<String>() {
-            @Override
-            public boolean test(String string) {
-              return conscryptProtocols.contains(string) && !string.equals(PROTOCOL_TLS_V1_3);
-            }
-        };
+        List<String> conscryptProtocols = getSupportedProtocols(conscryptContext);
+        Predicate<String> predicate = p -> conscryptProtocols.contains(p)
+            // TODO(prb): Certificate auth fails when connecting Conscrypt and JDK's TLS 1.3.
+            && !p.equals(PROTOCOL_TLS_V1_3);
         return getSupportedProtocols(jdkContext, predicate);
     }
 
     public static String[] getCommonCipherSuites() {
         SSLContext jdkContext = newClientSslContext(getJdkProvider());
         SSLContext conscryptContext = newClientSslContext(getConscryptProvider());
-        final Set<String> conscryptCiphers =  new HashSet<>(getSupportedCiphers(conscryptContext));
-        Predicate<String> predicate = new Predicate<String>() {
-            @Override
-            public boolean test(String string) {
-              return isTlsCipherSuite(string) && conscryptCiphers.contains(string);
-            }
-        };
+        Set<String> conscryptCiphers =  new HashSet<>(getSupportedCiphers(conscryptContext));
+        Predicate<String> predicate = c -> isTlsCipherSuite(c) && conscryptCiphers.contains(c);
         return getSupportedCiphers(jdkContext, predicate);
     }
 
@@ -376,15 +383,9 @@
     }
 
     public static String[] getSupportedCiphers(SSLContext ctx, Predicate<String> predicate) {
-        IntFunction<String[]> transform = new IntFunction<String[]>() {
-            @Override
-            public String[] apply(int value) {
-                return new String[value];
-            }
-        };
         return Arrays.stream(ctx.getDefaultSSLParameters().getCipherSuites())
             .filter(predicate)
-            .toArray(transform);
+            .toArray(String[]::new);
     }
 
     public static List<String> getSupportedProtocols(SSLContext ctx) {
@@ -392,15 +393,9 @@
     }
 
     public static String[] getSupportedProtocols(SSLContext ctx, Predicate<String> predicate) {
-        IntFunction<String[]> transform = new IntFunction<String[]>() {
-            @Override
-            public String[] apply(int value) {
-                return new String[value];
-            }
-        };
         return Arrays.stream(ctx.getDefaultSSLParameters().getProtocols())
             .filter(predicate)
-            .toArray(transform);
+            .toArray(String[]::new);
     }
 
     private static boolean isTlsCipherSuite(String cipher) {
@@ -481,7 +476,7 @@
     }
 
     /**
-     * Performs the intial TLS handshake between the two {@link SSLEngine} instances.
+     * Performs the initial TLS handshake between the two {@link SSLEngine} instances.
      */
     public static void doEngineHandshake(SSLEngine clientEngine, SSLEngine serverEngine,
         ByteBuffer clientAppBuffer, ByteBuffer clientPacketBuffer, ByteBuffer serverAppBuffer,
@@ -652,7 +647,6 @@
         return result;
     }
 
-
     private static int toDigit(char[] str, int offset) throws IllegalArgumentException {
         // NOTE: that this isn't really a code point in the traditional sense, since we're
         // just rejecting surrogate pairs outright.
@@ -670,6 +664,18 @@
                 " at offset " + offset);
     }
 
+    private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
+
+    public static String encodeHex(byte[] data) {
+        char[] hex = new char[data.length * 2];
+        for (int i = 0; i < data.length; i++) {
+            int value = data[i] & 0xff;
+            hex[2 * i] = HEX_CHARS[value >>> 4];
+            hex[2 * i + 1] = HEX_CHARS[value & 0x0f];
+        }
+        return new String(hex);
+    }
+
     private static final String BASE64_ALPHABET =
             "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
@@ -755,4 +761,21 @@
     public static void assumeJava8() {
         Assume.assumeTrue("Require Java 8: " + javaVersion(), isJavaVersion(8));
     }
+
+    public static String osName() {
+        return System.getProperty("os.name").toLowerCase(Locale.US).replaceAll("[^a-z0-9]+", "");
+    }
+
+    public static boolean isLinux() {
+        return osName().startsWith("linux");
+    }
+
+    public static boolean isWindows() {
+        return osName().startsWith("windows");
+    }
+
+    public static boolean isOsx() {
+        String name = osName();
+        return name.startsWith("macosx") || name.startsWith("osx");
+    }
 }
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 def5a88..3523e45 100644
--- a/testing/src/main/java/org/conscrypt/java/security/AbstractKeyFactoryTest.java
+++ b/testing/src/main/java/org/conscrypt/java/security/AbstractKeyFactoryTest.java
@@ -34,7 +34,7 @@
     private final Class<PublicKeySpec> publicKeySpecClass;
     private final Class<PrivateKeySpec> privateKeySpecClass;
 
-    public AbstractKeyFactoryTest(String algorithmName,
+    protected AbstractKeyFactoryTest(String algorithmName,
             Class<PublicKeySpec> publicKeySpecClass,
             Class<PrivateKeySpec> privateKeySpecClass) {
         this.algorithmName = algorithmName;
diff --git a/testing/src/main/java/org/conscrypt/java/security/AbstractKeyPairGeneratorTest.java b/testing/src/main/java/org/conscrypt/java/security/AbstractKeyPairGeneratorTest.java
index 109cf90..3353998 100644
--- a/testing/src/main/java/org/conscrypt/java/security/AbstractKeyPairGeneratorTest.java
+++ b/testing/src/main/java/org/conscrypt/java/security/AbstractKeyPairGeneratorTest.java
@@ -40,9 +40,13 @@
         generator = KeyPairGenerator.getInstance(algorithmName);
     }
 
+    protected int getKeySize() {
+        return 1024;
+    }
+
     @Test
     public void testKeyPairGenerator() throws Exception {
-        generator.initialize(1024);
+        generator.initialize(getKeySize());
 
         KeyPair keyPair = generator.generateKeyPair();
 
diff --git a/testing/src/main/java/org/conscrypt/java/security/CipherHelper.java b/testing/src/main/java/org/conscrypt/java/security/CipherHelper.java
index 30ac58e..d9c1d4b 100644
--- a/testing/src/main/java/org/conscrypt/java/security/CipherHelper.java
+++ b/testing/src/main/java/org/conscrypt/java/security/CipherHelper.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import java.nio.charset.StandardCharsets;
 import java.security.Key;
 import javax.crypto.Cipher;
 
@@ -28,7 +29,7 @@
     private final int mode1;
     private final int mode2;
 
-    public CipherHelper(String algorithmName, String plainData, int mode1, int mode2) {
+    protected CipherHelper(String algorithmName, String plainData, int mode1, int mode2) {
         this.algorithmName = algorithmName;
         this.plainData = plainData;
         this.mode1 = mode1;
@@ -38,11 +39,11 @@
     public void test(Key encryptKey, Key decryptKey) throws Exception {
         Cipher cipher = Cipher.getInstance(algorithmName);
         cipher.init(mode1, encryptKey);
-        byte[] encrypted = cipher.doFinal(plainData.getBytes("UTF-8"));
+        byte[] encrypted = cipher.doFinal(plainData.getBytes(StandardCharsets.UTF_8));
 
         cipher.init(mode2, decryptKey);
         byte[] decrypted = cipher.doFinal(encrypted);
-        String decryptedString = new String(decrypted, "UTF-8");
+        String decryptedString = new String(decrypted, StandardCharsets.UTF_8);
 
         assertEquals("transformed data does not match", plainData, decryptedString);
     }
diff --git a/testing/src/main/java/org/conscrypt/java/security/CpuFeatures.java b/testing/src/main/java/org/conscrypt/java/security/CpuFeatures.java
index 2b40bf7..721f453 100644
--- a/testing/src/main/java/org/conscrypt/java/security/CpuFeatures.java
+++ b/testing/src/main/java/org/conscrypt/java/security/CpuFeatures.java
@@ -59,9 +59,13 @@
                 EVP_has_aes_hardware.setAccessible(true);
                 return ((Integer) EVP_has_aes_hardware.invoke(null)) == 1;
             } catch (NoSuchMethodException ignored) {
+                // Ignored
             } catch (SecurityException ignored) {
+                // Ignored
             } catch (IllegalAccessException ignored) {
+                // Ignored
             } catch (IllegalArgumentException ignored) {
+                // Ignored
             } catch (InvocationTargetException e) {
                 throw new IllegalArgumentException(e);
             }
diff --git a/testing/src/main/java/org/conscrypt/java/security/DefaultKeys.java b/testing/src/main/java/org/conscrypt/java/security/DefaultKeys.java
index 0d2a278..bb8fa9f 100644
--- a/testing/src/main/java/org/conscrypt/java/security/DefaultKeys.java
+++ b/testing/src/main/java/org/conscrypt/java/security/DefaultKeys.java
@@ -189,6 +189,24 @@
             "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKoHwjEdyQBvyYUd/Oi+m05qO103dQdKBwj2qjz+f"
                 + "mC8y+cGAGwxMWgnc1xJYw767qY59R36o2TQlQHNI9d0CDA==");
 
+    private static final byte[] X25519_private = new byte[] {
+            (byte) 0x30, (byte) 0x2e, (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x30, (byte) 0x05, (byte) 0x06,
+            (byte) 0x03, (byte) 0x2b, (byte) 0x65, (byte) 0x6e, (byte) 0x04, (byte) 0x22, (byte) 0x04, (byte) 0x20,
+            (byte) 0xa5, (byte) 0x46, (byte) 0xe3, (byte) 0x6b, (byte) 0xf0, (byte) 0x52, (byte) 0x7c, (byte) 0x9d,
+            (byte) 0x3b, (byte) 0x16, (byte) 0x15, (byte) 0x4b, (byte) 0x82, (byte) 0x46, (byte) 0x5e, (byte) 0xdd,
+            (byte) 0x62, (byte) 0x14, (byte) 0x4c, (byte) 0x0a, (byte) 0xc1, (byte) 0xfc, (byte) 0x5a, (byte) 0x18,
+            (byte) 0x50, (byte) 0x6a, (byte) 0x22, (byte) 0x44, (byte) 0xba, (byte) 0x44, (byte) 0x9a, (byte) 0xc4,
+    };
+
+    private static final byte[] X25519_public = new byte[] {
+            (byte) 0x30, (byte) 0x2a, (byte) 0x30, (byte) 0x05, (byte) 0x06, (byte) 0x03, (byte) 0x2b, (byte) 0x65,
+            (byte) 0x6e, (byte) 0x03, (byte) 0x21, (byte) 0x00, (byte) 0xe6, (byte) 0xdb, (byte) 0x68, (byte) 0x67,
+            (byte) 0x58, (byte) 0x30, (byte) 0x30, (byte) 0xdb, (byte) 0x35, (byte) 0x94, (byte) 0xc1, (byte) 0xa4,
+            (byte) 0x24, (byte) 0xb1, (byte) 0x5f, (byte) 0x7c, (byte) 0x72, (byte) 0x66, (byte) 0x24, (byte) 0xec,
+            (byte) 0x26, (byte) 0xb3, (byte) 0x35, (byte) 0x3b, (byte) 0x10, (byte) 0xa9, (byte) 0x03, (byte) 0xa6,
+            (byte) 0xd0, (byte) 0xab, (byte) 0x1c, (byte) 0x4c,
+    };
+
     private static final HashMap<String, KeySpec> keys = new HashMap<String, KeySpec>();
     static {
         keys.put("DH_public", new X509EncodedKeySpec(DH_public));
@@ -199,6 +217,8 @@
         keys.put("RSA_private", new PKCS8EncodedKeySpec(RSA_private));
         keys.put("EC_public", new X509EncodedKeySpec(EC_public));
         keys.put("EC_private", new PKCS8EncodedKeySpec(EC_private));
+        keys.put("XDH_public", new X509EncodedKeySpec(X25519_public));
+        keys.put("XDH_private", new PKCS8EncodedKeySpec(X25519_private));
     }
 
     public static PrivateKey getPrivateKey(String algorithmName) throws NoSuchAlgorithmException, InvalidKeySpecException
diff --git a/testing/src/main/java/org/conscrypt/java/security/StandardNames.java b/testing/src/main/java/org/conscrypt/java/security/StandardNames.java
index 86c7d48..7a8672a 100644
--- a/testing/src/main/java/org/conscrypt/java/security/StandardNames.java
+++ b/testing/src/main/java/org/conscrypt/java/security/StandardNames.java
@@ -64,6 +64,27 @@
 
     public static final String KEY_STORE_ALGORITHM = IS_RI ? "JKS" : "BKS";
 
+    public static final boolean IS_15_OR_UP = majorVersionFromJavaSpecificationVersion() >= 15;
+
+    private static int majorVersionFromJavaSpecificationVersion() {
+        return majorVersion(System.getProperty("java.specification.version", "1.6"));
+    }
+
+    private static int majorVersion(final String javaSpecVersion) {
+        final String[] components = javaSpecVersion.split("\\.", -1);
+        final int[] version = new int[components.length];
+        for (int i = 0; i < components.length; i++) {
+            version[i] = Integer.parseInt(components[i]);
+        }
+
+        if (version[0] == 1) {
+            assertTrue(version[1] >= 6);
+            return version[1];
+        } else {
+            return version[0];
+        }
+    }
+
     /**
      * RFC 5746's Signaling Cipher Suite Value to indicate a request for secure renegotiation
      */
diff --git a/testing/src/main/java/org/conscrypt/javax/net/ssl/FakeSSLSession.java b/testing/src/main/java/org/conscrypt/javax/net/ssl/FakeSSLSession.java
index f83b62e..f001db2 100644
--- a/testing/src/main/java/org/conscrypt/javax/net/ssl/FakeSSLSession.java
+++ b/testing/src/main/java/org/conscrypt/javax/net/ssl/FakeSSLSession.java
@@ -19,6 +19,7 @@
 import java.nio.charset.Charset;
 import java.security.Principal;
 import java.security.cert.Certificate;
+import javax.net.ssl.SSLPeerUnverifiedException;
 import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSessionContext;
 
@@ -76,7 +77,7 @@
     }
 
     @Override
-    public Certificate[] getPeerCertificates() {
+    public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
         throw new UnsupportedOperationException();
     }
 
diff --git a/testing/src/main/java/org/conscrypt/javax/net/ssl/TestHostnameVerifier.java b/testing/src/main/java/org/conscrypt/javax/net/ssl/TestHostnameVerifier.java
index 7f8148e..9762bc3 100644
--- a/testing/src/main/java/org/conscrypt/javax/net/ssl/TestHostnameVerifier.java
+++ b/testing/src/main/java/org/conscrypt/javax/net/ssl/TestHostnameVerifier.java
@@ -35,7 +35,7 @@
     private static final int DNS_NAME_TYPE = 2;
 
     @SuppressWarnings("MixedMutabilityReturnType")
-    private List<String> getHostnames(X509Certificate cert) {
+    private static List<String> getHostnames(X509Certificate cert) {
         List<String> result = new ArrayList<String>();
         try {
             Collection<List<?>> altNamePairs = cert.getSubjectAlternativeNames();
diff --git a/testing/src/main/java/tests/util/ServiceTester.java b/testing/src/main/java/tests/util/ServiceTester.java
index 25b5881..813f845 100644
--- a/testing/src/main/java/tests/util/ServiceTester.java
+++ b/testing/src/main/java/tests/util/ServiceTester.java
@@ -44,11 +44,13 @@
     void test(Provider p, String algorithm) throws Exception;
   }
 
+  private static final String SEPARATOR = "||";
   private final String service;
-  private Set<Provider> providers = new LinkedHashSet<>();
-  private Set<Provider> skipProviders = new HashSet<>();
-  private Set<String> algorithms = new LinkedHashSet<>();
-  private Set<String> skipAlgorithms = new HashSet<>();
+  private final Set<Provider> providers = new LinkedHashSet<>();
+  private final Set<Provider> skipProviders = new HashSet<>();
+  private final Set<String> algorithms = new LinkedHashSet<>();
+  private final Set<String> skipAlgorithms = new HashSet<>();
+  private final Set<String> skipCombinations = new HashSet<>();
 
   private ServiceTester(String service) {
     this.service = service;
@@ -128,6 +130,18 @@
   }
 
   /**
+   * Causes the given combination of provider and algorithm to be omitted from this instance's
+   * testing. If no tested provider provides the given algorithm, does nothing.
+   */
+  public ServiceTester skipCombination(String provider, String algorithm) {
+    Provider p = Security.getProvider(provider);
+    if (p != null) {
+      skipCombinations.add(makeCombination(provider, algorithm));
+    }
+    return this;
+  }
+
+  /**
    * Runs the given test against the configured combination of providers and algorithms.  Continues
    * running all combinations even if some fail.  If any of the test runs fail, this throws
    * an exception with the details of the failure(s).
@@ -142,14 +156,17 @@
     for (Provider p : providers) {
       if (algorithms.isEmpty()) {
         for (Provider.Service s : p.getServices()) {
-          if (s.getType().equals(service) && !skipAlgorithms.contains(s.getAlgorithm())) {
+          if (s.getType().equals(service)
+              && !skipAlgorithms.contains(s.getAlgorithm())
+              && !shouldSkipCombination(p.getName(), s.getAlgorithm())) {
             doTest(test, p, s.getAlgorithm(), errors);
           }
         }
       } else {
         algorithms.removeAll(skipAlgorithms);
         for (String algorithm : algorithms) {
-          if (p.getService(service, algorithm) != null) {
+          if (p.getService(service, algorithm) != null
+              && !shouldSkipCombination(p.getName(), algorithm)) {
             doTest(test, p, algorithm, errors);
           }
         }
@@ -161,6 +178,14 @@
     }
   }
 
+  private String makeCombination(String provider, String algorithm) {
+    return provider + SEPARATOR + algorithm;
+  }
+
+  private boolean shouldSkipCombination(String provider, String algorithm) {
+    return skipCombinations.contains(makeCombination(provider, algorithm));
+  }
+
   private void doTest(Test test, Provider p, String algorithm, PrintStream errors) {
     try {
       test.test(p, algorithm);