am ea961ada: Apply conscrypt changes from merge commit

* commit 'ea961ada17d50ec1f2006c2843d1d4164d0e1342':
  Apply conscrypt changes from merge commit
diff --git a/Android.mk b/Android.mk
index b6dcefa..11ba82f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -54,7 +54,8 @@
 # Create the conscrypt library
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(call all-java-files-under,src/main/java)
-LOCAL_JAVA_LIBRARIES := core
+LOCAL_SRC_FILES += $(call all-java-files-under,src/platform/java)
+LOCAL_JAVA_LIBRARIES := core-libart
 LOCAL_NO_STANDARD_LIBRARIES := true
 LOCAL_JAVACFLAGS := $(local_javac_flags)
 LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
@@ -67,7 +68,8 @@
 # Create the conscrypt library without jarjar for tests
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(call all-java-files-under,src/main/java)
-LOCAL_JAVA_LIBRARIES := core
+LOCAL_SRC_FILES += $(call all-java-files-under,src/platform/java)
+LOCAL_JAVA_LIBRARIES := core-libart
 LOCAL_NO_STANDARD_LIBRARIES := true
 LOCAL_JAVACFLAGS := $(local_javac_flags)
 LOCAL_MODULE_TAGS := optional
@@ -80,7 +82,7 @@
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(call all-java-files-under,src/test/java)
 LOCAL_NO_STANDARD_LIBRARIES := true
-LOCAL_JAVA_LIBRARIES := bouncycastle core core-junit
+LOCAL_JAVA_LIBRARIES := core-libart core-junit bouncycastle
 LOCAL_STATIC_JAVA_LIBRARIES := core-tests-support conscrypt-nojarjar
 LOCAL_JAVACFLAGS := $(local_javac_flags)
 LOCAL_MODULE_TAGS := optional
@@ -100,6 +102,7 @@
         src/main/native/org_conscrypt_NativeCrypto.cpp
 LOCAL_C_INCLUDES += \
         external/openssl/include \
+        external/openssl \
         libcore/include \
         libcore/luni/src/main/native
 LOCAL_SHARED_LIBRARIES := libcrypto libjavacore liblog libnativehelper libssl libz
@@ -108,34 +111,82 @@
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 include $(BUILD_SHARED_LIBRARY)
 
+# Unbundled Conscrypt jar
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under,src/main/java)
+LOCAL_SRC_FILES += $(call all-java-files-under,src/compat/java)
+LOCAL_SDK_VERSION := 9
+LOCAL_JAVACFLAGS := $(local_javac_flags)
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := conscrypt_unbundled
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Unbundled Conscrypt crypto JNI library
+include $(CLEAR_VARS)
+LOCAL_CFLAGS += $(core_cflags)
+LOCAL_CPPFLAGS += $(core_cppflags)
+LOCAL_SRC_FILES := \
+        src/main/native/org_conscrypt_NativeCrypto.cpp \
+        src/compat/native/JNIHelp.cpp
+LOCAL_C_INCLUDES += \
+        external/openssl/include \
+        external/openssl \
+        external/conscrypt/src/compat/native
+LOCAL_LDFLAGS := -llog -lz -ldl
+LOCAL_STATIC_LIBRARIES := libssl_static libcrypto_static
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := libconscrypt_jni
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+LOCAL_SDK_VERSION := 9
+include $(BUILD_SHARED_LIBRARY)
+
+# Static unbundled Conscrypt crypto JNI library
+include $(CLEAR_VARS)
+LOCAL_CFLAGS += $(core_cflags)
+LOCAL_CPPFLAGS += $(core_cppflags) -DJNI_JARJAR_PREFIX="com/google/android/gms/" -DCONSCRYPT_UNBUNDLED -DSTATIC_LIB
+LOCAL_SRC_FILES := \
+        src/main/native/org_conscrypt_NativeCrypto.cpp \
+        src/compat/native/JNIHelp.cpp
+LOCAL_C_INCLUDES += \
+        external/openssl/include \
+        external/openssl \
+        external/conscrypt/src/compat/native
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := libconscrypt_static
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+LOCAL_SDK_VERSION := 9
+include $(BUILD_STATIC_LIBRARY)
+
 #
 # Build for the host.
 #
 
-ifeq ($(WITH_HOST_DALVIK),true)
-    # Make the conscrypt-hostdex library
-    include $(CLEAR_VARS)
-    LOCAL_SRC_FILES := $(call all-java-files-under,src/main/java)
-    LOCAL_JAVACFLAGS := $(local_javac_flags)
-    LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
-    LOCAL_MODULE_TAGS := optional
-    LOCAL_MODULE := conscrypt-hostdex
-    LOCAL_REQUIRED_MODULES := libjavacrypto
-    LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-    include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
+# Make the conscrypt-hostdex library
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under,src/main/java)
+LOCAL_SRC_FILES += $(call all-java-files-under,src/platform/java)
+LOCAL_JAVACFLAGS := $(local_javac_flags)
+LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := conscrypt-hostdex
+LOCAL_REQUIRED_MODULES := libjavacrypto
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
 
-    # Make the conscrypt-hostdex-nojarjar for tests
-    include $(CLEAR_VARS)
-    LOCAL_SRC_FILES := $(call all-java-files-under,src/main/java)
-    LOCAL_JAVACFLAGS := $(local_javac_flags)
-    LOCAL_BUILD_HOST_DEX := true
-    LOCAL_MODULE_TAGS := optional
-    LOCAL_MODULE := conscrypt-hostdex-nojarjar
-    LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-    include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
+# Make the conscrypt-hostdex-nojarjar for tests
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under,src/main/java)
+LOCAL_SRC_FILES += $(call all-java-files-under,src/platform/java)
+LOCAL_JAVACFLAGS := $(local_javac_flags)
+LOCAL_BUILD_HOST_DEX := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := conscrypt-hostdex-nojarjar
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
 
-    # Make the conscrypt-tests library.
-    ifeq ($(LIBCORE_SKIP_TESTS),)
+# Make the conscrypt-tests library.
+ifeq ($(LIBCORE_SKIP_TESTS),)
     include $(CLEAR_VARS)
     LOCAL_SRC_FILES := $(call all-java-files-under,src/test/java)
     LOCAL_JAVA_LIBRARIES := bouncycastle-hostdex core-junit-hostdex core-tests-support-hostdex conscrypt-hostdex-nojarjar
@@ -145,34 +196,40 @@
     LOCAL_REQUIRED_MODULES := libjavacrypto
     LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
     include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
-    endif
+endif
 
-    # Conscrypt native library for host
+# Conscrypt native library for host
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_SRC_FILES += \
+        src/main/native/org_conscrypt_NativeCrypto.cpp
+LOCAL_C_INCLUDES += \
+        external/openssl/include \
+        external/openssl \
+        libcore/include \
+        libcore/luni/src/main/native
+LOCAL_CPPFLAGS += $(core_cppflags)
+LOCAL_LDLIBS += -lpthread
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := libjavacrypto
+LOCAL_CFLAGS += -DJNI_JARJAR_PREFIX="com/android/"
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
+LOCAL_SHARED_LIBRARIES := libcrypto-host libjavacore liblog libnativehelper libssl-host
+include $(BUILD_HOST_SHARED_LIBRARY)
+
+# Conscrypt native library for nojarjar'd version
+# Don't build this for unbundled conscrypt build
+ifeq (,$(TARGET_BUILD_APPS))
     include $(CLEAR_VARS)
+    LOCAL_CLANG := true
     LOCAL_SRC_FILES += \
             src/main/native/org_conscrypt_NativeCrypto.cpp
     LOCAL_C_INCLUDES += \
             external/openssl/include \
+            external/openssl \
             libcore/include \
             libcore/luni/src/main/native
-    LOCAL_CPPFLAGS += $(core_cppflags)
-    LOCAL_LDLIBS += -lpthread
-    LOCAL_MODULE_TAGS := optional
-    LOCAL_MODULE := libjavacrypto
-    LOCAL_CFLAGS += -DJNI_JARJAR_PREFIX="com/android/"
-    LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-    LOCAL_SHARED_LIBRARIES := libcrypto-host libjavacore liblog libnativehelper libssl-host
-    include $(BUILD_HOST_SHARED_LIBRARY)
-
-    # Conscrypt native library for nojarjar'd version
-    include $(CLEAR_VARS)
-    LOCAL_SRC_FILES += \
-            src/main/native/org_conscrypt_NativeCrypto.cpp
-    LOCAL_C_INCLUDES += \
-            external/openssl/include \
-            libcore/include \
-            libcore/luni/src/main/native
-    LOCAL_CPPFLAGS += $(core_cppflags)
+    LOCAL_CPPFLAGS += $(core_cppflags) -DCONSCRYPT_NOT_UNBUNDLED
     LOCAL_LDLIBS += -lpthread
     LOCAL_MODULE_TAGS := optional
     LOCAL_MODULE := libconscrypt_jni
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..12d7b99
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: 'java'
+
+// this is the "Unbundled Conscrypt jar"
+sourceSets.main {
+    java.srcDirs = [
+        'src/main/java',
+        'src/compat/java',
+    ]
+}
+
+compileJava.options.encoding = 'UTF-8'
+compileJava.options.compilerArgs += ['-Xmaxwarns', '9999999']
+
+dependencies {
+    compile getAndroidPrebuilt('9')
+}
diff --git a/src/compat/java/dalvik/system/BlockGuard.java b/src/compat/java/dalvik/system/BlockGuard.java
new file mode 100644
index 0000000..e2911d8
--- /dev/null
+++ b/src/compat/java/dalvik/system/BlockGuard.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 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 dalvik.system;
+
+import java.lang.reflect.Method;
+
+public class BlockGuard {
+    private static Method m_getThreadPolicy;
+    private static Method m_onNetwork;
+    static {
+        try {
+            ClassLoader cl = ClassLoader.getSystemClassLoader();
+            Class<?> c_closeGuard = cl.loadClass("dalvik.system.BlockGuard");
+
+            m_getThreadPolicy = c_closeGuard.getDeclaredMethod("getThreadPolicy");
+            m_getThreadPolicy.setAccessible(true);
+
+            Class<?> c_policy = cl.loadClass("dalvik.system.BlockGuard.Policy");
+
+            m_onNetwork = c_policy.getDeclaredMethod("onNetwork");
+            m_onNetwork.setAccessible(true);
+        } catch (Exception ignored) {
+        }
+    }
+
+    private BlockGuard() {
+    }
+
+    public static Policy getThreadPolicy() {
+        if (m_getThreadPolicy != null) {
+            try {
+                Object wrappedPolicy = m_getThreadPolicy.invoke(null);
+                return new PolicyWrapper(wrappedPolicy);
+            } catch (Exception ignored) {
+            }
+        }
+        return new PolicyWrapper(null);
+    }
+
+    public interface Policy {
+        void onNetwork();
+    }
+
+    public static class PolicyWrapper implements Policy {
+        private final Object wrappedPolicy;
+
+        private PolicyWrapper(Object wrappedPolicy) {
+            this.wrappedPolicy = wrappedPolicy;
+        }
+
+        public void onNetwork() {
+            if (m_onNetwork != null && wrappedPolicy != null) {
+                try {
+                    m_onNetwork.invoke(wrappedPolicy);
+                } catch (Exception ignored) {
+                }
+            }
+        }
+    }
+}
diff --git a/src/compat/java/dalvik/system/CloseGuard.java b/src/compat/java/dalvik/system/CloseGuard.java
new file mode 100644
index 0000000..1063e07
--- /dev/null
+++ b/src/compat/java/dalvik/system/CloseGuard.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 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 dalvik.system;
+
+import java.lang.reflect.Method;
+
+public class CloseGuard {
+    private static Method m_get;
+    private static Method m_open;
+    private static Method m_close;
+    private static Method m_warnIfOpen;
+    static {
+        try {
+            Class<?> c_closeGuard = Class.forName("dalvik.system.CloseGuard");
+
+            m_get = c_closeGuard.getDeclaredMethod("get");
+            m_get.setAccessible(true);
+
+            m_open = c_closeGuard.getDeclaredMethod("open", String.class);
+            m_open.setAccessible(true);
+
+            m_close = c_closeGuard.getDeclaredMethod("close");
+            m_close.setAccessible(true);
+
+            m_warnIfOpen = c_closeGuard.getDeclaredMethod("warnIfOpen");
+            m_warnIfOpen.setAccessible(true);
+        } catch (Exception ignored) {
+        }
+    }
+
+    private final Object wrappedGuard;
+
+    private CloseGuard(Object wrappedGuard) {
+        this.wrappedGuard = wrappedGuard;
+    }
+
+    public static CloseGuard get() {
+        if (m_get != null) {
+            try {
+                return new CloseGuard(m_get.invoke(null));
+            } catch (Exception ignored) {
+            }
+        }
+        return new CloseGuard(null);
+    }
+
+    public void open(String message) {
+        if (wrappedGuard != null && m_open != null) {
+            try {
+                m_open.invoke(wrappedGuard, message);
+            } catch (Exception ignored) {
+            }
+        }
+    }
+
+    public void close() {
+        if (wrappedGuard != null && m_close != null) {
+            try {
+                m_close.invoke(wrappedGuard);
+            } catch (Exception ignored) {
+            }
+        }
+    }
+
+    public void warnIfOpen() {
+        if (wrappedGuard != null && m_warnIfOpen != null) {
+            try {
+                m_warnIfOpen.invoke(wrappedGuard);
+            } catch (Exception ignored) {
+            }
+        }
+    }
+}
diff --git a/src/compat/java/org/apache/harmony/security/utils/AlgNameMapper.java b/src/compat/java/org/apache/harmony/security/utils/AlgNameMapper.java
new file mode 100644
index 0000000..054ed87
--- /dev/null
+++ b/src/compat/java/org/apache/harmony/security/utils/AlgNameMapper.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 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.apache.harmony.security.utils;
+
+import org.conscrypt.NativeCrypto;
+
+public class AlgNameMapper {
+    private AlgNameMapper() {
+    }
+
+    public static String map2AlgName(String oid) {
+        return NativeCrypto.OBJ_txt2nid_longName(oid);
+    }
+
+    public static void setSource(Object o) {
+    }
+}
diff --git a/src/compat/java/org/apache/harmony/security/utils/AlgNameMapperSource.java b/src/compat/java/org/apache/harmony/security/utils/AlgNameMapperSource.java
new file mode 100644
index 0000000..abca36c
--- /dev/null
+++ b/src/compat/java/org/apache/harmony/security/utils/AlgNameMapperSource.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 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.apache.harmony.security.utils;
+
+/**
+ * Provides a mapping source that the {@link AlgNameMapper} can query for
+ * mapping between algorithm names and OIDs.
+ */
+public interface AlgNameMapperSource {
+    public String mapNameToOid(String algName);
+
+    public String mapOidToName(String oid);
+}
diff --git a/src/compat/java/org/conscrypt/Platform.java b/src/compat/java/org/conscrypt/Platform.java
new file mode 100644
index 0000000..d840a14
--- /dev/null
+++ b/src/compat/java/org/conscrypt/Platform.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 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 android.os.Build;
+import android.util.Log;
+import java.io.FileDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.Socket;
+import java.security.InvalidKeyException;
+import java.security.PrivateKey;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.spec.ECParameterSpec;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.X509TrustManager;
+
+/**
+ *
+ */
+public class Platform {
+    private static final String TAG = "Conscrypt";
+
+    private static Method m_getCurveName;
+    static {
+        try {
+            m_getCurveName = ECParameterSpec.class.getDeclaredMethod("getCurveName");
+            m_getCurveName.setAccessible(true);
+        } catch (Exception ignored) {
+        }
+    }
+
+    public static void setup() {
+    }
+
+    public static FileDescriptor getFileDescriptor(Socket s) {
+        try {
+            Field f_impl = Socket.class.getDeclaredField("impl");
+            f_impl.setAccessible(true);
+            Object socketImpl = f_impl.get(s);
+            Class<?> c_socketImpl = Class.forName("java.net.SocketImpl");
+            Field f_fd = c_socketImpl.getDeclaredField("fd");
+            f_fd.setAccessible(true);
+            return (FileDescriptor) f_fd.get(socketImpl);
+        } catch (Exception e) {
+            throw new RuntimeException("Can't get FileDescriptor from socket", e);
+        }
+    }
+
+    public static FileDescriptor getFileDescriptorFromSSLSocket(OpenSSLSocketImpl openSSLSocketImpl) {
+        return getFileDescriptor(openSSLSocketImpl);
+    }
+
+    public static String getCurveName(ECParameterSpec spec) {
+        if (m_getCurveName == null) {
+            return null;
+        }
+        try {
+            return (String) m_getCurveName.invoke(spec);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public static void setCurveName(ECParameterSpec spec, String curveName) {
+        try {
+            Method setCurveName = spec.getClass().getDeclaredMethod("setCurveName", String.class);
+            setCurveName.invoke(spec, curveName);
+        } catch (Exception ignored) {
+        }
+    }
+
+    public static void setSocketTimeout(Socket s, long timeoutMillis) {
+        // TODO: implement this for unbundled
+    }
+
+    public static void setEndpointIdentificationAlgorithm(SSLParameters params,
+            String endpointIdentificationAlgorithm) {
+        // TODO: implement this for unbundled
+    }
+
+    public static String getEndpointIdentificationAlgorithm(SSLParameters params) {
+        // TODO: implement this for unbundled
+        return null;
+    }
+
+    public static void checkServerTrusted(X509TrustManager x509tm, X509Certificate[] chain,
+            String authType, String host) throws CertificateException {
+        // TODO: use reflection to find whether we have TrustManagerImpl
+        /*
+        if (x509tm instanceof TrustManagerImpl) {
+            TrustManagerImpl tm = (TrustManagerImpl) x509tm;
+            tm.checkServerTrusted(chain, authType, host);
+        } else {
+        */
+            x509tm.checkServerTrusted(chain, authType);
+        /*
+        }
+        */
+    }
+
+    /**
+     * Wraps an old AndroidOpenSSL key instance. This is not needed on platform
+     * builds since we didn't backport, so return null. This code is from
+     * Chromium's net/android/java/src/org/chromium/net/DefaultAndroidKeyStore.java
+     */
+    public static OpenSSLKey wrapRsaKey(PrivateKey javaKey) throws InvalidKeyException {
+        // This fixup only applies to pre-JB-MR1
+        if (Build.VERSION.SDK_INT >= 17) {
+            return null;
+        }
+
+        // First, check that this is a proper instance of OpenSSLRSAPrivateKey
+        // or one of its sub-classes.
+        Class<?> superClass;
+        try {
+            superClass = Class
+                    .forName("org.apache.harmony.xnet.provider.jsse.OpenSSLRSAPrivateKey");
+        } catch (Exception e) {
+            // This may happen if the target device has a completely different
+            // implementation of the java.security APIs, compared to vanilla
+            // Android. Highly unlikely, but still possible.
+            Log.e(TAG, "Cannot find system OpenSSLRSAPrivateKey class: " + e);
+            return null;
+        }
+        if (!superClass.isInstance(javaKey)) {
+            // This may happen if the PrivateKey was not created by the
+            // "AndroidOpenSSL"
+            // provider, which should be the default. That could happen if an
+            // OEM decided
+            // to implement a different default provider. Also highly unlikely.
+            Log.e(TAG, "Private key is not an OpenSSLRSAPrivateKey instance, its class name is:"
+                    + javaKey.getClass().getCanonicalName());
+            return null;
+        }
+
+        try {
+            // Use reflection to invoke the 'getOpenSSLKey()' method on
+            // the private key. This returns another Java object that wraps
+            // a native EVP_PKEY. Note that the method is final, so calling
+            // the superclass implementation is ok.
+            Method getKey = superClass.getDeclaredMethod("getOpenSSLKey");
+            getKey.setAccessible(true);
+            Object opensslKey = null;
+            try {
+                opensslKey = getKey.invoke(javaKey);
+            } finally {
+                getKey.setAccessible(false);
+            }
+            if (opensslKey == null) {
+                // Bail when detecting OEM "enhancement".
+                Log.e(TAG, "Could not getOpenSSLKey on instance: " + javaKey.toString());
+                return null;
+            }
+
+            // Use reflection to invoke the 'getPkeyContext' method on the
+            // result of the getOpenSSLKey(). This is an 32-bit integer
+            // which is the address of an EVP_PKEY object. Note that this
+            // method these days returns a 64-bit long, but since this code
+            // path is used for older Android versions, it may still return
+            // a 32-bit int here. To be on the safe side, we cast the return
+            // value via Number rather than directly to Integer or Long.
+            Method getPkeyContext;
+            try {
+                getPkeyContext = opensslKey.getClass().getDeclaredMethod("getPkeyContext");
+            } catch (Exception e) {
+                // Bail here too, something really not working as expected.
+                Log.e(TAG, "No getPkeyContext() method on OpenSSLKey member:" + e);
+                return null;
+            }
+            getPkeyContext.setAccessible(true);
+            long evp_pkey = 0;
+            try {
+                evp_pkey = ((Number) getPkeyContext.invoke(opensslKey)).longValue();
+            } finally {
+                getPkeyContext.setAccessible(false);
+            }
+            if (evp_pkey == 0) {
+                // The PrivateKey is probably rotten for some reason.
+                Log.e(TAG, "getPkeyContext() returned null");
+                return null;
+            }
+            return new OpenSSLKey(evp_pkey);
+        } catch (Exception e) {
+            Log.e(TAG, "Error during conversion of privatekey instance: " + javaKey.toString(), e);
+            return null;
+        }
+    }
+
+    /**
+     * Logs to the system EventLog system.
+     */
+    public static void logEvent(String message) {
+        try {
+            Class processClass = Class.forName("android.os.Process");
+            Object processInstance = processClass.newInstance();
+            Method myUidMethod = processClass.getMethod("myUid", (Class[]) null);
+            int uid = (Integer) myUidMethod.invoke(processInstance);
+
+            Class eventLogClass = Class.forName("android.util.EventLog");
+            Object eventLogInstance = eventLogClass.newInstance();
+            Method writeEventMethod = eventLogClass.getMethod("writeEvent",
+                    new Class[] { Integer.TYPE, Object[].class });
+            writeEventMethod.invoke(eventLogInstance, 0x534e4554 /* SNET */,
+                    new Object[] { "conscrypt", uid, message });
+        } catch (Exception e) {
+            // Fail silently
+        }
+    }
+}
diff --git a/src/compat/native/JNIHelp.cpp b/src/compat/native/JNIHelp.cpp
new file mode 100644
index 0000000..a4aa996
--- /dev/null
+++ b/src/compat/native/JNIHelp.cpp
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2006 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.
+ */
+
+#define LOG_TAG "JNIHelp"
+
+#include "JNIHelp.h"
+
+#include <android/log.h>
+#include "log_compat.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+/**
+ * Equivalent to ScopedLocalRef, but for C_JNIEnv instead. (And slightly more powerful.)
+ */
+template<typename T>
+class scoped_local_ref {
+public:
+    scoped_local_ref(C_JNIEnv* env, T localRef = NULL)
+    : mEnv(env), mLocalRef(localRef)
+    {
+    }
+
+    ~scoped_local_ref() {
+        reset();
+    }
+
+    void reset(T localRef = NULL) {
+        if (mLocalRef != NULL) {
+            (*mEnv)->DeleteLocalRef(reinterpret_cast<JNIEnv*>(mEnv), mLocalRef);
+            mLocalRef = localRef;
+        }
+    }
+
+    T get() const {
+        return mLocalRef;
+    }
+
+private:
+    C_JNIEnv* mEnv;
+    T mLocalRef;
+
+    // Disallow copy and assignment.
+    scoped_local_ref(const scoped_local_ref&);
+    void operator=(const scoped_local_ref&);
+};
+
+static jclass findClass(C_JNIEnv* env, const char* className) {
+    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
+    return (*env)->FindClass(e, className);
+}
+
+extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
+    const JNINativeMethod* gMethods, int numMethods)
+{
+    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
+
+    ALOGV("Registering %s's %d native methods...", className, numMethods);
+
+    scoped_local_ref<jclass> c(env, findClass(env, className));
+    if (c.get() == NULL) {
+        char* msg;
+        asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className);
+        e->FatalError(msg);
+    }
+
+    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
+        char* msg;
+        asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className);
+        e->FatalError(msg);
+    }
+
+    return 0;
+}
+
+extern "C" int jniThrowException(C_JNIEnv* c_env, const char* className, const char* msg) {
+    JNIEnv* env = reinterpret_cast<JNIEnv*>(c_env);
+    jclass exceptionClass = env->FindClass(className);
+
+    if (exceptionClass == NULL) {
+        ALOGD("Unable to find exception class %s", className);
+        /* ClassNotFoundException now pending */
+        return -1;
+    }
+
+    if (env->ThrowNew(exceptionClass, msg) != JNI_OK) {
+        ALOGD("Failed throwing '%s' '%s'", className, msg);
+        /* an exception, most likely OOM, will now be pending */
+        return -1;
+    }
+
+    env->DeleteLocalRef(exceptionClass);
+    return 0;
+}
+
+int jniThrowExceptionFmt(C_JNIEnv* env, const char* className, const char* fmt, va_list args) {
+    char msgBuf[512];
+    vsnprintf(msgBuf, sizeof(msgBuf), fmt, args);
+    return jniThrowException(env, className, msgBuf);
+}
+
+int jniThrowNullPointerException(C_JNIEnv* env, const char* msg) {
+    return jniThrowException(env, "java/lang/NullPointerException", msg);
+}
+
+int jniThrowRuntimeException(C_JNIEnv* env, const char* msg) {
+    return jniThrowException(env, "java/lang/RuntimeException", msg);
+}
+
+int jniThrowIOException(C_JNIEnv* env, int errnum) {
+    char buffer[80];
+    const char* message = jniStrError(errnum, buffer, sizeof(buffer));
+    return jniThrowException(env, "java/io/IOException", message);
+}
+
+const char* jniStrError(int errnum, char* buf, size_t buflen) {
+#if __GLIBC__
+    // Note: glibc has a nonstandard strerror_r that returns char* rather than POSIX's int.
+    // char *strerror_r(int errnum, char *buf, size_t n);
+    return strerror_r(errnum, buf, buflen);
+#else
+    int rc = strerror_r(errnum, buf, buflen);
+    if (rc != 0) {
+        // (POSIX only guarantees a value other than 0. The safest
+        // way to implement this function is to use C++ and overload on the
+        // type of strerror_r to accurately distinguish GNU from POSIX.)
+        snprintf(buf, buflen, "errno %d", errnum);
+    }
+    return buf;
+#endif
+}
+
+int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor) {
+    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
+    scoped_local_ref<jclass> localClass(env, e->FindClass("java/io/FileDescriptor"));
+    static jfieldID fid = e->GetFieldID(localClass.get(), "descriptor", "I");
+    if (fileDescriptor != NULL) {
+        return (*env)->GetIntField(e, fileDescriptor, fid);
+    } else {
+        return -1;
+    }
+}
diff --git a/src/compat/native/JNIHelp.h b/src/compat/native/JNIHelp.h
new file mode 100644
index 0000000..cfab8f7
--- /dev/null
+++ b/src/compat/native/JNIHelp.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+/*
+ * JNI helper functions.
+ *
+ * This file may be included by C or C++ code, which is trouble because jni.h
+ * uses different typedefs for JNIEnv in each language.
+ *
+ * TODO: remove C support.
+ */
+#ifndef NATIVEHELPER_JNIHELP_H_
+#define NATIVEHELPER_JNIHELP_H_
+
+#include "jni.h"
+#include <errno.h>
+#include <unistd.h>
+
+#ifndef NELEM
+# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Register one or more native methods with a particular class.
+ * "className" looks like "java/lang/String". Aborts on failure.
+ * TODO: fix all callers and change the return type to void.
+ */
+int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods);
+
+/*
+ * Throw an exception with the specified class and an optional message.
+ *
+ * The "className" argument will be passed directly to FindClass, which
+ * takes strings with slashes (e.g. "java/lang/Object").
+ *
+ * If an exception is currently pending, we log a warning message and
+ * clear it.
+ *
+ * Returns 0 on success, nonzero if something failed (e.g. the exception
+ * class couldn't be found, so *an* exception will still be pending).
+ *
+ * Currently aborts the VM if it can't throw the exception.
+ */
+int jniThrowException(C_JNIEnv* env, const char* className, const char* msg);
+
+/*
+ * Throw a java.lang.NullPointerException, with an optional message.
+ */
+int jniThrowNullPointerException(C_JNIEnv* env, const char* msg);
+
+/*
+ * Throw a java.lang.RuntimeException, with an optional message.
+ */
+int jniThrowRuntimeException(C_JNIEnv* env, const char* msg);
+
+/*
+ * Throw a java.io.IOException, generating the message from errno.
+ */
+int jniThrowIOException(C_JNIEnv* env, int errnum);
+
+/*
+ * Return a pointer to a locale-dependent error string explaining errno
+ * value 'errnum'. The returned pointer may or may not be equal to 'buf'.
+ * This function is thread-safe (unlike strerror) and portable (unlike
+ * strerror_r).
+ */
+const char* jniStrError(int errnum, char* buf, size_t buflen);
+
+/*
+ * Returns a new java.io.FileDescriptor for the given int fd.
+ */
+jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd);
+
+/*
+ * Returns the int fd from a java.io.FileDescriptor.
+ */
+int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor);
+
+/*
+ * Sets the int fd in a java.io.FileDescriptor.
+ */
+void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value);
+
+/*
+ * Returns the reference from a java.lang.ref.Reference.
+ */
+jobject jniGetReferent(C_JNIEnv* env, jobject ref);
+
+/*
+ * Log a message and an exception.
+ * If exception is NULL, logs the current exception in the JNI environment.
+ */
+void jniLogException(C_JNIEnv* env, int priority, const char* tag, jthrowable exception);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+/*
+ * For C++ code, we provide inlines that map to the C functions.  g++ always
+ * inlines these, even on non-optimized builds.
+ */
+#if defined(__cplusplus)
+inline int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) {
+    return jniRegisterNativeMethods(&env->functions, className, gMethods, numMethods);
+}
+
+inline int jniThrowException(JNIEnv* env, const char* className, const char* msg) {
+    return jniThrowException(&env->functions, className, msg);
+}
+
+extern "C" int jniThrowExceptionFmt(C_JNIEnv* env, const char* className, const char* fmt, va_list args);
+
+/*
+ * Equivalent to jniThrowException but with a printf-like format string and
+ * variable-length argument list. This is only available in C++.
+ */
+inline int jniThrowExceptionFmt(JNIEnv* env, const char* className, const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    return jniThrowExceptionFmt(&env->functions, className, fmt, args);
+    va_end(args);
+}
+
+inline int jniThrowNullPointerException(JNIEnv* env, const char* msg) {
+    return jniThrowNullPointerException(&env->functions, msg);
+}
+
+inline int jniThrowRuntimeException(JNIEnv* env, const char* msg) {
+    return jniThrowRuntimeException(&env->functions, msg);
+}
+
+inline int jniThrowIOException(JNIEnv* env, int errnum) {
+    return jniThrowIOException(&env->functions, errnum);
+}
+
+inline jobject jniCreateFileDescriptor(JNIEnv* env, int fd) {
+    return jniCreateFileDescriptor(&env->functions, fd);
+}
+
+inline int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor) {
+    return jniGetFDFromFileDescriptor(&env->functions, fileDescriptor);
+}
+
+inline void jniSetFileDescriptorOfFD(JNIEnv* env, jobject fileDescriptor, int value) {
+    jniSetFileDescriptorOfFD(&env->functions, fileDescriptor, value);
+}
+
+inline jobject jniGetReferent(JNIEnv* env, jobject ref) {
+    return jniGetReferent(&env->functions, ref);
+}
+
+inline void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowable exception = NULL) {
+    jniLogException(&env->functions, priority, tag, exception);
+}
+
+#endif
+
+/*
+ * TEMP_FAILURE_RETRY is defined by some, but not all, versions of
+ * <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
+ * not already defined, then define it here.
+ */
+#ifndef TEMP_FAILURE_RETRY
+/* Used to retry syscalls that can return EINTR. */
+#define TEMP_FAILURE_RETRY(exp) ({         \
+    typeof (exp) _rc;                      \
+    do {                                   \
+        _rc = (exp);                       \
+    } while (_rc == -1 && errno == EINTR); \
+    _rc; })
+#endif
+
+#endif  /* NATIVEHELPER_JNIHELP_H_ */
diff --git a/src/compat/native/NetFd.h b/src/compat/native/NetFd.h
new file mode 100644
index 0000000..235b057
--- /dev/null
+++ b/src/compat/native/NetFd.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef NET_FD_H_included
+#define NET_FD_H_included
+
+/**
+ * Wraps access to the int inside a java.io.FileDescriptor, taking care of throwing exceptions.
+ */
+class NetFd {
+public:
+    NetFd(JNIEnv* env, jobject fileDescriptor)
+        : mEnv(env), mFileDescriptor(fileDescriptor), mFd(-1)
+    {
+    }
+
+    bool isClosed() {
+        mFd = jniGetFDFromFileDescriptor(mEnv, mFileDescriptor);
+        bool closed = (mFd == -1);
+        if (closed) {
+            jniThrowException(mEnv, "java/net/SocketException", "Socket closed");
+        }
+        return closed;
+    }
+
+    int get() const {
+        return mFd;
+    }
+
+private:
+    JNIEnv* mEnv;
+    jobject mFileDescriptor;
+    int mFd;
+
+    // Disallow copy and assignment.
+    NetFd(const NetFd&);
+    void operator=(const NetFd&);
+};
+
+/**
+ * Used to retry syscalls that can return EINTR. This differs from TEMP_FAILURE_RETRY in that
+ * it also considers the case where the reason for failure is that another thread called
+ * Socket.close.
+ */
+#define NET_FAILURE_RETRY(fd, exp) ({               \
+    typeof (exp) _rc;                               \
+    do {                                            \
+        _rc = (exp);                                \
+        if (_rc == -1) {                            \
+            if (fd.isClosed() || errno != EINTR) {  \
+                break;                              \
+            }                                       \
+        }                                           \
+    } while (_rc == -1);                            \
+    _rc; })
+
+#endif // NET_FD_H_included
diff --git a/src/compat/native/ScopedLocalRef.h b/src/compat/native/ScopedLocalRef.h
new file mode 100644
index 0000000..71d5776
--- /dev/null
+++ b/src/compat/native/ScopedLocalRef.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef SCOPED_LOCAL_REF_H_included
+#define SCOPED_LOCAL_REF_H_included
+
+#include "jni.h"
+
+#include <stddef.h>
+
+// A smart pointer that deletes a JNI local reference when it goes out of scope.
+template<typename T>
+class ScopedLocalRef {
+public:
+    ScopedLocalRef(JNIEnv* env, T localRef) : mEnv(env), mLocalRef(localRef) {
+    }
+
+    ~ScopedLocalRef() {
+        reset();
+    }
+
+    void reset(T ptr = NULL) {
+        if (ptr != mLocalRef) {
+            if (mLocalRef != NULL) {
+                mEnv->DeleteLocalRef(mLocalRef);
+            }
+            mLocalRef = ptr;
+        }
+    }
+
+    T release() __attribute__((warn_unused_result)) {
+        T localRef = mLocalRef;
+        mLocalRef = NULL;
+        return localRef;
+    }
+
+    T get() const {
+        return mLocalRef;
+    }
+
+private:
+    JNIEnv* mEnv;
+    T mLocalRef;
+
+    // Disallow copy and assignment.
+    ScopedLocalRef(const ScopedLocalRef&);
+    void operator=(const ScopedLocalRef&);
+};
+
+#endif  // SCOPED_LOCAL_REF_H_included
diff --git a/src/compat/native/ScopedPrimitiveArray.h b/src/compat/native/ScopedPrimitiveArray.h
new file mode 100644
index 0000000..d797b9d
--- /dev/null
+++ b/src/compat/native/ScopedPrimitiveArray.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef SCOPED_PRIMITIVE_ARRAY_H_included
+#define SCOPED_PRIMITIVE_ARRAY_H_included
+
+#include "JNIHelp.h"
+
+// ScopedBooleanArrayRO, ScopedByteArrayRO, ScopedCharArrayRO, ScopedDoubleArrayRO,
+// ScopedFloatArrayRO, ScopedIntArrayRO, ScopedLongArrayRO, and ScopedShortArrayRO provide
+// convenient read-only access to Java arrays from JNI code. This is cheaper than read-write
+// access and should be used by default.
+#define INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(PRIMITIVE_TYPE, NAME) \
+    class Scoped ## NAME ## ArrayRO { \
+    public: \
+        explicit Scoped ## NAME ## ArrayRO(JNIEnv* env) \
+        : mEnv(env), mJavaArray(NULL), mRawArray(NULL) {} \
+        Scoped ## NAME ## ArrayRO(JNIEnv* env, PRIMITIVE_TYPE ## Array javaArray) \
+        : mEnv(env), mJavaArray(javaArray), mRawArray(NULL) { \
+            if (mJavaArray == NULL) { \
+                jniThrowNullPointerException(mEnv, NULL); \
+            } else { \
+                mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, NULL); \
+            } \
+        } \
+        ~Scoped ## NAME ## ArrayRO() { \
+            if (mRawArray) { \
+                mEnv->Release ## NAME ## ArrayElements(mJavaArray, mRawArray, JNI_ABORT); \
+            } \
+        } \
+        void reset(PRIMITIVE_TYPE ## Array javaArray) { \
+            mJavaArray = javaArray; \
+            mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, NULL); \
+        } \
+        const PRIMITIVE_TYPE* get() const { return mRawArray; } \
+        PRIMITIVE_TYPE ## Array getJavaArray() const { return mJavaArray; } \
+        const PRIMITIVE_TYPE& operator[](size_t n) const { return mRawArray[n]; } \
+        size_t size() const { return mEnv->GetArrayLength(mJavaArray); } \
+    private: \
+        JNIEnv* mEnv; \
+        PRIMITIVE_TYPE ## Array mJavaArray; \
+        PRIMITIVE_TYPE* mRawArray; \
+        Scoped ## NAME ## ArrayRO(const Scoped ## NAME ## ArrayRO&); \
+        void operator=(const Scoped ## NAME ## ArrayRO&); \
+    }
+
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jboolean, Boolean);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jbyte, Byte);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jchar, Char);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jdouble, Double);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jfloat, Float);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jint, Int);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jlong, Long);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jshort, Short);
+
+#undef INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO
+
+// ScopedBooleanArrayRW, ScopedByteArrayRW, ScopedCharArrayRW, ScopedDoubleArrayRW,
+// ScopedFloatArrayRW, ScopedIntArrayRW, ScopedLongArrayRW, and ScopedShortArrayRW provide
+// convenient read-write access to Java arrays from JNI code. These are more expensive,
+// since they entail a copy back onto the Java heap, and should only be used when necessary.
+#define INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(PRIMITIVE_TYPE, NAME) \
+    class Scoped ## NAME ## ArrayRW { \
+    public: \
+        explicit Scoped ## NAME ## ArrayRW(JNIEnv* env) \
+        : mEnv(env), mJavaArray(NULL), mRawArray(NULL) {} \
+        Scoped ## NAME ## ArrayRW(JNIEnv* env, PRIMITIVE_TYPE ## Array javaArray) \
+        : mEnv(env), mJavaArray(javaArray), mRawArray(NULL) { \
+            if (mJavaArray == NULL) { \
+                jniThrowNullPointerException(mEnv, NULL); \
+            } else { \
+                mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, NULL); \
+            } \
+        } \
+        ~Scoped ## NAME ## ArrayRW() { \
+            if (mRawArray) { \
+                mEnv->Release ## NAME ## ArrayElements(mJavaArray, mRawArray, 0); \
+            } \
+        } \
+        void reset(PRIMITIVE_TYPE ## Array javaArray) { \
+            mJavaArray = javaArray; \
+            mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, NULL); \
+        } \
+        const PRIMITIVE_TYPE* get() const { return mRawArray; } \
+        PRIMITIVE_TYPE ## Array getJavaArray() const { return mJavaArray; } \
+        const PRIMITIVE_TYPE& operator[](size_t n) const { return mRawArray[n]; } \
+        PRIMITIVE_TYPE* get() { return mRawArray; } \
+        PRIMITIVE_TYPE& operator[](size_t n) { return mRawArray[n]; } \
+        size_t size() const { return mEnv->GetArrayLength(mJavaArray); } \
+    private: \
+        JNIEnv* mEnv; \
+        PRIMITIVE_TYPE ## Array mJavaArray; \
+        PRIMITIVE_TYPE* mRawArray; \
+        Scoped ## NAME ## ArrayRW(const Scoped ## NAME ## ArrayRW&); \
+        void operator=(const Scoped ## NAME ## ArrayRW&); \
+    }
+
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jboolean, Boolean);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jbyte, Byte);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jchar, Char);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jdouble, Double);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jfloat, Float);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jint, Int);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jlong, Long);
+INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jshort, Short);
+
+#undef INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW
+
+#endif  // SCOPED_PRIMITIVE_ARRAY_H_included
diff --git a/src/compat/native/ScopedUtfChars.h b/src/compat/native/ScopedUtfChars.h
new file mode 100644
index 0000000..7761450
--- /dev/null
+++ b/src/compat/native/ScopedUtfChars.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef SCOPED_UTF_CHARS_H_included
+#define SCOPED_UTF_CHARS_H_included
+
+#include "JNIHelp.h"
+#include <string.h>
+
+// A smart pointer that provides read-only access to a Java string's UTF chars.
+// Unlike GetStringUTFChars, we throw NullPointerException rather than abort if
+// passed a null jstring, and c_str will return NULL.
+// This makes the correct idiom very simple:
+//
+//   ScopedUtfChars name(env, java_name);
+//   if (name.c_str() == NULL) {
+//     return NULL;
+//   }
+class ScopedUtfChars {
+ public:
+  ScopedUtfChars(JNIEnv* env, jstring s) : env_(env), string_(s) {
+    if (s == NULL) {
+      utf_chars_ = NULL;
+      jniThrowNullPointerException(env, NULL);
+    } else {
+      utf_chars_ = env->GetStringUTFChars(s, NULL);
+    }
+  }
+
+  ~ScopedUtfChars() {
+    if (utf_chars_) {
+      env_->ReleaseStringUTFChars(string_, utf_chars_);
+    }
+  }
+
+  const char* c_str() const {
+    return utf_chars_;
+  }
+
+  size_t size() const {
+    return strlen(utf_chars_);
+  }
+
+  const char& operator[](size_t n) const {
+    return utf_chars_[n];
+  }
+
+ private:
+  JNIEnv* env_;
+  jstring string_;
+  const char* utf_chars_;
+
+  // Disallow copy and assignment.
+  ScopedUtfChars(const ScopedUtfChars&);
+  void operator=(const ScopedUtfChars&);
+};
+
+#endif  // SCOPED_UTF_CHARS_H_included
diff --git a/src/compat/native/UniquePtr.h b/src/compat/native/UniquePtr.h
new file mode 100644
index 0000000..50f75b2
--- /dev/null
+++ b/src/compat/native/UniquePtr.h
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef UNIQUE_PTR_H_included
+#define UNIQUE_PTR_H_included
+
+#include <cstdlib> // For NULL.
+
+// This is a fake declaration of std::swap to avoid including <algorithm>
+namespace std {
+template <class T> void swap(T&, T&);
+}
+
+// Default deleter for pointer types.
+template <typename T>
+struct DefaultDelete {
+    enum { type_must_be_complete = sizeof(T) };
+    DefaultDelete() {}
+    void operator()(T* p) const {
+        delete p;
+    }
+};
+
+// Default deleter for array types.
+template <typename T>
+struct DefaultDelete<T[]> {
+    enum { type_must_be_complete = sizeof(T) };
+    void operator()(T* p) const {
+        delete[] p;
+    }
+};
+
+// A smart pointer that deletes the given pointer on destruction.
+// Equivalent to C++0x's std::unique_ptr (a combination of boost::scoped_ptr
+// and boost::scoped_array).
+// Named to be in keeping with Android style but also to avoid
+// collision with any other implementation, until we can switch over
+// to unique_ptr.
+// Use thus:
+//   UniquePtr<C> c(new C);
+template <typename T, typename D = DefaultDelete<T> >
+class UniquePtr {
+public:
+    // Construct a new UniquePtr, taking ownership of the given raw pointer.
+    explicit UniquePtr(T* ptr = NULL) : mPtr(ptr) {
+    }
+
+    ~UniquePtr() {
+        reset();
+    }
+
+    // Accessors.
+    T& operator*() const { return *mPtr; }
+    T* operator->() const { return mPtr; }
+    T* get() const { return mPtr; }
+
+    // Returns the raw pointer and hands over ownership to the caller.
+    // The pointer will not be deleted by UniquePtr.
+    T* release() __attribute__((warn_unused_result)) {
+        T* result = mPtr;
+        mPtr = NULL;
+        return result;
+    }
+
+    // Takes ownership of the given raw pointer.
+    // If this smart pointer previously owned a different raw pointer, that
+    // raw pointer will be freed.
+    void reset(T* ptr = NULL) {
+        if (ptr != mPtr) {
+            D()(mPtr);
+            mPtr = ptr;
+        }
+    }
+
+    // Swap with another unique pointer.
+    void swap(UniquePtr<T>& other) {
+      std::swap(mPtr, other.mPtr);
+    }
+
+private:
+    // The raw pointer.
+    T* mPtr;
+
+    // Comparing unique pointers is probably a mistake, since they're unique.
+    template <typename T2> bool operator==(const UniquePtr<T2>& p) const;
+    template <typename T2> bool operator!=(const UniquePtr<T2>& p) const;
+
+    // Disallow copy and assignment.
+    UniquePtr(const UniquePtr&);
+    void operator=(const UniquePtr&);
+};
+
+// Partial specialization for array types. Like std::unique_ptr, this removes
+// operator* and operator-> but adds operator[].
+template <typename T, typename D>
+class UniquePtr<T[], D> {
+public:
+    explicit UniquePtr(T* ptr = NULL) : mPtr(ptr) {
+    }
+
+    ~UniquePtr() {
+        reset();
+    }
+
+    T& operator[](size_t i) const {
+        return mPtr[i];
+    }
+    T* get() const { return mPtr; }
+
+    T* release() __attribute__((warn_unused_result)) {
+        T* result = mPtr;
+        mPtr = NULL;
+        return result;
+    }
+
+    void reset(T* ptr = NULL) {
+        if (ptr != mPtr) {
+            D()(mPtr);
+            mPtr = ptr;
+        }
+    }
+
+private:
+    T* mPtr;
+
+    // Disallow copy and assignment.
+    UniquePtr(const UniquePtr&);
+    void operator=(const UniquePtr&);
+};
+
+#if UNIQUE_PTR_TESTS
+
+// Run these tests with:
+// g++ -g -DUNIQUE_PTR_TESTS -x c++ UniquePtr.h && ./a.out
+
+#include <stdio.h>
+
+static void assert(bool b) {
+    if (!b) {
+        fprintf(stderr, "FAIL\n");
+        abort();
+    }
+    fprintf(stderr, "OK\n");
+}
+static int cCount = 0;
+struct C {
+    C() { ++cCount; }
+    ~C() { --cCount; }
+};
+static bool freed = false;
+struct Freer {
+    void operator()(int* p) {
+        assert(*p == 123);
+        free(p);
+        freed = true;
+    }
+};
+
+int main(int argc, char* argv[]) {
+    //
+    // UniquePtr<T> tests...
+    //
+
+    // Can we free a single object?
+    {
+        UniquePtr<C> c(new C);
+        assert(cCount == 1);
+    }
+    assert(cCount == 0);
+    // Does release work?
+    C* rawC;
+    {
+        UniquePtr<C> c(new C);
+        assert(cCount == 1);
+        rawC = c.release();
+    }
+    assert(cCount == 1);
+    delete rawC;
+    // Does reset work?
+    {
+        UniquePtr<C> c(new C);
+        assert(cCount == 1);
+        c.reset(new C);
+        assert(cCount == 1);
+    }
+    assert(cCount == 0);
+
+    //
+    // UniquePtr<T[]> tests...
+    //
+
+    // Can we free an array?
+    {
+        UniquePtr<C[]> cs(new C[4]);
+        assert(cCount == 4);
+    }
+    assert(cCount == 0);
+    // Does release work?
+    {
+        UniquePtr<C[]> c(new C[4]);
+        assert(cCount == 4);
+        rawC = c.release();
+    }
+    assert(cCount == 4);
+    delete[] rawC;
+    // Does reset work?
+    {
+        UniquePtr<C[]> c(new C[4]);
+        assert(cCount == 4);
+        c.reset(new C[2]);
+        assert(cCount == 2);
+    }
+    assert(cCount == 0);
+
+    //
+    // Custom deleter tests...
+    //
+    assert(!freed);
+    {
+        UniquePtr<int, Freer> i(reinterpret_cast<int*>(malloc(sizeof(int))));
+        *i = 123;
+    }
+    assert(freed);
+    return 0;
+}
+#endif
+
+#endif  // UNIQUE_PTR_H_included
diff --git a/src/compat/native/log_compat.h b/src/compat/native/log_compat.h
new file mode 100644
index 0000000..2bd57c4
--- /dev/null
+++ b/src/compat/native/log_compat.h
@@ -0,0 +1,6 @@
+#define ALOGD(...) \
+            __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);
+#define ALOGE(...) \
+            __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);
+#define ALOGV(...) \
+            __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__);
diff --git a/src/main/java/org/conscrypt/AbstractSessionContext.java b/src/main/java/org/conscrypt/AbstractSessionContext.java
index cac1fb7..0ba06ea 100644
--- a/src/main/java/org/conscrypt/AbstractSessionContext.java
+++ b/src/main/java/org/conscrypt/AbstractSessionContext.java
@@ -295,7 +295,7 @@
     }
 
     static void log(Throwable t) {
-        System.logW("Error converting session.", t);
+        new Exception("Error converting session", t).printStackTrace();
     }
 
     @Override
diff --git a/src/main/java/org/conscrypt/AlertException.java b/src/main/java/org/conscrypt/AlertException.java
deleted file mode 100644
index a483021..0000000
--- a/src/main/java/org/conscrypt/AlertException.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- *  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 javax.net.ssl.SSLException;
-
-/**
- * This exception is used to signal that a fatal alert has occurred while working through the
- * protocol.
- */
-public class AlertException extends RuntimeException {
-
-    private static final long serialVersionUID = -4448327177165687581L;
-    // SSLException to be thrown to application side
-    private final SSLException reason;
-    // alert description code
-    private final byte description;
-
-    /**
-     * Constructs the instance.
-     *
-     * @param description The alert description code from {@link AlertProtocol}
-     * @param reason The SSLException to be thrown to application side after alert processing
-     *            (sending the record with alert, shutdown work, etc).
-     * @see AlertProtocol
-     */
-    protected AlertException(byte description, SSLException reason) {
-        super(reason);
-        this.reason = reason;
-        this.description = description;
-    }
-
-    /**
-     * Returns the reason of alert. This reason should be rethrown after alert processing.
-     *
-     * @return the reason of alert.
-     */
-    protected SSLException getReason() {
-        return reason;
-    }
-
-    /**
-     * Returns alert's description code.
-     *
-     * @return alert description code from {@link AlertProtocol}
-     * @see AlertProtocol for more information about possible reason codes.
-     */
-    protected byte getDescriptionCode() {
-        return description;
-    }
-}
diff --git a/src/main/java/org/conscrypt/AlertProtocol.java b/src/main/java/org/conscrypt/AlertProtocol.java
deleted file mode 100644
index 0330e79..0000000
--- a/src/main/java/org/conscrypt/AlertProtocol.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- *  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;
-
-/**
- * This class encapsulates the functionality of Alert Protocol.
- * Constant values are taken according to the TLS v1 specification
- * (http://www.ietf.org/rfc/rfc2246.txt), p 7.2.
- */
-public class AlertProtocol {
-
-    // ------------------------ AlertLevel codes --------------------------
-    /**
-     * Defines the severity of alert as warning
-     */
-    protected static final byte WARNING = 1;
-    /**
-     * Defines the severity of alert as fatal
-     */
-    protected static final byte FATAL = 2;
-
-    // --------------------- AlertDescription codes -----------------------
-    /**
-     * Defines the description code of the close_notify alert
-     */
-    protected static final byte CLOSE_NOTIFY = 0;
-    /**
-     * Defines the description code of the unexpected_message alert
-     */
-    protected static final byte UNEXPECTED_MESSAGE = 10;
-    /**
-     * Defines the description code of the bad_record_mac alert
-     */
-    protected static final byte BAD_RECORD_MAC = 20;
-    /**
-     * Defines the description code of the decryption_failed alert
-     */
-    protected static final byte DECRYPTION_FAILED = 21;
-    /**
-     * Defines the description code of the record_overflow alert
-     */
-    protected static final byte RECORD_OVERFLOW = 22;
-    /**
-     * Defines the description code of the decompression_failure alert
-     */
-    protected static final byte DECOMPRESSION_FAILURE = 30;
-    /**
-     * Defines the description code of the handshake_failure alert
-     */
-    protected static final byte HANDSHAKE_FAILURE = 40;
-    /**
-     * Defines the description code of the bad_certificate alert
-     */
-    protected static final byte BAD_CERTIFICATE = 42;
-    /**
-     * Defines the description code of the unsupported_certificate alert
-     */
-    protected static final byte UNSUPPORTED_CERTIFICATE = 43;
-    /**
-     * Defines the description code of the certificate_revoked alert
-     */
-    protected static final byte CERTIFICATE_REVOKED = 44;
-    /**
-     * Defines the description code of the certificate_expired alert
-     */
-    protected static final byte CERTIFICATE_EXPIRED = 45;
-    /**
-     * Defines the description code of the certificate_unknown alert
-     */
-    protected static final byte CERTIFICATE_UNKNOWN = 46;
-    /**
-     * Defines the description code of the illegal_parameter alert
-     */
-    protected static final byte ILLEGAL_PARAMETER = 47;
-    /**
-     * Defines the description code of the unknown_ca alert
-     */
-    protected static final byte UNKNOWN_CA = 48;
-    /**
-     * Defines the description code of the access_denied alert
-     */
-    protected static final byte ACCESS_DENIED = 49;
-    /**
-     * Defines the description code of the decode_error alert
-     */
-    protected static final byte DECODE_ERROR = 50;
-    /**
-     * Defines the description code of the decrypt_error alert
-     */
-    protected static final byte DECRYPT_ERROR = 51;
-    /**
-     * Defines the description code of the export_restriction alert
-     */
-    protected static final byte EXPORT_RESTRICTION = 60;
-    /**
-     * Defines the description code of the protocol_version alert
-     */
-    protected static final byte PROTOCOL_VERSION = 70;
-    /**
-     * Defines the description code of the insufficient_security alert
-     */
-    protected static final byte INSUFFICIENT_SECURITY = 71;
-    /**
-     * Defines the description code of the internal_error alert
-     */
-    protected static final byte INTERNAL_ERROR = 80;
-    /**
-     * Defines the description code of the user_canceled alert
-     */
-    protected static final byte USER_CANCELED = 90;
-    /**
-     * Defines the description code of the no_renegotiation alert
-     */
-    protected static final byte NO_RENEGOTIATION = 100;
-    // holds level and description codes
-    private final byte[] alert = new byte[2];
-    // record protocol to be used to wrap the alerts
-    private SSLRecordProtocol recordProtocol;
-
-    private Logger.Stream logger = Logger.getStream("alert");
-
-    /**
-     * Creates the instance of AlertProtocol.
-     * Note that class is not ready to work without providing of
-     * record protocol
-     * @see #setRecordProtocol
-     */
-    protected AlertProtocol() {}
-
-    /**
-     * Sets up the record protocol to be used by this allert protocol.
-     */
-    protected void setRecordProtocol(SSLRecordProtocol recordProtocol) {
-        this.recordProtocol = recordProtocol;
-    }
-
-    /**
-     * Reports an alert to be sent/received by transport.
-     * This method is usually called during processing
-     * of the income TSL record: if it contains alert message from another
-     * peer, or if warning alert occured during the processing of the
-     * message and this warning should be sent to another peer.
-     * @param level alert level code
-     * @param description alert description code
-     */
-    protected void alert(byte level, byte description) {
-        if (logger != null) {
-            logger.println("Alert.alert: "+level+" "+description);
-        }
-        this.alert[0] = level;
-        this.alert[1] = description;
-    }
-
-    /**
-     * Returns the description code of alert or -100 if there
-     * is no alert.
-     */
-    protected byte getDescriptionCode() {
-        return (alert[0] != 0) ? alert[1] : -100;
-    }
-
-    /**
-     * Resets the protocol to be in "no alert" state.
-     * This method shoud be called after processing of the reported alert.
-     */
-    protected void setProcessed() {
-        // free the info about alert
-        if (logger != null) {
-            logger.println("Alert.setProcessed");
-        }
-        this.alert[0] = 0;
-    }
-
-    /**
-     * Checks if any alert has occured.
-     */
-    protected boolean hasAlert() {
-        return (alert[0] != 0);
-    }
-
-    /**
-     * Checks if occured alert is fatal alert.
-     */
-    protected boolean isFatalAlert() {
-        return (alert[0] == 2);
-    }
-
-    /**
-     * Returns the string representation of occured alert.
-     * If no alert has occured null is returned.
-     */
-    protected String getAlertDescription() {
-        switch (alert[1]) {
-        case CLOSE_NOTIFY:
-            return "close_notify";
-        case UNEXPECTED_MESSAGE:
-            return "unexpected_message";
-        case BAD_RECORD_MAC:
-            return "bad_record_mac";
-        case DECRYPTION_FAILED:
-            return "decryption_failed";
-        case RECORD_OVERFLOW:
-            return "record_overflow";
-        case DECOMPRESSION_FAILURE:
-            return "decompression_failure";
-        case HANDSHAKE_FAILURE:
-            return "handshake_failure";
-        case BAD_CERTIFICATE:
-            return "bad_certificate";
-        case UNSUPPORTED_CERTIFICATE:
-            return "unsupported_certificate";
-        case CERTIFICATE_REVOKED:
-            return "certificate_revoked";
-        case CERTIFICATE_EXPIRED:
-            return "certificate_expired";
-        case CERTIFICATE_UNKNOWN:
-            return "certificate_unknown";
-        case ILLEGAL_PARAMETER:
-            return "illegal_parameter";
-        case UNKNOWN_CA:
-            return "unknown_ca";
-        case ACCESS_DENIED:
-            return "access_denied";
-        case DECODE_ERROR:
-            return "decode_error";
-        case DECRYPT_ERROR:
-            return "decrypt_error";
-        case EXPORT_RESTRICTION:
-            return "export_restriction";
-        case PROTOCOL_VERSION:
-            return "protocol_version";
-        case INSUFFICIENT_SECURITY:
-            return "insufficient_security";
-        case INTERNAL_ERROR:
-            return "internal_error";
-        case USER_CANCELED:
-            return "user_canceled";
-        case NO_RENEGOTIATION:
-            return "no_renegotiation";
-        }
-        return null;
-    }
-
-    /**
-     * Returns the record with reported alert message.
-     * The returned array of bytes is ready to be sent to another peer.
-     * Note, that this method does not automatically set the state of alert
-     * protocol in "no alert" state, so after wrapping the method setProcessed
-     * should be called.
-     */
-    protected byte[] wrap() {
-        byte[] res = recordProtocol.wrap(ContentType.ALERT, alert, 0, 2);
-        return res;
-    }
-
-    /**
-     * Shutdown the protocol. It will be impossible to use the instance
-     * after the calling of this method.
-     */
-    protected void shutdown() {
-        alert[0] = 0;
-        alert[1] = 0;
-        recordProtocol = null;
-    }
-}
-
diff --git a/src/main/java/org/conscrypt/Appendable.java b/src/main/java/org/conscrypt/Appendable.java
deleted file mode 100644
index d22c5a8..0000000
--- a/src/main/java/org/conscrypt/Appendable.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- *  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;
-
-/**
- * This interface represents the ability of the input stream related classes to provide additional
- * data to be read.
- */
-public interface Appendable {
-
-    /**
-     * Provides the additional data to be read.
-     *
-     * @param src the source data to be appended.
-     */
-    public void append(byte[] src);
-
-}
diff --git a/src/main/java/org/conscrypt/CertificateMessage.java b/src/main/java/org/conscrypt/CertificateMessage.java
deleted file mode 100644
index 0c0e092..0000000
--- a/src/main/java/org/conscrypt/CertificateMessage.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- *  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.io.IOException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-
-/**
- * Represents server/client certificate message
- * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS
- * 1.0 spec., 7.4.2. Server certificate; 7.4.6. Client certificate</a>
- *
- */
-public class CertificateMessage extends Message {
-
-    /**
-     * Certificates
-     */
-    X509Certificate[] certs;
-
-    /**
-     * Certificates in encoded form
-     */
-    byte[][] encoded_certs;
-
-    /**
-     * Creates inbound message
-     *
-     * @param in
-     * @param length
-     * @throws IOException
-     */
-    public CertificateMessage(HandshakeIODataStream in, int length) throws IOException {
-        int l = in.readUint24(); // total_length
-        if (l == 0) {  // message contais no certificates
-            if (length != 3) { // no more bytes after total_length
-                fatalAlert(AlertProtocol.DECODE_ERROR,
-                        "DECODE ERROR: incorrect CertificateMessage");
-            }
-            certs = new X509Certificate[0];
-            encoded_certs = new byte[0][0];
-            this.length = 3;
-            return;
-        }
-        CertificateFactory cf;
-        try {
-            cf = CertificateFactory.getInstance("X509");
-        } catch (CertificateException e) {
-            fatalAlert(AlertProtocol.INTERNAL_ERROR, "INTERNAL ERROR", e);
-            return;
-        }
-        ArrayList<X509Certificate> certsList = new ArrayList<X509Certificate>();
-        int size = 0;
-        int enc_size = 0;
-        while (l > 0) {
-            size = in.readUint24();
-            l -= 3;
-            try {
-                certsList.add((X509Certificate) cf.generateCertificate(in));
-            } catch (CertificateException e) {
-                fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR", e);
-            }
-            l -= size;
-            enc_size += size;
-        }
-        certs = certsList.toArray(new X509Certificate[certsList.size()]);
-        this.length = 3 + 3 * certs.length + enc_size;
-        if (this.length != length) {
-            fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect CertificateMessage");
-        }
-    }
-
-    /**
-     * Creates outbound message
-     *
-     * @param certs
-     */
-    public CertificateMessage(X509Certificate[] certs) {
-        if (certs == null) {
-            this.certs = new X509Certificate[0];
-            encoded_certs = new byte[0][0];
-            length = 3;
-            return;
-        }
-        this.certs = certs;
-        if (encoded_certs == null) {
-            encoded_certs = new byte[certs.length][];
-            for (int i = 0; i < certs.length; i++) {
-                try {
-                    encoded_certs[i] = certs[i].getEncoded();
-                } catch (CertificateEncodingException e) {
-                    fatalAlert(AlertProtocol.INTERNAL_ERROR, "INTERNAL ERROR",
-                            e);
-                }
-            }
-        }
-        length = 3 + 3 * encoded_certs.length;
-        for (int i = 0; i < encoded_certs.length; i++) {
-            length += encoded_certs[i].length;
-        }
-    }
-
-    /**
-     * Sends message
-     *
-     * @param out
-     */
-    @Override
-    public void send(HandshakeIODataStream out) {
-
-        int total_length = 0;
-        if (encoded_certs == null) {
-            encoded_certs = new byte[certs.length][];
-            for (int i = 0; i < certs.length; i++) {
-                try {
-                    encoded_certs[i] = certs[i].getEncoded();
-                } catch (CertificateEncodingException e) {
-                    fatalAlert(AlertProtocol.INTERNAL_ERROR, "INTERNAL ERROR",
-                            e);
-                }
-            }
-        }
-        total_length = 3 * encoded_certs.length;
-        for (int i = 0; i < encoded_certs.length; i++) {
-            total_length += encoded_certs[i].length;
-        }
-        out.writeUint24(total_length);
-        for (int i = 0; i < encoded_certs.length; i++) {
-            out.writeUint24(encoded_certs[i].length);
-            out.write(encoded_certs[i]);
-        }
-
-    }
-
-    public String getAuthType() {
-        return certs[0].getPublicKey().getAlgorithm();
-    }
-
-    /**
-     * Returns message type
-     */
-    @Override
-    public int getType() {
-        return Handshake.CERTIFICATE;
-    }
-
-}
diff --git a/src/main/java/org/conscrypt/CertificateRequest.java b/src/main/java/org/conscrypt/CertificateRequest.java
deleted file mode 100644
index 6d08cc2..0000000
--- a/src/main/java/org/conscrypt/CertificateRequest.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- *  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.io.IOException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import javax.security.auth.x500.X500Principal;
-import libcore.io.Streams;
-
-/**
- *
- * Represents certificate request message
- * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.4.
- * Certificate request</a>
- */
-public class CertificateRequest extends Message {
-
-    /**
-     * Requested certificate types
-     */
-    final byte[] certificate_types;
-
-    /**
-     * Certificate authorities
-     */
-    final X500Principal[] certificate_authorities;
-
-    /**
-     * Requested certificate types as Strings
-     * ("RSA", "DSA", "DH_RSA" or "DH_DSA")
-     */
-    private String[] types;
-
-    /**
-     * Encoded form of certificate authorities
-     */
-    private byte[][] encoded_principals;
-
-    /**
-     * Creates outbound message
-     *
-     * @param certificate_types
-     * @param accepted - array of certificate authority certificates
-     */
-    public CertificateRequest(byte[] certificate_types,
-                              X509Certificate[] accepted) {
-
-        if (accepted == null) {
-            fatalAlert(AlertProtocol.INTERNAL_ERROR,
-                    "CertificateRequest: array of certificate authority certificates is null");
-        }
-        this.certificate_types = certificate_types;
-
-        int totalPrincipalsLength = 0;
-        certificate_authorities = new X500Principal[accepted.length];
-        encoded_principals = new byte[accepted.length][];
-        for (int i = 0; i < accepted.length; i++) {
-            certificate_authorities[i] = accepted[i].getIssuerX500Principal();
-            encoded_principals[i] = certificate_authorities[i].getEncoded();
-            totalPrincipalsLength += encoded_principals[i].length + 2;
-        }
-
-        length = 3 + certificate_types.length + totalPrincipalsLength;
-    }
-
-    /**
-     * Creates inbound message
-     *
-     * @param in
-     * @param length
-     * @throws IOException
-     */
-    public CertificateRequest(HandshakeIODataStream in, int length) throws IOException {
-        int size = in.readUint8();
-        certificate_types = new byte[size];
-        Streams.readFully(in, certificate_types);
-        size = in.readUint16();
-        int totalPrincipalsLength = 0;
-        int principalLength = 0;
-        ArrayList<X500Principal> principals = new ArrayList<X500Principal>();
-        while (totalPrincipalsLength < size) {
-            principalLength = in.readUint16(); // encoded X500Principal size
-            principals.add(new X500Principal(in));
-            totalPrincipalsLength += 2;
-            totalPrincipalsLength += principalLength;
-        }
-        certificate_authorities = principals.toArray(new X500Principal[principals.size()]);
-        this.length = 3 + certificate_types.length + totalPrincipalsLength;
-        if (this.length != length) {
-            fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect CertificateRequest");
-        }
-    }
-
-    /**
-     * Sends message
-     *
-     * @param out
-     */
-    @Override
-    public void send(HandshakeIODataStream out) {
-
-        out.writeUint8(certificate_types.length);
-        for (int i = 0; i < certificate_types.length; i++) {
-            out.write(certificate_types[i]);
-        }
-        int authoritiesLength = 0;
-        for (int i = 0; i < certificate_authorities.length; i++) {
-            authoritiesLength += encoded_principals[i].length +2;
-        }
-        out.writeUint16(authoritiesLength);
-        for (int i = 0; i < certificate_authorities.length; i++) {
-            out.writeUint16(encoded_principals[i].length);
-            out.write(encoded_principals[i]);
-        }
-    }
-
-    /**
-     * Returns message type
-     */
-    @Override
-    public int getType() {
-        return Handshake.CERTIFICATE_REQUEST;
-    }
-
-    /**
-     * Returns requested certificate types as array of strings
-     */
-    public String[] getTypesAsString() {
-        if (types == null) {
-            types = new String[certificate_types.length];
-            for (int i = 0; i < types.length; i++) {
-                String type = CipherSuite.getClientKeyType(certificate_types[i]);
-                if (type == null) {
-                    fatalAlert(AlertProtocol.DECODE_ERROR,
-                            "DECODE ERROR: incorrect CertificateRequest");
-                }
-                types[i] = type;
-            }
-        }
-        return types;
-    }
-
-}
diff --git a/src/main/java/org/conscrypt/CertificateVerify.java b/src/main/java/org/conscrypt/CertificateVerify.java
deleted file mode 100644
index 8ba394a..0000000
--- a/src/main/java/org/conscrypt/CertificateVerify.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- *  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.io.IOException;
-
-/**
- * Represents certificate verify message
- * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.8.
- * Certificate verify</a>
- */
-public class CertificateVerify extends Message {
-
-    /**
-     * Signature
-     */
-    byte[] signedHash;
-
-    /**
-     * Creates outbound message
-     *
-     * @param hash
-     */
-    public CertificateVerify(byte[] hash) {
-        if (hash == null || hash.length == 0) {
-            fatalAlert(AlertProtocol.INTERNAL_ERROR,
-                    "INTERNAL ERROR: incorrect certificate verify hash");
-        }
-        this.signedHash = hash;
-        length = hash.length + 2;
-    }
-
-    /**
-     * Creates inbound message
-     *
-     * @param in
-     * @param length
-     * @throws IOException
-     */
-    public CertificateVerify(HandshakeIODataStream in, int length)
-            throws IOException {
-        if (length == 0) {
-            fatalAlert(AlertProtocol.DECODE_ERROR,
-                    "DECODE ERROR: incorrect CertificateVerify");
-        } else {
-            if (in.readUint16() != length - 2) {
-                fatalAlert(AlertProtocol.DECODE_ERROR,
-                        "DECODE ERROR: incorrect CertificateVerify");
-            }
-            signedHash = in.read(length -2);
-        }
-        this.length = length;
-    }
-
-    /**
-     * Sends message
-     *
-     * @param out
-     */
-    @Override
-    public void send(HandshakeIODataStream out) {
-        if (signedHash.length != 0) {
-            out.writeUint16(signedHash.length);
-            out.write(signedHash);
-        }
-    }
-
-    /**
-     * Returns message type
-     */
-    @Override
-    public int getType() {
-        return Handshake.CERTIFICATE_VERIFY;
-    }
-}
diff --git a/src/main/java/org/conscrypt/CipherSuite.java b/src/main/java/org/conscrypt/CipherSuite.java
deleted file mode 100644
index bd1a671..0000000
--- a/src/main/java/org/conscrypt/CipherSuite.java
+++ /dev/null
@@ -1,1190 +0,0 @@
-/*
- *  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.GeneralSecurityException;
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.List;
-import javax.crypto.Cipher;
-
-/**
- * Represents Cipher Suite as defined in TLS 1.0 spec.,
- * A.5. The CipherSuite;
- * C. CipherSuite definitions.
- * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec.</a>
- *
- */
-public class CipherSuite {
-
-    /**
-     * true if this cipher suite is supported
-     */
-    boolean supported = true;
-
-    /**
-     * cipher suite key exchange
-     */
-    final int keyExchange;
-
-    /**
-     * algorithm used for authentication ("RSA", "DSA", "DH", null for anonymous)
-     */
-    final String authType;
-
-    /**
-     * cipher
-     */
-    final String cipherName;
-
-    /**
-     * Cipher information
-     */
-    final int keyMaterial;
-    final int expandedKeyMaterial;
-    final int effectiveKeyBytes;
-    final int ivSize;
-    final private int blockSize;
-
-    // cipher suite code
-    private final byte[] cipherSuiteCode;
-
-    // cipher suite name
-    private final String name;
-
-    // true if cipher suite is exportable
-    private final boolean isExportable;
-
-    // Hash algorithm
-    final private String hashName;
-
-    // MAC algorithm
-    final private String hmacName;
-
-    // Hash size
-    final private int hashSize;
-
-    /**
-     * Whether this cipher needs BEAST mitigation or not. This enabled for CBC
-     * mode ciphers.
-     */
-    final private boolean needInitialRecordSplit;
-
-    /**
-     * key exchange values
-     */
-    static final int KEY_EXCHANGE_RSA = 1;
-    static final int KEY_EXCHANGE_RSA_EXPORT = 2;
-    static final int KEY_EXCHANGE_DHE_DSS = 3;
-    static final int KEY_EXCHANGE_DHE_DSS_EXPORT = 4;
-    static final int KEY_EXCHANGE_DHE_RSA = 5;
-    static final int KEY_EXCHANGE_DHE_RSA_EXPORT = 6;
-    // BEGIN android-removed
-    // static final int KEY_EXCHANGE_DH_DSS = 7;
-    // static final int KEY_EXCHANGE_DH_RSA = 8;
-    // END android-removed
-    static final int KEY_EXCHANGE_DH_anon = 9;
-    static final int KEY_EXCHANGE_DH_anon_EXPORT = 10;
-    // BEGIN android-removed
-    // static final int KEY_EXCHANGE_DH_DSS_EXPORT = 11;
-    // static final int KEY_EXCHANGE_DH_RSA_EXPORT = 12;
-    // END android-removed
-    static final int KEY_EXCHANGE_ECDH_ECDSA = 13;
-    static final int KEY_EXCHANGE_ECDHE_ECDSA = 14;
-    static final int KEY_EXCHANGE_ECDH_RSA = 15;
-    static final int KEY_EXCHANGE_ECDHE_RSA = 16;
-    static final int KEY_EXCHANGE_ECDH_anon = 17;
-
-    /**
-     * TLS cipher suite codes
-     */
-    static final byte[] CODE_SSL_NULL_WITH_NULL_NULL = { 0x00, 0x00 };
-    static final byte[] CODE_SSL_RSA_WITH_NULL_MD5 = { 0x00, 0x01 };
-    static final byte[] CODE_SSL_RSA_WITH_NULL_SHA = { 0x00, 0x02 };
-    static final byte[] CODE_SSL_RSA_EXPORT_WITH_RC4_40_MD5 = { 0x00, 0x03 };
-    static final byte[] CODE_SSL_RSA_WITH_RC4_128_MD5 = { 0x00, 0x04 };
-    static final byte[] CODE_SSL_RSA_WITH_RC4_128_SHA = { 0x00, 0x05 };
-    static final byte[] CODE_SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = { 0x00, 0x06 };
-    // BEGIN android-removed
-    // static final byte[] CODE_TLS_RSA_WITH_IDEA_CBC_SHA = { 0x00, 0x07 };
-    // END android-removed
-    static final byte[] CODE_SSL_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00, 0x08 };
-    static final byte[] CODE_SSL_RSA_WITH_DES_CBC_SHA = { 0x00, 0x09 };
-    static final byte[] CODE_SSL_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00, 0x0A };
-    // BEGIN android-removed
-    // static final byte[] CODE_SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = { 0x00, 0x0B };
-    // static final byte[] CODE_SSL_DH_DSS_WITH_DES_CBC_SHA = { 0x00, 0x0C };
-    // static final byte[] CODE_SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00, 0x0D };
-    // static final byte[] CODE_SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00, 0x0E };
-    // static final byte[] CODE_SSL_DH_RSA_WITH_DES_CBC_SHA = { 0x00, 0x0F };
-    // static final byte[] CODE_SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00, 0x10 };
-    // END android-removed
-    static final byte[] CODE_SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = { 0x00, 0x11 };
-    static final byte[] CODE_SSL_DHE_DSS_WITH_DES_CBC_SHA = { 0x00, 0x12 };
-    static final byte[] CODE_SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00, 0x13 };
-    static final byte[] CODE_SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00, 0x14 };
-    static final byte[] CODE_SSL_DHE_RSA_WITH_DES_CBC_SHA = { 0x00, 0x15 };
-    static final byte[] CODE_SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00, 0x16 };
-    static final byte[] CODE_SSL_DH_anon_EXPORT_WITH_RC4_40_MD5 = { 0x00, 0x17 };
-    static final byte[] CODE_SSL_DH_anon_WITH_RC4_128_MD5 = { 0x00, 0x18 };
-    static final byte[] CODE_SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA = { 0x00, 0x19 };
-    static final byte[] CODE_SSL_DH_anon_WITH_DES_CBC_SHA = { 0x00, 0x1A };
-    static final byte[] CODE_SSL_DH_anon_WITH_3DES_EDE_CBC_SHA = { 0x00, 0x1B };
-
-    // AES Cipher Suites from RFC 3268 - http://www.ietf.org/rfc/rfc3268.txt
-    static final byte[] CODE_TLS_RSA_WITH_AES_128_CBC_SHA = { 0x00, 0x2F };
-    //static final byte[] CODE_TLS_DH_DSS_WITH_AES_128_CBC_SHA = { 0x00, 0x30 };
-    //static final byte[] CODE_TLS_DH_RSA_WITH_AES_128_CBC_SHA = { 0x00, 0x31 };
-    static final byte[] CODE_TLS_DHE_DSS_WITH_AES_128_CBC_SHA = { 0x00, 0x32 };
-    static final byte[] CODE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA = { 0x00, 0x33 };
-    static final byte[] CODE_TLS_DH_anon_WITH_AES_128_CBC_SHA = { 0x00, 0x34 };
-    static final byte[] CODE_TLS_RSA_WITH_AES_256_CBC_SHA = { 0x00, 0x35 };
-    //static final byte[] CODE_TLS_DH_DSS_WITH_AES_256_CBC_SHA = { 0x00, 0x36 };
-    //static final byte[] CODE_TLS_DH_RSA_WITH_AES_256_CBC_SHA = { 0x00, 0x37 };
-    static final byte[] CODE_TLS_DHE_DSS_WITH_AES_256_CBC_SHA = { 0x00, 0x38 };
-    static final byte[] CODE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA = { 0x00, 0x39 };
-    static final byte[] CODE_TLS_DH_anon_WITH_AES_256_CBC_SHA = { 0x00, 0x3A };
-
-    // EC Cipher Suites from RFC 4492 - http://www.ietf.org/rfc/rfc4492.txt
-    static final byte[] CODE_TLS_ECDH_ECDSA_WITH_NULL_SHA = { (byte) 0xc0, 0x01};
-    static final byte[] CODE_TLS_ECDH_ECDSA_WITH_RC4_128_SHA = { (byte) 0xc0, 0x02};
-    static final byte[] CODE_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA = { (byte) 0xc0, 0x03};
-    static final byte[] CODE_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA = { (byte) 0xc0, 0x04};
-    static final byte[] CODE_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA = { (byte) 0xc0, 0x05};
-    static final byte[] CODE_TLS_ECDHE_ECDSA_WITH_NULL_SHA = { (byte) 0xc0, 0x06};
-    static final byte[] CODE_TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = { (byte) 0xc0, 0x07};
-    static final byte[] CODE_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = { (byte) 0xc0, 0x08};
-    static final byte[] CODE_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = { (byte) 0xc0, 0x09};
-    static final byte[] CODE_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = { (byte) 0xc0, 0x0A};
-    static final byte[] CODE_TLS_ECDH_RSA_WITH_NULL_SHA = { (byte) 0xc0, 0x0B};
-    static final byte[] CODE_TLS_ECDH_RSA_WITH_RC4_128_SHA = { (byte) 0xc0, 0x0C};
-    static final byte[] CODE_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA = { (byte) 0xc0, 0x0D};
-    static final byte[] CODE_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA = { (byte) 0xc0, 0x0E};
-    static final byte[] CODE_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA = { (byte) 0xc0, 0x0F};
-    static final byte[] CODE_TLS_ECDHE_RSA_WITH_NULL_SHA = { (byte) 0xc0, 0x10};
-    static final byte[] CODE_TLS_ECDHE_RSA_WITH_RC4_128_SHA = { (byte) 0xc0, 0x11};
-    static final byte[] CODE_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = { (byte) 0xc0, 0x12};
-    static final byte[] CODE_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = { (byte) 0xc0, 0x13};
-    static final byte[] CODE_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = { (byte) 0xc0, 0x14};
-    static final byte[] CODE_TLS_ECDH_anon_WITH_NULL_SHA = { (byte) 0xc0, 0x15};
-    static final byte[] CODE_TLS_ECDH_anon_WITH_RC4_128_SHA = { (byte) 0xc0, 0x16};
-    static final byte[] CODE_TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA = { (byte) 0xc0, 0x17};
-    static final byte[] CODE_TLS_ECDH_anon_WITH_AES_128_CBC_SHA = { (byte) 0xc0, 0x18};
-    static final byte[] CODE_TLS_ECDH_anon_WITH_AES_256_CBC_SHA = { (byte) 0xc0, 0x19};
-
-    static final CipherSuite SSL_NULL_WITH_NULL_NULL = new CipherSuite(
-            "SSL_NULL_WITH_NULL_NULL", true, 0, null, null, null,
-            CODE_SSL_NULL_WITH_NULL_NULL);
-
-    static final CipherSuite SSL_RSA_WITH_NULL_MD5 = new CipherSuite(
-            "SSL_RSA_WITH_NULL_MD5", true, KEY_EXCHANGE_RSA, "RSA", null, "MD5",
-            CODE_SSL_RSA_WITH_NULL_MD5);
-
-    static final CipherSuite SSL_RSA_WITH_NULL_SHA = new CipherSuite(
-            "SSL_RSA_WITH_NULL_SHA", true, KEY_EXCHANGE_RSA, "RSA", null, "SHA",
-            CODE_SSL_RSA_WITH_NULL_SHA);
-
-    static final CipherSuite SSL_RSA_EXPORT_WITH_RC4_40_MD5 = new CipherSuite(
-            "SSL_RSA_EXPORT_WITH_RC4_40_MD5", true, KEY_EXCHANGE_RSA_EXPORT,
-            "RSA", "RC4_40", "MD5", CODE_SSL_RSA_EXPORT_WITH_RC4_40_MD5);
-
-    static final CipherSuite SSL_RSA_WITH_RC4_128_MD5 = new CipherSuite(
-            "SSL_RSA_WITH_RC4_128_MD5", false, KEY_EXCHANGE_RSA, "RSA", "RC4_128",
-            "MD5", CODE_SSL_RSA_WITH_RC4_128_MD5);
-
-    static final CipherSuite SSL_RSA_WITH_RC4_128_SHA = new CipherSuite(
-            "SSL_RSA_WITH_RC4_128_SHA", false, KEY_EXCHANGE_RSA, "RSA", "RC4_128",
-            "SHA", CODE_SSL_RSA_WITH_RC4_128_SHA);
-
-    static final CipherSuite SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = new CipherSuite(
-            "SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5", true, KEY_EXCHANGE_RSA_EXPORT,
-            "RSA", "RC2_CBC_40", "MD5", CODE_SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5);
-
-    // BEGIN android-removed
-    // static final CipherSuite TLS_RSA_WITH_IDEA_CBC_SHA = new CipherSuite(
-    //         "TLS_RSA_WITH_IDEA_CBC_SHA", false, KEY_EXCHANGE_RSA, "RSA", "IDEA_CBC",
-    //         "SHA", CODE_TLS_RSA_WITH_IDEA_CBC_SHA);
-    // END android-removed
-
-    static final CipherSuite SSL_RSA_EXPORT_WITH_DES40_CBC_SHA = new CipherSuite(
-            "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", true, KEY_EXCHANGE_RSA_EXPORT,
-            "RSA", "DES40_CBC", "SHA", CODE_SSL_RSA_EXPORT_WITH_DES40_CBC_SHA);
-
-    static final CipherSuite SSL_RSA_WITH_DES_CBC_SHA = new CipherSuite(
-            "SSL_RSA_WITH_DES_CBC_SHA", false, KEY_EXCHANGE_RSA, "RSA", "DES_CBC",
-            "SHA", CODE_SSL_RSA_WITH_DES_CBC_SHA);
-
-    static final CipherSuite SSL_RSA_WITH_3DES_EDE_CBC_SHA = new CipherSuite(
-            "SSL_RSA_WITH_3DES_EDE_CBC_SHA", false, KEY_EXCHANGE_RSA,
-            "RSA", "3DES_EDE_CBC", "SHA", CODE_SSL_RSA_WITH_3DES_EDE_CBC_SHA);
-
-    // BEGIN android-removed
-    // static final CipherSuite SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = new CipherSuite(
-    //         "SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", true,
-    //         KEY_EXCHANGE_DH_DSS_EXPORT, "DH", "DES40_CBC", "SHA",
-    //         CODE_SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA);
-    //
-    // static final CipherSuite SSL_DH_DSS_WITH_DES_CBC_SHA = new CipherSuite(
-    //         "SSL_DH_DSS_WITH_DES_CBC_SHA", false, KEY_EXCHANGE_DH_DSS,
-    //         "DH", "DES_CBC", "SHA", CODE_SSL_DH_DSS_WITH_DES_CBC_SHA);
-    //
-    // static final CipherSuite SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA = new CipherSuite(
-    //         "SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA", false, KEY_EXCHANGE_DH_DSS,
-    //         "DH", "3DES_EDE_CBC", "SHA", CODE_SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA);
-    //
-    // static final CipherSuite SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = new CipherSuite(
-    //         "SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", true,
-    //         KEY_EXCHANGE_DH_RSA_EXPORT, "DH", "DES40_CBC", "SHA",
-    //         CODE_SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA);
-    //
-    // static final CipherSuite SSL_DH_RSA_WITH_DES_CBC_SHA = new CipherSuite(
-    //         "SSL_DH_RSA_WITH_DES_CBC_SHA", false, KEY_EXCHANGE_DH_RSA,
-    //         "DH", "DES_CBC", "SHA", CODE_SSL_DH_RSA_WITH_DES_CBC_SHA);
-    //
-    // static final CipherSuite SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA = new CipherSuite(
-    //         "SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA", false, KEY_EXCHANGE_DH_RSA,
-    //         "DH", "3DES_EDE_CBC", "SHA", CODE_SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA);
-    // END android-removed
-
-    static final CipherSuite SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = new CipherSuite(
-            "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", true,
-            KEY_EXCHANGE_DHE_DSS_EXPORT, "DSA", "DES40_CBC", "SHA",
-            CODE_SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA);
-
-    static final CipherSuite SSL_DHE_DSS_WITH_DES_CBC_SHA = new CipherSuite(
-            "SSL_DHE_DSS_WITH_DES_CBC_SHA", false, KEY_EXCHANGE_DHE_DSS,
-            "DSA", "DES_CBC", "SHA", CODE_SSL_DHE_DSS_WITH_DES_CBC_SHA);
-
-    static final CipherSuite SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA = new CipherSuite(
-            "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", false, KEY_EXCHANGE_DHE_DSS,
-            "DSA", "3DES_EDE_CBC", "SHA", CODE_SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA);
-
-    static final CipherSuite SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = new CipherSuite(
-            "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", true,
-            KEY_EXCHANGE_DHE_RSA_EXPORT, "RSA", "DES40_CBC", "SHA",
-            CODE_SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA);
-
-    static final CipherSuite SSL_DHE_RSA_WITH_DES_CBC_SHA = new CipherSuite(
-            "SSL_DHE_RSA_WITH_DES_CBC_SHA", false, KEY_EXCHANGE_DHE_RSA,
-            "RSA", "DES_CBC", "SHA", CODE_SSL_DHE_RSA_WITH_DES_CBC_SHA);
-
-    static final CipherSuite SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA = new CipherSuite(
-            "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", false, KEY_EXCHANGE_DHE_RSA,
-            "RSA", "3DES_EDE_CBC", "SHA", CODE_SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA);
-
-    static final CipherSuite SSL_DH_anon_EXPORT_WITH_RC4_40_MD5 = new CipherSuite(
-            "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", true,
-            KEY_EXCHANGE_DH_anon_EXPORT, "DH", "RC4_40", "MD5",
-            CODE_SSL_DH_anon_EXPORT_WITH_RC4_40_MD5);
-
-    static final CipherSuite SSL_DH_anon_WITH_RC4_128_MD5 = new CipherSuite(
-            "SSL_DH_anon_WITH_RC4_128_MD5", false, KEY_EXCHANGE_DH_anon,
-            "DH", "RC4_128", "MD5", CODE_SSL_DH_anon_WITH_RC4_128_MD5);
-
-    static final CipherSuite SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA = new CipherSuite(
-            "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA", true,
-            KEY_EXCHANGE_DH_anon_EXPORT, "DH", "DES40_CBC", "SHA",
-            CODE_SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA);
-
-    static final CipherSuite SSL_DH_anon_WITH_DES_CBC_SHA = new CipherSuite(
-            "SSL_DH_anon_WITH_DES_CBC_SHA", false, KEY_EXCHANGE_DH_anon,
-            "DH", "DES_CBC", "SHA", CODE_SSL_DH_anon_WITH_DES_CBC_SHA);
-
-    static final CipherSuite SSL_DH_anon_WITH_3DES_EDE_CBC_SHA = new CipherSuite(
-            "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA", false, KEY_EXCHANGE_DH_anon,
-            "DH", "3DES_EDE_CBC", "SHA", CODE_SSL_DH_anon_WITH_3DES_EDE_CBC_SHA);
-
-    static final CipherSuite TLS_RSA_WITH_AES_128_CBC_SHA
-            = new CipherSuite("TLS_RSA_WITH_AES_128_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_RSA,
-                              "RSA",
-                              "AES_128_CBC",
-                              "SHA",
-                              CODE_TLS_RSA_WITH_AES_128_CBC_SHA);
-    static final CipherSuite TLS_DHE_DSS_WITH_AES_128_CBC_SHA
-            = new CipherSuite("TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_DHE_DSS,
-                              "DSA",
-                              "AES_128_CBC",
-                              "SHA",
-                              CODE_TLS_DHE_DSS_WITH_AES_128_CBC_SHA);
-    static final CipherSuite TLS_DHE_RSA_WITH_AES_128_CBC_SHA
-            = new CipherSuite("TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_DHE_RSA,
-                              "RSA",
-                              "AES_128_CBC",
-                              "SHA",
-                              CODE_TLS_DHE_RSA_WITH_AES_128_CBC_SHA);
-    static final CipherSuite TLS_DH_anon_WITH_AES_128_CBC_SHA
-            = new CipherSuite("TLS_DH_anon_WITH_AES_128_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_DH_anon,
-                              "DH",
-                              "AES_128_CBC",
-                              "SHA",
-                              CODE_TLS_DH_anon_WITH_AES_128_CBC_SHA);
-    static final CipherSuite TLS_RSA_WITH_AES_256_CBC_SHA
-            = new CipherSuite("TLS_RSA_WITH_AES_256_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_RSA,
-                              "RSA",
-                              "AES_256_CBC",
-                              "SHA",
-                              CODE_TLS_RSA_WITH_AES_256_CBC_SHA);
-    static final CipherSuite TLS_DHE_DSS_WITH_AES_256_CBC_SHA
-            = new CipherSuite("TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_DHE_DSS,
-                              "DSA",
-                              "AES_256_CBC",
-                              "SHA",
-                              CODE_TLS_DHE_DSS_WITH_AES_256_CBC_SHA);
-    static final CipherSuite TLS_DHE_RSA_WITH_AES_256_CBC_SHA
-            = new CipherSuite("TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_DHE_RSA,
-                              "RSA",
-                              "AES_256_CBC",
-                              "SHA",
-                              CODE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA);
-    static final CipherSuite TLS_DH_anon_WITH_AES_256_CBC_SHA
-            = new CipherSuite("TLS_DH_anon_WITH_AES_256_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_DH_anon,
-                              "DH",
-                              "AES_256_CBC",
-                              "SHA",
-                              CODE_TLS_DH_anon_WITH_AES_256_CBC_SHA);
-
-    static final CipherSuite TLS_ECDH_ECDSA_WITH_NULL_SHA
-            = new CipherSuite("TLS_ECDH_ECDSA_WITH_NULL_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDH_ECDSA,
-                              "EC",
-                              null,
-                              "SHA",
-                              CODE_TLS_ECDH_ECDSA_WITH_NULL_SHA);
-    static final CipherSuite TLS_ECDH_ECDSA_WITH_RC4_128_SHA
-            = new CipherSuite("TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDH_ECDSA,
-                              "EC",
-                              "RC4_128",
-                              "SHA",
-                              CODE_TLS_ECDH_ECDSA_WITH_RC4_128_SHA);
-    static final CipherSuite TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA
-            = new CipherSuite("TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDH_ECDSA,
-                              "EC",
-                              "3DES_EDE_CBC",
-                              "SHA",
-                              CODE_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA);
-    static final CipherSuite TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
-            = new CipherSuite("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDH_ECDSA,
-                              "EC",
-                              "AES_128_CBC",
-                              "SHA",
-                              CODE_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA);
-    static final CipherSuite TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
-            = new CipherSuite("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDH_ECDSA,
-                              "EC",
-                              "AES_256_CBC",
-                              "SHA",
-                              CODE_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA);
-    static final CipherSuite TLS_ECDHE_ECDSA_WITH_NULL_SHA
-            = new CipherSuite("TLS_ECDHE_ECDSA_WITH_NULL_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDHE_ECDSA,
-                              "EC",
-                              null,
-                              "SHA",
-                              CODE_TLS_ECDHE_ECDSA_WITH_NULL_SHA);
-    static final CipherSuite TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
-            = new CipherSuite("TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDHE_ECDSA,
-                              "EC",
-                              "RC4_128",
-                              "SHA",
-                              CODE_TLS_ECDHE_ECDSA_WITH_RC4_128_SHA);
-    static final CipherSuite TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
-            = new CipherSuite("TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDHE_ECDSA,
-                              "EC",
-                              "3DES_EDE_CBC",
-                              "SHA",
-                              CODE_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA);
-    static final CipherSuite TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
-            = new CipherSuite("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDHE_ECDSA,
-                              "EC",
-                              "AES_128_CBC",
-                              "SHA",
-                              CODE_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
-    static final CipherSuite TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
-            = new CipherSuite("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDHE_ECDSA,
-                              "EC",
-                              "AES_256_CBC",
-                              "SHA",
-                              CODE_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA);
-    static final CipherSuite TLS_ECDH_RSA_WITH_NULL_SHA
-            = new CipherSuite("TLS_ECDH_RSA_WITH_NULL_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDH_RSA,
-                              "EC",
-                              null,
-                              "SHA",
-                              CODE_TLS_ECDH_RSA_WITH_NULL_SHA);
-    static final CipherSuite TLS_ECDH_RSA_WITH_RC4_128_SHA
-            = new CipherSuite("TLS_ECDH_RSA_WITH_RC4_128_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDH_RSA,
-                              "EC",
-                              "RC4_128",
-                              "SHA",
-                              CODE_TLS_ECDH_RSA_WITH_RC4_128_SHA);
-    static final CipherSuite TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA
-            = new CipherSuite("TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDH_RSA,
-                              "EC",
-                              "3DES_EDE_CBC",
-                              "SHA",
-                              CODE_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA);
-    static final CipherSuite TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
-            = new CipherSuite("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDH_RSA,
-                              "EC",
-                              "AES_128_CBC",
-                              "SHA",
-                              CODE_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA);
-    static final CipherSuite TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
-            = new CipherSuite("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDH_RSA,
-                              "EC",
-                              "AES_256_CBC",
-                              "SHA",
-                              CODE_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA);
-    static final CipherSuite TLS_ECDHE_RSA_WITH_NULL_SHA
-            = new CipherSuite("TLS_ECDHE_RSA_WITH_NULL_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDHE_RSA,
-                              "EC",
-                              null,
-                              "SHA",
-                              CODE_TLS_ECDHE_RSA_WITH_NULL_SHA);
-    static final CipherSuite TLS_ECDHE_RSA_WITH_RC4_128_SHA
-            = new CipherSuite("TLS_ECDHE_RSA_WITH_RC4_128_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDHE_RSA,
-                              "EC",
-                              "RC4_128",
-                              "SHA",
-                              CODE_TLS_ECDHE_RSA_WITH_RC4_128_SHA);
-    static final CipherSuite TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
-            = new CipherSuite("TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDHE_RSA,
-                              "EC",
-                              "3DES_EDE_CBC",
-                              "SHA",
-                              CODE_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA);
-    static final CipherSuite TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
-            = new CipherSuite("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDHE_RSA,
-                              "EC",
-                              "AES_128_CBC",
-                              "SHA",
-                              CODE_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA);
-    static final CipherSuite TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
-            = new CipherSuite("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDHE_RSA,
-                              "EC",
-                              "AES_256_CBC",
-                              "SHA",
-                              CODE_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA);
-    static final CipherSuite TLS_ECDH_anon_WITH_NULL_SHA
-            = new CipherSuite("TLS_ECDH_anon_WITH_NULL_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDH_anon,
-                              "EC",
-                              null,
-                              "SHA",
-                              CODE_TLS_ECDH_anon_WITH_NULL_SHA);
-    static final CipherSuite TLS_ECDH_anon_WITH_RC4_128_SHA
-            = new CipherSuite("TLS_ECDH_anon_WITH_RC4_128_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDH_anon,
-                              "EC",
-                              "RC4_128",
-                              "SHA",
-                              CODE_TLS_ECDH_anon_WITH_RC4_128_SHA);
-    static final CipherSuite TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA
-            = new CipherSuite("TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDH_anon,
-                              "EC",
-                              "3DES_EDE_CBC",
-                              "SHA",
-                              CODE_TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA);
-    static final CipherSuite TLS_ECDH_anon_WITH_AES_128_CBC_SHA
-            = new CipherSuite("TLS_ECDH_anon_WITH_AES_128_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDH_anon,
-                              "EC",
-                              "AES_128_CBC",
-                              "SHA",
-                              CODE_TLS_ECDH_anon_WITH_AES_128_CBC_SHA);
-    static final CipherSuite TLS_ECDH_anon_WITH_AES_256_CBC_SHA
-            = new CipherSuite("TLS_ECDH_anon_WITH_AES_256_CBC_SHA",
-                              false,
-                              KEY_EXCHANGE_ECDH_anon,
-                              "EC",
-                              "AES_256_CBC",
-                              "SHA",
-                              CODE_TLS_ECDH_anon_WITH_AES_256_CBC_SHA);
-
-    // arrays for quick access to cipher suite by code
-    private static final CipherSuite[] SUITES_BY_CODE_0x00 = {
-        // http://www.iana.org/assignments/tls-parameters/tls-parameters.xml
-        SSL_NULL_WITH_NULL_NULL,                          // { 0x00, 0x00 };
-        SSL_RSA_WITH_NULL_MD5,                            // { 0x00, 0x01 };
-        SSL_RSA_WITH_NULL_SHA,                            // { 0x00, 0x02 };
-        SSL_RSA_EXPORT_WITH_RC4_40_MD5,                   // { 0x00, 0x03 };
-        SSL_RSA_WITH_RC4_128_MD5,                         // { 0x00, 0x04 };
-        SSL_RSA_WITH_RC4_128_SHA,                         // { 0x00, 0x05 };
-        // BEGIN android-changed
-        null, // SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5,      // { 0x00, 0x06 };
-        null, // TLS_RSA_WITH_IDEA_CBC_SHA,               // { 0x00, 0x07 };
-        // END android-changed
-        SSL_RSA_EXPORT_WITH_DES40_CBC_SHA,                // { 0x00, 0x08 };
-        SSL_RSA_WITH_DES_CBC_SHA,                         // { 0x00, 0x09 };
-        SSL_RSA_WITH_3DES_EDE_CBC_SHA,                    // { 0x00, 0x0a };
-        // BEGIN android-changed
-        null, // SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA     // { 0x00, 0x0b };
-        null, // SSL_DH_DSS_WITH_DES_CBC_SHA,             // { 0x00, 0x0c };
-        null, // SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA,        // { 0x00, 0x0d };
-        null, // SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA,    // { 0x00, 0x0e };
-        null, // SSL_DH_RSA_WITH_DES_CBC_SHA,             // { 0x00, 0x0f };
-        null, // SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA,        // { 0x00, 0x10 };
-        // END android-changed
-        SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA,            // { 0x00, 0x11 };
-        SSL_DHE_DSS_WITH_DES_CBC_SHA,                     // { 0x00, 0x12 };
-        SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA,                // { 0x00, 0x13 };
-        SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA,            // { 0x00, 0x14 };
-        SSL_DHE_RSA_WITH_DES_CBC_SHA,                     // { 0x00, 0x15 };
-        SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA,                // { 0x00, 0x16 };
-        SSL_DH_anon_EXPORT_WITH_RC4_40_MD5,               // { 0x00, 0x17 };
-        SSL_DH_anon_WITH_RC4_128_MD5,                     // { 0x00, 0x18 };
-        SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA,            // { 0x00, 0x19 };
-        SSL_DH_anon_WITH_DES_CBC_SHA,                     // { 0x00, 0x1A };
-        SSL_DH_anon_WITH_3DES_EDE_CBC_SHA,                // { 0x00, 0x1B };
-        // BEGIN android-added
-        null, // SSL_FORTEZZA_KEA_WITH_NULL_SHA           // { 0x00, 0x1C };
-        null, // SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA   // { 0x00, 0x1D };
-        null, // TLS_KRB5_WITH_DES_CBC_SHA                // { 0x00, 0x1E };
-        null, // TLS_KRB5_WITH_3DES_EDE_CBC_SHA           // { 0x00, 0x1F };
-        null, // TLS_KRB5_WITH_RC4_128_SHA                // { 0x00, 0x20 };
-        null, // TLS_KRB5_WITH_IDEA_CBC_SHA               // { 0x00, 0x21 };
-        null, // TLS_KRB5_WITH_DES_CBC_MD5                // { 0x00, 0x22 };
-        null, // TLS_KRB5_WITH_3DES_EDE_CBC_MD5           // { 0x00, 0x23 };
-        null, // TLS_KRB5_WITH_RC4_128_MD5                // { 0x00, 0x24 };
-        null, // TLS_KRB5_WITH_IDEA_CBC_MD5               // { 0x00, 0x25 };
-        null, // TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA      // { 0x00, 0x26 };
-        null, // TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA      // { 0x00, 0x27 };
-        null, // TLS_KRB5_EXPORT_WITH_RC4_40_SHA          // { 0x00, 0x28 };
-        null, // TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5      // { 0x00, 0x29 };
-        null, // TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5      // { 0x00, 0x2A };
-        null, // TLS_KRB5_EXPORT_WITH_RC4_40_MD5          // { 0x00, 0x2B };
-        null, // TLS_PSK_WITH_NULL_SHA                    // { 0x00, 0x2C };
-        null, // TLS_DHE_PSK_WITH_NULL_SHA                // { 0x00, 0x2D };
-        null, // TLS_RSA_PSK_WITH_NULL_SHA                // { 0x00, 0x2E };
-        TLS_RSA_WITH_AES_128_CBC_SHA,                     // { 0x00, 0x2F };
-        null, // TLS_DH_DSS_WITH_AES_128_CBC_SHA          // { 0x00, 0x30 };
-        null, // TLS_DH_RSA_WITH_AES_128_CBC_SHA          // { 0x00, 0x31 };
-        TLS_DHE_DSS_WITH_AES_128_CBC_SHA,                 // { 0x00, 0x32 };
-        TLS_DHE_RSA_WITH_AES_128_CBC_SHA,                 // { 0x00, 0x33 };
-        TLS_DH_anon_WITH_AES_128_CBC_SHA,                 // { 0x00, 0x34 };
-        TLS_RSA_WITH_AES_256_CBC_SHA,                     // { 0x00, 0x35 };
-        null, // TLS_DH_DSS_WITH_AES_256_CBC_SHA,         // { 0x00, 0x36 };
-        null, // TLS_DH_RSA_WITH_AES_256_CBC_SHA,         // { 0x00, 0x37 };
-        TLS_DHE_DSS_WITH_AES_256_CBC_SHA,                 // { 0x00, 0x38 };
-        TLS_DHE_RSA_WITH_AES_256_CBC_SHA,                 // { 0x00, 0x39 };
-        TLS_DH_anon_WITH_AES_256_CBC_SHA,                 // { 0x00, 0x3A };
-        // END android-added
-    };
-    private static final CipherSuite[] SUITES_BY_CODE_0xc0 = {
-        null,                                             // { 0xc0, 0x00};
-        TLS_ECDH_ECDSA_WITH_NULL_SHA,                     // { 0xc0, 0x01};
-        TLS_ECDH_ECDSA_WITH_RC4_128_SHA,                  // { 0xc0, 0x02};
-        TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,             // { 0xc0, 0x03};
-        TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,              // { 0xc0, 0x04};
-        TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,              // { 0xc0, 0x05};
-        TLS_ECDHE_ECDSA_WITH_NULL_SHA,                    // { 0xc0, 0x06};
-        TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,                 // { 0xc0, 0x07};
-        TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,            // { 0xc0, 0x08};
-        TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,             // { 0xc0, 0x09};
-        TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,             // { 0xc0, 0x0A};
-        TLS_ECDH_RSA_WITH_NULL_SHA,                       // { 0xc0, 0x0B};
-        TLS_ECDH_RSA_WITH_RC4_128_SHA,                    // { 0xc0, 0x0C};
-        TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,               // { 0xc0, 0x0D};
-        TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,                // { 0xc0, 0x0E};
-        TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,                // { 0xc0, 0x0F};
-        TLS_ECDHE_RSA_WITH_NULL_SHA,                      // { 0xc0, 0x10};
-        TLS_ECDHE_RSA_WITH_RC4_128_SHA,                   // { 0xc0, 0x11};
-        TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,              // { 0xc0, 0x12};
-        TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,               // { 0xc0, 0x13};
-        TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,               // { 0xc0, 0x14};
-        TLS_ECDH_anon_WITH_NULL_SHA,                      // { 0xc0, 0x15};
-        TLS_ECDH_anon_WITH_RC4_128_SHA,                   // { 0xc0, 0x16};
-        TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA,              // { 0xc0, 0x17};
-        TLS_ECDH_anon_WITH_AES_128_CBC_SHA,               // { 0xc0, 0x18};
-        TLS_ECDH_anon_WITH_AES_256_CBC_SHA,               // { 0xc0, 0x19};
-        // TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA,             // { 0xc0, 0x1A};
-        // TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,         // { 0xc0, 0x1B};
-        // TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA,         // { 0xc0, 0x1C};
-        // TLS_SRP_SHA_WITH_AES_128_CBC_SHA,              // { 0xc0, 0x1D};
-        // TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA,          // { 0xc0, 0x1E};
-        // TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,          // { 0xc0, 0x1F};
-        // TLS_SRP_SHA_WITH_AES_256_CBC_SHA,              // { 0xc0, 0x20};
-        // TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA,          // { 0xc0, 0x21};
-        // TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,          // { 0xc0, 0x22};
-        // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,       // { 0xc0, 0x23};
-        // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,       // { 0xc0, 0x24};
-        // TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,        // { 0xc0, 0x25};
-        // TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,        // { 0xc0, 0x26};
-        // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,         // { 0xc0, 0x27};
-        // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,         // { 0xc0, 0x28};
-        // TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,          // { 0xc0, 0x29};
-        // TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,          // { 0xc0, 0x2A};
-        // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,       // { 0xc0, 0x2B};
-        // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,       // { 0xc0, 0x2C};
-        // TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,        // { 0xc0, 0x2D};
-        // TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,        // { 0xc0, 0x2E};
-        // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,         // { 0xc0, 0x2F};
-        // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,         // { 0xc0, 0x30};
-        // TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,          // { 0xc0, 0x31};
-        // TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,          // { 0xc0, 0x32};
-        // TLS_ECDHE_PSK_WITH_RC4_128_SHA,                // { 0xc0, 0x33};
-        // TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA,           // { 0xc0, 0x34};
-        // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,            // { 0xc0, 0x35};
-        // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA,            // { 0xc0, 0x36};
-        // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256,         // { 0xc0, 0x37};
-        // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384,         // { 0xc0, 0x38};
-        // TLS_ECDHE_PSK_WITH_NULL_SHA,                   // { 0xc0, 0x39};
-        // TLS_ECDHE_PSK_WITH_NULL_SHA256,                // { 0xc0, 0x3A};
-        // TLS_ECDHE_PSK_WITH_NULL_SHA384,                // { 0xc0, 0x3B};
-    };
-
-    // hash for quick access to cipher suite by name
-    private static final Hashtable<String, CipherSuite> SUITES_BY_NAME;
-
-    /**
-     * array of supported cipher suites.
-     * Set of supported suites is defined at the moment provider's start
-     */
-    //  TODO Dynamically supported suites: new providers may be dynamically
-    //  added/removed and the set of supported suites may be changed
-    static final CipherSuite[] SUPPORTED_CIPHER_SUITES;
-
-    /**
-     * array of supported cipher suites names
-     */
-    static final String[] SUPPORTED_CIPHER_SUITE_NAMES;
-
-    /**
-     * default cipher suites
-     */
-    static final CipherSuite[] DEFAULT_CIPHER_SUITES;
-
-    static {
-        SUITES_BY_NAME = new Hashtable<String, CipherSuite>();
-        int count_0x00 = registerCipherSuitesByCode(SUITES_BY_CODE_0x00);
-        int count_0xc0 = registerCipherSuitesByCode(SUITES_BY_CODE_0xc0);
-        int count = count_0x00 + count_0xc0;
-        SUPPORTED_CIPHER_SUITES = new CipherSuite[count];
-        SUPPORTED_CIPHER_SUITE_NAMES = new String[count];
-        registerSupportedCipherSuites(0, SUITES_BY_CODE_0x00);
-        registerSupportedCipherSuites(count_0x00, SUITES_BY_CODE_0xc0);
-
-        // Make DEFAULT_CIPHER_SUITES the sublist of NativeCrypto's default cipher suites supported
-        // by this SSLEngine implementation.
-        List<CipherSuite> defaultCipherSuitesList = new ArrayList<CipherSuite>();
-        for (String cipherSuiteName : NativeCrypto.getDefaultCipherSuites()) {
-            CipherSuite cipherSuite = CipherSuite.getByName(cipherSuiteName);
-            if ((cipherSuite != null) && (cipherSuite.supported)) {
-                defaultCipherSuitesList.add(cipherSuite);
-            }
-        }
-        DEFAULT_CIPHER_SUITES = defaultCipherSuitesList.toArray(
-                new CipherSuite[defaultCipherSuitesList.size()]);
-    }
-    private static int registerCipherSuitesByCode(CipherSuite[] cipherSuites) {
-        int count = 0;
-        for (int i = 0; i < cipherSuites.length; i++) {
-            if (cipherSuites[i] == SSL_NULL_WITH_NULL_NULL) {
-                continue;
-            }
-            if (cipherSuites[i] == null) {
-                continue;
-            }
-            SUITES_BY_NAME.put(cipherSuites[i].getName(), cipherSuites[i]);
-            if (cipherSuites[i].supported) {
-                count++;
-            }
-        }
-        return count;
-    }
-    private static void registerSupportedCipherSuites(int offset, CipherSuite[] cipherSuites) {
-        int count = offset;
-        for (int i = 0; i < cipherSuites.length; i++) {
-            if (cipherSuites[i] == SSL_NULL_WITH_NULL_NULL) {
-                continue;
-            }
-            if (cipherSuites[i] == null) {
-                continue;
-            }
-            if (cipherSuites[i].supported) {
-                SUPPORTED_CIPHER_SUITES[count] = cipherSuites[i];
-                SUPPORTED_CIPHER_SUITE_NAMES[count] = SUPPORTED_CIPHER_SUITES[count].getName();
-                count++;
-            }
-        }
-    }
-
-    /**
-     * Returns CipherSuite by name
-     */
-    public static CipherSuite getByName(String name) {
-        return SUITES_BY_NAME.get(name);
-    }
-
-    /**
-     * Returns CipherSuite based on TLS CipherSuite code
-     * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., A.5. The CipherSuite</a>
-     */
-    public static CipherSuite getByCode(byte b1, byte b2) {
-        int i1 = b1 & 0xff;
-        int i2 = b2 & 0xff;
-        CipherSuite cs = getCipherSuiteByCode(0, i1, i2);
-        if (cs != null) {
-            return cs;
-        }
-        return new CipherSuite("UNKNOWN_" + i1 + "_" + i2, false, 0, null,
-                               null, null, new byte[] { b1, b2 });
-    }
-
-    /**
-     * Returns CipherSuite based on V2CipherSpec code
-     * as described in TLS 1.0 spec., E. Backward Compatibility With SSL
-     */
-    public static CipherSuite getByCode(byte b1, byte b2, byte b3) {
-        int i1 = b1 & 0xff;
-        int i2 = b2 & 0xff;
-        int i3 = b3 & 0xff;
-        CipherSuite cs = getCipherSuiteByCode(i1, i2, i3);
-        if (cs != null) {
-            return cs;
-        }
-        return new CipherSuite("UNKNOWN_" + i1 + "_" + i2 + "_" + i3, false, 0,
-                               null, null, null, new byte[] { b1, b2, b3 });
-    }
-
-    private static CipherSuite getCipherSuiteByCode(int i1, int i2, int i3) {
-        CipherSuite[] cipherSuites;
-        if (i1 == 0x00 && i2 == 0x00) {
-            cipherSuites = SUITES_BY_CODE_0x00;
-        } else if (i1 == 0x00 && i2 == 0xc0) {
-            cipherSuites = SUITES_BY_CODE_0xc0;
-        } else {
-            return null;
-        }
-        if (i3 >= cipherSuites.length) {
-            return null;
-        }
-        return cipherSuites[i3];
-    }
-
-    /**
-     * Creates CipherSuite
-     */
-    private CipherSuite(String name, boolean isExportable, int keyExchange,
-            String authType, String cipherName, String hash, byte[] code) {
-        this.name = name;
-        this.keyExchange = keyExchange;
-        this.authType = authType;
-        this.isExportable = isExportable;
-        if (cipherName == null) {
-            this.cipherName = null;
-            keyMaterial = 0;
-            expandedKeyMaterial = 0;
-            effectiveKeyBytes = 0;
-            ivSize = 0;
-            blockSize = 0;
-            needInitialRecordSplit = false;
-        // BEGIN android-removed
-        // } else if ("IDEA_CBC".equals(cipherName)) {
-        //     this.cipherName = "IDEA/CBC/NoPadding";
-        //     keyMaterial = 16;
-        //     expandedKeyMaterial = 16;
-        //     effectiveKeyBytes = 16;
-        //     ivSize = 8;
-        //     blockSize = 8;
-        // } else if ("RC2_CBC_40".equals(cipherName)) {
-        //     this.cipherName = "RC2/CBC/NoPadding";
-        //     keyMaterial = 5;
-        //     expandedKeyMaterial = 16;
-        //     effectiveKeyBytes = 5;
-        //     ivSize = 8;
-        //     blockSize = 8;
-        // END android-removed
-        } else if ("RC4_40".equals(cipherName)) {
-            this.cipherName = "RC4";
-            keyMaterial = 5;
-            expandedKeyMaterial = 16;
-            effectiveKeyBytes = 5;
-            ivSize = 0;
-            blockSize = 0;
-            needInitialRecordSplit = false;
-        } else if ("RC4_128".equals(cipherName)) {
-            this.cipherName = "RC4";
-            keyMaterial = 16;
-            expandedKeyMaterial = 16;
-            effectiveKeyBytes = 16;
-            ivSize = 0;
-            blockSize = 0;
-            needInitialRecordSplit = false;
-        } else if ("DES40_CBC".equals(cipherName)) {
-            this.cipherName = "DES/CBC/NoPadding";
-            keyMaterial = 5;
-            expandedKeyMaterial = 8;
-            effectiveKeyBytes = 5;
-            ivSize = 8;
-            blockSize = 8;
-            needInitialRecordSplit = true;
-        } else if ("DES_CBC".equals(cipherName)) {
-            this.cipherName = "DES/CBC/NoPadding";
-            keyMaterial = 8;
-            expandedKeyMaterial = 8;
-            effectiveKeyBytes = 7;
-            ivSize = 8;
-            blockSize = 8;
-            needInitialRecordSplit = true;
-        } else if ("3DES_EDE_CBC".equals(cipherName)) {
-            this.cipherName = "DESede/CBC/NoPadding";
-            keyMaterial = 24;
-            expandedKeyMaterial = 24;
-            effectiveKeyBytes = 24;
-            ivSize = 8;
-            blockSize = 8;
-            needInitialRecordSplit = true;
-        } else if ("AES_128_CBC".equals(cipherName)) {
-            this.cipherName = "AES/CBC/NoPadding";
-            keyMaterial = 16;
-            expandedKeyMaterial = 16;
-            effectiveKeyBytes = 16;
-            ivSize = 16;
-            blockSize = 16;
-            needInitialRecordSplit = true;
-        } else if ("AES_256_CBC".equals(cipherName)) {
-            this.cipherName = "AES/CBC/NoPadding";
-            keyMaterial = 32;
-            expandedKeyMaterial = 32;
-            effectiveKeyBytes = 32;
-            ivSize = 16;
-            blockSize = 16;
-            needInitialRecordSplit = true;
-        } else {
-            this.cipherName = cipherName;
-            keyMaterial = 0;
-            expandedKeyMaterial = 0;
-            effectiveKeyBytes = 0;
-            ivSize = 0;
-            blockSize = 0;
-            needInitialRecordSplit = false;
-        }
-
-        if ("MD5".equals(hash)) {
-            this.hmacName = "HmacMD5";
-            this.hashName = "MD5";
-            hashSize = 16;
-        } else if ("SHA".equals(hash)) {
-            this.hmacName = "HmacSHA1";
-            this.hashName = "SHA-1";
-            hashSize = 20;
-        } else {
-            this.hmacName = null;
-            this.hashName = null;
-            hashSize = 0;
-        }
-
-        cipherSuiteCode = code;
-
-        if (this.cipherName != null) {
-            try {
-                Cipher.getInstance(this.cipherName);
-            } catch (GeneralSecurityException e) {
-                supported = false;
-            }
-        }
-
-        // We define the Elliptic Curve cipher suites for use with
-        // code shared by OpenSSL, but they are not supported by
-        // SSLEngine or SSLSocket's built with SSLEngine.
-        if (this.name.startsWith("TLS_EC")) {
-            supported = false;
-        }
-    }
-
-    /**
-     * Returns true if cipher suite is anonymous
-     */
-    public boolean isAnonymous() {
-        if (keyExchange == KEY_EXCHANGE_DH_anon
-                || keyExchange == KEY_EXCHANGE_DH_anon_EXPORT
-                || keyExchange == KEY_EXCHANGE_ECDH_anon) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Returns array of supported CipherSuites
-     */
-    public static CipherSuite[] getSupported() {
-        return SUPPORTED_CIPHER_SUITES;
-    }
-
-    /**
-     * Returns array of supported cipher suites names
-     */
-    public static String[] getSupportedCipherSuiteNames() {
-        return SUPPORTED_CIPHER_SUITE_NAMES.clone();
-    }
-
-    /**
-     * Returns cipher suite name
-     */
-    public String getName() {
-        return name;
-    }
-
-    /**
-     * Returns cipher suite code as byte array
-     */
-    public byte[] toBytes() {
-        return cipherSuiteCode;
-    }
-
-    /**
-     * Returns cipher suite description
-     */
-    @Override
-    public String toString() {
-        return name + ": " + cipherSuiteCode[0] + " " + cipherSuiteCode[1];
-    }
-
-    /**
-     * Returns cipher algorithm name
-     */
-    public String getBulkEncryptionAlgorithm() {
-        return cipherName;
-    }
-
-    /**
-     * Returns cipher block size
-     */
-    public int getBlockSize() {
-        return blockSize;
-    }
-
-    /**
-     * Returns MAC algorithm name
-     */
-    public String getHmacName() {
-        return hmacName;
-    }
-
-    /**
-     * Returns hash algorithm name
-     */
-    public String getHashName() {
-        return hashName;
-    }
-
-    /**
-     * Returns hash size
-     */
-    public int getMACLength() {
-        return hashSize;
-    }
-
-    /**
-     * Indicates whether this cipher suite is exportable
-     */
-    public boolean isExportable() {
-        return isExportable;
-    }
-
-    /**
-     * Indicates whether this cipher suite needs the initial record split to
-     * mitigate the BEAST attack.
-     */
-    public boolean isInitialRecordSplit() {
-        return needInitialRecordSplit;
-    }
-
-    static final String KEY_TYPE_RSA = "RSA";
-    static final String KEY_TYPE_DSA = "DSA";
-    static final String KEY_TYPE_DH_RSA = "DH_RSA";
-    static final String KEY_TYPE_DH_DSA = "DH_DSA";
-    static final String KEY_TYPE_EC = "EC";
-    static final String KEY_TYPE_EC_EC = "EC_EC";
-    static final String KEY_TYPE_EC_RSA = "EC_RSA";
-
-    /**
-     * Returns key type constant suitable for calling
-     * X509KeyManager.chooseServerAlias or
-     * X509ExtendedKeyManager.chooseEngineServerAlias.
-     */
-    public String getServerKeyType() {
-        switch (keyExchange) {
-            case KEY_EXCHANGE_DHE_RSA:
-            case KEY_EXCHANGE_DHE_RSA_EXPORT:
-            case KEY_EXCHANGE_ECDHE_RSA:
-            case KEY_EXCHANGE_RSA:
-            case KEY_EXCHANGE_RSA_EXPORT:
-                return KEY_TYPE_RSA;
-            case KEY_EXCHANGE_DHE_DSS:
-            case KEY_EXCHANGE_DHE_DSS_EXPORT:
-                return KEY_TYPE_DSA;
-            case KEY_EXCHANGE_ECDH_ECDSA:
-            case KEY_EXCHANGE_ECDHE_ECDSA:
-                return KEY_TYPE_EC_EC;
-            case KEY_EXCHANGE_ECDH_RSA:
-                return KEY_TYPE_EC_RSA;
-            case KEY_EXCHANGE_DH_anon:
-            case KEY_EXCHANGE_DH_anon_EXPORT:
-            case KEY_EXCHANGE_ECDH_anon:
-                return null;
-            default:
-                throw new IllegalStateException("Unknown key type for key exchange " + keyExchange);
-        }
-    }
-
-    /**
-     * Client certificate types as defined in
-     * TLS 1.0 spec., 7.4.4. Certificate request.
-     * EC constants from RFC 4492.
-     * Names match openssl constants.
-     */
-    static final byte TLS_CT_RSA_SIGN = 1;
-    static final byte TLS_CT_DSS_SIGN = 2;
-    static final byte TLS_CT_RSA_FIXED_DH = 3;
-    static final byte TLS_CT_DSS_FIXED_DH = 4;
-    static final byte TLS_CT_ECDSA_SIGN = 64;
-    static final byte TLS_CT_RSA_FIXED_ECDH = 65;
-    static final byte TLS_CT_ECDSA_FIXED_ECDH = 66;
-
-    /**
-     * Similar to getServerKeyType, but returns value given TLS
-     * ClientCertificateType byte values from a CertificateRequest
-     * message for use with X509KeyManager.chooseClientAlias or
-     * X509ExtendedKeyManager.chooseEngineClientAlias.
-     */
-    public static String getClientKeyType(byte keyType) {
-        // See also http://www.ietf.org/assignments/tls-parameters/tls-parameters.xml
-        switch (keyType) {
-            case TLS_CT_RSA_SIGN:
-                return KEY_TYPE_RSA; // RFC rsa_sign
-            case TLS_CT_DSS_SIGN:
-                return KEY_TYPE_DSA; // RFC dss_sign
-            case TLS_CT_RSA_FIXED_DH:
-                return KEY_TYPE_DH_RSA; // RFC rsa_fixed_dh
-            case TLS_CT_DSS_FIXED_DH:
-                return KEY_TYPE_DH_DSA; // RFC dss_fixed_dh
-            case TLS_CT_ECDSA_SIGN:
-                return KEY_TYPE_EC; // RFC ecdsa_sign
-            case TLS_CT_RSA_FIXED_ECDH:
-                return KEY_TYPE_EC_RSA; // RFC rsa_fixed_ecdh
-            case TLS_CT_ECDSA_FIXED_ECDH:
-                return KEY_TYPE_EC_EC; // RFC ecdsa_fixed_ecdh
-            default:
-                return null;
-        }
-    }
-
-    private static final String AUTH_TYPE_RSA = "RSA";
-    private static final String AUTH_TYPE_RSA_EXPORT = "RSA_EXPORT";
-    private static final String AUTH_TYPE_DHE_DSS = "DHE_DSS";
-    private static final String AUTH_TYPE_DHE_RSA = "DHE_RSA";
-    private static final String AUTH_TYPE_DH_DSS = "DH_DSS";
-    private static final String AUTH_TYPE_DH_RSA = "DH_RSA";
-    private static final String AUTH_TYPE_ECDH_ECDSA = "ECDH_ECDSA";
-    private static final String AUTH_TYPE_ECDH_RSA = "ECDH_RSA";
-    private static final String AUTH_TYPE_ECDHE_ECDSA = "ECDHE_ECDSA";
-    private static final String AUTH_TYPE_ECDHE_RSA = "ECDHE_RSA";
-
-    /**
-     * Returns auth type constant suitable for calling X509TrustManager.checkServerTrusted.
-     */
-    public String getAuthType(boolean emphemeral) {
-        switch (keyExchange) {
-            case KEY_EXCHANGE_RSA:
-                return AUTH_TYPE_RSA;
-            case KEY_EXCHANGE_RSA_EXPORT:
-                return emphemeral ? AUTH_TYPE_RSA_EXPORT : AUTH_TYPE_RSA;
-            case KEY_EXCHANGE_DHE_DSS:
-            case KEY_EXCHANGE_DHE_DSS_EXPORT:
-                return AUTH_TYPE_DHE_DSS;
-            case KEY_EXCHANGE_DHE_RSA:
-            case KEY_EXCHANGE_DHE_RSA_EXPORT:
-                return AUTH_TYPE_DHE_RSA;
-            case KEY_EXCHANGE_ECDH_ECDSA:
-                return AUTH_TYPE_ECDH_ECDSA;
-            case KEY_EXCHANGE_ECDHE_ECDSA:
-                return AUTH_TYPE_ECDHE_ECDSA;
-            case KEY_EXCHANGE_ECDH_RSA:
-                return AUTH_TYPE_ECDH_RSA;
-            case KEY_EXCHANGE_ECDHE_RSA:
-                return AUTH_TYPE_ECDHE_RSA;
-            case KEY_EXCHANGE_DH_anon:
-            case KEY_EXCHANGE_DH_anon_EXPORT:
-            case KEY_EXCHANGE_ECDH_anon:
-                return null;
-            default:
-                throw new IllegalStateException("Unknown auth type for key exchange " + keyExchange);
-        }
-    }
-}
diff --git a/src/main/java/org/conscrypt/ClientHandshakeImpl.java b/src/main/java/org/conscrypt/ClientHandshakeImpl.java
deleted file mode 100644
index 17815ea..0000000
--- a/src/main/java/org/conscrypt/ClientHandshakeImpl.java
+++ /dev/null
@@ -1,579 +0,0 @@
-/*
- *  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.io.IOException;
-import java.security.Key;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.util.Arrays;
-import javax.crypto.Cipher;
-import javax.crypto.KeyAgreement;
-import javax.crypto.interfaces.DHKey;
-import javax.crypto.interfaces.DHPublicKey;
-import javax.crypto.spec.DHParameterSpec;
-import javax.crypto.spec.DHPublicKeySpec;
-import javax.crypto.spec.SecretKeySpec;
-import javax.net.ssl.X509ExtendedKeyManager;
-import javax.net.ssl.X509KeyManager;
-import javax.net.ssl.X509TrustManager;
-import javax.security.auth.x500.X500Principal;
-
-/**
- * Client side handshake protocol implementation.
- * Handshake protocol operates on top of the Record Protocol.
- * It is responsible for session negotiating.
- *
- * The implementation processes inbound server handshake messages,
- * creates and sends respond messages. Outbound messages are supplied
- * to Record Protocol. Detected errors are reported to the Alert protocol.
- *
- * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7. The
- * TLS Handshake Protocol</a>
- *
- */
-public class ClientHandshakeImpl extends HandshakeProtocol {
-
-    /**
-     * Creates Client Handshake Implementation
-     *
-     * @param owner
-     */
-    ClientHandshakeImpl(SSLEngineImpl owner) {
-        super(owner);
-    }
-
-    /**
-     * Starts handshake
-     *
-     */
-    @Override
-    public void start() {
-        if (session == null) { // initial handshake
-            session = findSessionToResume();
-        } else { // start session renegotiation
-            if (clientHello != null && this.status != FINISHED) {
-                // current negotiation has not completed
-                return; // ignore
-            }
-            if (!session.isValid()) {
-                session = null;
-            }
-        }
-        if (session != null) {
-            isResuming = true;
-        } else if (parameters.getEnableSessionCreation()){
-            isResuming = false;
-            session = new SSLSessionImpl(parameters.getSecureRandom());
-            session.setPeer(engineOwner.getPeerHost(), engineOwner.getPeerPort());
-            session.protocol = ProtocolVersion.getLatestVersion(parameters.getEnabledProtocols());
-            recordProtocol.setVersion(session.protocol.version);
-        } else {
-            fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "SSL Session may not be created ");
-        }
-        startSession();
-    }
-
-    /**
-     * Starts renegotiation on a new session
-     *
-     */
-    private void renegotiateNewSession() {
-        if (parameters.getEnableSessionCreation()){
-            isResuming = false;
-            session = new SSLSessionImpl(parameters.getSecureRandom());
-            session.setPeer(engineOwner.getPeerHost(), engineOwner.getPeerPort());
-            session.protocol = ProtocolVersion.getLatestVersion(parameters.getEnabledProtocols());
-            recordProtocol.setVersion(session.protocol.version);
-            startSession();
-        } else {
-            status = NOT_HANDSHAKING;
-            sendWarningAlert(AlertProtocol.NO_RENEGOTIATION);
-        }
-    }
-
-    /*
-     * Starts/resumes session
-     */
-    private void startSession() {
-        CipherSuite[] cipher_suites;
-        if (isResuming) {
-            cipher_suites = new CipherSuite[] { session.cipherSuite };
-        } else {
-            cipher_suites = parameters.getEnabledCipherSuitesMember();
-        }
-        clientHello = new ClientHello(parameters.getSecureRandom(),
-                session.protocol.version, session.id, cipher_suites);
-        session.clientRandom = clientHello.random;
-        send(clientHello);
-        status = NEED_UNWRAP;
-    }
-
-    /**
-     * Processes inbound handshake messages
-     * @param bytes
-     */
-    @Override
-    public void unwrap(byte[] bytes) {
-        if (this.delegatedTaskErr != null) {
-            Exception e = this.delegatedTaskErr;
-            this.delegatedTaskErr = null;
-            this.fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "Error in delegated task", e);
-        }
-        int handshakeType;
-        io_stream.append(bytes);
-        while (io_stream.available() > 0) {
-            io_stream.mark();
-            int length;
-            try {
-                handshakeType = io_stream.read();
-                length = io_stream.readUint24();
-                if (io_stream.available() < length) {
-                    io_stream.reset();
-                    return;
-                }
-                switch (handshakeType) {
-                case 0: // HELLO_REQUEST
-                    // we don't need to take this message into account
-                    // during FINISH message verification, so remove it
-                    io_stream.removeFromMarkedPosition();
-                    if (clientHello != null
-                            && (clientFinished == null || serverFinished == null)) {
-                        //currently negotiating - ignore
-                        break;
-                    }
-                    // renegotiate
-                    if (session.isValid()) {
-                        session = (SSLSessionImpl) session.clone();
-                        isResuming = true;
-                        startSession();
-                    } else {
-                        // if SSLSession is invalidated (e.g. timeout limit is
-                        // exceeded) connection can't resume the session.
-                        renegotiateNewSession();
-                    }
-                    break;
-                case 2: // SERVER_HELLO
-                    if (clientHello == null || serverHello != null) {
-                        unexpectedMessage();
-                        return;
-                    }
-                    serverHello = new ServerHello(io_stream, length);
-
-                    //check protocol version
-                    ProtocolVersion servProt = ProtocolVersion.getByVersion(serverHello.server_version);
-                    String[] enabled = parameters.getEnabledProtocols();
-                    find: {
-                        for (int i = 0; i < enabled.length; i++) {
-                            if (servProt.equals(ProtocolVersion.getByName(enabled[i]))) {
-                                break find;
-                            }
-                        }
-                        fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
-                                   "Bad server hello protocol version");
-                    }
-
-                    // check compression method
-                    if (serverHello.compression_method != 0) {
-                        fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
-                                   "Bad server hello compression method");
-                    }
-
-                    //check cipher_suite
-                    CipherSuite[] enabledSuites = parameters.getEnabledCipherSuitesMember();
-                    find: {
-                        for (int i = 0; i < enabledSuites.length; i++) {
-                            if (serverHello.cipher_suite.equals(enabledSuites[i])) {
-                                break find;
-                            }
-                        }
-                        fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
-                                   "Bad server hello cipher suite");
-                    }
-
-                    if (isResuming) {
-                        if (serverHello.session_id.length == 0) {
-                            // server is not willing to establish the new connection
-                            // using specified session
-                            isResuming = false;
-                        } else if (!Arrays.equals(serverHello.session_id, clientHello.session_id)) {
-                            isResuming = false;
-                        } else if (!session.protocol.equals(servProt)) {
-                            fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
-                                       "Bad server hello protocol version");
-                        } else if (!session.cipherSuite.equals(serverHello.cipher_suite)) {
-                            fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
-                                       "Bad server hello cipher suite");
-                        }
-                        if (serverHello.server_version[1] == 1) {
-                            computerReferenceVerifyDataTLS("server finished");
-                        } else {
-                            computerReferenceVerifyDataSSLv3(SSLv3Constants.server);
-                        }
-                    }
-                    session.protocol = servProt;
-                    recordProtocol.setVersion(session.protocol.version);
-                    session.cipherSuite = serverHello.cipher_suite;
-                    session.id = serverHello.session_id.clone();
-                    session.serverRandom = serverHello.random;
-                    break;
-                case 11: // CERTIFICATE
-                    if (serverHello == null || serverKeyExchange != null
-                            || serverCert != null || isResuming) {
-                        unexpectedMessage();
-                        return;
-                    }
-                    serverCert = new CertificateMessage(io_stream, length);
-                    break;
-                case 12: // SERVER_KEY_EXCHANGE
-                    if (serverHello == null || serverKeyExchange != null
-                            || isResuming) {
-                        unexpectedMessage();
-                        return;
-                    }
-                    serverKeyExchange = new ServerKeyExchange(io_stream,
-                            length, session.cipherSuite.keyExchange);
-                    break;
-                case 13: // CERTIFICATE_REQUEST
-                    if (serverCert == null || certificateRequest != null
-                            || session.cipherSuite.isAnonymous() || isResuming) {
-                        unexpectedMessage();
-                        return;
-                    }
-                    certificateRequest = new CertificateRequest(io_stream, length);
-                    break;
-                case 14: // SERVER_HELLO_DONE
-                    if (serverHello == null || serverHelloDone != null || isResuming) {
-                        unexpectedMessage();
-                        return;
-                    }
-                    serverHelloDone = new ServerHelloDone(io_stream, length);
-                    if (this.nonBlocking) {
-                        delegatedTasks.add(new DelegatedTask(new Runnable() {
-                            @Override
-                            public void run() {
-                                processServerHelloDone();
-                            }
-                        }, this));
-                        return;
-                    }
-                    processServerHelloDone();
-                    break;
-                case 20: // FINISHED
-                    if (!changeCipherSpecReceived) {
-                        unexpectedMessage();
-                        return;
-                    }
-                    serverFinished = new Finished(io_stream, length);
-                    verifyFinished(serverFinished.getData());
-                    session.lastAccessedTime = System.currentTimeMillis();
-                    session.context = parameters.getClientSessionContext();
-                    parameters.getClientSessionContext().putSession(session);
-                    if (isResuming) {
-                        sendChangeCipherSpec();
-                    } else {
-                        session.lastAccessedTime = System.currentTimeMillis();
-                        status = FINISHED;
-                    }
-                    // XXX there is no cleanup work
-                    break;
-                default:
-                    unexpectedMessage();
-                    return;
-                }
-            } catch (IOException e) {
-                // io stream dosn't contain complete handshake message
-                io_stream.reset();
-                return;
-            }
-        }
-
-    }
-
-    /**
-     * Processes SSLv2 Hello message.
-     * SSLv2 client hello message message is an unexpected message
-     * for client side of handshake protocol.
-     * See TLS 1.0 spec., E.1. Version 2 client hello
-     * @param bytes
-     */
-    @Override
-    public void unwrapSSLv2(byte[] bytes) {
-        unexpectedMessage();
-    }
-
-    /**
-     * Creates and sends Finished message
-     */
-    @Override
-    protected void makeFinished() {
-        byte[] verify_data;
-        if (serverHello.server_version[1] == 1) {
-            verify_data = new byte[12];
-            computerVerifyDataTLS("client finished", verify_data);
-        } else {
-            verify_data = new byte[36];
-            computerVerifyDataSSLv3(SSLv3Constants.client, verify_data);
-        }
-        clientFinished = new Finished(verify_data);
-        send(clientFinished);
-        if (isResuming) {
-            session.lastAccessedTime = System.currentTimeMillis();
-            status = FINISHED;
-        } else {
-            if (serverHello.server_version[1] == 1) {
-                computerReferenceVerifyDataTLS("server finished");
-            } else {
-                computerReferenceVerifyDataSSLv3(SSLv3Constants.server);
-            }
-            status = NEED_UNWRAP;
-        }
-    }
-
-    /**
-     * Processes ServerHelloDone: makes verification of the server messages; sends
-     * client messages, computers masterSecret, sends ChangeCipherSpec
-     */
-    void processServerHelloDone() {
-        PrivateKey clientKey = null;
-
-        if (serverCert != null) {
-            if (session.cipherSuite.isAnonymous()) {
-                unexpectedMessage();
-                return;
-            }
-            verifyServerCert();
-        } else {
-            if (!session.cipherSuite.isAnonymous()) {
-                unexpectedMessage();
-                return;
-            }
-        }
-
-        // Client certificate
-        if (certificateRequest != null) {
-            X509Certificate[] certs = null;
-            // obtain certificates from key manager
-            String alias = null;
-            String[] certTypes = certificateRequest.getTypesAsString();
-            X500Principal[] issuers = certificateRequest.certificate_authorities;
-            X509KeyManager km = parameters.getKeyManager();
-            if (km instanceof X509ExtendedKeyManager) {
-                X509ExtendedKeyManager ekm = (X509ExtendedKeyManager)km;
-                alias = ekm.chooseEngineClientAlias(certTypes, issuers, this.engineOwner);
-                if (alias != null) {
-                    certs = ekm.getCertificateChain(alias);
-                }
-            } else {
-                alias = km.chooseClientAlias(certTypes, issuers, null);
-                if (alias != null) {
-                    certs = km.getCertificateChain(alias);
-                }
-            }
-
-            session.localCertificates = certs;
-            clientCert = new CertificateMessage(certs);
-            clientKey = km.getPrivateKey(alias);
-            send(clientCert);
-        }
-        // Client key exchange
-        if (session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA
-                || session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA_EXPORT) {
-            // RSA encrypted premaster secret message
-            Cipher c;
-            try {
-                c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
-                if (serverKeyExchange != null) {
-                    if (!session.cipherSuite.isAnonymous()) {
-                        DigitalSignature ds = new DigitalSignature(serverCert.getAuthType());
-                        ds.init(serverCert.certs[0]);
-                        ds.update(clientHello.getRandom());
-                        ds.update(serverHello.getRandom());
-                        if (!serverKeyExchange.verifySignature(ds)) {
-                            fatalAlert(AlertProtocol.DECRYPT_ERROR, "Cannot verify RSA params");
-                            return;
-                        }
-                    }
-                    c.init(Cipher.WRAP_MODE, serverKeyExchange
-                            .getRSAPublicKey());
-                } else {
-                    c.init(Cipher.WRAP_MODE, serverCert.certs[0]);
-                }
-            } catch (Exception e) {
-                fatalAlert(AlertProtocol.INTERNAL_ERROR,
-                        "Unexpected exception", e);
-                return;
-            }
-            preMasterSecret = new byte[48];
-            parameters.getSecureRandom().nextBytes(preMasterSecret);
-            System.arraycopy(clientHello.client_version, 0, preMasterSecret, 0, 2);
-            try {
-                clientKeyExchange = new ClientKeyExchange(c
-                        .wrap(new SecretKeySpec(preMasterSecret, "preMasterSecret")),
-                        serverHello.server_version[1] == 1);
-            } catch (Exception e) {
-                fatalAlert(AlertProtocol.INTERNAL_ERROR,
-                        "Unexpected exception", e);
-                return;
-            }
-        } else if (session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_DSS
-                || session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_DSS_EXPORT
-                || session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_RSA
-                || session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_RSA_EXPORT
-                || session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_DH_anon
-                || session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_DH_anon_EXPORT) {
-            /*
-             * All other key exchanges should have had a DH key communicated via
-             * ServerKeyExchange beforehand.
-             */
-            if (serverKeyExchange == null) {
-                fatalAlert(AlertProtocol.UNEXPECTED_MESSAGE, "Expected ServerKeyExchange");
-                return;
-            }
-            if (session.cipherSuite.isAnonymous() != serverKeyExchange.isAnonymous()) {
-                fatalAlert(AlertProtocol.DECRYPT_ERROR, "Wrong type in ServerKeyExchange");
-                return;
-            }
-            try {
-                if (!session.cipherSuite.isAnonymous()) {
-                    DigitalSignature ds = new DigitalSignature(serverCert.getAuthType());
-                    ds.init(serverCert.certs[0]);
-                    ds.update(clientHello.getRandom());
-                    ds.update(serverHello.getRandom());
-                    if (!serverKeyExchange.verifySignature(ds)) {
-                        fatalAlert(AlertProtocol.DECRYPT_ERROR, "Cannot verify DH params");
-                        return;
-                    }
-                }
-                KeyFactory kf = KeyFactory.getInstance("DH");
-                KeyAgreement agreement = KeyAgreement.getInstance("DH");
-                KeyPairGenerator kpg = KeyPairGenerator.getInstance("DH");
-                PublicKey serverDhPublic = kf.generatePublic(new DHPublicKeySpec(
-                        serverKeyExchange.par3, serverKeyExchange.par1,
-                        serverKeyExchange.par2));
-                DHParameterSpec spec = new DHParameterSpec(serverKeyExchange.par1,
-                        serverKeyExchange.par2);
-                kpg.initialize(spec);
-                KeyPair kp = kpg.generateKeyPair();
-                DHPublicKey pubDhKey = (DHPublicKey) kp.getPublic();
-                clientKeyExchange = new ClientKeyExchange(pubDhKey.getY());
-                PrivateKey privDhKey = kp.getPrivate();
-                agreement.init(privDhKey);
-                agreement.doPhase(serverDhPublic, true);
-                preMasterSecret = agreement.generateSecret();
-            } catch (Exception e) {
-                fatalAlert(AlertProtocol.INTERNAL_ERROR,
-                        "Unexpected exception", e);
-                return;
-            }
-        } else {
-            fatalAlert(AlertProtocol.DECRYPT_ERROR, "Unsupported handshake type");
-            return;
-        }
-
-        if (clientKeyExchange != null) {
-            send(clientKeyExchange);
-        }
-
-        computerMasterSecret();
-
-        // send certificate verify for all certificates except those containing
-        // fixed DH parameters
-        if (clientCert != null && clientCert.certs.length > 0 && !clientKeyExchange.isEmpty()) {
-            // Certificate verify
-            String authType = clientKey.getAlgorithm();
-            DigitalSignature ds = new DigitalSignature(authType);
-            ds.init(clientKey);
-
-            if ("RSA".equals(authType)) {
-                ds.setMD5(io_stream.getDigestMD5());
-                ds.setSHA(io_stream.getDigestSHA());
-            } else if ("DSA".equals(authType)) {
-                ds.setSHA(io_stream.getDigestSHA());
-            // The Signature should be empty in case of anonymous signature algorithm:
-            // } else if ("DH".equals(authType)) {
-            }
-            certificateVerify = new CertificateVerify(ds.sign());
-            send(certificateVerify);
-        }
-
-        sendChangeCipherSpec();
-    }
-
-    /*
-     * Verifies certificate path
-     */
-    private void verifyServerCert() {
-        String authType = session.cipherSuite.getAuthType(serverKeyExchange != null);
-        if (authType == null) {
-            return;
-        }
-        String hostname = engineOwner.getPeerHost();
-        try {
-            X509TrustManager x509tm = parameters.getTrustManager();
-            if (x509tm instanceof TrustManagerImpl) {
-                TrustManagerImpl tm = (TrustManagerImpl) x509tm;
-                tm.checkServerTrusted(serverCert.certs, authType, hostname);
-            } else {
-                x509tm.checkServerTrusted(serverCert.certs, authType);
-            }
-        } catch (CertificateException e) {
-            fatalAlert(AlertProtocol.BAD_CERTIFICATE, "Not trusted server certificate", e);
-            return;
-        }
-        session.peerCertificates = serverCert.certs;
-    }
-
-    /**
-     * Processes ChangeCipherSpec message
-     */
-    @Override
-    public void receiveChangeCipherSpec() {
-        if (isResuming) {
-            if (serverHello == null) {
-                unexpectedMessage();
-            }
-        } else if (clientFinished == null) {
-            unexpectedMessage();
-        }
-        changeCipherSpecReceived = true;
-    }
-
-    // Find session to resume in client session context
-    private SSLSessionImpl findSessionToResume() {
-        String host = engineOwner.getPeerHost();
-        int port = engineOwner.getPeerPort();
-        if (host == null || port == -1) {
-            return null; // starts new session
-        }
-
-        ClientSessionContext context = parameters.getClientSessionContext();
-        SSLSessionImpl session
-                = (SSLSessionImpl) context.getSession(host, port);
-        if (session != null) {
-            session = (SSLSessionImpl) session.clone();
-        }
-        return session;
-    }
-
-}
diff --git a/src/main/java/org/conscrypt/ClientHello.java b/src/main/java/org/conscrypt/ClientHello.java
deleted file mode 100644
index 2bf9f5f..0000000
--- a/src/main/java/org/conscrypt/ClientHello.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- *  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.io.IOException;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import libcore.io.Streams;
-import org.conscrypt.util.EmptyArray;
-
-/**
- * Represents Client Hello message
- * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.1.2.
- * Client hello</a>
- *
- */
-public class ClientHello extends Message {
-
-    /**
-     * Client version
-     */
-    final byte[] client_version;
-
-    /**
-     * Random bytes
-     */
-    final byte[] random = new byte[32];
-
-    /**
-     * Session id
-     */
-    final byte[] session_id;
-
-    /**
-     * Cipher suites supported by the client
-     */
-    final CipherSuite[] cipher_suites;
-
-    /**
-     * Compression methods supported by the client
-     */
-    final byte[] compression_methods;
-
-    /**
-     * Creates outbound message
-     * @param sr
-     * @param version
-     * @param ses_id
-     * @param cipher_suite
-     */
-    public ClientHello(SecureRandom sr, byte[] version, byte[] ses_id,
-            CipherSuite[] cipher_suite) {
-        client_version = version;
-        long gmt_unix_time = System.currentTimeMillis()/1000;
-        sr.nextBytes(random);
-        random[0] = (byte) (gmt_unix_time & 0xFF000000 >>> 24);
-        random[1] = (byte) (gmt_unix_time & 0xFF0000 >>> 16);
-        random[2] = (byte) (gmt_unix_time & 0xFF00 >>> 8);
-        random[3] = (byte) (gmt_unix_time & 0xFF);
-        session_id = ses_id;
-        this.cipher_suites = cipher_suite;
-        compression_methods = new byte[] { 0 }; // CompressionMethod.null
-        length = 38 + session_id.length + (this.cipher_suites.length << 1)
-                + compression_methods.length;
-    }
-
-    /**
-     * Creates inbound message
-     * @param in
-     * @param length
-     * @throws IOException
-     */
-    public ClientHello(HandshakeIODataStream in, int length) throws IOException {
-        client_version = new byte[2];
-        client_version[0] = (byte) in.readUint8();
-        client_version[1] = (byte) in.readUint8();
-        Streams.readFully(in, random);
-        int size = in.read();
-        session_id = new byte[size];
-        in.read(session_id, 0, size);
-        int l = in.readUint16();
-        if ((l & 0x01) == 0x01) { // cipher suites length must be an even number
-            fatalAlert(AlertProtocol.DECODE_ERROR,
-                    "DECODE ERROR: incorrect ClientHello");
-        }
-        size = l >> 1;
-        cipher_suites = new CipherSuite[size];
-        for (int i = 0; i < size; i++) {
-            byte b0 = (byte) in.read();
-            byte b1 = (byte) in.read();
-            cipher_suites[i] = CipherSuite.getByCode(b0, b1);
-        }
-        size = in.read();
-        compression_methods = new byte[size];
-        in.read(compression_methods, 0, size);
-        this.length = 38 + session_id.length + (cipher_suites.length << 1)
-                + compression_methods.length;
-        if (this.length > length) {
-            fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect ClientHello");
-        }
-        // for forward compatibility, extra data is permitted;
-        // must be ignored
-        if (this.length < length) {
-            in.skip(length - this.length);
-            this.length = length;
-        }
-    }
-    /**
-     * Parse V2ClientHello
-     * @param in
-     * @throws IOException
-     */
-    public ClientHello(HandshakeIODataStream in) throws IOException {
-        if (in.readUint8() != 1) {
-            fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect V2ClientHello");
-        }
-        client_version = new byte[2];
-        client_version[0] = (byte) in.readUint8();
-        client_version[1] = (byte) in.readUint8();
-        int cipher_spec_length = in.readUint16();
-        if (in.readUint16() != 0) { // session_id_length
-            // as client already knows the protocol known to a server it should
-            // initiate the connection in that native protocol
-            fatalAlert(AlertProtocol.DECODE_ERROR,
-                    "DECODE ERROR: incorrect V2ClientHello, cannot be used for resuming");
-        }
-        int challenge_length = in.readUint16();
-        if (challenge_length < 16) {
-            fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect V2ClientHello, short challenge data");
-        }
-        session_id = EmptyArray.BYTE;
-        cipher_suites = new CipherSuite[cipher_spec_length/3];
-        for (int i = 0; i < cipher_suites.length; i++) {
-            byte b0 = (byte) in.read();
-            byte b1 = (byte) in.read();
-            byte b2 = (byte) in.read();
-            cipher_suites[i] = CipherSuite.getByCode(b0, b1, b2);
-        }
-        compression_methods = new byte[] { 0 }; // CompressionMethod.null
-
-        if (challenge_length < 32) {
-            Arrays.fill(random, 0, 32 - challenge_length, (byte)0);
-            System.arraycopy(in.read(challenge_length), 0, random, 32 - challenge_length, challenge_length);
-        } else if (challenge_length == 32) {
-            System.arraycopy(in.read(32), 0, random, 0, 32);
-        } else {
-            System.arraycopy(in.read(challenge_length), challenge_length - 32, random, 0, 32);
-        }
-        if (in.available() > 0) {
-            fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect V2ClientHello, extra data");
-        }
-        this.length = 38 + session_id.length + (cipher_suites.length << 1)
-                + compression_methods.length;
-    }
-
-    /**
-     * Sends message
-     * @param out
-     */
-    @Override
-    public void send(HandshakeIODataStream out) {
-        out.write(client_version);
-        out.write(random);
-        out.writeUint8(session_id.length);
-        out.write(session_id);
-        int size = cipher_suites.length << 1;
-        out.writeUint16(size);
-        for (int i = 0; i < cipher_suites.length; i++) {
-            out.write(cipher_suites[i].toBytes());
-        }
-        out.writeUint8(compression_methods.length);
-        for (int i = 0; i < compression_methods.length; i++) {
-            out.write(compression_methods[i]);
-        }
-    }
-
-    /**
-     * Returns client random
-     * @return client random
-     */
-    public byte[] getRandom() {
-        return random;
-    }
-
-    /**
-     * Returns message type
-     */
-    @Override
-    public int getType() {
-        return Handshake.CLIENT_HELLO;
-    }
-}
diff --git a/src/main/java/org/conscrypt/ClientKeyExchange.java b/src/main/java/org/conscrypt/ClientKeyExchange.java
deleted file mode 100644
index 5cad36b..0000000
--- a/src/main/java/org/conscrypt/ClientKeyExchange.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- *  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.io.IOException;
-import java.math.BigInteger;
-import libcore.io.Streams;
-import org.conscrypt.util.EmptyArray;
-
-/**
- * Represents client key exchange message
- * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.7.
- * Client key exchange message</a>
- *
- */
-public class ClientKeyExchange extends Message {
-
-    /**
-     * Exchange keys
-     */
-    final byte[] exchange_keys;
-
-    /**
-     * Equals true if TLS1.0 protocol is used
-     */
-    boolean isTLS;
-
-    /**
-     * Equals true if key exchange algorithm is RSA
-     */
-    final boolean isRSA;
-
-    /**
-     * Creates outbound message
-     * @param encrypted_pre_master_secret
-     * @param isTLS
-     */
-    public ClientKeyExchange(byte[] encrypted_pre_master_secret, boolean isTLS) {
-        this.exchange_keys = encrypted_pre_master_secret;
-        length = this.exchange_keys.length;
-        if (isTLS) {
-            length += 2;
-        }
-        this.isTLS = isTLS;
-        isRSA = true;
-    }
-
-    /**
-     * Creates outbound message
-     * @param dh_Yc
-     */
-    public ClientKeyExchange(BigInteger dh_Yc) {
-        byte[] bb = dh_Yc.toByteArray();
-        if (bb[0] == 0) {
-            exchange_keys = new byte[bb.length-1];
-            System.arraycopy(bb, 1, exchange_keys, 0, exchange_keys.length);
-        } else {
-            exchange_keys = bb;
-        }
-        length = exchange_keys.length +2;
-        isRSA = false;
-    }
-
-    /**
-     * Creates empty message
-     *
-     */
-    public ClientKeyExchange() {
-        exchange_keys = EmptyArray.BYTE;
-        length = 0;
-        isRSA = false;
-    }
-
-    /**
-     * Creates inbound message
-     * @param length
-     * @param isTLS
-     * @param isRSA
-     * @throws IOException
-     */
-    public ClientKeyExchange(HandshakeIODataStream in, int length, boolean isTLS, boolean isRSA)
-            throws IOException {
-        this.isTLS = isTLS;
-        this.isRSA = isRSA;
-        if (length == 0) {
-            this.length = 0;
-            exchange_keys = EmptyArray.BYTE;
-        } else {
-            int size;
-            if (isRSA && !isTLS) {// SSL3.0 RSA
-                size = length;
-                this.length = size;
-            } else { // DH or TLSv1 RSA
-                size = in.readUint16();
-                this.length = 2 + size;
-            }
-            exchange_keys = new byte[size];
-            Streams.readFully(in, exchange_keys);
-            if (this.length != length) {
-                fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect ClientKeyExchange");
-            }
-        }
-    }
-
-    /**
-     * Sends message
-     * @param out
-     */
-    @Override
-    public void send(HandshakeIODataStream out) {
-        if (exchange_keys.length != 0) {
-            if (!isRSA || isTLS) {// DH or TLSv1 RSA
-                out.writeUint16(exchange_keys.length);
-            }
-            out.write(exchange_keys);
-        }
-    }
-
-    /**
-     * Returns message type
-     */
-    @Override
-    public int getType() {
-        return Handshake.CLIENT_KEY_EXCHANGE;
-    }
-
-    /**
-     * Returns true if the message is empty (in case of implicit DH Yc)
-     */
-    public boolean isEmpty() {
-        return (exchange_keys.length == 0);
-    }
-}
diff --git a/src/main/java/org/conscrypt/ConnectionState.java b/src/main/java/org/conscrypt/ConnectionState.java
deleted file mode 100644
index 82fbf3c..0000000
--- a/src/main/java/org/conscrypt/ConnectionState.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- *  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 javax.crypto.Cipher;
-
-/**
- * This abstract class is a base for Record Protocol operating environmet
- * of different SSL protocol versions.
- */
-public abstract class ConnectionState {
-
-    /**
-     * The cipher used for encode operations
-     */
-    protected Cipher encCipher;
-
-    /**
-     * The cipher used for decode operations
-     */
-    protected Cipher decCipher;
-
-    /**
-     * The block size, or zero if not a block cipher
-     */
-    protected int block_size;
-
-    /**
-     * The size of MAC used under this connection state
-     */
-    protected int hash_size;
-
-    /**
-     * Write sequence number which is incremented after each
-     * encrypt call
-     */
-    protected final byte[] write_seq_num = {0, 0, 0, 0, 0, 0, 0, 0};
-
-    /**
-     * Read sequence number which is incremented after each
-     * decrypt call
-     */
-    protected final byte[] read_seq_num = {0, 0, 0, 0, 0, 0, 0, 0};
-
-    protected Logger.Stream logger = Logger.getStream("conn_state");
-
-    /**
-     * Returns the minimal possible size of the
-     * Generic[Stream|Block]Cipher structure under this
-     * connection state.
-     */
-    protected int getMinFragmentSize() {
-        // block ciphers return value with padding included
-        return encCipher.getOutputSize(1+hash_size); // 1 byte for data
-    }
-
-    /**
-     * Returns the size of the Generic[Stream|Block]Cipher structure
-     * corresponding to the content data of specified size.
-     */
-    protected int getFragmentSize(int content_size) {
-        return encCipher.getOutputSize(content_size+hash_size);
-    }
-
-    /**
-     * Returns the minimal upper bound of the content size enclosed
-     * into the Generic[Stream|Block]Cipher structure of specified size.
-     * For stream ciphers the returned value will be exact value.
-     */
-    protected int getContentSize(int generic_cipher_size) {
-        //it does not take the padding of block ciphered structures
-        //into account (so returned value can be greater than actual)
-        return decCipher.getOutputSize(generic_cipher_size)-hash_size;
-    }
-
-    /**
-     * Returns the number of bytes of padding required to round the
-     * content up to the required block size. Assumes power of two
-     * block size.
-     */
-    protected int getPaddingSize(int content_size) {
-        int mask = block_size - 1;
-        return (block_size - (content_size & mask));
-    }
-
-    /**
-     * Creates the GenericStreamCipher or GenericBlockCipher
-     * data structure for specified data of specified type.
-     * @param type - the ContentType of the provided data
-     * @param fragment - the byte array containing the
-     * data to be encrypted under the current connection state.
-     */
-    protected byte[] encrypt(byte type, byte[] fragment) {
-        return encrypt(type, fragment, 0, fragment.length);
-    }
-
-    /**
-     * Creates the GenericStreamCipher or GenericBlockCipher
-     * data structure for specified data of specified type.
-     * @param type - the ContentType of the provided data
-     * @param fragment - the byte array containing the
-     * data to be encrypted under the current connection state.
-     * @param offset - the offset from which the data begins with.
-     * @param len - the length of the data.
-     */
-    protected abstract byte[] encrypt
-        (byte type, byte[] fragment, int offset, int len);
-
-    /**
-     * Retrieves the fragment of the Plaintext structure of
-     * the specified type from the provided data.
-     * @param type - the ContentType of the data to be decrypted.
-     * @param fragment - the byte array containing the
-     * data to be encrypted under the current connection state.
-     */
-    protected byte[] decrypt(byte type, byte[] fragment) {
-        return decrypt(type, fragment, 0, fragment.length);
-    }
-
-    /**
-     * Retrieves the fragment of the Plaintext structure of
-     * the specified type from the provided data.
-     * @param type - the ContentType of the data to be decrypted.
-     * @param fragment - the byte array containing the
-     * data to be encrypted under the current connection state.
-     * @param offset - the offset from which the data begins with.
-     * @param len - the length of the data.
-     */
-    protected abstract byte[] decrypt
-        (byte type, byte[] fragment, int offset, int len);
-
-    /**
-     * Increments the sequence number.
-     */
-    protected static void incSequenceNumber(byte[] seq_num) {
-        int octet = 7;
-        while (octet >= 0) {
-            seq_num[octet] ++;
-            if (seq_num[octet] == 0) {
-                // characteristic overflow, so
-                // carrying a number in adding
-                octet --;
-            } else {
-                return;
-            }
-        }
-    }
-
-    /**
-     * Shutdownes the protocol. It will be impossiblke to use the instance
-     * after the calling of this method.
-     */
-    protected void shutdown() {
-        encCipher = null;
-        decCipher = null;
-        for (int i=0; i<write_seq_num.length; i++) {
-            write_seq_num[i] = 0;
-            read_seq_num[i] = 0;
-        }
-    }
-}
-
diff --git a/src/main/java/org/conscrypt/ConnectionStateSSLv3.java b/src/main/java/org/conscrypt/ConnectionStateSSLv3.java
deleted file mode 100644
index e3cebbb..0000000
--- a/src/main/java/org/conscrypt/ConnectionStateSSLv3.java
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- *  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.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.util.Arrays;
-import javax.crypto.Cipher;
-import javax.crypto.NullCipher;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-import javax.net.ssl.SSLProtocolException;
-
-
-/**
- * This class encapsulates the operating environment of the SSL v3
- * (http://wp.netscape.com/eng/ssl3) Record Protocol and provides
- * relating encryption/decryption functionality.
- * The work functionality is based on the security
- * parameters negotiated during the handshake.
- */
-public class ConnectionStateSSLv3 extends ConnectionState {
-
-    // digest to create and check the message integrity info
-    private final MessageDigest messageDigest;
-    private final byte[] mac_write_secret;
-    private final byte[] mac_read_secret;
-
-    // paddings
-    private final byte[] pad_1;
-    private final byte[] pad_2;
-
-    // array will hold the part of the MAC material:
-    // length of 6 == 2 X (1(SSLCompressed.type) + 2(SSLCompressed.length))
-    // 1 each for encryption and decryption.
-    //
-    // (more on SSLv3 MAC computation and payload protection see
-    // SSL v3 specification, p. 5.2.3)
-    private final byte[] mac_material_part = new byte[6];
-
-    /**
-     * Creates the instance of SSL v3 Connection State. All of the
-     * security parameters are provided by session object.
-     * @param session the sessin object which incapsulates
-     * all of the security parameters established by handshake protocol.
-     * The key calculation for the state is done according
-     * to the SSL v3 Protocol specification.
-     * (http://www.mozilla.org/projects/security/pki/nss/ssl/draft302.txt)
-     */
-    protected ConnectionStateSSLv3(SSLSessionImpl session) {
-        try {
-            CipherSuite cipherSuite = session.cipherSuite;
-
-            boolean is_exportabe =  cipherSuite.isExportable();
-            hash_size = cipherSuite.getMACLength();
-            int key_size = (is_exportabe)
-                ? cipherSuite.keyMaterial
-                : cipherSuite.expandedKeyMaterial;
-            int iv_size = cipherSuite.ivSize;
-            block_size = cipherSuite.getBlockSize();
-
-            String algName = cipherSuite.getBulkEncryptionAlgorithm();
-            String hashName = cipherSuite.getHashName();
-            if (logger != null) {
-                logger.println("ConnectionStateSSLv3.create:");
-                logger.println("  cipher suite name: "
-                                        + session.getCipherSuite());
-                logger.println("  encryption alg name: " + algName);
-                logger.println("  hash alg name: " + hashName);
-                logger.println("  hash size: " + hash_size);
-                logger.println("  block size: " + block_size);
-                logger.println("  IV size:" + iv_size);
-                logger.println("  key size: " + key_size);
-            }
-
-            byte[] clientRandom = session.clientRandom;
-            byte[] serverRandom = session.serverRandom;
-            // so we need PRF value of size of
-            // 2*hash_size + 2*key_size + 2*iv_size
-            byte[] key_block = new byte[2*hash_size + 2*key_size + 2*iv_size];
-            byte[] seed = new byte[clientRandom.length + serverRandom.length];
-            System.arraycopy(serverRandom, 0, seed, 0, serverRandom.length);
-            System.arraycopy(clientRandom, 0, seed, serverRandom.length,
-                    clientRandom.length);
-
-            PRF.computePRF_SSLv3(key_block, session.master_secret, seed);
-
-            byte[] client_mac_secret = new byte[hash_size];
-            byte[] server_mac_secret = new byte[hash_size];
-            byte[] client_key = new byte[key_size];
-            byte[] server_key = new byte[key_size];
-
-            boolean is_client = !session.isServer;
-
-            System.arraycopy(key_block, 0, client_mac_secret, 0, hash_size);
-            System.arraycopy(key_block, hash_size,
-                    server_mac_secret, 0, hash_size);
-            System.arraycopy(key_block, 2*hash_size, client_key, 0, key_size);
-            System.arraycopy(key_block, 2*hash_size+key_size,
-                    server_key, 0, key_size);
-
-            IvParameterSpec clientIV = null;
-            IvParameterSpec serverIV = null;
-
-            if (is_exportabe) {
-                if (logger != null) {
-                    logger.println("ConnectionStateSSLv3: is_exportable");
-                }
-
-                MessageDigest md5 = MessageDigest.getInstance("MD5");
-                md5.update(client_key);
-                md5.update(clientRandom);
-                md5.update(serverRandom);
-                client_key = md5.digest();
-
-                md5.update(server_key);
-                md5.update(serverRandom);
-                md5.update(clientRandom);
-                server_key = md5.digest();
-
-                key_size = cipherSuite.expandedKeyMaterial;
-
-                if (block_size != 0) {
-                    md5.update(clientRandom);
-                    md5.update(serverRandom);
-                    clientIV = new IvParameterSpec(md5.digest(), 0, iv_size);
-                    md5.update(serverRandom);
-                    md5.update(clientRandom);
-                    serverIV = new IvParameterSpec(md5.digest(), 0, iv_size);
-                }
-            } else if (block_size != 0) {
-                clientIV = new IvParameterSpec(key_block,
-                        2*hash_size+2*key_size, iv_size);
-                serverIV = new IvParameterSpec(key_block,
-                        2*hash_size+2*key_size+iv_size, iv_size);
-            }
-
-            if (logger != null) {
-                logger.println("is exportable: "+is_exportabe);
-                logger.println("master_secret");
-                logger.print(session.master_secret);
-                logger.println("client_random");
-                logger.print(clientRandom);
-                logger.println("server_random");
-                logger.print(serverRandom);
-                //logger.println("key_block");
-                //logger.print(key_block);
-                logger.println("client_mac_secret");
-                logger.print(client_mac_secret);
-                logger.println("server_mac_secret");
-                logger.print(server_mac_secret);
-                logger.println("client_key");
-                logger.print(client_key, 0, key_size);
-                logger.println("server_key");
-                logger.print(server_key, 0, key_size);
-                if (clientIV != null) {
-                    logger.println("client_iv");
-                    logger.print(clientIV.getIV());
-                    logger.println("server_iv");
-                    logger.print(serverIV.getIV());
-                } else {
-                    logger.println("no IV.");
-                }
-            }
-
-            if (algName == null) {
-                encCipher = new NullCipher();
-                decCipher = new NullCipher();
-            } else {
-                encCipher = Cipher.getInstance(algName);
-                decCipher = Cipher.getInstance(algName);
-                if (is_client) { // client side
-                    encCipher.init(Cipher.ENCRYPT_MODE,
-                                   new SecretKeySpec(client_key, 0, key_size, algName),
-                                   clientIV);
-                    decCipher.init(Cipher.DECRYPT_MODE,
-                                   new SecretKeySpec(server_key, 0, key_size, algName),
-                                   serverIV);
-                } else { // server side
-                    encCipher.init(Cipher.ENCRYPT_MODE,
-                                   new SecretKeySpec(server_key, 0, key_size, algName),
-                                   serverIV);
-                    decCipher.init(Cipher.DECRYPT_MODE,
-                                   new SecretKeySpec(client_key, 0, key_size, algName),
-                                   clientIV);
-                }
-            }
-
-            messageDigest = MessageDigest.getInstance(hashName);
-            if (is_client) { // client side
-                mac_write_secret = client_mac_secret;
-                mac_read_secret = server_mac_secret;
-            } else { // server side
-                mac_write_secret = server_mac_secret;
-                mac_read_secret = client_mac_secret;
-            }
-            if (hashName.equals("MD5")) {
-                pad_1 = SSLv3Constants.MD5pad1;
-                pad_2 = SSLv3Constants.MD5pad2;
-            } else {
-                pad_1 = SSLv3Constants.SHApad1;
-                pad_2 = SSLv3Constants.SHApad2;
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-            throw new AlertException(AlertProtocol.INTERNAL_ERROR,
-                    new SSLProtocolException(
-                        "Error during computation of security parameters"));
-        }
-    }
-
-    /**
-     * Creates the GenericStreamCipher or GenericBlockCipher
-     * data structure for specified data of specified type.
-     * @throws AlertException if alert was occurred.
-     */
-    @Override
-    protected byte[] encrypt(byte type, byte[] fragment, int offset, int len) {
-        try {
-            int content_mac_length = len + hash_size;
-            int padding_length = (block_size == 0) ? 0 : getPaddingSize(++content_mac_length);
-            byte[] res = new byte[content_mac_length + padding_length];
-            System.arraycopy(fragment, offset, res, 0, len);
-
-            messageDigest.update(mac_write_secret);
-            messageDigest.update(pad_1);
-            messageDigest.update(write_seq_num);
-
-            // Use the first 3 bytes of mac_material_part as a scratch area,
-            // the last 3 bytes are used by decrypt.
-            mac_material_part[0] = type;
-            mac_material_part[1] = (byte) ((0x00FF00 & len) >> 8);
-            mac_material_part[2] = (byte) (0x0000FF & len);
-            messageDigest.update(mac_material_part, 0, 3);
-
-            messageDigest.update(fragment, offset, len);
-            byte[] digest = messageDigest.digest();
-            messageDigest.update(mac_write_secret);
-            messageDigest.update(pad_2);
-            messageDigest.update(digest);
-            digest = messageDigest.digest();
-            System.arraycopy(digest, 0, res, len, hash_size);
-
-            //if (logger != null) {
-            //    logger.println("MAC Material:");
-            //    logger.print(write_seq_num);
-            //    logger.print(mac_material_header);
-            //    logger.print(fragment, offset, len);
-            //}
-
-            if (block_size != 0) {
-                // do padding:
-                Arrays.fill(res, content_mac_length-1,
-                        res.length, (byte) (padding_length));
-            }
-            if (logger != null) {
-                logger.println("SSLRecordProtocol.encrypt: "
-                        + (block_size != 0
-                            ? "GenericBlockCipher with padding["
-                                +padding_length+"]:"
-                            : "GenericStreamCipher:"));
-                logger.print(res);
-            }
-            byte[] rez = new byte[encCipher.getOutputSize(res.length)];
-            encCipher.update(res, 0, res.length, rez);
-            incSequenceNumber(write_seq_num);
-            return rez;
-        } catch (GeneralSecurityException e) {
-            e.printStackTrace();
-            throw new AlertException(AlertProtocol.INTERNAL_ERROR,
-                    new SSLProtocolException("Error during the encryption"));
-        }
-    }
-
-    /**
-     * Retrieves the fragment of the Plaintext structure of
-     * the specified type from the provided data.
-     * @throws AlertException if alert was occured.
-     */
-    @Override
-    protected byte[] decrypt(byte type, byte[] fragment,
-            int offset, int len) {
-        // plain data of the Generic[Stream|Block]Cipher structure
-        byte[] data = decCipher.update(fragment, offset, len);
-        // the 'content' part of the structure
-        byte[] content;
-        if (block_size != 0) {
-            // check padding
-            int padding_length = data[data.length-1] & 0xFF;
-            for (int i=0; i<padding_length; i++) {
-                if ((data[data.length-2-i] & 0xFF) != padding_length) {
-                    throw new AlertException(
-                            AlertProtocol.DECRYPTION_FAILED,
-                            new SSLProtocolException(
-                                "Received message has bad padding"));
-                }
-            }
-            content = new byte[data.length - hash_size - padding_length - 1];
-        } else {
-            content = new byte[data.length - hash_size];
-        }
-
-        byte[] mac_value;
-
-        messageDigest.update(mac_read_secret);
-        messageDigest.update(pad_1);
-        messageDigest.update(read_seq_num);
-
-        // The first 3 bytes of mac_material_part are used as a scratch area
-        // by encrypt() so we use the last three bytes here.
-        mac_material_part[3] = type;
-        mac_material_part[4] = (byte) ((0x00FF00 & content.length) >> 8);
-        mac_material_part[5] = (byte) (0x0000FF & content.length);
-        messageDigest.update(mac_material_part, 3, mac_material_part.length);
-
-        messageDigest.update(data, 0, content.length);
-        mac_value = messageDigest.digest();
-        messageDigest.update(mac_read_secret);
-        messageDigest.update(pad_2);
-        messageDigest.update(mac_value);
-        mac_value = messageDigest.digest();
-
-        if (logger != null) {
-            logger.println("Decrypted:");
-            logger.print(data);
-            //logger.println("MAC Material:");
-            //logger.print(read_seq_num);
-            //logger.print(mac_material_header);
-            //logger.print(data, 0, content.length);
-            logger.println("Expected mac value:");
-            logger.print(mac_value);
-        }
-        // checking the mac value
-        for (int i=0; i<hash_size; i++) {
-            if (mac_value[i] != data[i+content.length]) {
-                throw new AlertException(AlertProtocol.BAD_RECORD_MAC,
-                        new SSLProtocolException("Bad record MAC"));
-            }
-        }
-        System.arraycopy(data, 0, content, 0, content.length);
-        incSequenceNumber(read_seq_num);
-        return content;
-    }
-
-    /**
-     * Shutdown the protocol. It will be impossible to use the instance
-     * after the calling of this method.
-     */
-    @Override
-    protected void shutdown() {
-        Arrays.fill(mac_write_secret, (byte) 0);
-        Arrays.fill(mac_read_secret, (byte) 0);
-        super.shutdown();
-    }
-}
-
diff --git a/src/main/java/org/conscrypt/ConnectionStateTLS.java b/src/main/java/org/conscrypt/ConnectionStateTLS.java
deleted file mode 100644
index a4d9de8..0000000
--- a/src/main/java/org/conscrypt/ConnectionStateTLS.java
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- *  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.GeneralSecurityException;
-import java.util.Arrays;
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-import javax.crypto.NullCipher;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-import javax.net.ssl.SSLProtocolException;
-
-/**
- * This class encapsulates the operating environment of the TLS v1
- * (http://www.ietf.org/rfc/rfc2246.txt) Record Protocol and provides
- * relating encryption/decryption functionality.
- * The work functionality is based on the security
- * parameters negotiated during the handshake.
- */
-public class ConnectionStateTLS extends ConnectionState {
-
-    // Pre-calculated prf label values:
-    // "key expansion".getBytes()
-    private static byte[] KEY_EXPANSION_LABEL = {
-        (byte) 0x6B, (byte) 0x65, (byte) 0x79, (byte) 0x20, (byte) 0x65,
-        (byte) 0x78, (byte) 0x70, (byte) 0x61, (byte) 0x6E, (byte) 0x73,
-        (byte) 0x69, (byte) 0x6F, (byte) 0x6E };
-
-    // "client write key".getBytes()
-    private static byte[] CLIENT_WRITE_KEY_LABEL = {
-        (byte) 0x63, (byte) 0x6C, (byte) 0x69, (byte) 0x65, (byte) 0x6E,
-        (byte) 0x74, (byte) 0x20, (byte) 0x77, (byte) 0x72, (byte) 0x69,
-        (byte) 0x74, (byte) 0x65, (byte) 0x20, (byte) 0x6B, (byte) 0x65,
-        (byte) 0x79 };
-
-    // "server write key".getBytes()
-    private static byte[] SERVER_WRITE_KEY_LABEL = {
-        (byte) 0x73, (byte) 0x65, (byte) 0x72, (byte) 0x76, (byte) 0x65,
-        (byte) 0x72, (byte) 0x20, (byte) 0x77, (byte) 0x72, (byte) 0x69,
-        (byte) 0x74, (byte) 0x65, (byte) 0x20, (byte) 0x6B, (byte) 0x65,
-        (byte) 0x79 };
-
-    // "IV block".getBytes()
-    private static byte[] IV_BLOCK_LABEL = {
-        (byte) 0x49, (byte) 0x56, (byte) 0x20, (byte) 0x62, (byte) 0x6C,
-        (byte) 0x6F, (byte) 0x63, (byte) 0x6B };
-
-    // MACs to create and check the message integrity info
-    private final Mac encMac;
-    private final Mac decMac;
-
-    // Once created permanently used array:
-    // is used to create the header of the MAC material value:
-    // 5 == 1(TLSCompressed.type) + 2(TLSCompressed.version) +
-    //      2(TLSCompressed.length)
-    private final byte[] mac_material_header = new byte[] {0, 3, 1, 0, 0};
-
-    /**
-     * Creates the instance of TLS v1 Connection State. All of the
-     * security parameters are provided by session object.
-     * @param session the sessin object which incapsulates
-     * all of the security parameters established by handshake protocol.
-     * The key calculation for the state is done according
-     * to the TLS v 1.0 Protocol specification.
-     * (http://www.ietf.org/rfc/rfc2246.txt)
-     */
-    protected ConnectionStateTLS(SSLSessionImpl session) {
-        try {
-            CipherSuite cipherSuite = session.cipherSuite;
-
-            hash_size = cipherSuite.getMACLength();
-            boolean is_exportabe =  cipherSuite.isExportable();
-            int key_size = (is_exportabe)
-                ? cipherSuite.keyMaterial
-                : cipherSuite.expandedKeyMaterial;
-            int iv_size = cipherSuite.ivSize;
-            block_size = cipherSuite.getBlockSize();
-
-            String algName = cipherSuite.getBulkEncryptionAlgorithm();
-            String macName = cipherSuite.getHmacName();
-            if (logger != null) {
-                logger.println("ConnectionStateTLS.create:");
-                logger.println("  cipher suite name: "
-                                            + cipherSuite.getName());
-                logger.println("  encryption alg name: " + algName);
-                logger.println("  mac alg name: " + macName);
-                logger.println("  hash size: " + hash_size);
-                logger.println("  block size: " + block_size);
-                logger.println("  IV size:" + iv_size);
-                logger.println("  key size: " + key_size);
-            }
-
-            byte[] clientRandom = session.clientRandom;
-            byte[] serverRandom = session.serverRandom;
-            // so we need PRF value of size of
-            // 2*hash_size + 2*key_size + 2*iv_size
-            byte[] key_block = new byte[2*hash_size + 2*key_size + 2*iv_size];
-            byte[] seed = new byte[clientRandom.length + serverRandom.length];
-            System.arraycopy(serverRandom, 0, seed, 0, serverRandom.length);
-            System.arraycopy(clientRandom, 0, seed, serverRandom.length,
-                    clientRandom.length);
-
-            PRF.computePRF(key_block, session.master_secret,
-                    KEY_EXPANSION_LABEL, seed);
-
-            byte[] client_mac_secret = new byte[hash_size];
-            byte[] server_mac_secret = new byte[hash_size];
-            byte[] client_key = new byte[key_size];
-            byte[] server_key = new byte[key_size];
-
-            boolean is_client = !session.isServer;
-
-            System.arraycopy(key_block, 0, client_mac_secret, 0, hash_size);
-            System.arraycopy(key_block, hash_size,
-                    server_mac_secret, 0, hash_size);
-            System.arraycopy(key_block, 2*hash_size, client_key, 0, key_size);
-            System.arraycopy(key_block, 2*hash_size+key_size,
-                    server_key, 0, key_size);
-
-            IvParameterSpec clientIV = null;
-            IvParameterSpec serverIV = null;
-
-            if (is_exportabe) {
-                System.arraycopy(clientRandom, 0,
-                        seed, 0, clientRandom.length);
-                System.arraycopy(serverRandom, 0,
-                        seed, clientRandom.length, serverRandom.length);
-                byte[] final_client_key =
-                    new byte[cipherSuite.expandedKeyMaterial];
-                byte[] final_server_key =
-                    new byte[cipherSuite.expandedKeyMaterial];
-                PRF.computePRF(final_client_key, client_key,
-                        CLIENT_WRITE_KEY_LABEL, seed);
-                PRF.computePRF(final_server_key, server_key,
-                        SERVER_WRITE_KEY_LABEL, seed);
-                client_key = final_client_key;
-                server_key = final_server_key;
-                if (block_size != 0) {
-                    byte[] iv_block = new byte[2*iv_size];
-                    PRF.computePRF(iv_block, null, IV_BLOCK_LABEL, seed);
-                    clientIV = new IvParameterSpec(iv_block, 0, iv_size);
-                    serverIV = new IvParameterSpec(iv_block, iv_size, iv_size);
-                }
-            } else if (block_size != 0) {
-                clientIV = new IvParameterSpec(key_block,
-                        2*(hash_size+key_size), iv_size);
-                serverIV = new IvParameterSpec(key_block,
-                        2*(hash_size+key_size)+iv_size, iv_size);
-            }
-
-            if (logger != null) {
-                logger.println("is exportable: "+is_exportabe);
-                logger.println("master_secret");
-                logger.print(session.master_secret);
-                logger.println("client_random");
-                logger.print(clientRandom);
-                logger.println("server_random");
-                logger.print(serverRandom);
-                //logger.println("key_block");
-                //logger.print(key_block);
-                logger.println("client_mac_secret");
-                logger.print(client_mac_secret);
-                logger.println("server_mac_secret");
-                logger.print(server_mac_secret);
-                logger.println("client_key");
-                logger.print(client_key);
-                logger.println("server_key");
-                logger.print(server_key);
-                if (clientIV == null) {
-                    logger.println("no IV.");
-                } else {
-                    logger.println("client_iv");
-                    logger.print(clientIV.getIV());
-                    logger.println("server_iv");
-                    logger.print(serverIV.getIV());
-                }
-            }
-
-            if (algName == null) {
-                encCipher = new NullCipher();
-                decCipher = new NullCipher();
-            } else {
-                encCipher = Cipher.getInstance(algName);
-                decCipher = Cipher.getInstance(algName);
-                if (is_client) { // client side
-                    encCipher.init(Cipher.ENCRYPT_MODE,
-                                   new SecretKeySpec(client_key, algName), clientIV);
-                    decCipher.init(Cipher.DECRYPT_MODE,
-                                   new SecretKeySpec(server_key, algName), serverIV);
-                } else { // server side
-                    encCipher.init(Cipher.ENCRYPT_MODE,
-                                   new SecretKeySpec(server_key, algName), serverIV);
-                    decCipher.init(Cipher.DECRYPT_MODE,
-                                   new SecretKeySpec(client_key, algName), clientIV);
-                }
-            }
-
-            encMac = Mac.getInstance(macName);
-            decMac = Mac.getInstance(macName);
-            if (is_client) { // client side
-                encMac.init(new SecretKeySpec(client_mac_secret, macName));
-                decMac.init(new SecretKeySpec(server_mac_secret, macName));
-            } else { // server side
-                encMac.init(new SecretKeySpec(server_mac_secret, macName));
-                decMac.init(new SecretKeySpec(client_mac_secret, macName));
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-            throw new AlertException(AlertProtocol.INTERNAL_ERROR,
-                    new SSLProtocolException(
-                        "Error during computation of security parameters"));
-        }
-    }
-
-    /**
-     * Creates the GenericStreamCipher or GenericBlockCipher
-     * data structure for specified data of specified type.
-     * @throws AlertException if alert was occurred.
-     */
-    @Override
-    protected byte[] encrypt(byte type, byte[] fragment, int offset, int len) {
-        try {
-            int content_mac_length = len + hash_size;
-            int padding_length = (block_size == 0) ? 0 : getPaddingSize(++content_mac_length);
-            byte[] res = new byte[content_mac_length + padding_length];
-            System.arraycopy(fragment, offset, res, 0, len);
-
-            mac_material_header[0] = type;
-            mac_material_header[3] = (byte) ((0x00FF00 & len) >> 8);
-            mac_material_header[4] = (byte) (0x0000FF & len);
-
-            encMac.update(write_seq_num);
-            encMac.update(mac_material_header);
-            encMac.update(fragment, offset, len);
-            encMac.doFinal(res, len);
-
-            //if (logger != null) {
-            //    logger.println("MAC Material:");
-            //    logger.print(write_seq_num);
-            //    logger.print(mac_material_header);
-            //    logger.print(fragment, offset, len);
-            //}
-
-            if (block_size != 0) {
-                // do padding:
-                Arrays.fill(res, content_mac_length-1,
-                        res.length, (byte) (padding_length));
-            }
-            if (logger != null) {
-                logger.println("SSLRecordProtocol.do_encryption: Generic"
-                        + (block_size != 0
-                            ? "BlockCipher with padding["+padding_length+"]:"
-                            : "StreamCipher:"));
-                logger.print(res);
-            }
-            byte[] rez = new byte[encCipher.getOutputSize(res.length)];
-            // We should not call just doFinal because it reinitialize
-            // the cipher, but as says rfc 2246:
-            // "For stream ciphers that do not use a synchronization
-            // vector (such as RC4), the stream cipher state from the end
-            // of one record is simply used on the subsequent packet."
-            // and for block ciphers:
-            // "The IV for subsequent records is the last ciphertext block from
-            // the previous record."
-            // i.e. we should keep the cipher state.
-            encCipher.update(res, 0, res.length, rez);
-            incSequenceNumber(write_seq_num);
-            return rez;
-        } catch (GeneralSecurityException e) {
-            e.printStackTrace();
-            throw new AlertException(AlertProtocol.INTERNAL_ERROR,
-                    new SSLProtocolException("Error during the encryption"));
-        }
-    }
-
-    /**
-     * Retrieves the fragment of the Plaintext structure of
-     * the specified type from the provided data representing
-     * the Generic[Stream|Block]Cipher structure.
-     * @throws AlertException if alert was occurred.
-     */
-    @Override
-    protected byte[] decrypt(byte type, byte[] fragment,
-            int offset, int len) {
-        // plain data of the Generic[Stream|Block]Cipher structure
-        byte[] data = decCipher.update(fragment, offset, len);
-        // the 'content' part of the structure
-        byte[] content;
-        if (block_size != 0) {
-            // check padding
-            int padding_length = data[data.length - 1] & 0xFF;
-            for (int i=0; i<padding_length; i++) {
-                if ((data[data.length-2-i] & 0xFF) != padding_length) {
-                    throw new AlertException(
-                            AlertProtocol.DECRYPTION_FAILED,
-                            new SSLProtocolException(
-                                "Received message has bad padding"));
-                }
-            }
-            content = new byte[data.length - hash_size - padding_length - 1];
-        } else {
-            content = new byte[data.length - hash_size];
-        }
-
-        mac_material_header[0] = type;
-        mac_material_header[3] = (byte) ((0x00FF00 & content.length) >> 8);
-        mac_material_header[4] = (byte) (0x0000FF & content.length);
-
-        decMac.update(read_seq_num);
-        decMac.update(mac_material_header);
-        decMac.update(data, 0, content.length); // mac.update(fragment);
-        byte[] mac_value = decMac.doFinal();
-        if (logger != null) {
-            logger.println("Decrypted:");
-            logger.print(data);
-            //logger.println("MAC Material:");
-            //logger.print(read_seq_num);
-            //logger.print(mac_material_header);
-            //logger.print(data, 0, content.length);
-            logger.println("Expected mac value:");
-            logger.print(mac_value);
-        }
-        // checking the mac value
-        for (int i=0; i<hash_size; i++) {
-            if (mac_value[i] != data[i+content.length]) {
-                throw new AlertException(AlertProtocol.BAD_RECORD_MAC,
-                        new SSLProtocolException("Bad record MAC"));
-            }
-        }
-        System.arraycopy(data, 0, content, 0, content.length);
-        incSequenceNumber(read_seq_num);
-        return content;
-    }
-}
-
diff --git a/src/main/java/org/conscrypt/ContentType.java b/src/main/java/org/conscrypt/ContentType.java
deleted file mode 100644
index b09f029..0000000
--- a/src/main/java/org/conscrypt/ContentType.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- *  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;
-
-/**
- * This class incapsulates the constants determining the
- * types of SSL/TLS record's content data.
- * Constant values are taken according to the TLS v1 specification
- * (http://www.ietf.org/rfc/rfc2246.txt).
- */
-public class ContentType {
-
-    /**
-     * Identifies change cipher spec message
-     */
-    protected static final byte CHANGE_CIPHER_SPEC = 20;
-
-    /**
-     * Identifies alert message
-     */
-    protected static final byte ALERT = 21;
-
-    /**
-     * Identifies handshake message
-     */
-    protected static final byte HANDSHAKE = 22;
-
-    /**
-     * Identifies application data message
-     */
-    protected static final byte APPLICATION_DATA = 23;
-
-}
-
diff --git a/src/main/java/org/conscrypt/CryptoUpcalls.java b/src/main/java/org/conscrypt/CryptoUpcalls.java
new file mode 100644
index 0000000..9ab4fa1
--- /dev/null
+++ b/src/main/java/org/conscrypt/CryptoUpcalls.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 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.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.Security;
+import java.security.Signature;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.RSAPrivateKey;
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+
+/**
+ * Provides a place where NativeCrypto can call back up to do Java language
+ * calls to work on delegated key types from native code.
+ */
+public final class CryptoUpcalls {
+    private static final String RSA_CRYPTO_ALGORITHM = "RSA/ECB/PKCS1Padding";
+
+    private CryptoUpcalls() {
+    }
+
+    /**
+     * Finds the first provider which provides {@code algorithm} but is not from
+     * the same ClassLoader as ours.
+     */
+    public static Provider getExternalProvider(String algorithm) {
+        Provider selectedProvider = null;
+        for (Provider p : Security.getProviders(algorithm)) {
+            if (!p.getClass().getClassLoader().equals(CryptoUpcalls.class.getClassLoader())) {
+                selectedProvider = p;
+                break;
+            }
+        }
+        if (selectedProvider == null) {
+            System.err.println("Could not find external provider for algorithm: " + algorithm);
+        }
+        return selectedProvider;
+    }
+
+    public static byte[] rawSignDigestWithPrivateKey(PrivateKey javaKey, byte[] message) {
+        // Get the raw signature algorithm for this key type.
+        String algorithm = null;
+        // Hint: Algorithm names come from:
+        // http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html
+        if (javaKey instanceof RSAPrivateKey) {
+            // IMPORTANT: Due to a platform bug, this will throw
+            // NoSuchAlgorithmException
+            // on Android 4.0.x and 4.1.x. Fixed in 4.2 and higher.
+            // See https://android-review.googlesource.com/#/c/40352/
+            algorithm = "NONEwithRSA";
+        } else if (javaKey instanceof DSAPrivateKey) {
+            algorithm = "NONEwithDSA";
+        } else if (javaKey instanceof ECPrivateKey) {
+            algorithm = "NONEwithECDSA";
+        } else {
+            throw new RuntimeException("Unexpected key type: " + javaKey.toString());
+        }
+
+        Provider p = getExternalProvider("Signature." + algorithm);
+        if (p == null) {
+            return null;
+        }
+
+        // Get the Signature for this key.
+        Signature signature = null;
+        try {
+            signature = Signature.getInstance(algorithm, p);
+        } catch (NoSuchAlgorithmException e) {
+            ;
+        }
+
+        if (signature == null) {
+            System.err.println("Unsupported private key algorithm: " + javaKey.getAlgorithm());
+            return null;
+        }
+
+        // Sign the message.
+        try {
+            signature.initSign(javaKey);
+            signature.update(message);
+            return signature.sign();
+        } catch (Exception e) {
+            System.err.println("Exception while signing message with " + javaKey.getAlgorithm()
+                    + " private key:");
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public static byte[] rawCipherWithPrivateKey(PrivateKey javaKey, boolean encrypt,
+            byte[] input) {
+        if (!(javaKey instanceof RSAPrivateKey)) {
+            System.err.println("Unexpected key type: " + javaKey.toString());
+            return null;
+        }
+
+        Provider p = getExternalProvider("Cipher." + RSA_CRYPTO_ALGORITHM);
+        if (p == null) {
+            return null;
+        }
+
+        Cipher c = null;
+        try {
+            c = Cipher.getInstance(RSA_CRYPTO_ALGORITHM, p);
+        } catch (NoSuchAlgorithmException e) {
+            ;
+        } catch (NoSuchPaddingException e) {
+            ;
+        }
+
+        if (c == null) {
+            System.err.println("Unsupported private key algorithm: " + javaKey.getAlgorithm());
+        }
+
+        try {
+            c.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, javaKey);
+            return c.doFinal(input);
+        } catch (Exception e) {
+            System.err.println("Exception while ciphering message with " + javaKey.getAlgorithm()
+                    + " private key:");
+            e.printStackTrace();
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/org/conscrypt/DHParameters.java b/src/main/java/org/conscrypt/DHParameters.java
deleted file mode 100644
index 7a742b2..0000000
--- a/src/main/java/org/conscrypt/DHParameters.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- *  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;
-
-/**
- * This class contains well-known primes
- */
-public class DHParameters {
-
-    // Well-known 512 bit prime
-    // http://news.hping.org/sci.crypt.archive/2370.html
-    private static byte[] prime512 = new byte[] { (byte) 0xF5, (byte) 0x2A, (byte) 0xFF,
-            (byte) 0x3C, (byte) 0xE1, (byte) 0xB1, (byte) 0x29, (byte) 0x40,
-            (byte) 0x18, (byte) 0x11, (byte) 0x8D, (byte) 0x7C, (byte) 0x84,
-            (byte) 0xA7, (byte) 0x0A, (byte) 0x72, (byte) 0xD6, (byte) 0x86,
-            (byte) 0xC4, (byte) 0x03, (byte) 0x19, (byte) 0xC8, (byte) 0x07,
-            (byte) 0x29, (byte) 0x7A, (byte) 0xCA, (byte) 0x95, (byte) 0x0C,
-            (byte) 0xD9, (byte) 0x96, (byte) 0x9F, (byte) 0xAB, (byte) 0xD0,
-            (byte) 0x0A, (byte) 0x50, (byte) 0x9B, (byte) 0x02, (byte) 0x46,
-            (byte) 0xD3, (byte) 0x08, (byte) 0x3D, (byte) 0x66, (byte) 0xA4,
-            (byte) 0x5D, (byte) 0x41, (byte) 0x9F, (byte) 0x9C, (byte) 0x7C,
-            (byte) 0xBD, (byte) 0x89, (byte) 0x4B, (byte) 0x22, (byte) 0x19,
-            (byte) 0x26, (byte) 0xBA, (byte) 0xAB, (byte) 0xA2, (byte) 0x5E,
-            (byte) 0xC3, (byte) 0x55, (byte) 0xE9, (byte) 0x2A, (byte) 0x05,
-            (byte) 0x5F };
-
-    // Well-Known Group 1: A 768 bit prime rfc 2539
-    // (http://www.ietf.org/rfc/rfc2539.txt?number=2539)
-    private static byte[] primeGroup1 = { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
-            (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xC9,
-            (byte) 0x0F, (byte) 0xDA, (byte) 0xA2, (byte) 0x21, (byte) 0x68,
-            (byte) 0xC2, (byte) 0x34, (byte) 0xC4, (byte) 0xC6, (byte) 0x62,
-            (byte) 0x8B, (byte) 0x80, (byte) 0xDC, (byte) 0x1C, (byte) 0xD1,
-            (byte) 0x29, (byte) 0x02, (byte) 0x4E, (byte) 0x08, (byte) 0x8A,
-            (byte) 0x67, (byte) 0xCC, (byte) 0x74, (byte) 0x02, (byte) 0x0B,
-            (byte) 0xBE, (byte) 0xA6, (byte) 0x3B, (byte) 0x13, (byte) 0x9B,
-            (byte) 0x22, (byte) 0x51, (byte) 0x4A, (byte) 0x08, (byte) 0x79,
-            (byte) 0x8E, (byte) 0x34, (byte) 0x04, (byte) 0xDD, (byte) 0xEF,
-            (byte) 0x95, (byte) 0x19, (byte) 0xB3, (byte) 0xCD, (byte) 0x3A,
-            (byte) 0x43, (byte) 0x1B, (byte) 0x30, (byte) 0x2B, (byte) 0x0A,
-            (byte) 0x6D, (byte) 0xF2, (byte) 0x5F, (byte) 0x14, (byte) 0x37,
-            (byte) 0x4F, (byte) 0xE1, (byte) 0x35, (byte) 0x6D, (byte) 0x6D,
-            (byte) 0x51, (byte) 0xC2, (byte) 0x45, (byte) 0xE4, (byte) 0x85,
-            (byte) 0xB5, (byte) 0x76, (byte) 0x62, (byte) 0x5E, (byte) 0x7E,
-            (byte) 0xC6, (byte) 0xF4, (byte) 0x4C, (byte) 0x42, (byte) 0xE9,
-            (byte) 0xA6, (byte) 0x3A, (byte) 0x36, (byte) 0x20, (byte) 0xFF,
-            (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
-            (byte) 0xFF, (byte) 0xFF };
-
-    // Well-Known Group 2: A 1024 bit prime rfc 2539
-    // (http://www.ietf.org/rfc/rfc2539.txt?number=2539)
-    private static byte[] primeGroup2 = { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
-            (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xC9,
-            (byte) 0x0F, (byte) 0xDA, (byte) 0xA2, (byte) 0x21, (byte) 0x68,
-            (byte) 0xC2, (byte) 0x34, (byte) 0xC4, (byte) 0xC6, (byte) 0x62,
-            (byte) 0x8B, (byte) 0x80, (byte) 0xDC, (byte) 0x1C, (byte) 0xD1,
-            (byte) 0x29, (byte) 0x02, (byte) 0x4E, (byte) 0x08, (byte) 0x8A,
-            (byte) 0x67, (byte) 0xCC, (byte) 0x74, (byte) 0x02, (byte) 0x0B,
-            (byte) 0xBE, (byte) 0xA6, (byte) 0x3B, (byte) 0x13, (byte) 0x9B,
-            (byte) 0x22, (byte) 0x51, (byte) 0x4A, (byte) 0x08, (byte) 0x79,
-            (byte) 0x8E, (byte) 0x34, (byte) 0x04, (byte) 0xDD, (byte) 0xEF,
-            (byte) 0x95, (byte) 0x19, (byte) 0xB3, (byte) 0xCD, (byte) 0x3A,
-            (byte) 0x43, (byte) 0x1B, (byte) 0x30, (byte) 0x2B, (byte) 0x0A,
-            (byte) 0x6D, (byte) 0xF2, (byte) 0x5F, (byte) 0x14, (byte) 0x37,
-            (byte) 0x4F, (byte) 0xE1, (byte) 0x35, (byte) 0x6D, (byte) 0x6D,
-            (byte) 0x51, (byte) 0xC2, (byte) 0x45, (byte) 0xE4, (byte) 0x85,
-            (byte) 0xB5, (byte) 0x76, (byte) 0x62, (byte) 0x5E, (byte) 0x7E,
-            (byte) 0xC6, (byte) 0xF4, (byte) 0x4C, (byte) 0x42, (byte) 0xE9,
-            (byte) 0xA6, (byte) 0x37, (byte) 0xED, (byte) 0x6B, (byte) 0x0B,
-            (byte) 0xFF, (byte) 0x5C, (byte) 0xB6, (byte) 0xF4, (byte) 0x06,
-            (byte) 0xB7, (byte) 0xED, (byte) 0xEE, (byte) 0x38, (byte) 0x6B,
-            (byte) 0xFB, (byte) 0x5A, (byte) 0x89, (byte) 0x9F, (byte) 0xA5,
-            (byte) 0xAE, (byte) 0x9F, (byte) 0x24, (byte) 0x11, (byte) 0x7C,
-            (byte) 0x4B, (byte) 0x1F, (byte) 0xE6, (byte) 0x49, (byte) 0x28,
-            (byte) 0x66, (byte) 0x51, (byte) 0xEC, (byte) 0xE6, (byte) 0x53,
-            (byte) 0x81, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
-            (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF
-    };
-
-    private static byte[] prime;
-
-    static {
-//TODO set prime depand on some system or security property
-        prime = prime512;
-    }
-
-    /**
-     * Returns prime bytes
-     * @return
-     */
-    public static byte[] getPrime() {
-        return prime;
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/org/conscrypt/DataStream.java b/src/main/java/org/conscrypt/DataStream.java
deleted file mode 100644
index d65b01a..0000000
--- a/src/main/java/org/conscrypt/DataStream.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- *  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;
-
-/**
- * This interface represents the ability of the
- * classes to provide the chunks of data.
- */
-public interface DataStream {
-
-    /**
-     * Checks if there is data to be read.
-     * @return true if there is the input data in the stream,
-     * false otherwise
-     */
-    public boolean hasData();
-
-    /**
-     * Retrieves the data of specified length from the stream.
-     * If the data size in the stream is less than specified length,
-     * method returns all the data contained in the stream.
-     * @return byte array containing the demanded data.
-     */
-    public byte[] getData(int length);
-
-}
-
diff --git a/src/main/java/org/conscrypt/DelegatedTask.java b/src/main/java/org/conscrypt/DelegatedTask.java
deleted file mode 100644
index 39c0913..0000000
--- a/src/main/java/org/conscrypt/DelegatedTask.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- *  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;
-
-/**
- * Delegated Runnable task for SSLEngine
- */
-public class DelegatedTask implements Runnable {
-
-    private final HandshakeProtocol handshaker;
-    private final Runnable action;
-
-    public DelegatedTask(Runnable action, HandshakeProtocol handshaker) {
-        this.action = action;
-        this.handshaker = handshaker;
-    }
-
-    @Override
-    public void run() {
-        synchronized (handshaker) {
-            try {
-                action.run();
-            } catch (RuntimeException e) {
-                // pass exception to HandshakeProtocol
-                handshaker.delegatedTaskErr = e;
-            }
-        }
-    }
-}
diff --git a/src/main/java/org/conscrypt/DigitalSignature.java b/src/main/java/org/conscrypt/DigitalSignature.java
deleted file mode 100644
index e24056e..0000000
--- a/src/main/java/org/conscrypt/DigitalSignature.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- *  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.DigestException;
-import java.security.InvalidKeyException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.Certificate;
-import java.util.Arrays;
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
-import javax.net.ssl.SSLException;
-import org.conscrypt.util.EmptyArray;
-
-/**
- * This class represents Signature type, as described in TLS v 1.0 Protocol
- * specification, 7.4.3. It allow to init, update and sign hash. Hash algorithm
- * depends on SignatureAlgorithm.
- *
- * select (SignatureAlgorithm)
- *       {   case anonymous: struct { };
- *           case rsa:
- *               digitally-signed struct {
- *                   opaque md5_hash[16];
- *                   opaque sha_hash[20];
- *               };
- *           case dsa:
- *               digitally-signed struct {
- *                   opaque sha_hash[20];
- *               };
- *       } Signature;
- *
- * Digital signing description see in TLS spec., 4.7.
- * (http://www.ietf.org/rfc/rfc2246.txt)
- *
- */
-public class DigitalSignature {
-
-    private final MessageDigest md5;
-    private final MessageDigest sha;
-    private final Signature signature;
-    private final Cipher cipher;
-
-    private byte[] md5_hash;
-    private byte[] sha_hash;
-
-    /**
-     * Create Signature type
-     * @param algorithm the key algorithm used for the signature
-     */
-    public DigitalSignature(String algorithm) {
-        try {
-            sha = MessageDigest.getInstance("SHA-1");
-
-            if ("RSA".equals(algorithm)) {
-                md5 = MessageDigest.getInstance("MD5");
-                cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
-                signature = null;
-            } else if ("DSA".equals(algorithm)) {
-                // SignatureAlgorithm is dsa
-                signature = Signature.getInstance("NONEwithDSA");
-                cipher = null;
-                md5 = null;
-            } else {
-                cipher = null;
-                signature = null;
-                md5 = null;
-            }
-        } catch (NoSuchAlgorithmException e) {
-            // this should never happen
-            throw new AssertionError(e);
-        } catch (NoSuchPaddingException e) {
-            // this should never happen
-            throw new AssertionError(e);
-        }
-    }
-
-    /**
-     * Initiate Signature type by private key
-     * @param key
-     */
-    public void init(PrivateKey key) {
-        try {
-            if (signature != null) {
-                signature.initSign(key);
-            } else if (cipher != null) {
-                cipher.init(Cipher.ENCRYPT_MODE, key);
-            }
-        } catch (InvalidKeyException e){
-            throw new AlertException(AlertProtocol.BAD_CERTIFICATE,
-                    new SSLException("init - invalid private key", e));
-        }
-    }
-
-    /**
-     * Initiate Signature type by certificate
-     * @param cert
-     */
-    public void init(Certificate cert) {
-        try {
-            if (signature != null) {
-                signature.initVerify(cert);
-            } else if (cipher != null) {
-                cipher.init(Cipher.DECRYPT_MODE, cert);
-            }
-        } catch (InvalidKeyException e){
-            throw new AlertException(AlertProtocol.BAD_CERTIFICATE,
-                    new SSLException("init - invalid certificate", e));
-        }
-    }
-
-    /**
-     * Update Signature hash
-     * @param data
-     */
-    public void update(byte[] data) {
-        if (sha != null) {
-            sha.update(data);
-        }
-        if (md5 != null) {
-            md5.update(data);
-        }
-    }
-
-    /**
-     * Sets MD5 hash
-     * @param data
-     */
-    public void setMD5(byte[] data) {
-        md5_hash = data;
-    }
-
-    /**
-     * Sets SHA hash
-     * @param data
-     */
-    public void setSHA(byte[] data) {
-        sha_hash = data;
-    }
-
-    /**
-     * Sign hash
-     * @return Signature bytes
-     */
-    public byte[] sign() {
-        try {
-            if (md5 != null && md5_hash == null) {
-                md5_hash = new byte[16];
-                md5.digest(md5_hash, 0, md5_hash.length);
-            }
-            if (md5_hash != null) {
-                if (signature != null) {
-                    signature.update(md5_hash);
-                } else if (cipher != null) {
-                    cipher.update(md5_hash);
-                }
-            }
-            if (sha != null && sha_hash == null) {
-                sha_hash = new byte[20];
-                sha.digest(sha_hash, 0, sha_hash.length);
-            }
-            if (sha_hash != null) {
-                if (signature != null) {
-                    signature.update(sha_hash);
-                } else if (cipher != null) {
-                    cipher.update(sha_hash);
-                }
-            }
-            if (signature != null) {
-                return signature.sign();
-            } else if (cipher != null) {
-                return cipher.doFinal();
-            }
-            return EmptyArray.BYTE;
-        } catch (DigestException e){
-            return EmptyArray.BYTE;
-        } catch (SignatureException e){
-            return EmptyArray.BYTE;
-        } catch (BadPaddingException e){
-            return EmptyArray.BYTE;
-        } catch (IllegalBlockSizeException e){
-            return EmptyArray.BYTE;
-        }
-    }
-
-    /**
-     * Verifies the signature data.
-     * @param data - the signature bytes
-     * @return true if verified
-     */
-    public boolean verifySignature(byte[] data) {
-        if (signature != null) {
-            try {
-                if (sha_hash == null) {
-                    sha_hash = sha.digest();
-                }
-                signature.update(sha_hash);
-                return signature.verify(data);
-            } catch (SignatureException e) {
-                return false;
-            }
-        }
-
-        if (cipher != null) {
-            final byte[] decrypt;
-            try {
-                decrypt = cipher.doFinal(data);
-            } catch (IllegalBlockSizeException e) {
-                return false;
-            } catch (BadPaddingException e) {
-                return false;
-            }
-
-            final byte[] md5_sha;
-            if (sha != null && sha_hash == null) {
-                sha_hash = sha.digest();
-            }
-            if (md5 != null && md5_hash == null) {
-                md5_hash = md5.digest();
-            }
-            if (md5_hash != null && sha_hash != null) {
-                md5_sha = new byte[md5_hash.length + sha_hash.length];
-                System.arraycopy(md5_hash, 0, md5_sha, 0, md5_hash.length);
-                System.arraycopy(sha_hash, 0, md5_sha, md5_hash.length, sha_hash.length);
-            } else if (md5_hash != null) {
-                md5_sha = md5_hash;
-            } else {
-                md5_sha = sha_hash;
-            }
-
-            return Arrays.equals(decrypt, md5_sha);
-        } else if (data == null || data.length == 0) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-}
diff --git a/src/main/java/org/conscrypt/EndOfBufferException.java b/src/main/java/org/conscrypt/EndOfBufferException.java
deleted file mode 100644
index acd3417..0000000
--- a/src/main/java/org/conscrypt/EndOfBufferException.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- *  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.io.IOException;
-
-/**
- * This exception indicates that data could not be read from the stream because the underlying input
- * stream reached its end.
- */
-public class EndOfBufferException extends IOException {
-
-    private static final long serialVersionUID = 1838636631255369519L;
-
-    public EndOfBufferException() {
-    }
-
-}
diff --git a/src/main/java/org/conscrypt/EndOfSourceException.java b/src/main/java/org/conscrypt/EndOfSourceException.java
deleted file mode 100644
index 4789cd8..0000000
--- a/src/main/java/org/conscrypt/EndOfSourceException.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- *  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.io.IOException;
-
-/**
- * This exception indicates that data could not be read from the buffered stream because underlying
- * data buffer was exhausted.
- */
-public class EndOfSourceException extends IOException {
-
-    private static final long serialVersionUID = -4673611435974054413L;
-
-    public EndOfSourceException() {
-    }
-
-}
diff --git a/src/main/java/org/conscrypt/FileClientSessionCache.java b/src/main/java/org/conscrypt/FileClientSessionCache.java
index be95622..26e32ac 100644
--- a/src/main/java/org/conscrypt/FileClientSessionCache.java
+++ b/src/main/java/org/conscrypt/FileClientSessionCache.java
@@ -30,14 +30,12 @@
 import java.util.Set;
 import java.util.TreeSet;
 import javax.net.ssl.SSLSession;
-import libcore.io.IoUtils;
 
 /**
  * File-based cache implementation. Only one process should access the
  * underlying directory at a time.
  */
 public class FileClientSessionCache {
-
     public static final int MAX_SIZE = 12; // ~72k
 
     private FileClientSessionCache() {}
@@ -171,12 +169,20 @@
                 logReadError(host, file, e);
                 return null;
             } finally {
-                IoUtils.closeQuietly(in);
+                if (in != null) {
+                    try {
+                        in.close();
+                    } catch (RuntimeException rethrown) {
+                        throw rethrown;
+                    } catch (Exception ignored) {
+                    }
+                }
             }
         }
 
         static void logReadError(String host, File file, Throwable t) {
-            System.logW("Error reading session data for " + host + " from " + file + ".", t);
+            System.err.println("FileClientSessionCache: Error reading session data for " + host + " from " + file + ".");
+            t.printStackTrace();
         }
 
         @Override
@@ -290,13 +296,14 @@
         @SuppressWarnings("ThrowableInstanceNeverThrown")
         private void delete(File file) {
             if (!file.delete()) {
-                System.logW("Failed to delete " + file + ".", new IOException());
+                new IOException("FileClientSessionCache: Failed to delete " + file + ".").printStackTrace();
             }
             size--;
         }
 
         static void logWriteError(String host, File file, Throwable t) {
-            System.logW("Error writing session data for " + host + " to " + file + ".", t);
+            System.err.println("FileClientSessionCache: Error writing session data for " + host + " to " + file + ".");
+            t.printStackTrace();
         }
     }
 
diff --git a/src/main/java/org/conscrypt/Finished.java b/src/main/java/org/conscrypt/Finished.java
deleted file mode 100644
index e0b7eaa..0000000
--- a/src/main/java/org/conscrypt/Finished.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- *  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.io.IOException;
-
-/**
- *
- * Represents Finished message
- * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.9.
- * Finished</a>
- *
- */
-public class Finished extends Message {
-
-    // verify data
-    private byte[] data;
-
-    /**
-     * Creates outbound message
-     * @param bytes
-     */
-    public Finished(byte[] bytes) {
-        data = bytes;
-        length = data.length;
-    }
-
-    /**
-     * Creates inbound message
-     * @param in
-     * @param length
-     * @throws IOException
-     */
-    public Finished(HandshakeIODataStream in, int length)
-            throws IOException {
-        if (length == 12 || length == 36) {
-            data = in.read(length);
-            this.length = data.length;
-        } else {
-            fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect Finished");
-        }
-    }
-
-    @Override
-    public void send(HandshakeIODataStream out) {
-        out.write(data);
-    }
-
-    /**
-     * Returns message type
-     */
-    @Override
-    public int getType() {
-        return Handshake.FINISHED;
-    }
-
-    /**
-     * Returns verify data
-     */
-    public byte[] getData() {
-        return data;
-    }
-}
diff --git a/src/main/java/org/conscrypt/Handshake.java b/src/main/java/org/conscrypt/Handshake.java
deleted file mode 100644
index 7cee71b..0000000
--- a/src/main/java/org/conscrypt/Handshake.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- *  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;
-
-/**
- *
- * This class incapsulates the constants determining the types of handshake
- * messages as defined in TLS 1.0 spec., 7.4. Handshake protocol.
- * (http://www.ietf.org/rfc/rfc2246.txt)
- *
- */
-public class Handshake {
-
-    /**
-     *
-     * hello_request handshake type
-     */
-    public static final byte HELLO_REQUEST = 0;
-
-    /**
-     *
-     * client_hello handshake type
-     */
-    public static final byte CLIENT_HELLO = 1;
-
-    /**
-     *
-     * server_hello handshake type
-     */
-    public static final byte SERVER_HELLO = 2;
-
-    /**
-     *
-     * certificate handshake type
-     */
-    public static final byte CERTIFICATE = 11;
-
-    /**
-     *
-     * server_key_exchange  handshake type
-     */
-    public static final byte SERVER_KEY_EXCHANGE = 12;
-
-    /**
-     *
-     * certificate_request handshake type
-     */
-    public static final byte CERTIFICATE_REQUEST = 13;
-
-    /**
-     *
-     * server_hello_done handshake type
-     */
-    public static final byte SERVER_HELLO_DONE = 14;
-
-    /**
-     *
-     * certificate_verify handshake type
-     */
-    public static final byte CERTIFICATE_VERIFY = 15;
-
-    /**
-     *
-     * client_key_exchange handshake type
-     */
-    public static final byte CLIENT_KEY_EXCHANGE = 16;
-
-    /**
-     *
-     * finished handshake type
-     */
-    public static final byte FINISHED = 20;
-
-}
\ No newline at end of file
diff --git a/src/main/java/org/conscrypt/HandshakeIODataStream.java b/src/main/java/org/conscrypt/HandshakeIODataStream.java
deleted file mode 100644
index 7091525..0000000
--- a/src/main/java/org/conscrypt/HandshakeIODataStream.java
+++ /dev/null
@@ -1,436 +0,0 @@
-/*
- *  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.io.IOException;
-import java.security.MessageDigest;
-import java.util.Arrays;
-import javax.net.ssl.SSLHandshakeException;
-
-/**
- * This class provides Input/Output data functionality
- * for handshake layer. It provides read and write operations
- * and accumulates all sent/received handshake's data.
- * This class can be presented as a combination of 2 data pipes.
- * The first data pipe is a pipe of income data: append method
- * places the data at the beginning of the pipe, and read methods
- * consume the data from the pipe. The second pipe is an outcoming
- * data pipe: write operations plases the data into the pipe,
- * and getData methods consume the data.
- * It is important to note that work with pipe cound not be
- * started if there is unconsumed data in another pipe. It is
- * reasoned by the following: handshake protocol performs read
- * and write operations consecuently. I.e. it first reads all
- * income data and only than produces the responce and places it
- * into the stream.
- * The read operations of the stream presented by the methods
- * of SSLInputStream which in its turn is an extension of InputStream.
- * So this stream can be used as an InputStream parameter for
- * certificate generation.
- * Also input stream functionality supports marks. The marks
- * help to reset the position of the stream in case of incompleate
- * handshake records. Note that in case of exhausting
- * of income data the EndOfBufferException is thown which implies
- * the following:
- *  1. the stream contains scrappy handshake record,
- *  2. the read position should be reseted to marked,
- *  3. and more income data is expected.
- * The throwing of the exception (instead of returning of -1 value
- * or incompleate filling of destination buffer)
- * helps to speed up the process of scrappy data recognition and
- * processing.
- * For more information about TLS handshake process see
- * TLS v 1 specification at http://www.ietf.org/rfc/rfc2246.txt.
- */
-public class HandshakeIODataStream
-        extends SSLInputStream implements org.conscrypt.Appendable, DataStream {
-
-    // Objects are used to compute digests of data passed
-    // during the handshake phase
-    private static final MessageDigest md5;
-    private static final MessageDigest sha;
-
-    static {
-        try {
-            md5 = MessageDigest.getInstance("MD5");
-            sha = MessageDigest.getInstance("SHA-1");
-        } catch (Exception e) {
-            e.printStackTrace();
-            throw new RuntimeException(
-                    "Could not initialize the Digest Algorithms.");
-        }
-    }
-
-    public HandshakeIODataStream() {}
-
-    // buffer is used to keep the handshaking data;
-    private int buff_size = 1024;
-    private int inc_buff_size = 1024;
-    private byte[] buffer = new byte[buff_size];
-
-
-    // ---------------- Input related functionality -----------------
-
-    // position of the next byte to read
-    private int read_pos;
-    private int marked_pos;
-    // position of the last byte to read + 1
-    private int read_pos_end;
-
-    @Override
-    public int available() {
-        return read_pos_end - read_pos;
-    }
-
-    @Override
-    public boolean markSupported() {
-        return true;
-    }
-
-    @Override
-    public void mark(int limit) {
-        marked_pos = read_pos;
-    }
-
-    public void mark() {
-        marked_pos = read_pos;
-    }
-
-    @Override
-    public void reset() {
-        read_pos = marked_pos;
-    }
-
-    /**
-     * Removes the data from the marked position to
-     * the current read position. The method is usefull when it is needed
-     * to delete one message from the internal buffer.
-     */
-    protected void removeFromMarkedPosition() {
-        System.arraycopy(buffer, read_pos,
-                buffer, marked_pos, read_pos_end - read_pos);
-        read_pos_end -= (read_pos - marked_pos);
-        read_pos = marked_pos;
-    }
-
-    /**
-     * read an opaque value;
-     * @param   byte:   byte
-     * @return
-     */
-    @Override
-    public int read() throws IOException {
-        if (read_pos == read_pos_end) {
-            //return -1;
-            throw new EndOfBufferException();
-        }
-        return buffer[read_pos++] & 0xFF;
-    }
-
-    /**
-     * reads vector of opaque values
-     * @param   new:    long
-     * @return
-     */
-    @Override
-    public byte[] read(int length) throws IOException {
-        if (length > available()) {
-            throw new EndOfBufferException();
-        }
-        byte[] res = new byte[length];
-        System.arraycopy(buffer, read_pos, res, 0, length);
-        read_pos = read_pos + length;
-        return res;
-    }
-
-    @Override
-    public int read(byte[] dst, int offset, int length) throws IOException {
-        if (length > available()) {
-            throw new EndOfBufferException();
-        }
-        System.arraycopy(buffer, read_pos, dst, offset, length);
-        read_pos = read_pos + length;
-        return length;
-    }
-
-    // ------------------- Extending of the input data ---------------------
-
-    /**
-     * Appends the income data to be read by handshake protocol.
-     * The attempts to overflow the buffer by means of this methods
-     * seem to be futile because of:
-     * 1. The SSL protocol specifies the maximum size of the record
-     * and record protocol does not pass huge messages.
-     * (see TLS v1 specification http://www.ietf.org/rfc/rfc2246.txt ,
-     * p 6.2)
-     * 2. After each call of this method, handshake protocol should
-     * start (and starts) the operations on received data and recognize
-     * the fake data if such was provided (to check the size of certificate
-     * for example).
-     */
-    @Override
-    public void append(byte[] src) {
-        append(src, 0, src.length);
-    }
-
-    private void append(byte[] src, int from, int length) {
-        if (read_pos == read_pos_end) {
-            // start reading state after writing
-            if (write_pos_beg != write_pos) {
-                // error: outboud handshake data was not sent,
-                // but inbound handshake data has been received.
-                throw new AlertException(
-                    AlertProtocol.UNEXPECTED_MESSAGE,
-                    new SSLHandshakeException(
-                        "Handshake message has been received before "
-                        + "the last oubound message had been sent."));
-            }
-            if (read_pos < write_pos) {
-                read_pos = write_pos;
-                read_pos_end = read_pos;
-            }
-        }
-        if (read_pos_end + length > buff_size) {
-            enlargeBuffer(read_pos_end+length-buff_size);
-        }
-        System.arraycopy(src, from, buffer, read_pos_end, length);
-        read_pos_end += length;
-    }
-
-    private void enlargeBuffer(int size) {
-        buff_size = (size < inc_buff_size)
-            ? buff_size + inc_buff_size
-            : buff_size + size;
-        byte[] new_buff = new byte[buff_size];
-        System.arraycopy(buffer, 0, new_buff, 0, buffer.length);
-        buffer = new_buff;
-    }
-
-    protected void clearBuffer() {
-        read_pos = 0;
-        marked_pos = 0;
-        read_pos_end = 0;
-        write_pos = 0;
-        write_pos_beg = 0;
-        Arrays.fill(buffer, (byte) 0);
-    }
-
-    // ------------------- Output related functionality --------------------
-
-    // position in the buffer available for write
-    private int write_pos;
-    // position in the buffer where the last write session has begun
-    private int write_pos_beg;
-
-    // checks if the data can be written in the buffer
-    private void check(int length) {
-        // (write_pos == write_pos_beg) iff:
-        // 1. there were not write operations yet
-        // 2. all written data was demanded by getData methods
-        if (write_pos == write_pos_beg) {
-            // just started to write after the reading
-            if (read_pos != read_pos_end) {
-                // error: attempt to write outbound data into the stream before
-                // all the inbound handshake data had been read
-                throw new AlertException(
-                        AlertProtocol.INTERNAL_ERROR,
-                        new SSLHandshakeException("Data was not fully read: "
-                        + read_pos + " " + read_pos_end));
-            }
-            // set up the write positions
-            if (write_pos_beg < read_pos_end) {
-                write_pos_beg = read_pos_end;
-                write_pos = write_pos_beg;
-            }
-        }
-        // if there is not enought free space in the buffer - enlarge it:
-        if (write_pos + length >= buff_size) {
-            enlargeBuffer(length);
-        }
-    }
-
-    /**
-     * Writes an opaque value
-     * @param   byte:   byte
-     */
-    public void write(byte b) {
-        check(1);
-        buffer[write_pos++] = b;
-    }
-
-    /**
-     * Writes Uint8 value
-     * @param long: the value to be written (last byte)
-     */
-    public void writeUint8(long n) {
-        check(1);
-        buffer[write_pos++] = (byte) (n & 0x00ff);
-    }
-
-    /**
-     * Writes Uint16 value
-     * @param long: the value to be written (last 2 bytes)
-     */
-    public void writeUint16(long n) {
-        check(2);
-        buffer[write_pos++] = (byte) ((n & 0x00ff00) >> 8);
-        buffer[write_pos++] = (byte) (n & 0x00ff);
-    }
-
-    /**
-     * Writes Uint24 value
-     * @param long: the value to be written (last 3 bytes)
-     */
-    public void writeUint24(long n) {
-        check(3);
-        buffer[write_pos++] = (byte) ((n & 0x00ff0000) >> 16);
-        buffer[write_pos++] = (byte) ((n & 0x00ff00) >> 8);
-        buffer[write_pos++] = (byte) (n & 0x00ff);
-    }
-
-    /**
-     * Writes Uint32 value
-     * @param long: the value to be written (last 4 bytes)
-     */
-    public void writeUint32(long n) {
-        check(4);
-        buffer[write_pos++] = (byte) ((n & 0x00ff000000) >> 24);
-        buffer[write_pos++] = (byte) ((n & 0x00ff0000) >> 16);
-        buffer[write_pos++] = (byte) ((n & 0x00ff00) >> 8);
-        buffer[write_pos++] = (byte) (n & 0x00ff);
-    }
-
-    /**
-     * Writes Uint64 value
-     * @param long: the value to be written
-     */
-    public void writeUint64(long n) {
-        check(8);
-        buffer[write_pos++] = (byte) ((n & 0x00ff00000000000000L) >> 56);
-        buffer[write_pos++] = (byte) ((n & 0x00ff000000000000L) >> 48);
-        buffer[write_pos++] = (byte) ((n & 0x00ff0000000000L) >> 40);
-        buffer[write_pos++] = (byte) ((n & 0x00ff00000000L) >> 32);
-        buffer[write_pos++] = (byte) ((n & 0x00ff000000) >> 24);
-        buffer[write_pos++] = (byte) ((n & 0x00ff0000) >> 16);
-        buffer[write_pos++] = (byte) ((n & 0x00ff00) >> 8);
-        buffer[write_pos++] = (byte) (n & 0x00ff);
-    }
-
-    /**
-     * writes vector of opaque values
-     * @param  vector the vector to be written
-     */
-    public void write(byte[] vector) {
-        check(vector.length);
-        System.arraycopy(vector, 0, buffer, write_pos, vector.length);
-        write_pos += vector.length;
-    }
-
-    // ------------------- Retrieve the written bytes ----------------------
-
-    @Override
-    public boolean hasData() {
-        return (write_pos > write_pos_beg);
-    }
-
-    /**
-     * returns the chunk of stored data with the length no more than specified.
-     * @param   length: int
-     * @return
-     */
-    @Override
-    public byte[] getData(int length) {
-        byte[] res;
-        if (write_pos - write_pos_beg < length) {
-            res = new byte[write_pos - write_pos_beg];
-            System.arraycopy(buffer, write_pos_beg,
-                    res, 0, write_pos-write_pos_beg);
-            write_pos_beg = write_pos;
-        } else {
-            res = new byte[length];
-            System.arraycopy(buffer, write_pos_beg, res, 0, length);
-            write_pos_beg += length;
-        }
-        return res;
-    }
-
-    // ---------------------- Message Digest Functionality ----------------
-
-    /**
-     * Returns the MD5 digest of the data passed throught the stream
-     * @return MD5 digest
-     */
-    protected byte[] getDigestMD5() {
-        synchronized (md5) {
-            int len = (read_pos_end > write_pos)
-                ? read_pos_end
-                : write_pos;
-            md5.update(buffer, 0, len);
-            return md5.digest();
-        }
-    }
-
-    /**
-     * Returns the SHA-1 digest of the data passed throught the stream
-     * @return SHA-1 digest
-     */
-    protected byte[] getDigestSHA() {
-        synchronized (sha) {
-            int len = (read_pos_end > write_pos)
-                ? read_pos_end
-                : write_pos;
-            sha.update(buffer, 0, len);
-            return sha.digest();
-        }
-    }
-
-    /**
-     * Returns the MD5 digest of the data passed throught the stream
-     * except last message
-     * @return MD5 digest
-     */
-    protected byte[] getDigestMD5withoutLast() {
-        synchronized (md5) {
-            md5.update(buffer, 0, marked_pos);
-            return md5.digest();
-        }
-    }
-
-    /**
-     * Returns the SHA-1 digest of the data passed throught the stream
-     * except last message
-     * @return SHA-1 digest
-     */
-    protected byte[] getDigestSHAwithoutLast() {
-        synchronized (sha) {
-            sha.update(buffer, 0, marked_pos);
-            return sha.digest();
-        }
-    }
-
-    /**
-     * Returns all the data passed throught the stream
-     * @return all the data passed throught the stream at the moment
-     */
-    protected byte[] getMessages() {
-        int len = (read_pos_end > write_pos) ? read_pos_end : write_pos;
-        byte[] res = new byte[len];
-        System.arraycopy(buffer, 0, res, 0, len);
-        return res;
-    }
-}
diff --git a/src/main/java/org/conscrypt/HandshakeProtocol.java b/src/main/java/org/conscrypt/HandshakeProtocol.java
deleted file mode 100644
index 76bbafe..0000000
--- a/src/main/java/org/conscrypt/HandshakeProtocol.java
+++ /dev/null
@@ -1,518 +0,0 @@
-/*
- *  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.math.BigInteger;
-import java.security.GeneralSecurityException;
-import java.security.KeyFactory;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.interfaces.RSAKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.RSAPublicKeySpec;
-import java.util.Arrays;
-import java.util.Vector;
-import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLHandshakeException;
-
-/**
- * Base class for ClientHandshakeImpl and ServerHandshakeImpl classes.
- * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.
- * Handshake protocol</a>
- *
- */
-public abstract class HandshakeProtocol {
-
-    /**
-     * Handshake status NEED_UNWRAP - HandshakeProtocol needs to receive data
-     */
-    public static final int NEED_UNWRAP = 1;
-
-    /**
-     * Handshake status NOT_HANDSHAKING - is not currently handshaking
-     */
-    public static final int NOT_HANDSHAKING = 2;
-
-    /**
-     * Handshake status FINISHED - HandshakeProtocol has just finished
-     */
-    public static final int FINISHED = 3;
-
-    /**
-     * Handshake status NEED_TASK - HandshakeProtocol needs the results of delegated task
-     */
-    public static final int NEED_TASK = 4;
-
-    /**
-     * Current handshake status
-     */
-    protected int status = NOT_HANDSHAKING;
-
-    /**
-     * IO stream for income/outcome handshake data
-     */
-    protected HandshakeIODataStream io_stream = new HandshakeIODataStream();
-
-    /**
-     * SSL Record Protocol implementation.
-     */
-    protected SSLRecordProtocol recordProtocol;
-
-    /**
-     * SSLParametersImpl suplied by SSLSocket or SSLEngine
-     */
-    protected SSLParametersImpl parameters;
-
-    /**
-     * Delegated tasks for this handshake implementation
-     */
-    protected Vector<DelegatedTask> delegatedTasks = new Vector<DelegatedTask>();
-
-    /**
-     * Indicates non-blocking handshake
-     */
-    protected boolean nonBlocking;
-
-    /**
-     * Pending session
-     */
-    protected SSLSessionImpl session;
-
-    /**
-     * Sent and received handshake messages
-     */
-    protected ClientHello clientHello;
-    protected ServerHello serverHello;
-    protected CertificateMessage serverCert;
-    protected ServerKeyExchange serverKeyExchange;
-    protected CertificateRequest certificateRequest;
-    protected ServerHelloDone serverHelloDone;
-    protected CertificateMessage clientCert;
-    protected ClientKeyExchange clientKeyExchange;
-    protected CertificateVerify certificateVerify;
-    protected Finished clientFinished;
-    protected Finished serverFinished;
-
-    /**
-     * Indicates that change cipher spec message has been received
-     */
-    protected boolean changeCipherSpecReceived = false;
-
-    /**
-     * Indicates previous session resuming
-     */
-    protected boolean isResuming = false;
-
-    /**
-     *  Premaster secret
-     */
-    protected byte[] preMasterSecret;
-
-    /**
-     * Exception occured in delegated task
-     */
-    protected Exception delegatedTaskErr;
-
-    // reference verify_data used to verify finished message
-    private byte[] verify_data = new byte[12];
-
-    // Encoding of "master secret" string: "master secret".getBytes()
-    private byte[] master_secret_bytes =
-            {109, 97, 115, 116, 101, 114, 32, 115, 101, 99, 114, 101, 116 };
-
-    // indicates whether protocol needs to send change cipher spec message
-    private boolean needSendCCSpec = false;
-
-    // indicates whether protocol needs to send change cipher spec message
-    protected boolean needSendHelloRequest = false;
-
-    /**
-     * SSLEngine owning this HandshakeProtocol
-     */
-    protected final SSLEngineImpl engineOwner;
-
-    /**
-     * Creates HandshakeProtocol instance
-     * @param owner
-     */
-    protected HandshakeProtocol(SSLEngineImpl owner) {
-          if (owner == null) {
-             throw new NullPointerException();
-          }
-          engineOwner = owner;
-          nonBlocking = true;
-          this.parameters = engineOwner.sslParameters;
-    }
-
-    /**
-     * Sets SSL Record Protocol
-     * @param recordProtocol
-     */
-    public void setRecordProtocol(SSLRecordProtocol recordProtocol) {
-        this.recordProtocol = recordProtocol;
-    }
-
-    /**
-     * Start session negotiation
-     */
-    public abstract void start();
-
-    /**
-     * Stops the current session renegotiation process.
-     * Such functionality is needed when it is session renegotiation
-     * process and no_renegotiation alert message is received
-     * from another peer.
-     * @param session
-     */
-    protected void stop() {
-        clearMessages();
-        status = NOT_HANDSHAKING;
-    }
-
-    /**
-     * Returns handshake status
-     */
-    public SSLEngineResult.HandshakeStatus getStatus() {
-        if (io_stream.hasData() || needSendCCSpec ||
-                needSendHelloRequest || delegatedTaskErr != null) {
-            return SSLEngineResult.HandshakeStatus.NEED_WRAP;
-        }
-        if (!delegatedTasks.isEmpty()) {
-            return SSLEngineResult.HandshakeStatus.NEED_TASK;
-        }
-
-        switch (status) {
-        case HandshakeProtocol.NEED_UNWRAP:
-            return SSLEngineResult.HandshakeStatus.NEED_UNWRAP;
-        case HandshakeProtocol.FINISHED:
-            status = NOT_HANDSHAKING;
-            clearMessages();
-            return SSLEngineResult.HandshakeStatus.FINISHED;
-        default: // HandshakeProtocol.NOT_HANDSHAKING:
-            return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
-        }
-    }
-
-    /**
-     * Returns pending session
-     * @return session
-     */
-    public SSLSessionImpl getSession() {
-        return session;
-    }
-
-    protected void sendChangeCipherSpec() {
-        needSendCCSpec = true;
-    }
-
-    protected void sendHelloRequest() {
-        needSendHelloRequest = true;
-    }
-
-    /**
-     * Proceses inbound ChangeCipherSpec message
-     */
-    abstract void receiveChangeCipherSpec();
-
-    /**
-     * Creates and sends finished message
-     */
-    abstract void makeFinished();
-
-    /**
-     * Proceses inbound handshake messages
-     * @param bytes
-     */
-    public abstract void unwrap(byte[] bytes);
-
-    /**
-     * Processes SSLv2 Hello message
-     * @param bytes
-     */
-    public abstract void unwrapSSLv2(byte[] bytes);
-
-    /**
-     * Processes outbound handshake messages
-     */
-    public byte[] wrap() {
-        if (delegatedTaskErr != null) {
-            // process error occured in delegated task
-            Exception e = delegatedTaskErr;
-            delegatedTaskErr = null;
-            fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
-                    "Error occured in delegated task:" + e.getMessage(), e);
-        }
-        if (io_stream.hasData()) {
-            return recordProtocol.wrap(ContentType.HANDSHAKE, io_stream);
-        } else if (needSendCCSpec) {
-            makeFinished();
-            needSendCCSpec = false;
-            return recordProtocol.getChangeCipherSpecMesage(getSession());
-        } else if (needSendHelloRequest) {
-            needSendHelloRequest = false;
-            return recordProtocol.wrap(ContentType.HANDSHAKE,
-                    // hello request message
-                    // (see TLS v 1 specification:
-                    // http://www.ietf.org/rfc/rfc2246.txt)
-                    new byte[] {0, 0, 0, 0}, 0, 4);
-        } else {
-            return null; // nothing to send;
-        }
-    }
-
-    /**
-     * Sends fatal alert, breaks execution
-     *
-     * @param description
-     */
-    protected void sendWarningAlert(byte description) {
-        recordProtocol.alert(AlertProtocol.WARNING, description);
-    }
-
-    /**
-     * Sends fatal alert, breaks execution
-     *
-     * @param description
-     * @param reason
-     */
-    protected void fatalAlert(byte description, String reason) {
-        throw new AlertException(description, new SSLHandshakeException(reason));
-    }
-
-    /**
-     * Sends fatal alert, breaks execution
-     *
-     * @param description
-     * @param reason
-     * @param cause
-     */
-    protected void fatalAlert(byte description, String reason, Exception cause) {
-        throw new AlertException(description, new SSLException(reason, cause));
-    }
-
-    /**
-     * Sends fatal alert, breaks execution
-     *
-     * @param description
-     * @param cause
-     */
-    protected void fatalAlert(byte description, SSLException cause) {
-        throw new AlertException(description, cause);
-    }
-
-    /**
-     * Computers reference TLS verify_data that is used to verify finished message
-     * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS spec. 7.4.9. Finished</a>
-     * @param label
-     */
-    protected void computerReferenceVerifyDataTLS(String label) {
-        computerVerifyDataTLS(label, verify_data);
-    }
-
-    /**
-     * Computer TLS verify_data
-     * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS spec. 7.4.9. Finished</a>
-     * @param label
-     * @param buf
-     */
-    protected void computerVerifyDataTLS(String label, byte[] buf) {
-        byte[] md5_digest = io_stream.getDigestMD5();
-        byte[] sha_digest = io_stream.getDigestSHA();
-
-        byte[] digest = new byte[md5_digest.length + sha_digest.length];
-        System.arraycopy(md5_digest, 0, digest, 0, md5_digest.length);
-        System.arraycopy(sha_digest, 0, digest, md5_digest.length,
-                sha_digest.length);
-        try {
-            PRF.computePRF(buf, session.master_secret,
-                    label.getBytes(), digest);
-        } catch (GeneralSecurityException e) {
-            fatalAlert(AlertProtocol.INTERNAL_ERROR, "PRF error", e);
-        }
-    }
-
-    /**
-     * Computer reference SSLv3 verify_data that is used to verify finished message
-     * @see "SSLv3 spec. 7.6.9. Finished"
-     * @param label
-     */
-    protected void computerReferenceVerifyDataSSLv3(byte[] sender) {
-        verify_data = new byte[36];
-        computerVerifyDataSSLv3(sender, verify_data);
-    }
-
-    /**
-     * Computer SSLv3 verify_data
-     * @see "SSLv3 spec. 7.6.9. Finished"
-     * @param label
-     * @param buf
-     */
-    protected void computerVerifyDataSSLv3(byte[] sender, byte[] buf) {
-        MessageDigest md5;
-        MessageDigest sha;
-        try {
-            md5 = MessageDigest.getInstance("MD5");
-            sha = MessageDigest.getInstance("SHA-1");
-        } catch (Exception e) {
-            fatalAlert(AlertProtocol.INTERNAL_ERROR,
-                       "Could not initialize the Digest Algorithms.",
-                       e);
-            return;
-        }
-        try {
-            byte[] handshake_messages = io_stream.getMessages();
-            md5.update(handshake_messages);
-            md5.update(sender);
-            md5.update(session.master_secret);
-            byte[] b = md5.digest(SSLv3Constants.MD5pad1);
-            md5.update(session.master_secret);
-            md5.update(SSLv3Constants.MD5pad2);
-            System.arraycopy(md5.digest(b), 0, buf, 0, 16);
-
-            sha.update(handshake_messages);
-            sha.update(sender);
-            sha.update(session.master_secret);
-            b = sha.digest(SSLv3Constants.SHApad1);
-            sha.update(session.master_secret);
-            sha.update(SSLv3Constants.SHApad2);
-            System.arraycopy(sha.digest(b), 0, buf, 16, 20);
-        } catch (Exception e) {
-            fatalAlert(AlertProtocol.INTERNAL_ERROR, "INTERNAL ERROR", e);
-
-        }
-    }
-
-    /**
-     * Verifies finished data
-     *
-     * @param data
-     * @param isServer
-     */
-    protected void verifyFinished(byte[] data) {
-        if (!Arrays.equals(verify_data, data)) {
-            fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "Incorrect FINISED");
-        }
-    }
-
-    /**
-     * Sends fatal alert "UNEXPECTED MESSAGE"
-     *
-     */
-    protected void unexpectedMessage() {
-        fatalAlert(AlertProtocol.UNEXPECTED_MESSAGE, "UNEXPECTED MESSAGE");
-    }
-
-    /**
-     * Writes message to HandshakeIODataStream
-     *
-     * @param message
-     */
-    public void send(Message message) {
-        io_stream.writeUint8(message.getType());
-        io_stream.writeUint24(message.length());
-        message.send(io_stream);
-    }
-
-    /**
-     * Computers master secret
-     *
-     */
-    public void computerMasterSecret() {
-        byte[] seed = new byte[64];
-        System.arraycopy(clientHello.getRandom(), 0, seed, 0, 32);
-        System.arraycopy(serverHello.getRandom(), 0, seed, 32, 32);
-        session.master_secret = new byte[48];
-        if (serverHello.server_version[1] == 1) { // TLSv1
-            try {
-                PRF.computePRF(session.master_secret, preMasterSecret,
-                        master_secret_bytes, seed);
-            } catch (GeneralSecurityException e) {
-                fatalAlert(AlertProtocol.INTERNAL_ERROR, "PRF error", e);
-            }
-        } else { // SSL3.0
-            PRF.computePRF_SSLv3(session.master_secret, preMasterSecret, seed);
-        }
-
-        //delete preMasterSecret from memory
-        Arrays.fill(preMasterSecret, (byte)0);
-        preMasterSecret = null;
-    }
-
-    /**
-     * Returns a delegated task.
-     * @return Delegated task or null
-     */
-    public Runnable getTask() {
-        if (delegatedTasks.isEmpty()) {
-            return null;
-        }
-        return delegatedTasks.remove(0);
-    }
-
-    /**
-     * Clears previously sent and received handshake messages
-     */
-    protected void clearMessages() {
-        io_stream.clearBuffer();
-        clientHello = null;
-        serverHello = null;
-        serverCert = null;
-        serverKeyExchange = null;
-        certificateRequest = null;
-        serverHelloDone = null;
-        clientCert = null;
-        clientKeyExchange = null;
-        certificateVerify = null;
-        clientFinished = null;
-        serverFinished = null;
-    }
-
-    /**
-     * Returns RSA key length
-     * @param pk
-     * @return
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidKeySpecException
-     */
-    protected static int getRSAKeyLength(PublicKey pk)
-            throws NoSuchAlgorithmException, InvalidKeySpecException {
-
-        BigInteger mod;
-        if (pk instanceof RSAKey) {
-            mod = ((RSAKey) pk).getModulus();
-        } else {
-            KeyFactory kf = KeyFactory.getInstance("RSA");
-            mod = kf.getKeySpec(pk, RSAPublicKeySpec.class)
-                    .getModulus();
-        }
-        return mod.bitLength();
-    }
-
-    /**
-     * Shuts down the protocol. It will be impossible to use the instance
-     * after calling this method.
-     */
-    protected void shutdown() {
-        clearMessages();
-        session = null;
-        preMasterSecret = null;
-        delegatedTasks.clear();
-    }
-}
diff --git a/src/main/java/org/conscrypt/HelloRequest.java b/src/main/java/org/conscrypt/HelloRequest.java
deleted file mode 100644
index 20efbfd..0000000
--- a/src/main/java/org/conscrypt/HelloRequest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- *  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.io.IOException;
-
-/**
- *
- * Represents Hello Request message
- * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.1.1.
- * Hello request</a>
- *
- */
-public class HelloRequest extends Message {
-
-    /**
-     * Creates outbound message
-     *
-     */
-    public HelloRequest() {
-    }
-
-    /**
-     * Creates inbound message
-     * @param in
-     * @param length
-     * @throws IOException
-     */
-    public HelloRequest(HandshakeIODataStream in, int length)
-            throws IOException {
-        if (length != 0) {
-            fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect HelloRequest");
-        }
-    }
-
-    /**
-     * Sends message
-     * @param out
-     */
-    @Override
-    public void send(HandshakeIODataStream out) {
-    }
-
-    @Override
-    public int length() {
-        return 0;
-    }
-
-    /**
-     * Returns message type
-     * @return
-     */
-    @Override
-    public int getType() {
-        return Handshake.HELLO_REQUEST;
-    }
-
-}
diff --git a/src/main/java/org/conscrypt/Logger.java b/src/main/java/org/conscrypt/Logger.java
deleted file mode 100644
index a908557..0000000
--- a/src/main/java/org/conscrypt/Logger.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- *  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.io.PrintStream;
-import org.conscrypt.util.EmptyArray;
-
-/**
- * This class provides debug logging for JSSE provider implementation
- * TODO: Use java.util.logging
- */
-public class Logger {
-
-    public static class Stream extends PrintStream {
-        private final String prefix;
-        private static int indent = 0;
-
-        public Stream(String name) {
-            super(System.err);
-            prefix = name + "["+Thread.currentThread().getName()+"] ";
-        }
-
-        @Override
-        public void print(String msg) {
-            for (int i=0; i<indent; i++) {
-                super.print("  ");
-            }
-            super.print(msg);
-        }
-
-        public void newIndent() {
-            indent ++;
-        }
-
-        public void endIndent() {
-            indent --;
-        }
-
-        @Override
-        public void println(String msg) {
-            print(prefix);
-            super.println(msg);
-        }
-
-        public void print(byte[] data) {
-            printAsHex(16, " ", "", data, 0, data.length);
-        }
-
-        public void print(byte[] data, int offset, int len) {
-            printAsHex(16, " ", "", data, offset, len);
-        }
-
-        public void printAsHex(int perLine, String prefix, String delimiter, byte[] data) {
-            printAsHex(perLine, prefix, delimiter, data, 0, data.length);
-        }
-
-        public void printAsHex(int perLine, String prefix, String delimiter,
-                byte[] data, int offset, int len) {
-            StringBuilder line = new StringBuilder();
-            for (int i = 0; i < len; i++) {
-                line.append(prefix);
-                line.append(Byte.toHexString(data[i+offset], false));
-                line.append(delimiter);
-
-                if (((i+1)%perLine) == 0) {
-                    super.println(line.toString());
-                    line = new StringBuilder();
-                }
-            }
-            super.println(line.toString());
-        }
-    }
-
-    private static String[] names;
-
-    static {
-        try {
-            names = System.getProperty("jsse", "").split(",");
-        } catch (Exception e) {
-            names = EmptyArray.STRING;
-        }
-    }
-
-    public static Stream getStream(String name) {
-        for (int i=0; i<names.length; i++) {
-            if (names[i].equals(name)) {
-                return new Stream(name);
-            }
-        }
-        return null;
-    }
-}
diff --git a/src/main/java/org/conscrypt/Message.java b/src/main/java/org/conscrypt/Message.java
deleted file mode 100644
index 3b932d0..0000000
--- a/src/main/java/org/conscrypt/Message.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- *  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 javax.net.ssl.SSLException;
-import javax.net.ssl.SSLHandshakeException;
-
-/**
- *
- * Base class for handshake messages
- */
-public abstract class Message {
-
-    /*
-     * Message length
-     */
-    protected int length;
-
-    /**
-     * Returns message type
-     */
-    abstract int getType();
-
-    /**
-     * Returns message length
-     */
-    public int length() {
-        return length;
-    }
-
-    /**
-     * Sends message
-     * @param out
-     */
-    abstract void send(HandshakeIODataStream out);
-
-    /**
-     * Sends fatal alert
-     * @param description
-     * @param reason
-     */
-    protected void fatalAlert(byte description, String reason) {
-        throw new AlertException(description, new SSLHandshakeException(reason));
-    }
-
-    /**
-     * Sends fatal alert
-     * @param description
-     * @param reason
-     * @param cause
-     */
-    protected void fatalAlert(byte description, String reason, Throwable cause) {
-        throw new AlertException(description, new SSLException(reason, cause));
-    }
-}
diff --git a/src/main/java/org/conscrypt/NativeCrypto.java b/src/main/java/org/conscrypt/NativeCrypto.java
index 127d47d..7f85b34 100644
--- a/src/main/java/org/conscrypt/NativeCrypto.java
+++ b/src/main/java/org/conscrypt/NativeCrypto.java
@@ -26,6 +26,9 @@
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateParsingException;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.RSAPrivateKey;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.HashMap;
@@ -50,6 +53,9 @@
          */
         if ("com.android.org.conscrypt".equals(NativeCrypto.class.getPackage().getName())) {
             System.loadLibrary("javacrypto");
+        } else if ("com.google.android.gms.org.conscrypt".equals(NativeCrypto.class.getPackage().getName())) {
+            System.loadLibrary("gmscore");
+            System.loadLibrary("conscrypt_gmscore_jni");
         } else {
             System.loadLibrary("conscrypt_jni");
         }
@@ -109,6 +115,12 @@
 
     public static native long d2i_PUBKEY(byte[] data);
 
+    public static native long getRSAPrivateKeyWrapper(RSAPrivateKey key, byte[] modulus);
+
+    public static native long getDSAPrivateKeyWrapper(DSAPrivateKey key);
+
+    public static native long getECPrivateKeyWrapper(ECPrivateKey key, long ecGroupRef);
+
     public static native long RSA_generate_key_ex(int modulusBits, byte[] publicExponent);
 
     public static native int RSA_size(long pkey);
@@ -143,6 +155,8 @@
      */
     public static native byte[][] get_DSA_params(long dsa);
 
+    public static native void set_DSA_flag_nonce_from_hash(long dsa);
+
     public static native byte[] i2d_RSAPublicKey(long rsa);
 
     public static native byte[] i2d_RSAPrivateKey(long rsa);
@@ -151,6 +165,19 @@
 
     public static native byte[] i2d_DSAPrivateKey(long dsa);
 
+    // --- DH public/private key handling functions ----------------------------
+
+    public static native long EVP_PKEY_new_DH(byte[] p, byte[] g, byte[] pub_key, byte[] priv_key);
+
+    public static native long DH_generate_parameters_ex(int primeBits, long generator);
+
+    public static native void DH_generate_key(long pkeyRef);
+
+    /**
+     * @return array of {p, g, y(pub), x(priv)}
+     */
+    public static native byte[][] get_DH_params(long dh);
+
     // --- EC functions --------------------------
 
     /**
@@ -234,6 +261,8 @@
 
     public static native long EC_KEY_get_public_key(long keyRef);
 
+    public static native void EC_KEY_set_nonce_from_hash(long keyRef, boolean enabled);
+
     public static native int ECDH_compute_key(
             byte[] out, int outOffset, long publicKeyRef, long privateKeyRef);
 
@@ -249,43 +278,47 @@
 
     public static native long EVP_MD_CTX_create();
 
-    public static native void EVP_MD_CTX_init(long ctx);
+    public static native void EVP_MD_CTX_init(OpenSSLDigestContext ctx);
 
     public static native void EVP_MD_CTX_destroy(long ctx);
 
-    public static native long EVP_MD_CTX_copy(long ctx);
+    public static native int EVP_MD_CTX_copy(OpenSSLDigestContext dst_ctx,
+            OpenSSLDigestContext src_ctx);
 
     // --- Digest handling functions -------------------------------------------
 
-    public static native long EVP_DigestInit(long evp_md);
+    public static native int EVP_DigestInit(OpenSSLDigestContext ctx, long evp_md);
 
-    public static native void EVP_DigestUpdate(long ctx, byte[] buffer, int offset, int length);
+    public static native void EVP_DigestUpdate(OpenSSLDigestContext ctx, byte[] buffer,
+            int offset, int length);
 
-    public static native int EVP_DigestFinal(long ctx, byte[] hash, int offset);
+    public static native int EVP_DigestFinal(OpenSSLDigestContext ctx, byte[] hash, int offset);
 
     // --- MAC handling functions ----------------------------------------------
 
-    public static native void EVP_DigestSignInit(long evp_md_ctx, long evp_md, long evp_pkey);
+    public static native void EVP_DigestSignInit(OpenSSLDigestContext evp_md_ctx, long evp_md,
+            long evp_pkey);
 
-    public static native void EVP_DigestSignUpdate(long evp_md_ctx, byte[] in);
+    public static native void EVP_DigestSignUpdate(OpenSSLDigestContext evp_md_ctx, byte[] in);
 
-    public static native byte[] EVP_DigestSignFinal(long evp_md_ctx);
+    public static native byte[] EVP_DigestSignFinal(OpenSSLDigestContext evp_md_ctx);
 
     // --- Signature handling functions ----------------------------------------
 
-    public static native long EVP_SignInit(String algorithm);
+    public static native int EVP_SignInit(OpenSSLDigestContext ctx, long evpRef);
 
-    public static native void EVP_SignUpdate(long ctx, byte[] buffer,
+    public static native void EVP_SignUpdate(OpenSSLDigestContext ctx, byte[] buffer,
                                                int offset, int length);
 
-    public static native int EVP_SignFinal(long ctx, byte[] signature, int offset, long key);
+    public static native int EVP_SignFinal(OpenSSLDigestContext ctx, byte[] signature, int offset,
+            long key);
 
-    public static native long EVP_VerifyInit(String algorithm);
+    public static native int EVP_VerifyInit(OpenSSLDigestContext ctx, long evpRef);
 
-    public static native void EVP_VerifyUpdate(long ctx, byte[] buffer,
+    public static native void EVP_VerifyUpdate(OpenSSLDigestContext ctx, byte[] buffer,
                                                int offset, int length);
 
-    public static native int EVP_VerifyFinal(long ctx, byte[] signature,
+    public static native int EVP_VerifyFinal(OpenSSLDigestContext ctx, byte[] signature,
                                              int offset, int length, long key);
 
 
@@ -554,7 +587,7 @@
     public static native void BIO_write(long bioRef, byte[] buffer, int offset, int length)
             throws IOException;
 
-    public static native void BIO_free(long bioRef);
+    public static native void BIO_free_all(long bioRef);
 
     // --- SSL handling --------------------------------------------------------
 
@@ -718,11 +751,13 @@
         // add("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", "EXP-KRB5-RC2-CBC-SHA");
         // add("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", "EXP-KRB5-RC2-CBC-MD5");
 
-        // PSK is Private Shared Key - didn't exist in Froyo's openssl - no JSSE equivalent
-        // add(null, "PSK-3DES-EDE-CBC-SHA");
-        // add(null, "PSK-AES128-CBC-SHA");
-        // add(null, "PSK-AES256-CBC-SHA");
-        // add(null, "PSK-RC4-SHA");
+        // Pre-Shared Key (PSK) cipher suites
+        add("TLS_PSK_WITH_3DES_EDE_CBC_SHA", "PSK-3DES-EDE-CBC-SHA");
+        add("TLS_PSK_WITH_AES_128_CBC_SHA", "PSK-AES128-CBC-SHA");
+        add("TLS_PSK_WITH_AES_256_CBC_SHA", "PSK-AES256-CBC-SHA");
+        add("TLS_PSK_WITH_RC4_128_SHA", "PSK-RC4-SHA");
+        add("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", "ECDHE-PSK-AES128-CBC-SHA");
+        add("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", "ECDHE-PSK-AES256-CBC-SHA");
 
         // Signaling Cipher Suite Value for secure renegotiation handled as special case.
         // add("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", null);
@@ -758,6 +793,7 @@
     public static final long SSL_MODE_SEND_FALLBACK_SCSV   = 0x00000200L;
 
     // SSL options from ssl.h
+    public static final long SSL_OP_TLSEXT_PADDING                         = 0x00000010L;
     public static final long SSL_OP_NO_TICKET                              = 0x00004000L;
     public static final long SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION = 0x00010000L;
     public static final long SSL_OP_NO_SSLv3                               = 0x02000000L;
@@ -779,52 +815,65 @@
     public static final byte TLS_CT_RSA_FIXED_ECDH = 65;
     public static final byte TLS_CT_ECDSA_FIXED_ECDH = 66;
 
+    /*
+     * Used in the SSL_get_shutdown and SSL_set_shutdown functions.
+     */
+    public static final int SSL_SENT_SHUTDOWN = 1;
+    public static final int SSL_RECEIVED_SHUTDOWN = 2;
+
     public static native long SSL_CTX_new();
 
-    public static String[] getDefaultCipherSuites() {
-        // The default list of cipher suites is a trade-off between what we'd like to use and what
-        // servers currently support. We strive to be secure enough by default. We thus avoid
-        // unacceptably weak suites (e.g., those with bulk cipher secret key shorter than 128 bits),
-        // while maintaining the capability to connect to the majority of servers.
-        //
-        // Cipher suites are listed in preference order (favorite choice first) of the client.
-        // However, servers are not required to honor the order. The key rules governing the
-        // preference order are:
-        // * Prefer Forward Secrecy (i.e., cipher suites that use ECDHE and DHE for key agreement).
-        // * Prefer AES-GCM to AES-CBC whose MAC-pad-then-encrypt approach leads to weaknesses
-        //   (e.g., Lucky 13).
-        // * Prefer AES to RC4 whose foundations are a bit shaky. See
-        //   http://www.isg.rhul.ac.uk/tls/. BEAST and Lucky13 mitigations are enabled.
-        // * Prefer 128-bit bulk encryption to 256-bit one, because 128-bit is safe enough while
-        //   consuming less CPU/time/energy.
-        //
-        // NOTE: Removing cipher suites from this list needs to be done with caution, because this
-        // may prevent apps from connecting to servers they were previously able to connect to.
-        return new String[] {
-            "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
-            "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
-            "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
-            "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
-            "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
-            "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
-            "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
-            "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
-            "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
-            "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
-            "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
-            "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
-            "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
-            "TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
-            "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
-            "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
-            "TLS_RSA_WITH_AES_128_GCM_SHA256",
-            "TLS_RSA_WITH_AES_256_GCM_SHA384",
-            "TLS_RSA_WITH_AES_128_CBC_SHA",
-            "TLS_RSA_WITH_AES_256_CBC_SHA",
-            "SSL_RSA_WITH_RC4_128_SHA",
-            TLS_EMPTY_RENEGOTIATION_INFO_SCSV
-        };
-    }
+    // IMPLEMENTATION NOTE: The default list of cipher suites is a trade-off between what we'd like
+    // to use and what servers currently support. We strive to be secure enough by default. We thus
+    // avoid unacceptably weak suites (e.g., those with bulk cipher secret key shorter than 128
+    // bits), while maintaining the capability to connect to the majority of servers.
+    //
+    // Cipher suites are listed in preference order (favorite choice first) of the client. However,
+    // servers are not required to honor the order. The key rules governing the preference order
+    // are:
+    // * Prefer Forward Secrecy (i.e., cipher suites that use ECDHE and DHE for key agreement).
+    // * Prefer AES-GCM to AES-CBC whose MAC-pad-then-encrypt approach leads to weaknesses (e.g.,
+    //   Lucky 13).
+    // * Prefer AES to RC4 whose foundations are a bit shaky. See http://www.isg.rhul.ac.uk/tls/.
+    //   BEAST and Lucky13 mitigations are enabled.
+    // * Prefer 128-bit bulk encryption to 256-bit one, because 128-bit is safe enough while
+    //   consuming less CPU/time/energy.
+    //
+    // NOTE: Removing cipher suites from this list needs to be done with caution, because this may
+    // prevent apps from connecting to servers they were previously able to connect to.
+
+    /** X.509 based cipher suites enabled by default (if requested), in preference order. */
+    static final String[] DEFAULT_X509_CIPHER_SUITES = new String[] {
+        "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
+        "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
+        "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+        "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
+        "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
+        "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
+        "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
+        "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
+        "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
+        "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
+        "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
+        "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
+        "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
+        "TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
+        "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
+        "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
+        "TLS_RSA_WITH_AES_128_GCM_SHA256",
+        "TLS_RSA_WITH_AES_256_GCM_SHA384",
+        "TLS_RSA_WITH_AES_128_CBC_SHA",
+        "TLS_RSA_WITH_AES_256_CBC_SHA",
+        "SSL_RSA_WITH_RC4_128_SHA",
+    };
+
+    /** TLS-PSK cipher suites enabled by default (if requested), in preference order. */
+    static final String[] DEFAULT_PSK_CIPHER_SUITES = new String[] {
+        "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA",
+        "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA",
+        "TLS_PSK_WITH_AES_128_CBC_SHA",
+        "TLS_PSK_WITH_AES_256_CBC_SHA",
+    };
 
     public static String[] getSupportedCipherSuites() {
         return SUPPORTED_CIPHER_SUITES.clone();
@@ -862,13 +911,19 @@
 
     public static native long SSL_clear_options(long ssl, long options);
 
-    public static String[] getDefaultProtocols() {
-        return new String[] { SUPPORTED_PROTOCOL_SSLV3,
-                              SUPPORTED_PROTOCOL_TLSV1,
-                              SUPPORTED_PROTOCOL_TLSV1_1,
-                              SUPPORTED_PROTOCOL_TLSV1_2,
-        };
-    }
+    public static native void SSL_use_psk_identity_hint(long ssl, String identityHint)
+            throws SSLException;
+
+    public static native void set_SSL_psk_client_callback_enabled(long ssl, boolean enabled);
+
+    public static native void set_SSL_psk_server_callback_enabled(long ssl, boolean enabled);
+
+    public static final String[] DEFAULT_PROTOCOLS = new String[] {
+        SUPPORTED_PROTOCOL_SSLV3,
+        SUPPORTED_PROTOCOL_TLSV1,
+        SUPPORTED_PROTOCOL_TLSV1_1,
+        SUPPORTED_PROTOCOL_TLSV1_2,
+    };
 
     public static String[] getSupportedProtocols() {
         return new String[] { SUPPORTED_PROTOCOL_SSLV3,
@@ -1045,6 +1100,10 @@
     public static final int SSL_VERIFY_PEER =                 0x01;
     public static final int SSL_VERIFY_FAIL_IF_NO_PEER_CERT = 0x02;
 
+    public static native void SSL_set_accept_state(long sslNativePointer);
+
+    public static native void SSL_set_connect_state(long sslNativePointer);
+
     public static native void SSL_set_verify(long sslNativePointer, int mode);
 
     public static native void SSL_set_session(long sslNativePointer, long sslSessionNativePointer)
@@ -1080,9 +1139,9 @@
 
     /**
      * For clients, sets the list of supported ALPN protocols in wire-format
-     * (length-prefixed 8-bit strings) on an SSL context.
+     * (length-prefixed 8-bit strings).
      */
-    public static native int SSL_CTX_set_alpn_protos(long sslCtxPointer, byte[] protos);
+    public static native int SSL_set_alpn_protos(long sslPointer, byte[] protos);
 
     /**
      * Returns the selected ALPN protocol. If the server did not select a
@@ -1104,6 +1163,20 @@
                                                byte[] alpnProtocols)
         throws SSLException, SocketTimeoutException, CertificateException;
 
+    /**
+     * Returns the sslSessionNativePointer of the negotiated session. If this is
+     * a server negotiation, supplying the {@code alpnProtocols} will enable
+     * ALPN negotiation.
+     */
+    public static native long SSL_do_handshake_bio(long sslNativePointer,
+                                                   long sourceBioRef,
+                                                   long sinkBioRef,
+                                                   SSLHandshakeCallbacks shc,
+                                                   boolean client_mode,
+                                                   byte[] npnProtocols,
+                                                   byte[] alpnProtocols)
+        throws SSLException, SocketTimeoutException, CertificateException;
+
     public static native byte[] SSL_get_npn_negotiated_protocol(long sslNativePointer);
 
     /**
@@ -1132,6 +1205,15 @@
                                       byte[] b, int off, int len, int readTimeoutMillis)
         throws IOException;
 
+    public static native int SSL_read_BIO(long sslNativePointer,
+                                          byte[] dest,
+                                          int destOffset,
+                                          int destLength,
+                                          long sourceBioRef,
+                                          long sinkBioRef,
+                                          SSLHandshakeCallbacks shc)
+        throws IOException;
+
     /**
      * Writes with the native SSL_write function to the encrypted data stream.
      */
@@ -1141,11 +1223,24 @@
                                         byte[] b, int off, int len, int writeTimeoutMillis)
         throws IOException;
 
+    public static native int SSL_write_BIO(long sslNativePointer,
+                                           byte[] source,
+                                           int length,
+                                           long sinkBioRef,
+                                           SSLHandshakeCallbacks shc)
+        throws IOException;
+
     public static native void SSL_interrupt(long sslNativePointer);
     public static native void SSL_shutdown(long sslNativePointer,
                                            FileDescriptor fd,
                                            SSLHandshakeCallbacks shc) throws IOException;
 
+    public static native void SSL_shutdown_BIO(long sslNativePointer,
+                                               long sourceBioRef, long sinkBioRef,
+                                               SSLHandshakeCallbacks shc) throws IOException;
+
+    public static native int SSL_get_shutdown(long sslNativePointer);
+
     public static native void SSL_free(long sslNativePointer);
 
     public static native byte[] SSL_SESSION_session_id(long sslSessionNativePointer);
@@ -1170,13 +1265,14 @@
         /**
          * Verify that we trust the certificate chain is trusted.
          *
+         * @param sslSessionNativePtr pointer to a reference of the SSL_SESSION
          * @param certificateChainRefs chain of X.509 certificate references
          * @param authMethod auth algorithm name
          *
          * @throws CertificateException if the certificate is untrusted
          */
-        public void verifyCertificateChain(long[] certificateChainRefs, String authMethod)
-            throws CertificateException;
+        public void verifyCertificateChain(long sslSessionNativePtr, long[] certificateChainRefs,
+                String authMethod) throws CertificateException;
 
         /**
          * Called on an SSL client when the server requests (or
@@ -1194,12 +1290,78 @@
             throws CertificateEncodingException, SSLException;
 
         /**
-         * Called when SSL handshake is completed. Note that this can
-         * be after SSL_do_handshake returns when handshake cutthrough
-         * is enabled.
+         * Gets the key to be used in client mode for this connection in Pre-Shared Key (PSK) key
+         * exchange.
+         *
+         * @param identityHint PSK identity hint provided by the server or {@code null} if no hint
+         *        provided.
+         * @param identity buffer to be populated with PSK identity (NULL-terminated modified UTF-8)
+         *        by this method. This identity will be provided to the server.
+         * @param key buffer to be populated with key material by this method.
+         *
+         * @return number of bytes this method stored in the {@code key} buffer or {@code 0} if an
+         *         error occurred in which case the handshake will be aborted.
          */
-        public void handshakeCompleted();
+        public int clientPSKKeyRequested(String identityHint, byte[] identity, byte[] key);
+
+        /**
+         * Gets the key to be used in server mode for this connection in Pre-Shared Key (PSK) key
+         * exchange.
+         *
+         * @param identityHint PSK identity hint provided by this server to the client or
+         *        {@code null} if no hint was provided.
+         * @param identity PSK identity provided by the client.
+         * @param key buffer to be populated with key material by this method.
+         *
+         * @return number of bytes this method stored in the {@code key} buffer or {@code 0} if an
+         *         error occurred in which case the handshake will be aborted.
+         */
+        public int serverPSKKeyRequested(String identityHint, String identity, byte[] key);
+
+        /**
+         * Called when SSL state changes. This could be handshake completion.
+         */
+        public void onSSLStateChange(long sslSessionNativePtr, int type, int val);
     }
 
+    // Values used in the SSLHandshakeCallbacks#onSSLStateChange as the {@code type}.
+    public static final int SSL_ST_CONNECT = 0x1000;
+    public static final int SSL_ST_ACCEPT = 0x2000;
+    public static final int SSL_ST_MASK = 0x0FFF;
+    public static final int SSL_ST_INIT = (SSL_ST_CONNECT | SSL_ST_ACCEPT);
+    public static final int SSL_ST_BEFORE = 0x4000;
+    public static final int SSL_ST_OK = 0x03;
+    public static final int SSL_ST_RENEGOTIATE = (0x04 | SSL_ST_INIT);
+
+    public static final int SSL_CB_LOOP = 0x01;
+    public static final int SSL_CB_EXIT = 0x02;
+    public static final int SSL_CB_READ = 0x04;
+    public static final int SSL_CB_WRITE = 0x08;
+    public static final int SSL_CB_ALERT = 0x4000;
+    public static final int SSL_CB_READ_ALERT = (SSL_CB_ALERT | SSL_CB_READ);
+    public static final int SSL_CB_WRITE_ALERT = (SSL_CB_ALERT | SSL_CB_WRITE);
+    public static final int SSL_CB_ACCEPT_LOOP = (SSL_ST_ACCEPT | SSL_CB_LOOP);
+    public static final int SSL_CB_ACCEPT_EXIT = (SSL_ST_ACCEPT | SSL_CB_EXIT);
+    public static final int SSL_CB_CONNECT_LOOP = (SSL_ST_CONNECT | SSL_CB_LOOP);
+    public static final int SSL_CB_CONNECT_EXIT = (SSL_ST_CONNECT | SSL_CB_EXIT);
+    public static final int SSL_CB_HANDSHAKE_START = 0x10;
+    public static final int SSL_CB_HANDSHAKE_DONE = 0x20;
+
+    /*
+     * From ssl/ssl3.h
+     */
+    public static final int SSL3_RT_HEADER_LENGTH = 5;
+    public static final int SSL_RT_MAX_CIPHER_BLOCK_SIZE = 16;
+    public static final int SSL3_RT_MAX_MD_SIZE = 64;
+    public static final int SSL3_RT_MAX_PLAIN_LENGTH = 16384;
+    public static final int SSL3_RT_MAX_ENCRYPTED_OVERHEAD = 256 + SSL3_RT_MAX_MD_SIZE;
+    public static final int SSL3_RT_SEND_MAX_ENCRYPTED_OVERHEAD = SSL_RT_MAX_CIPHER_BLOCK_SIZE
+            + SSL3_RT_MAX_MD_SIZE;
+    public static final int SSL3_RT_MAX_COMPRESSED_LENGTH = SSL3_RT_MAX_PLAIN_LENGTH;
+    public static final int SSL3_RT_MAX_ENCRYPTED_LENGTH = SSL3_RT_MAX_ENCRYPTED_OVERHEAD
+            + SSL3_RT_MAX_COMPRESSED_LENGTH;
+    public static final int SSL3_RT_MAX_PACKET_SIZE = SSL3_RT_MAX_ENCRYPTED_LENGTH
+            + SSL3_RT_HEADER_LENGTH;
+
     public static native long ERR_peek_last_error();
 }
diff --git a/src/main/java/org/conscrypt/OpenSSLBIOInputStream.java b/src/main/java/org/conscrypt/OpenSSLBIOInputStream.java
index 26971d5..cb5d0ca 100644
--- a/src/main/java/org/conscrypt/OpenSSLBIOInputStream.java
+++ b/src/main/java/org/conscrypt/OpenSSLBIOInputStream.java
@@ -38,6 +38,10 @@
         return ctx;
     }
 
+    public void release() {
+        NativeCrypto.BIO_free_all(ctx);
+    }
+
     /**
      * Similar to a {@code readLine} method, but matches what OpenSSL expects
      * from a {@code BIO_gets} method.
diff --git a/src/main/java/org/conscrypt/OpenSSLBIOSink.java b/src/main/java/org/conscrypt/OpenSSLBIOSink.java
new file mode 100644
index 0000000..e599da9
--- /dev/null
+++ b/src/main/java/org/conscrypt/OpenSSLBIOSink.java
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+public final class OpenSSLBIOSink {
+    private final long ctx;
+    private final ByteArrayOutputStream buffer;
+    private int position;
+
+    public static OpenSSLBIOSink create() {
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        return new OpenSSLBIOSink(buffer);
+    }
+
+    private OpenSSLBIOSink(ByteArrayOutputStream buffer) {
+        ctx = NativeCrypto.create_BIO_OutputStream(buffer);
+        this.buffer = buffer;
+    }
+
+    public int available() {
+        return buffer.size() - position;
+    }
+
+    public void reset() {
+        buffer.reset();
+        position = 0;
+    }
+
+    public long skip(long byteCount) {
+        int maxLength = Math.min(available(), (int) byteCount);
+        position += maxLength;
+        if (position == buffer.size()) {
+            reset();
+        }
+        return maxLength;
+    }
+
+    public long getContext() {
+        return ctx;
+    }
+
+    public byte[] toByteArray() {
+        return buffer.toByteArray();
+    }
+
+    public int position() {
+        return position;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            NativeCrypto.BIO_free_all(ctx);
+        } finally {
+            super.finalize();
+        }
+    }
+}
diff --git a/src/main/java/org/conscrypt/OpenSSLBIOSource.java b/src/main/java/org/conscrypt/OpenSSLBIOSource.java
new file mode 100644
index 0000000..61e966b
--- /dev/null
+++ b/src/main/java/org/conscrypt/OpenSSLBIOSource.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+public final class OpenSSLBIOSource {
+    private OpenSSLBIOInputStream source;
+
+    public static OpenSSLBIOSource wrap(ByteBuffer buffer) {
+        return new OpenSSLBIOSource(new OpenSSLBIOInputStream(new ByteBufferInputStream(buffer)));
+    }
+
+    public OpenSSLBIOSource(OpenSSLBIOInputStream source) {
+        this.source = source;
+    }
+
+    public long getContext() {
+        return source.getBioContext();
+    }
+
+    public 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;
+
+        public 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 {
+            int originalPosition = source.position();
+            source.position((int) (originalPosition + byteCount));
+            return source.position() - originalPosition;
+        }
+    }
+}
diff --git a/src/main/java/org/conscrypt/OpenSSLCipher.java b/src/main/java/org/conscrypt/OpenSSLCipher.java
index e2ae8ab..29e2d4d 100644
--- a/src/main/java/org/conscrypt/OpenSSLCipher.java
+++ b/src/main/java/org/conscrypt/OpenSSLCipher.java
@@ -84,6 +84,12 @@
     private Padding padding = Padding.PKCS5PADDING;
 
     /**
+     * May be used when reseting the cipher instance after calling
+     * {@code doFinal}.
+     */
+    private byte[] encodedKey;
+
+    /**
      * The Initial Vector (IV) used for the current cipher.
      */
     private byte[] iv;
@@ -252,8 +258,8 @@
         if (encodedKey == null) {
             throw new InvalidKeyException("key.getEncoded() == null");
         }
-
         checkSupportedKeySize(encodedKey.length);
+        this.encodedKey = encodedKey;
 
         final long cipherType = NativeCrypto.EVP_get_cipherbyname(getCipherName(encodedKey.length,
                 mode));
@@ -392,7 +398,7 @@
      * Reset this Cipher instance state to process a new chunk of data.
      */
     private void reset() {
-        NativeCrypto.EVP_CipherInit_ex(cipherCtx.getContext(), 0, null, null, encrypting);
+        NativeCrypto.EVP_CipherInit_ex(cipherCtx.getContext(), 0, encodedKey, iv, encrypting);
         calledUpdate = false;
     }
 
diff --git a/src/main/java/org/conscrypt/OpenSSLContextImpl.java b/src/main/java/org/conscrypt/OpenSSLContextImpl.java
index 7a2beab..6cbd19e 100644
--- a/src/main/java/org/conscrypt/OpenSSLContextImpl.java
+++ b/src/main/java/org/conscrypt/OpenSSLContextImpl.java
@@ -115,7 +115,7 @@
       }
       SSLParametersImpl p = (SSLParametersImpl) sslParameters.clone();
       p.setUseClientMode(false);
-      return new SSLEngineImpl(host, port, p);
+      return new OpenSSLEngineImpl(host, port, p);
   }
 
   @Override
@@ -125,7 +125,7 @@
       }
       SSLParametersImpl p = (SSLParametersImpl) sslParameters.clone();
       p.setUseClientMode(false);
-      return new SSLEngineImpl(p);
+      return new OpenSSLEngineImpl(p);
   }
 
   @Override
diff --git a/src/main/java/org/conscrypt/OpenSSLDHKeyFactory.java b/src/main/java/org/conscrypt/OpenSSLDHKeyFactory.java
new file mode 100644
index 0000000..40402e6
--- /dev/null
+++ b/src/main/java/org/conscrypt/OpenSSLDHKeyFactory.java
@@ -0,0 +1,194 @@
+/*
+ * 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.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.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPrivateKeySpec;
+import javax.crypto.spec.DHPublicKeySpec;
+
+public class OpenSSLDHKeyFactory extends KeyFactorySpi {
+
+    @Override
+    protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException {
+        if (keySpec == null) {
+            throw new InvalidKeySpecException("keySpec == null");
+        }
+
+        if (keySpec instanceof DHPublicKeySpec) {
+            return new OpenSSLDHPublicKey((DHPublicKeySpec) keySpec);
+        } else if (keySpec instanceof X509EncodedKeySpec) {
+            return OpenSSLKey.getPublicKey((X509EncodedKeySpec) keySpec, NativeCrypto.EVP_PKEY_DH);
+        }
+        throw new InvalidKeySpecException("Must use DHPublicKeySpec 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 DHPrivateKeySpec) {
+            return new OpenSSLDHPrivateKey((DHPrivateKeySpec) keySpec);
+        } else if (keySpec instanceof PKCS8EncodedKeySpec) {
+            return OpenSSLKey.getPrivateKey((PKCS8EncodedKeySpec) keySpec,
+                    NativeCrypto.EVP_PKEY_DH);
+        }
+        throw new InvalidKeySpecException("Must use DHPrivateKeySpec 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 (!"DH".equals(key.getAlgorithm())) {
+            throw new InvalidKeySpecException("Key must be a DH key");
+        }
+
+        if (key instanceof DHPublicKey && DHPublicKeySpec.class.isAssignableFrom(keySpec)) {
+            DHPublicKey dhKey = (DHPublicKey) key;
+            DHParameterSpec params = dhKey.getParams();
+            return (T) new DHPublicKeySpec(dhKey.getY(), params.getP(), params.getG());
+        } else if (key instanceof PublicKey && DHPublicKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"X.509".equals(key.getFormat()) || encoded == null) {
+                throw new InvalidKeySpecException("Not a valid X.509 encoding");
+            }
+            DHPublicKey dhKey = (DHPublicKey) engineGeneratePublic(new X509EncodedKeySpec(encoded));
+            DHParameterSpec params = dhKey.getParams();
+            return (T) new DHPublicKeySpec(dhKey.getY(), params.getP(), params.getG());
+        } else if (key instanceof DHPrivateKey && DHPrivateKeySpec.class.isAssignableFrom(keySpec)) {
+            DHPrivateKey dhKey = (DHPrivateKey) key;
+            DHParameterSpec params = dhKey.getParams();
+            return (T) new DHPrivateKeySpec(dhKey.getX(), params.getP(), params.getG());
+        } else if (key instanceof PrivateKey && DHPrivateKeySpec.class.isAssignableFrom(keySpec)) {
+            final byte[] encoded = key.getEncoded();
+            if (!"PKCS#8".equals(key.getFormat()) || encoded == null) {
+                throw new InvalidKeySpecException("Not a valid PKCS#8 encoding");
+            }
+            DHPrivateKey dhKey = (DHPrivateKey) engineGeneratePrivate(new PKCS8EncodedKeySpec(
+                    encoded));
+            DHParameterSpec params = dhKey.getParams();
+            return (T) new DHPrivateKeySpec(dhKey.getX(), params.getP(), params.getG());
+        } 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");
+            }
+            return (T) new PKCS8EncodedKeySpec(encoded);
+        } 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");
+            }
+            return (T) new X509EncodedKeySpec(encoded);
+        } else {
+            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 OpenSSLDHPublicKey) || (key instanceof OpenSSLDHPrivateKey)) {
+            return key;
+        } else if (key instanceof DHPublicKey) {
+            DHPublicKey dhKey = (DHPublicKey) key;
+
+            BigInteger y = dhKey.getY();
+
+            DHParameterSpec params = dhKey.getParams();
+            BigInteger p = params.getP();
+            BigInteger g = params.getG();
+
+            try {
+                return engineGeneratePublic(new DHPublicKeySpec(y, p, g));
+            } catch (InvalidKeySpecException e) {
+                throw new InvalidKeyException(e);
+            }
+        } else if (key instanceof DHPrivateKey) {
+            DHPrivateKey dhKey = (DHPrivateKey) key;
+
+            BigInteger x = dhKey.getX();
+
+            DHParameterSpec params = dhKey.getParams();
+            BigInteger p = params.getP();
+            BigInteger g = params.getG();
+
+            try {
+                return engineGeneratePrivate(new DHPrivateKeySpec(x, p, g));
+            } catch (InvalidKeySpecException e) {
+                throw new InvalidKeyException(e);
+            }
+        } 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 DH public or private key; was "
+                    + key.getClass().getName());
+        }
+    }
+}
diff --git a/src/main/java/org/conscrypt/OpenSSLDHKeyPairGenerator.java b/src/main/java/org/conscrypt/OpenSSLDHKeyPairGenerator.java
new file mode 100644
index 0000000..142ef0b
--- /dev/null
+++ b/src/main/java/org/conscrypt/OpenSSLDHKeyPairGenerator.java
@@ -0,0 +1,85 @@
+/*
+ * 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.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGeneratorSpi;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.spec.DHParameterSpec;
+
+public class OpenSSLDHKeyPairGenerator extends KeyPairGeneratorSpi {
+
+    /** The safe prime to use for the generated DH key pair. */
+    private BigInteger prime;
+
+    /** If {@code prime} is unspecified, this is the size of the generated prime. */
+    private int primeBits = 1024;
+
+    private static final BigInteger DEFAULT_GENERATOR = BigInteger.valueOf(2);
+
+    private BigInteger generator = DEFAULT_GENERATOR;
+
+    @Override
+    public KeyPair generateKeyPair() {
+        final OpenSSLKey key;
+        if (prime != null) {
+            key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DH(prime.toByteArray(),
+                    generator.toByteArray(), null, null));
+        } else {
+            key = new OpenSSLKey(NativeCrypto.DH_generate_parameters_ex(primeBits,
+                    generator.longValue()));
+        }
+
+        NativeCrypto.DH_generate_key(key.getPkeyContext());
+
+        final OpenSSLDHPrivateKey privKey = new OpenSSLDHPrivateKey(key);
+        final OpenSSLDHPublicKey pubKey = new OpenSSLDHPublicKey(key);
+
+        return new KeyPair(pubKey, privKey);
+    }
+
+    @Override
+    public void initialize(int keysize, SecureRandom random) {
+        prime = null;
+        primeBits = keysize;
+        generator = DEFAULT_GENERATOR;
+    }
+
+    @Override
+    public void initialize(AlgorithmParameterSpec params, SecureRandom random)
+            throws InvalidAlgorithmParameterException {
+        prime = null;
+        primeBits = 1024;
+        generator = DEFAULT_GENERATOR;
+
+        if (params instanceof DHParameterSpec) {
+            DHParameterSpec dhParams = (DHParameterSpec) params;
+
+            prime = dhParams.getP();
+            BigInteger gen = dhParams.getG();
+            if (gen != null) {
+                generator = gen;
+            }
+        } else if (params != null) {
+            throw new InvalidAlgorithmParameterException("Params must be DHParameterSpec");
+        }
+    }
+}
diff --git a/src/main/java/org/conscrypt/OpenSSLDHPrivateKey.java b/src/main/java/org/conscrypt/OpenSSLDHPrivateKey.java
new file mode 100644
index 0000000..b138f77
--- /dev/null
+++ b/src/main/java/org/conscrypt/OpenSSLDHPrivateKey.java
@@ -0,0 +1,244 @@
+/*
+ * 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.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.spec.InvalidKeySpecException;
+import javax.crypto.interfaces.DHPrivateKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPrivateKeySpec;
+
+public class OpenSSLDHPrivateKey implements DHPrivateKey, OpenSSLKeyHolder {
+    private static final long serialVersionUID = -7321023036951606638L;
+
+    private transient OpenSSLKey key;
+
+    /** base prime */
+    private transient byte[] p;
+
+    /** generator */
+    private transient byte[] g;
+
+    /** private key */
+    private transient byte[] x;
+
+    private transient Object mParamsLock = new Object();
+
+    private transient boolean readParams;
+
+    OpenSSLDHPrivateKey(OpenSSLKey key) {
+        this.key = key;
+    }
+
+    @Override
+    public OpenSSLKey getOpenSSLKey() {
+        return key;
+    }
+
+    OpenSSLDHPrivateKey(DHPrivateKeySpec dhKeySpec) throws InvalidKeySpecException {
+        try {
+            key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DH(
+                    dhKeySpec.getP().toByteArray(),
+                    dhKeySpec.getG().toByteArray(),
+                    null,
+                    dhKeySpec.getX().toByteArray()));
+        } catch (Exception e) {
+            throw new InvalidKeySpecException(e);
+        }
+    }
+
+    private void ensureReadParams() {
+        synchronized (mParamsLock) {
+            if (readParams) {
+                return;
+            }
+
+            byte[][] params = NativeCrypto.get_DH_params(key.getPkeyContext());
+
+            p = params[0];
+            g = params[1];
+            x = params[3];
+
+            readParams = true;
+        }
+    }
+
+    static OpenSSLKey getInstance(DHPrivateKey dhPrivateKey) throws InvalidKeyException {
+        try {
+            DHParameterSpec dhParams = dhPrivateKey.getParams();
+            return new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DH(
+                    dhParams.getP().toByteArray(),
+                    dhParams.getG().toByteArray(),
+                    null,
+                    dhPrivateKey.getX().toByteArray()));
+        } catch (Exception e) {
+            throw new InvalidKeyException(e);
+        }
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return "DH";
+    }
+
+    @Override
+    public String getFormat() {
+        /*
+         * If we're using an OpenSSL ENGINE, there's no guarantee we can export
+         * the key. Returning {@code null} tells the caller that there's no
+         * encoded format.
+         */
+        if (key.isEngineBased()) {
+            return null;
+        }
+
+        return "PKCS#8";
+    }
+
+    @Override
+    public byte[] getEncoded() {
+        /*
+         * If we're using an OpenSSL ENGINE, there's no guarantee we can export
+         * the key. Returning {@code null} tells the caller that there's no
+         * encoded format.
+         */
+        if (key.isEngineBased()) {
+            return null;
+        }
+
+        return NativeCrypto.i2d_PKCS8_PRIV_KEY_INFO(key.getPkeyContext());
+    }
+
+    @Override
+    public DHParameterSpec getParams() {
+        ensureReadParams();
+        return new DHParameterSpec(new BigInteger(p), new BigInteger(g));
+    }
+
+    @Override
+    public BigInteger getX() {
+        if (key.isEngineBased()) {
+            throw new UnsupportedOperationException("private key value X cannot be extracted");
+        }
+
+        ensureReadParams();
+        return new BigInteger(x);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+
+        if (o instanceof OpenSSLDHPrivateKey) {
+            OpenSSLDHPrivateKey other = (OpenSSLDHPrivateKey) o;
+
+            /*
+             * We can shortcut the true case, but it still may be equivalent but
+             * different copies.
+             */
+            if (key.equals(other.getOpenSSLKey())) {
+                return true;
+            }
+        }
+
+        if (!(o instanceof DHPrivateKey)) {
+            return false;
+        }
+
+        ensureReadParams();
+
+        final DHPrivateKey other = (DHPrivateKey) o;
+        if (!x.equals(other.getX())) {
+            return false;
+        }
+
+        DHParameterSpec spec = other.getParams();
+        return g.equals(spec.getG()) && p.equals(spec.getP());
+    }
+
+    @Override
+    public int hashCode() {
+        ensureReadParams();
+        int hash = 1;
+        if (!key.isEngineBased()) {
+            hash = hash * 3 + x.hashCode();
+        }
+        hash = hash * 7 + p.hashCode();
+        hash = hash * 13 + g.hashCode();
+        return hash;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("OpenSSLDHPrivateKey{");
+
+        if (key.isEngineBased()) {
+            sb.append("key=");
+            sb.append(key);
+            sb.append('}');
+            return sb.toString();
+        }
+
+        ensureReadParams();
+        sb.append("X=");
+        sb.append(new BigInteger(x).toString(16));
+        sb.append(',');
+        sb.append("P=");
+        sb.append(new BigInteger(p).toString(16));
+        sb.append(',');
+        sb.append("G=");
+        sb.append(new BigInteger(g).toString(16));
+        sb.append('}');
+
+        return sb.toString();
+    }
+
+    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+        stream.defaultReadObject();
+
+        final BigInteger g = (BigInteger) stream.readObject();
+        final BigInteger p = (BigInteger) stream.readObject();
+        final BigInteger x = (BigInteger) stream.readObject();
+
+        key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DH(
+                p.toByteArray(),
+                g.toByteArray(),
+                null,
+                x.toByteArray()));
+        mParamsLock = new Object();
+    }
+
+    private void writeObject(ObjectOutputStream stream) throws IOException {
+        if (getOpenSSLKey().isEngineBased()) {
+            throw new NotSerializableException("engine-based keys can not be serialized");
+        }
+
+        stream.defaultWriteObject();
+
+        ensureReadParams();
+        stream.writeObject(new BigInteger(g));
+        stream.writeObject(new BigInteger(p));
+        stream.writeObject(new BigInteger(x));
+    }
+}
diff --git a/src/main/java/org/conscrypt/OpenSSLDHPublicKey.java b/src/main/java/org/conscrypt/OpenSSLDHPublicKey.java
new file mode 100644
index 0000000..7d0a6bc
--- /dev/null
+++ b/src/main/java/org/conscrypt/OpenSSLDHPublicKey.java
@@ -0,0 +1,208 @@
+/*
+ * 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.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.spec.InvalidKeySpecException;
+import javax.crypto.interfaces.DHPublicKey;
+import javax.crypto.spec.DHParameterSpec;
+import javax.crypto.spec.DHPublicKeySpec;
+
+public class OpenSSLDHPublicKey implements DHPublicKey, OpenSSLKeyHolder {
+    private static final long serialVersionUID = 6123717708079837723L;
+
+    private transient OpenSSLKey key;
+
+    /** base prime */
+    private transient byte[] p;
+
+    /** generator */
+    private transient byte[] g;
+
+    /** public key */
+    private transient byte[] y;
+
+    private transient final Object mParamsLock = new Object();
+
+    private transient boolean readParams;
+
+    OpenSSLDHPublicKey(OpenSSLKey key) {
+        this.key = key;
+    }
+
+    @Override
+    public OpenSSLKey getOpenSSLKey() {
+        return key;
+    }
+
+    OpenSSLDHPublicKey(DHPublicKeySpec dsaKeySpec) throws InvalidKeySpecException {
+        try {
+            key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DH(
+                    dsaKeySpec.getP().toByteArray(),
+                    dsaKeySpec.getG().toByteArray(),
+                    dsaKeySpec.getY().toByteArray(),
+                    null));
+        } catch (Exception e) {
+            throw new InvalidKeySpecException(e);
+        }
+    }
+
+    private void ensureReadParams() {
+        synchronized (mParamsLock) {
+            if (readParams) {
+                return;
+            }
+
+            byte[][] params = NativeCrypto.get_DH_params(key.getPkeyContext());
+
+            p = params[0];
+            g = params[1];
+            y = params[2];
+
+            readParams = true;
+        }
+    }
+
+    static OpenSSLKey getInstance(DHPublicKey DHPublicKey) throws InvalidKeyException {
+        try {
+            final DHParameterSpec dhParams = DHPublicKey.getParams();
+            return new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DH(
+                    dhParams.getP().toByteArray(),
+                    dhParams.getG().toByteArray(),
+                    DHPublicKey.getY().toByteArray(),
+                    null));
+        } catch (Exception e) {
+            throw new InvalidKeyException(e);
+        }
+    }
+
+    @Override
+    public DHParameterSpec getParams() {
+        ensureReadParams();
+        return new DHParameterSpec(new BigInteger(p), new BigInteger(g));
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return "DH";
+    }
+
+    @Override
+    public String getFormat() {
+        return "X.509";
+    }
+
+    @Override
+    public byte[] getEncoded() {
+        return NativeCrypto.i2d_PUBKEY(key.getPkeyContext());
+    }
+
+    @Override
+    public BigInteger getY() {
+        ensureReadParams();
+        return new BigInteger(y);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+
+        if (o instanceof OpenSSLDHPublicKey) {
+            OpenSSLDHPublicKey other = (OpenSSLDHPublicKey) o;
+
+            /*
+             * We can shortcut the true case, but it still may be equivalent but
+             * different copies.
+             */
+            if (key.equals(other.getOpenSSLKey())) {
+                return true;
+            }
+        }
+
+        if (!(o instanceof DHPublicKey)) {
+            return false;
+        }
+
+        ensureReadParams();
+
+        final DHPublicKey other = (DHPublicKey) o;
+        if (!y.equals(other.getY())) {
+            return false;
+        }
+
+        DHParameterSpec spec = other.getParams();
+        return g.equals(spec.getG()) && p.equals(spec.getP());
+    }
+
+    @Override
+    public int hashCode() {
+        ensureReadParams();
+        int hash = 1;
+        hash = hash * 3 + y.hashCode();
+        hash = hash * 7 + p.hashCode();
+        hash = hash * 13 + g.hashCode();
+        return hash;
+    }
+
+
+    @Override
+    public String toString() {
+        ensureReadParams();
+
+        final StringBuilder sb = new StringBuilder("OpenSSLDHPublicKey{");
+        sb.append("Y=");
+        sb.append(new BigInteger(y).toString(16));
+        sb.append(',');
+        sb.append("P=");
+        sb.append(new BigInteger(p).toString(16));
+        sb.append(',');
+        sb.append("G=");
+        sb.append(new BigInteger(g).toString(16));
+        sb.append('}');
+
+        return sb.toString();
+    }
+
+    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
+        stream.defaultReadObject();
+
+        final BigInteger g = (BigInteger) stream.readObject();
+        final BigInteger p = (BigInteger) stream.readObject();
+        final BigInteger y = (BigInteger) stream.readObject();
+
+        key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DH(
+                p.toByteArray(),
+                g.toByteArray(),
+                y.toByteArray(),
+                null));
+    }
+
+    private void writeObject(ObjectOutputStream stream) throws IOException {
+        stream.defaultWriteObject();
+
+        ensureReadParams();
+        stream.writeObject(new BigInteger(g));
+        stream.writeObject(new BigInteger(p));
+        stream.writeObject(new BigInteger(y));
+    }
+}
diff --git a/src/main/java/org/conscrypt/OpenSSLDSAPrivateKey.java b/src/main/java/org/conscrypt/OpenSSLDSAPrivateKey.java
index 0fc4db7..dc3df76 100644
--- a/src/main/java/org/conscrypt/OpenSSLDSAPrivateKey.java
+++ b/src/main/java/org/conscrypt/OpenSSLDSAPrivateKey.java
@@ -63,6 +63,14 @@
     }
 
     static OpenSSLKey getInstance(DSAPrivateKey dsaPrivateKey) throws InvalidKeyException {
+        /**
+         * If the key is not encodable (PKCS11-like key), then wrap it and use
+         * JNI upcalls to satisfy requests.
+         */
+        if (dsaPrivateKey.getFormat() == null) {
+            return wrapPlatformKey(dsaPrivateKey);
+        }
+
         try {
             DSAParams dsaParams = dsaPrivateKey.getParams();
             return new OpenSSLKey(NativeCrypto.EVP_PKEY_new_DSA(
@@ -76,6 +84,10 @@
         }
     }
 
+    public static OpenSSLKey wrapPlatformKey(DSAPrivateKey dsaPrivateKey) {
+        return new OpenSSLKey(NativeCrypto.getDSAPrivateKeyWrapper(dsaPrivateKey));
+    }
+
     @Override
     public DSAParams getParams() {
         ensureReadParams();
diff --git a/src/main/java/org/conscrypt/OpenSSLDigestContext.java b/src/main/java/org/conscrypt/OpenSSLDigestContext.java
index b11a862..914f9aa 100644
--- a/src/main/java/org/conscrypt/OpenSSLDigestContext.java
+++ b/src/main/java/org/conscrypt/OpenSSLDigestContext.java
@@ -16,15 +16,9 @@
 
 package org.conscrypt;
 
-public class OpenSSLDigestContext {
-    private final long context;
-
+public class OpenSSLDigestContext extends OpenSSLNativeReference {
     public OpenSSLDigestContext(long ctx) {
-        if (ctx == 0) {
-            throw new NullPointerException("ctx == 0");
-        }
-
-        this.context = ctx;
+        super(ctx);
     }
 
     @Override
@@ -35,8 +29,4 @@
             super.finalize();
         }
     }
-
-    long getContext() {
-        return context;
-    }
 }
diff --git a/src/main/java/org/conscrypt/OpenSSLECDHKeyAgreement.java b/src/main/java/org/conscrypt/OpenSSLECDHKeyAgreement.java
index 0d8729c..4361fd2 100644
--- a/src/main/java/org/conscrypt/OpenSSLECDHKeyAgreement.java
+++ b/src/main/java/org/conscrypt/OpenSSLECDHKeyAgreement.java
@@ -61,7 +61,7 @@
         if (!(key instanceof PublicKey)) {
             throw new InvalidKeyException("Not a public key: " + key.getClass());
         }
-        OpenSSLKey openSslPublicKey = translateKeyToEcOpenSSLKey(key);
+        OpenSSLKey openSslPublicKey = OpenSSLKey.fromPublicKey((PublicKey) key);
 
         byte[] buffer = new byte[mExpectedResultLength];
         int actualResultLength = NativeCrypto.ECDH_compute_key(
@@ -124,7 +124,7 @@
             throw new InvalidKeyException("Not a private key: " + key.getClass());
         }
 
-        OpenSSLKey openSslKey = translateKeyToEcOpenSSLKey(key);
+        OpenSSLKey openSslKey = OpenSSLKey.fromPrivateKey((PrivateKey) key);
         int fieldSizeBits = NativeCrypto.EC_GROUP_get_degree(NativeCrypto.EC_KEY_get0_group(
                 openSslKey.getPkeyContext()));
         mExpectedResultLength = (fieldSizeBits + 7) / 8;
@@ -146,13 +146,4 @@
             throw new IllegalStateException("Key agreement not completed");
         }
     }
-
-    private static OpenSSLKey translateKeyToEcOpenSSLKey(Key key) throws InvalidKeyException {
-        try {
-            return ((OpenSSLKeyHolder) KeyFactory.getInstance(
-                    "EC", OpenSSLProvider.PROVIDER_NAME).translateKey(key)).getOpenSSLKey();
-        } catch (Exception e) {
-            throw new InvalidKeyException("Failed to translate key to OpenSSL EC key", e);
-        }
-    }
 }
diff --git a/src/main/java/org/conscrypt/OpenSSLECGroupContext.java b/src/main/java/org/conscrypt/OpenSSLECGroupContext.java
index f7f52a6..ce7aad8 100644
--- a/src/main/java/org/conscrypt/OpenSSLECGroupContext.java
+++ b/src/main/java/org/conscrypt/OpenSSLECGroupContext.java
@@ -112,7 +112,7 @@
 
     public static OpenSSLECGroupContext getInstance(ECParameterSpec params)
             throws InvalidAlgorithmParameterException {
-        final String curveName = params.getCurveName();
+        final String curveName = Platform.getCurveName(params);
         if (curveName != null) {
             return OpenSSLECGroupContext.getCurveByName(curveName);
         }
@@ -166,6 +166,8 @@
         final BigInteger order = new BigInteger(NativeCrypto.EC_GROUP_get_order(groupCtx));
         final BigInteger cofactor = new BigInteger(NativeCrypto.EC_GROUP_get_cofactor(groupCtx));
 
-        return new ECParameterSpec(curve, generator, order, cofactor.intValue(), curveName);
+        ECParameterSpec spec = new ECParameterSpec(curve, generator, order, cofactor.intValue());
+        Platform.setCurveName(spec, curveName);
+        return spec;
     }
 }
diff --git a/src/main/java/org/conscrypt/OpenSSLECPrivateKey.java b/src/main/java/org/conscrypt/OpenSSLECPrivateKey.java
index 4010ec5..e439985 100644
--- a/src/main/java/org/conscrypt/OpenSSLECPrivateKey.java
+++ b/src/main/java/org/conscrypt/OpenSSLECPrivateKey.java
@@ -21,6 +21,7 @@
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.interfaces.ECPrivateKey;
 import java.security.spec.ECParameterSpec;
@@ -59,10 +60,34 @@
         }
     }
 
+    public static OpenSSLKey wrapPlatformKey(ECPrivateKey ecPrivateKey) throws InvalidKeyException {
+        OpenSSLECGroupContext group;
+        try {
+            group = OpenSSLECGroupContext.getInstance(ecPrivateKey.getParams());
+        } catch (InvalidAlgorithmParameterException e) {
+            throw new InvalidKeyException("Unknown group parameters", e);
+        }
+        return wrapPlatformKey(ecPrivateKey, group);
+    }
+
+    private static OpenSSLKey wrapPlatformKey(ECPrivateKey ecPrivateKey, OpenSSLECGroupContext group)
+            throws InvalidKeyException {
+        return new OpenSSLKey(NativeCrypto.getECPrivateKeyWrapper(ecPrivateKey, group.getContext()));
+    }
+
     public static OpenSSLKey getInstance(ECPrivateKey ecPrivateKey) throws InvalidKeyException {
         try {
             OpenSSLECGroupContext group = OpenSSLECGroupContext.getInstance(ecPrivateKey
                     .getParams());
+
+            /**
+             * If the key is not encodable (PKCS11-like key), then wrap it and
+             * use JNI upcalls to satisfy requests.
+             */
+            if (ecPrivateKey.getFormat() == null) {
+                return wrapPlatformKey(ecPrivateKey, group);
+            }
+
             final BigInteger privKey = ecPrivateKey.getS();
             return new OpenSSLKey(NativeCrypto.EVP_PKEY_new_EC_KEY(group.getContext(), 0,
                     privKey.toByteArray()));
diff --git a/src/main/java/org/conscrypt/OpenSSLEngineImpl.java b/src/main/java/org/conscrypt/OpenSSLEngineImpl.java
new file mode 100644
index 0000000..34d231f
--- /dev/null
+++ b/src/main/java/org/conscrypt/OpenSSLEngineImpl.java
@@ -0,0 +1,754 @@
+/*
+ * Copyright 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.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ReadOnlyBufferException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+
+import javax.crypto.SecretKey;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.X509ExtendedKeyManager;
+import javax.net.ssl.X509KeyManager;
+import javax.net.ssl.X509TrustManager;
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * Implements the {@link SSLEngine} API using OpenSSL's non-blocking interfaces.
+ */
+public class OpenSSLEngineImpl extends SSLEngine implements NativeCrypto.SSLHandshakeCallbacks,
+        SSLParametersImpl.AliasChooser, SSLParametersImpl.PSKCallbacks {
+    private final SSLParametersImpl sslParameters;
+
+    /**
+     * Protects handshakeStarted and handshakeCompleted.
+     */
+    private final Object stateLock = new Object();
+
+    private static enum EngineState {
+        /**
+         * The {@link OpenSSLSocketImpl} object is constructed, but {@link #beginHandshake()}
+         * has not yet been called.
+         */
+        NEW,
+        /**
+         * {@link #setUseClientMode(boolean)} has been called at least once.
+         */
+        MODE_SET,
+        /**
+         * {@link #beginHandshake()} has been called at least once.
+         */
+        HANDSHAKE_WANTED,
+        /**
+         * Handshake task has been started.
+         */
+        HANDSHAKE_STARTED,
+        /**
+         * Handshake has been completed, but {@link #beginHandshake()} hasn't returned yet.
+         */
+        HANDSHAKE_COMPLETED,
+        /**
+         * {@link #beginHandshake()} has completed but the task hasn't
+         * been called. This is expected behaviour in cut-through mode, where SSL_do_handshake
+         * returns before the handshake is complete. We can now start writing data to the socket.
+         */
+        READY_HANDSHAKE_CUT_THROUGH,
+        /**
+         * {@link #beginHandshake()} has completed and socket is ready to go.
+         */
+        READY,
+        CLOSED_INBOUND,
+        CLOSED_OUTBOUND,
+        /**
+         * Inbound and outbound has been called.
+         */
+        CLOSED,
+    }
+
+    // @GuardedBy("stateLock");
+    private EngineState engineState = EngineState.NEW;
+
+    /**
+     * Protected by synchronizing on stateLock. Starts as 0, set by
+     * startHandshake, reset to 0 on close.
+     */
+    // @GuardedBy("stateLock");
+    private long sslNativePointer;
+
+    /** Used during handshake when {@link #wrap(ByteBuffer, ByteBuffer)} is called. */
+    // TODO: make this use something similar to BIO_s_null() in native code
+    private static OpenSSLBIOSource nullSource = OpenSSLBIOSource.wrap(ByteBuffer.allocate(0));
+
+    /** A BIO sink written to only during handshakes. */
+    private OpenSSLBIOSink handshakeSink;
+
+    /** A BIO sink written to during regular operation. */
+    private final OpenSSLBIOSink localToRemoteSink = OpenSSLBIOSink.create();
+
+    /** Set during startHandshake. */
+    private OpenSSLSessionImpl sslSession;
+
+    /** Used during handshake callbacks. */
+    private OpenSSLSessionImpl handshakeSession;
+
+    /**
+     * Private key for the TLS Channel ID extension. This field is client-side
+     * only. Set during startHandshake.
+     */
+    OpenSSLKey channelIdPrivateKey;
+
+    public OpenSSLEngineImpl(SSLParametersImpl sslParameters) {
+        this.sslParameters = sslParameters;
+    }
+
+    public OpenSSLEngineImpl(String host, int port, SSLParametersImpl sslParameters) {
+        super(host, port);
+        this.sslParameters = sslParameters;
+    }
+
+    @Override
+    public void beginHandshake() throws SSLException {
+        synchronized (stateLock) {
+            if (engineState == EngineState.CLOSED || engineState == EngineState.CLOSED_OUTBOUND
+                    || engineState == EngineState.CLOSED_INBOUND) {
+                throw new IllegalStateException("Engine has already been closed");
+            }
+            if (engineState == EngineState.HANDSHAKE_STARTED) {
+                throw new IllegalStateException("Handshake has already been started");
+            }
+            if (engineState != EngineState.MODE_SET) {
+                throw new IllegalStateException("Client/server mode must be set before handshake");
+            }
+            if (getUseClientMode()) {
+                engineState = EngineState.HANDSHAKE_WANTED;
+            } else {
+                engineState = EngineState.HANDSHAKE_STARTED;
+            }
+        }
+
+        boolean releaseResources = true;
+        try {
+            final AbstractSessionContext sessionContext = sslParameters.getSessionContext();
+            final long sslCtxNativePointer = sessionContext.sslCtxNativePointer;
+            sslNativePointer = NativeCrypto.SSL_new(sslCtxNativePointer);
+            sslParameters.setSSLParameters(sslCtxNativePointer, sslNativePointer, this, this,
+                    getPeerHost());
+            sslParameters.setCertificateValidation(sslNativePointer);
+            sslParameters.setTlsChannelId(sslNativePointer, channelIdPrivateKey);
+            if (getUseClientMode()) {
+                NativeCrypto.SSL_set_connect_state(sslNativePointer);
+            } else {
+                NativeCrypto.SSL_set_accept_state(sslNativePointer);
+            }
+            handshakeSink = OpenSSLBIOSink.create();
+            releaseResources = false;
+        } catch (IOException e) {
+            // Write CCS errors to EventLog
+            String message = e.getMessage();
+            // Must match error reason string of SSL_R_UNEXPECTED_CCS (in ssl/ssl_err.c)
+            if (message.contains("unexpected CCS")) {
+                String logMessage = String.format("ssl_unexpected_ccs: host=%s", getPeerHost());
+                Platform.logEvent(logMessage);
+            }
+            throw new SSLException(e);
+        } finally {
+            if (releaseResources) {
+                synchronized (stateLock) {
+                    engineState = EngineState.CLOSED;
+                }
+                shutdownAndFreeSslNative();
+            }
+        }
+    }
+
+    @Override
+    public void closeInbound() throws SSLException {
+        synchronized (stateLock) {
+            if (engineState == EngineState.CLOSED) {
+                return;
+            }
+            if (engineState == EngineState.CLOSED_OUTBOUND) {
+                engineState = EngineState.CLOSED;
+            } else {
+                engineState = EngineState.CLOSED_INBOUND;
+            }
+        }
+        // TODO anything else to notify OpenSSL layer?
+    }
+
+    @Override
+    public void closeOutbound() {
+        synchronized (stateLock) {
+            if (engineState == EngineState.CLOSED || engineState == EngineState.CLOSED_OUTBOUND) {
+                return;
+            }
+            if (engineState != EngineState.MODE_SET && engineState != EngineState.NEW) {
+                shutdownAndFreeSslNative();
+            }
+            if (engineState == EngineState.CLOSED_INBOUND) {
+                engineState = EngineState.CLOSED;
+            } else {
+                engineState = EngineState.CLOSED_OUTBOUND;
+            }
+        }
+        shutdown();
+    }
+
+    @Override
+    public Runnable getDelegatedTask() {
+        /* This implementation doesn't use any delegated tasks. */
+        return null;
+    }
+
+    @Override
+    public String[] getEnabledCipherSuites() {
+        return sslParameters.getEnabledCipherSuites();
+    }
+
+    @Override
+    public String[] getEnabledProtocols() {
+        return sslParameters.getEnabledProtocols();
+    }
+
+    @Override
+    public boolean getEnableSessionCreation() {
+        return sslParameters.getEnableSessionCreation();
+    }
+
+    @Override
+    public HandshakeStatus getHandshakeStatus() {
+        synchronized (stateLock) {
+            switch (engineState) {
+                case HANDSHAKE_WANTED:
+                    if (getUseClientMode()) {
+                        return HandshakeStatus.NEED_WRAP;
+                    } else {
+                        return HandshakeStatus.NEED_UNWRAP;
+                    }
+                case HANDSHAKE_STARTED:
+                    if (handshakeSink.available() > 0) {
+                        return HandshakeStatus.NEED_WRAP;
+                    } else {
+                        return HandshakeStatus.NEED_UNWRAP;
+                    }
+                case HANDSHAKE_COMPLETED:
+                    if (handshakeSink.available() == 0) {
+                        handshakeSink = null;
+                        engineState = EngineState.READY;
+                        return HandshakeStatus.FINISHED;
+                    } else {
+                        return HandshakeStatus.NEED_WRAP;
+                    }
+                case NEW:
+                case MODE_SET:
+                case CLOSED:
+                case CLOSED_INBOUND:
+                case CLOSED_OUTBOUND:
+                case READY:
+                case READY_HANDSHAKE_CUT_THROUGH:
+                    return HandshakeStatus.NOT_HANDSHAKING;
+                default:
+                    break;
+            }
+            throw new IllegalStateException("Unexpected engine state: " + engineState);
+        }
+    }
+
+    @Override
+    public boolean getNeedClientAuth() {
+        return sslParameters.getNeedClientAuth();
+    }
+
+    @Override
+    public SSLSession getSession() {
+        if (sslSession == null) {
+            return SSLNullSession.getNullSession();
+        }
+        return sslSession;
+    }
+
+    @Override
+    public String[] getSupportedCipherSuites() {
+        return NativeCrypto.getSupportedCipherSuites();
+    }
+
+    @Override
+    public String[] getSupportedProtocols() {
+        return NativeCrypto.getSupportedProtocols();
+    }
+
+    @Override
+    public boolean getUseClientMode() {
+        return sslParameters.getUseClientMode();
+    }
+
+    @Override
+    public boolean getWantClientAuth() {
+        return sslParameters.getWantClientAuth();
+    }
+
+    @Override
+    public boolean isInboundDone() {
+        if (sslNativePointer == 0) {
+            synchronized (stateLock) {
+                return engineState == EngineState.CLOSED
+                        || engineState == EngineState.CLOSED_INBOUND;
+            }
+        }
+        return (NativeCrypto.SSL_get_shutdown(sslNativePointer)
+                & NativeCrypto.SSL_RECEIVED_SHUTDOWN) != 0;
+    }
+
+    @Override
+    public boolean isOutboundDone() {
+        if (sslNativePointer == 0) {
+            synchronized (stateLock) {
+                return engineState == EngineState.CLOSED
+                        || engineState == EngineState.CLOSED_OUTBOUND;
+            }
+        }
+        return (NativeCrypto.SSL_get_shutdown(sslNativePointer)
+                & NativeCrypto.SSL_SENT_SHUTDOWN) != 0;
+    }
+
+    @Override
+    public void setEnabledCipherSuites(String[] suites) {
+        sslParameters.setEnabledCipherSuites(suites);
+    }
+
+    @Override
+    public void setEnabledProtocols(String[] protocols) {
+        sslParameters.setEnabledProtocols(protocols);
+    }
+
+    @Override
+    public void setEnableSessionCreation(boolean flag) {
+        sslParameters.setEnableSessionCreation(flag);
+    }
+
+    @Override
+    public void setNeedClientAuth(boolean need) {
+        sslParameters.setNeedClientAuth(need);
+    }
+
+    @Override
+    public void setUseClientMode(boolean mode) {
+        synchronized (stateLock) {
+            if (engineState != EngineState.MODE_SET && engineState != EngineState.NEW) {
+                throw new IllegalArgumentException(
+                        "Can not change mode after handshake: engineState == " + engineState);
+            }
+            engineState = EngineState.MODE_SET;
+        }
+        sslParameters.setUseClientMode(mode);
+    }
+
+    @Override
+    public void setWantClientAuth(boolean want) {
+        sslParameters.setWantClientAuth(want);
+    }
+
+    private static void checkIndex(int length, int offset, int count) {
+        if (offset < 0) {
+            throw new IndexOutOfBoundsException("offset < 0");
+        } else if (count < 0) {
+            throw new IndexOutOfBoundsException("count < 0");
+        } else if (offset > length) {
+            throw new IndexOutOfBoundsException("offset > length");
+        } else if (offset > length - count) {
+            throw new IndexOutOfBoundsException("offset + count > length");
+        }
+    }
+
+    @Override
+    public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts, int offset, int length)
+            throws SSLException {
+        if (src == null) {
+            throw new IllegalArgumentException("src == null");
+        } else if (dsts == null) {
+            throw new IllegalArgumentException("dsts == null");
+        }
+        checkIndex(dsts.length, offset, length);
+        int dstRemaining = 0;
+        for (int i = 0; i < dsts.length; i++) {
+            ByteBuffer dst = dsts[i];
+            if (dst == null) {
+                throw new IllegalArgumentException("one of the dst == null");
+            } else if (dst.isReadOnly()) {
+                throw new ReadOnlyBufferException();
+            }
+            if (i >= offset && i < offset + length) {
+                dstRemaining += dst.remaining();
+            }
+        }
+
+        synchronized (stateLock) {
+            // If the inbound direction is closed. we can't send anymore.
+            if (engineState == EngineState.CLOSED || engineState == EngineState.CLOSED_INBOUND) {
+                return new SSLEngineResult(Status.CLOSED, getHandshakeStatus(), 0, 0);
+            }
+            if (engineState == EngineState.NEW || engineState == EngineState.MODE_SET) {
+                beginHandshake();
+            }
+        }
+
+        // If we haven't completed the handshake yet, just let the caller know.
+        HandshakeStatus handshakeStatus = getHandshakeStatus();
+        if (handshakeStatus == HandshakeStatus.NEED_UNWRAP) {
+            OpenSSLBIOSource source = OpenSSLBIOSource.wrap(src);
+            long sslSessionCtx = 0L;
+            try {
+                sslSessionCtx = NativeCrypto.SSL_do_handshake_bio(sslNativePointer,
+                        source.getContext(), handshakeSink.getContext(), this, getUseClientMode(),
+                        sslParameters.npnProtocols, sslParameters.alpnProtocols);
+                if (sslSessionCtx != 0) {
+                    if (sslSession != null && engineState == EngineState.HANDSHAKE_STARTED) {
+                        engineState = EngineState.READY_HANDSHAKE_CUT_THROUGH;
+                    }
+                    sslSession = sslParameters.setupSession(sslSessionCtx, sslNativePointer, sslSession,
+                            getPeerHost(), getPeerPort(), true);
+                }
+                int bytesWritten = handshakeSink.position();
+                return new SSLEngineResult(Status.OK, getHandshakeStatus(), 0, bytesWritten);
+            } catch (Exception e) {
+                throw (SSLHandshakeException) new SSLHandshakeException("Handshake failed")
+                        .initCause(e);
+            } finally {
+                if (sslSession == null && sslSessionCtx != 0) {
+                    NativeCrypto.SSL_SESSION_free(sslSessionCtx);
+                }
+                source.release();
+            }
+        } else if (handshakeStatus != HandshakeStatus.NOT_HANDSHAKING) {
+            return new SSLEngineResult(Status.OK, handshakeStatus, 0, 0);
+        }
+
+        if (dstRemaining == 0) {
+            return new SSLEngineResult(Status.BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0);
+        }
+
+        ByteBuffer srcDuplicate = src.duplicate();
+        OpenSSLBIOSource source = OpenSSLBIOSource.wrap(srcDuplicate);
+        try {
+            int positionBeforeRead = srcDuplicate.position();
+            int produced = 0;
+            boolean shouldStop = false;
+
+            while (!shouldStop) {
+                ByteBuffer dst = getNextAvailableByteBuffer(dsts, offset, length);
+                if (dst == null) {
+                    shouldStop = true;
+                    continue;
+                }
+                ByteBuffer arrayDst = dst;
+                if (dst.isDirect()) {
+                    arrayDst = ByteBuffer.allocate(dst.remaining());
+                }
+
+                int dstOffset = arrayDst.arrayOffset() + arrayDst.position();
+
+                int internalProduced = NativeCrypto.SSL_read_BIO(sslNativePointer,
+                        arrayDst.array(), dstOffset, dst.remaining(), source.getContext(),
+                        localToRemoteSink.getContext(), this);
+                if (internalProduced <= 0) {
+                    shouldStop = true;
+                    continue;
+                }
+                arrayDst.position(arrayDst.position() + internalProduced);
+                produced += internalProduced;
+                if (dst != arrayDst) {
+                    arrayDst.flip();
+                    dst.put(arrayDst);
+                }
+            }
+
+            int consumed = srcDuplicate.position() - positionBeforeRead;
+            src.position(srcDuplicate.position());
+            return new SSLEngineResult(Status.OK, getHandshakeStatus(), consumed, produced);
+        } catch (IOException e) {
+            throw new SSLException(e);
+        } finally {
+            source.release();
+        }
+    }
+
+    /** Returns the next non-empty ByteBuffer. */
+    private ByteBuffer getNextAvailableByteBuffer(ByteBuffer[] buffers, int offset, int length) {
+        for (int i = offset; i < length; ++i) {
+            if (buffers[i].remaining() > 0) {
+                return buffers[i];
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public SSLEngineResult wrap(ByteBuffer[] srcs, int offset, int length, ByteBuffer dst)
+            throws SSLException {
+        if (srcs == null) {
+            throw new IllegalArgumentException("srcs == null");
+        } else if (dst == null) {
+            throw new IllegalArgumentException("dst == null");
+        } else if (dst.isReadOnly()) {
+            throw new ReadOnlyBufferException();
+        }
+        for (ByteBuffer src : srcs) {
+            if (src == null) {
+                throw new IllegalArgumentException("one of the src == null");
+            }
+        }
+        checkIndex(srcs.length, offset, length);
+
+        if (dst.remaining() < NativeCrypto.SSL3_RT_MAX_PACKET_SIZE) {
+            return new SSLEngineResult(Status.BUFFER_OVERFLOW, getHandshakeStatus(), 0, 0);
+        }
+
+        synchronized (stateLock) {
+            // If the outbound direction is closed. we can't send anymore.
+            if (engineState == EngineState.CLOSED || engineState == EngineState.CLOSED_OUTBOUND) {
+                return new SSLEngineResult(Status.CLOSED, getHandshakeStatus(), 0, 0);
+            }
+            if (engineState == EngineState.NEW || engineState == EngineState.MODE_SET) {
+                beginHandshake();
+            }
+        }
+
+        // If we haven't completed the handshake yet, just let the caller know.
+        HandshakeStatus handshakeStatus = getHandshakeStatus();
+        if (handshakeStatus == HandshakeStatus.NEED_WRAP) {
+            if (handshakeSink.available() == 0) {
+                long sslSessionCtx = 0L;
+                try {
+                    sslSessionCtx = NativeCrypto.SSL_do_handshake_bio(sslNativePointer,
+                            nullSource.getContext(), handshakeSink.getContext(), this,
+                            getUseClientMode(), sslParameters.npnProtocols,
+                            sslParameters.alpnProtocols);
+                    if (sslSessionCtx != 0) {
+                        if (sslSession != null && engineState == EngineState.HANDSHAKE_STARTED) {
+                            engineState = EngineState.READY_HANDSHAKE_CUT_THROUGH;
+                        }
+                        sslSession = sslParameters.setupSession(sslSessionCtx, sslNativePointer, sslSession,
+                                getPeerHost(), getPeerPort(), true);
+                    }
+                } catch (Exception e) {
+                    throw (SSLHandshakeException) new SSLHandshakeException("Handshake failed")
+                            .initCause(e);
+                } finally {
+                    if (sslSession == null && sslSessionCtx != 0) {
+                        NativeCrypto.SSL_SESSION_free(sslSessionCtx);
+                    }
+                }
+            }
+            int bytesWritten = writeSinkToByteBuffer(handshakeSink, dst);
+            return new SSLEngineResult(Status.OK, getHandshakeStatus(), 0, bytesWritten);
+        } else if (handshakeStatus != HandshakeStatus.NOT_HANDSHAKING) {
+            return new SSLEngineResult(Status.OK, handshakeStatus, 0, 0);
+        }
+
+        try {
+            int totalRead = 0;
+            byte[] buffer = null;
+
+            for (ByteBuffer src : srcs) {
+                int toRead = src.remaining();
+                if (buffer == null || toRead > buffer.length) {
+                    buffer = new byte[toRead];
+                }
+                /*
+                 * We can't just use .mark() here because the caller might be
+                 * using it.
+                 */
+                src.duplicate().get(buffer, 0, toRead);
+                int numRead = NativeCrypto.SSL_write_BIO(sslNativePointer, buffer, toRead,
+                        localToRemoteSink.getContext(), this);
+                if (numRead > 0) {
+                    src.position(src.position() + numRead);
+                    totalRead += numRead;
+                }
+            }
+
+            return new SSLEngineResult(Status.OK, getHandshakeStatus(), totalRead,
+                    writeSinkToByteBuffer(localToRemoteSink, dst));
+        } catch (IOException e) {
+            throw new SSLException(e);
+        }
+    }
+
+    /** Writes data available in a BIO sink to a ByteBuffer. */
+    private static int writeSinkToByteBuffer(OpenSSLBIOSink sink, ByteBuffer dst) {
+        int toWrite = Math.min(sink.available(), dst.remaining());
+        dst.put(sink.toByteArray(), sink.position(), toWrite);
+        sink.skip(toWrite);
+        return toWrite;
+    }
+
+    @Override
+    public int clientPSKKeyRequested(String identityHint, byte[] identity, byte[] key) {
+        return sslParameters.clientPSKKeyRequested(identityHint, identity, key, this);
+    }
+
+    @Override
+    public int serverPSKKeyRequested(String identityHint, String identity, byte[] key) {
+        return sslParameters.serverPSKKeyRequested(identityHint, identity, key, this);
+    }
+
+    @Override
+    public void onSSLStateChange(long sslSessionNativePtr, int type, int val) {
+        synchronized (stateLock) {
+            switch (type) {
+                case NativeCrypto.SSL_CB_HANDSHAKE_DONE:
+                    if (engineState != EngineState.HANDSHAKE_STARTED &&
+                        engineState != EngineState.READY_HANDSHAKE_CUT_THROUGH) {
+                        throw new IllegalStateException("Completed handshake while in mode "
+                                + engineState);
+                    }
+                    engineState = EngineState.HANDSHAKE_COMPLETED;
+                    break;
+                case NativeCrypto.SSL_CB_HANDSHAKE_START:
+                    // For clients, this will allow the NEED_UNWRAP status to be
+                    // returned.
+                    engineState = EngineState.HANDSHAKE_STARTED;
+                    break;
+            }
+        }
+    }
+
+    @Override
+    public void verifyCertificateChain(long sslSessionNativePtr, long[] certRefs,
+            String authMethod) throws CertificateException {
+        try {
+            X509TrustManager x509tm = sslParameters.getX509TrustManager();
+            if (x509tm == null) {
+                throw new CertificateException("No X.509 TrustManager");
+            }
+            if (certRefs == null || certRefs.length == 0) {
+                throw new SSLException("Peer sent no certificate");
+            }
+            OpenSSLX509Certificate[] peerCertChain = new OpenSSLX509Certificate[certRefs.length];
+            for (int i = 0; i < certRefs.length; i++) {
+                peerCertChain[i] = new OpenSSLX509Certificate(certRefs[i]);
+            }
+
+            // Used for verifyCertificateChain callback
+            handshakeSession = new OpenSSLSessionImpl(sslSessionNativePtr, null, peerCertChain,
+                    getPeerHost(), getPeerPort(), null);
+
+            boolean client = sslParameters.getUseClientMode();
+            if (client) {
+                Platform.checkServerTrusted(x509tm, peerCertChain, authMethod, getPeerHost());
+            } else {
+                String authType = peerCertChain[0].getPublicKey().getAlgorithm();
+                x509tm.checkClientTrusted(peerCertChain, authType);
+            }
+        } catch (CertificateException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new CertificateException(e);
+        } finally {
+            // Clear this before notifying handshake completed listeners
+            handshakeSession = null;
+        }
+    }
+
+    @Override
+    public void clientCertificateRequested(byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals)
+            throws CertificateEncodingException, SSLException {
+        sslParameters.chooseClientCertificate(keyTypeBytes, asn1DerEncodedPrincipals,
+                sslNativePointer, this);
+    }
+
+    private void shutdown() {
+        try {
+            NativeCrypto.SSL_shutdown_BIO(sslNativePointer, nullSource.getContext(),
+                    localToRemoteSink.getContext(), this);
+        } catch (IOException ignored) {
+            /*
+             * TODO: The RI ignores close failures in SSLSocket, but need to
+             * investigate whether it does for SSLEngine.
+             */
+        }
+    }
+
+    private void shutdownAndFreeSslNative() {
+        try {
+            shutdown();
+        } finally {
+            free();
+        }
+    }
+
+    private void free() {
+        if (sslNativePointer == 0) {
+            return;
+        }
+        NativeCrypto.SSL_free(sslNativePointer);
+        sslNativePointer = 0;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            free();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @Override
+    public String chooseServerAlias(X509KeyManager keyManager, String keyType) {
+        if (keyManager instanceof X509ExtendedKeyManager) {
+            X509ExtendedKeyManager ekm = (X509ExtendedKeyManager) keyManager;
+            return ekm.chooseEngineServerAlias(keyType, null, this);
+        } else {
+            return keyManager.chooseServerAlias(keyType, null, null);
+        }
+    }
+
+    @Override
+    public String chooseClientAlias(X509KeyManager keyManager, X500Principal[] issuers,
+            String[] keyTypes) {
+        if (keyManager instanceof X509ExtendedKeyManager) {
+            X509ExtendedKeyManager ekm = (X509ExtendedKeyManager) keyManager;
+            return ekm.chooseEngineClientAlias(keyTypes, issuers, this);
+        } else {
+            return keyManager.chooseClientAlias(keyTypes, issuers, null);
+        }
+    }
+
+    @Override
+    public String chooseServerPSKIdentityHint(PSKKeyManager keyManager) {
+        return keyManager.chooseServerKeyIdentityHint(this);
+    }
+
+    @Override
+    public String chooseClientPSKIdentity(PSKKeyManager keyManager, String identityHint) {
+        return keyManager.chooseClientKeyIdentity(identityHint, this);
+    }
+
+    @Override
+    public SecretKey getPSKKey(PSKKeyManager keyManager, String identityHint, String identity) {
+        return keyManager.getKey(identityHint, identity, this);
+    }
+}
diff --git a/src/main/java/org/conscrypt/OpenSSLKey.java b/src/main/java/org/conscrypt/OpenSSLKey.java
index 8413fd0..9dbd60c 100644
--- a/src/main/java/org/conscrypt/OpenSSLKey.java
+++ b/src/main/java/org/conscrypt/OpenSSLKey.java
@@ -20,6 +20,9 @@
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
+import java.security.interfaces.DSAPrivateKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.RSAPrivateKey;
 import java.security.spec.InvalidKeySpecException;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.security.spec.X509EncodedKeySpec;
@@ -70,8 +73,11 @@
             return ((OpenSSLKeyHolder) key).getOpenSSLKey();
         }
 
-        if (!"PKCS#8".equals(key.getFormat())) {
-            throw new InvalidKeyException("Unknown key format " + key.getFormat());
+        final String keyFormat = key.getFormat();
+        if (keyFormat == null) {
+            return wrapPrivateKey(key);
+        } else if (!"PKCS#8".equals(key.getFormat())) {
+            throw new InvalidKeyException("Unknown key format " + keyFormat);
         }
 
         final byte[] encoded = key.getEncoded();
@@ -82,10 +88,41 @@
         return new OpenSSLKey(NativeCrypto.d2i_PKCS8_PRIV_KEY_INFO(key.getEncoded()));
     }
 
+    private static OpenSSLKey wrapPrivateKey(PrivateKey key) throws InvalidKeyException {
+        if (key instanceof RSAPrivateKey) {
+            return OpenSSLRSAPrivateKey.wrapPlatformKey((RSAPrivateKey) key);
+        } else if (key instanceof DSAPrivateKey) {
+            return OpenSSLDSAPrivateKey.wrapPlatformKey((DSAPrivateKey) key);
+        } else if (key instanceof ECPrivateKey) {
+            return OpenSSLECPrivateKey.wrapPlatformKey((ECPrivateKey) key);
+        } else {
+            throw new InvalidKeyException("Unknown key type: " + key.toString());
+        }
+    }
+
+    public static OpenSSLKey fromPublicKey(PublicKey key) throws InvalidKeyException {
+        if (key instanceof OpenSSLKeyHolder) {
+            return ((OpenSSLKeyHolder) key).getOpenSSLKey();
+        }
+
+        if (!"X.509".equals(key.getFormat())) {
+            throw new InvalidKeyException("Unknown key format " + key.getFormat());
+        }
+
+        final byte[] encoded = key.getEncoded();
+        if (encoded == null) {
+            throw new InvalidKeyException("Key encoding is null");
+        }
+
+        return new OpenSSLKey(NativeCrypto.d2i_PUBKEY(key.getEncoded()));
+    }
+
     public PublicKey getPublicKey() throws NoSuchAlgorithmException {
         switch (NativeCrypto.EVP_PKEY_type(ctx)) {
             case NativeCrypto.EVP_PKEY_RSA:
                 return new OpenSSLRSAPublicKey(this);
+            case NativeCrypto.EVP_PKEY_DH:
+                return new OpenSSLDHPublicKey(this);
             case NativeCrypto.EVP_PKEY_DSA:
                 return new OpenSSLDSAPublicKey(this);
             case NativeCrypto.EVP_PKEY_EC:
@@ -121,6 +158,8 @@
         switch (NativeCrypto.EVP_PKEY_type(ctx)) {
             case NativeCrypto.EVP_PKEY_RSA:
                 return new OpenSSLRSAPrivateKey(this);
+            case NativeCrypto.EVP_PKEY_DH:
+                return new OpenSSLDHPrivateKey(this);
             case NativeCrypto.EVP_PKEY_DSA:
                 return new OpenSSLDSAPrivateKey(this);
             case NativeCrypto.EVP_PKEY_EC:
diff --git a/src/main/java/org/conscrypt/OpenSSLMac.java b/src/main/java/org/conscrypt/OpenSSLMac.java
index ed163ec..2cadc3f 100644
--- a/src/main/java/org/conscrypt/OpenSSLMac.java
+++ b/src/main/java/org/conscrypt/OpenSSLMac.java
@@ -26,8 +26,7 @@
 import javax.crypto.SecretKey;
 
 public abstract class OpenSSLMac extends MacSpi {
-    private final OpenSSLDigestContext ctx = new OpenSSLDigestContext(
-            NativeCrypto.EVP_MD_CTX_create());
+    private OpenSSLDigestContext ctx;
 
     /**
      * Holds the EVP_MD for the hashing algorithm, e.g.
@@ -88,17 +87,19 @@
             macKey = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_mac_key(evp_pkey_type, keyBytes));
         }
 
-        NativeCrypto.EVP_MD_CTX_init(ctx.getContext());
-
-        reset();
+        resetContext();
     }
 
-    private void reset() {
+    private final void resetContext() {
+        OpenSSLDigestContext ctxLocal = new OpenSSLDigestContext(NativeCrypto.EVP_MD_CTX_create());
+        NativeCrypto.EVP_MD_CTX_init(ctxLocal);
+
         final OpenSSLKey macKey = this.macKey;
-        if (macKey == null) {
-            return;
+        if (macKey != null) {
+            NativeCrypto.EVP_DigestSignInit(ctxLocal, evp_md, macKey.getPkeyContext());
         }
-        NativeCrypto.EVP_DigestSignInit(ctx.getContext(), evp_md, macKey.getPkeyContext());
+
+        this.ctx = ctxLocal;
     }
 
     @Override
@@ -109,19 +110,21 @@
 
     @Override
     protected void engineUpdate(byte[] input, int offset, int len) {
-        NativeCrypto.EVP_DigestUpdate(ctx.getContext(), input, offset, len);
+        final OpenSSLDigestContext ctxLocal = ctx;
+        NativeCrypto.EVP_DigestUpdate(ctxLocal, input, offset, len);
     }
 
     @Override
     protected byte[] engineDoFinal() {
-        final byte[] output = NativeCrypto.EVP_DigestSignFinal(ctx.getContext());
-        reset();
+        final OpenSSLDigestContext ctxLocal = ctx;
+        final byte[] output = NativeCrypto.EVP_DigestSignFinal(ctxLocal);
+        resetContext();
         return output;
     }
 
     @Override
     protected void engineReset() {
-        reset();
+        resetContext();
     }
 
     public static class HmacMD5 extends OpenSSLMac {
diff --git a/src/main/java/org/conscrypt/OpenSSLMessageDigestJDK.java b/src/main/java/org/conscrypt/OpenSSLMessageDigestJDK.java
index 7b6c608..f028421 100644
--- a/src/main/java/org/conscrypt/OpenSSLMessageDigestJDK.java
+++ b/src/main/java/org/conscrypt/OpenSSLMessageDigestJDK.java
@@ -16,18 +16,14 @@
 
 package org.conscrypt;
 
-import java.security.MessageDigest;
+import java.security.MessageDigestSpi;
 import java.security.NoSuchAlgorithmException;
 
 /**
  * Implements the JDK MessageDigest interface using OpenSSL's EVP API.
  */
-public class OpenSSLMessageDigestJDK extends MessageDigest implements Cloneable {
-
-    /**
-     * Holds a pointer to the native message digest context.
-     */
-    private long ctx;
+public class OpenSSLMessageDigestJDK extends MessageDigestSpi implements Cloneable {
+    private OpenSSLDigestContext ctx;
 
     /**
      * Holds the EVP_MD for the hashing algorithm, e.g. EVP_get_digestbyname("sha1");
@@ -45,19 +41,31 @@
     private final byte[] singleByte = new byte[1];
 
     /**
-     * Creates a new OpenSSLMessageDigest instance for the given algorithm
-     * name.
+     * Creates a new OpenSSLMessageDigest instance for the given algorithm name.
      */
-    private OpenSSLMessageDigestJDK(String algorithm, long evp_md, int size)
-            throws NoSuchAlgorithmException {
-        super(algorithm);
+    private OpenSSLMessageDigestJDK(long evp_md, int size) throws NoSuchAlgorithmException {
         this.evp_md = evp_md;
         this.size = size;
+
+        resetContext();
+    }
+
+    private OpenSSLMessageDigestJDK(long evp_md, int size, OpenSSLDigestContext ctx) {
+        this.evp_md = evp_md;
+        this.size = size;
+        this.ctx = ctx;
+    }
+
+    private final void resetContext() {
+        OpenSSLDigestContext ctxLocal = new OpenSSLDigestContext(NativeCrypto.EVP_MD_CTX_create());
+        NativeCrypto.EVP_MD_CTX_init(ctxLocal);
+        NativeCrypto.EVP_DigestInit(ctxLocal, evp_md);
+        ctx = ctxLocal;
     }
 
     @Override
     protected void engineReset() {
-        free();
+        resetContext();
     }
 
     @Override
@@ -73,52 +81,22 @@
 
     @Override
     protected void engineUpdate(byte[] input, int offset, int len) {
-        NativeCrypto.EVP_DigestUpdate(getCtx(), input, offset, len);
+        NativeCrypto.EVP_DigestUpdate(ctx, input, offset, len);
     }
 
     @Override
     protected byte[] engineDigest() {
-        byte[] result = new byte[size];
-        NativeCrypto.EVP_DigestFinal(getCtx(), result, 0);
-        ctx = 0; // EVP_DigestFinal frees the context as a side effect
+        final byte[] result = new byte[size];
+        NativeCrypto.EVP_DigestFinal(ctx, result, 0);
+        resetContext();
         return result;
     }
 
-    @Override
-    public Object clone() throws CloneNotSupportedException {
-        OpenSSLMessageDigestJDK d = (OpenSSLMessageDigestJDK) super.clone();
-        d.ctx = NativeCrypto.EVP_MD_CTX_copy(getCtx());
-        return d;
-    }
-
-    private long getCtx() {
-        if (ctx == 0) {
-            ctx = NativeCrypto.EVP_DigestInit(evp_md);
-        }
-        return ctx;
-    }
-
-    private void free() {
-        if (ctx != 0) {
-            NativeCrypto.EVP_MD_CTX_destroy(ctx);
-            ctx = 0;
-        }
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            free();
-        } finally {
-            super.finalize();
-        }
-    }
-
     public static class MD5 extends OpenSSLMessageDigestJDK {
         private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("md5");
         private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
         public MD5() throws NoSuchAlgorithmException {
-            super("MD5",EVP_MD, SIZE);
+            super(EVP_MD, SIZE);
         }
     }
 
@@ -126,7 +104,7 @@
         private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("sha1");
         private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
         public SHA1() throws NoSuchAlgorithmException {
-            super("SHA-1", EVP_MD, SIZE);
+            super(EVP_MD, SIZE);
         }
     }
 
@@ -134,7 +112,7 @@
         private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("sha224");
         private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
         public SHA224() throws NoSuchAlgorithmException {
-            super("SHA-224", EVP_MD, SIZE);
+            super(EVP_MD, SIZE);
         }
     }
 
@@ -142,7 +120,7 @@
         private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("sha256");
         private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
         public SHA256() throws NoSuchAlgorithmException {
-            super("SHA-256", EVP_MD, SIZE);
+            super(EVP_MD, SIZE);
         }
     }
 
@@ -150,7 +128,7 @@
         private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("sha384");
         private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
         public SHA384() throws NoSuchAlgorithmException {
-            super("SHA-384", EVP_MD, SIZE);
+            super(EVP_MD, SIZE);
         }
     }
 
@@ -158,7 +136,15 @@
         private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("sha512");
         private static final int SIZE = NativeCrypto.EVP_MD_size(EVP_MD);
         public SHA512() throws NoSuchAlgorithmException {
-            super("SHA-512", EVP_MD, SIZE);
+            super(EVP_MD, SIZE);
         }
     }
+
+    @Override
+    public Object clone() {
+        OpenSSLDigestContext ctxCopy = new OpenSSLDigestContext(NativeCrypto.EVP_MD_CTX_create());
+        NativeCrypto.EVP_MD_CTX_init(ctxCopy);
+        NativeCrypto.EVP_MD_CTX_copy(ctxCopy, ctx);
+        return new OpenSSLMessageDigestJDK(evp_md, size, ctxCopy);
+    }
 }
diff --git a/src/main/java/org/conscrypt/OpenSSLNativeReference.java b/src/main/java/org/conscrypt/OpenSSLNativeReference.java
new file mode 100644
index 0000000..dc2d893
--- /dev/null
+++ b/src/main/java/org/conscrypt/OpenSSLNativeReference.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 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;
+
+/**
+ * Used to hold onto native OpenSSL references and run finalization on those
+ * objects. Individual types must subclass this and implement finalizer.
+ */
+public abstract class OpenSSLNativeReference {
+    final long context;
+
+    public OpenSSLNativeReference(long ctx) {
+        if (ctx == 0) {
+            throw new NullPointerException("ctx == 0");
+        }
+
+        this.context = ctx;
+    }
+}
diff --git a/src/main/java/org/conscrypt/OpenSSLProvider.java b/src/main/java/org/conscrypt/OpenSSLProvider.java
index 90e08a2..c5ee329 100644
--- a/src/main/java/org/conscrypt/OpenSSLProvider.java
+++ b/src/main/java/org/conscrypt/OpenSSLProvider.java
@@ -34,7 +34,11 @@
     public static final String PROVIDER_NAME = "AndroidOpenSSL";
 
     public OpenSSLProvider() {
-        super(PROVIDER_NAME, 1.0, "Android's OpenSSL-backed security provider");
+        this(PROVIDER_NAME);
+    }
+
+    public OpenSSLProvider(String providerName) {
+        super(providerName, 1.0, "Android's OpenSSL-backed security provider");
 
         // Make sure the platform is initialized.
         Platform.setup();
@@ -81,6 +85,9 @@
         put("KeyPairGenerator.RSA", prefix + "OpenSSLRSAKeyPairGenerator");
         put("Alg.Alias.KeyPairGenerator.1.2.840.113549.1.1.1", "RSA");
 
+        put("KeyPairGenerator.DH", prefix + "OpenSSLDHKeyPairGenerator");
+        put("Alg.Alias.KeyPairGenerator.1.2.840.113549.1.3.1", "DH");
+
         put("KeyPairGenerator.DSA", prefix + "OpenSSLDSAKeyPairGenerator");
 
         put("KeyPairGenerator.EC", prefix + "OpenSSLECKeyPairGenerator");
@@ -89,6 +96,9 @@
         put("KeyFactory.RSA", prefix + "OpenSSLRSAKeyFactory");
         put("Alg.Alias.KeyFactory.1.2.840.113549.1.1.1", "RSA");
 
+        put("KeyFactory.DH", prefix + "OpenSSLDHKeyFactory");
+        put("Alg.Alias.KeyFactory.1.2.840.113549.1.3.1", "DH");
+
         put("KeyFactory.DSA", prefix + "OpenSSLDSAKeyFactory");
 
         put("KeyFactory.EC", prefix + "OpenSSLECKeyFactory");
@@ -197,19 +207,32 @@
         /*
          * OpenSSL only supports a subset of modes, so we'll name them
          * explicitly here.
+         *
+         * Moreover, OpenSSL only supports PKCS#7 padding. PKCS#5 padding
+         * is also supported because it's a special case of PKCS#7 for 64-bit
+         * blocks. PKCS#5 technically supports only 64-bit blocks and won't
+         * produce the same result as PKCS#7 for blocks that are not 64 bits
+         * long. However, everybody assumes PKCS#7 when they say PKCS#5. For
+         * example, lots of code uses PKCS#5 with AES whose blocks are longer
+         * than 64 bits. We solve this confusion by making PKCS7Padding an
+         * alias for PKCS5Padding.
          */
         put("Cipher.AES/ECB/NoPadding", prefix + "OpenSSLCipher$AES$ECB$NoPadding");
         put("Cipher.AES/ECB/PKCS5Padding", prefix + "OpenSSLCipher$AES$ECB$PKCS5Padding");
+        put("Alg.Alias.Cipher.AES/ECB/PKCS7Padding", "AES/ECB/PKCS5Padding");
         put("Cipher.AES/CBC/NoPadding", prefix + "OpenSSLCipher$AES$CBC$NoPadding");
         put("Cipher.AES/CBC/PKCS5Padding", prefix + "OpenSSLCipher$AES$CBC$PKCS5Padding");
+        put("Alg.Alias.Cipher.AES/CBC/PKCS7Padding", "AES/CBC/PKCS5Padding");
         put("Cipher.AES/CFB/NoPadding", prefix + "OpenSSLCipher$AES$CFB");
         put("Cipher.AES/CTR/NoPadding", prefix + "OpenSSLCipher$AES$CTR");
         put("Cipher.AES/OFB/NoPadding", prefix + "OpenSSLCipher$AES$OFB");
 
         put("Cipher.DESEDE/ECB/NoPadding", prefix + "OpenSSLCipher$DESEDE$ECB$NoPadding");
         put("Cipher.DESEDE/ECB/PKCS5Padding", prefix + "OpenSSLCipher$DESEDE$ECB$PKCS5Padding");
+        put("Alg.Alias.Cipher.DESEDE/ECB/PKCS7Padding", "DESEDE/ECB/PKCS5Padding");
         put("Cipher.DESEDE/CBC/NoPadding", prefix + "OpenSSLCipher$DESEDE$CBC$NoPadding");
         put("Cipher.DESEDE/CBC/PKCS5Padding", prefix + "OpenSSLCipher$DESEDE$CBC$PKCS5Padding");
+        put("Alg.Alias.Cipher.DESEDE/CBC/PKCS7Padding", "DESEDE/CBC/PKCS5Padding");
         put("Cipher.DESEDE/CFB/NoPadding", prefix + "OpenSSLCipher$DESEDE$CFB");
         put("Cipher.DESEDE/OFB/NoPadding", prefix + "OpenSSLCipher$DESEDE$OFB");
 
diff --git a/src/main/java/org/conscrypt/OpenSSLRSAPrivateCrtKey.java b/src/main/java/org/conscrypt/OpenSSLRSAPrivateCrtKey.java
index 1cbf5de..9e559a3 100644
--- a/src/main/java/org/conscrypt/OpenSSLRSAPrivateCrtKey.java
+++ b/src/main/java/org/conscrypt/OpenSSLRSAPrivateCrtKey.java
@@ -93,6 +93,14 @@
     }
 
     static OpenSSLKey getInstance(RSAPrivateCrtKey rsaPrivateKey) throws InvalidKeyException {
+        /**
+         * If the key is not encodable (PKCS11-like key), then wrap it and use
+         * JNI upcalls to satisfy requests.
+         */
+        if (rsaPrivateKey.getFormat() == null) {
+            return wrapPlatformKey(rsaPrivateKey);
+        }
+
         BigInteger modulus = rsaPrivateKey.getModulus();
         BigInteger privateExponent = rsaPrivateKey.getPrivateExponent();
 
diff --git a/src/main/java/org/conscrypt/OpenSSLRSAPrivateKey.java b/src/main/java/org/conscrypt/OpenSSLRSAPrivateKey.java
index efb85b1..1aa4dba 100644
--- a/src/main/java/org/conscrypt/OpenSSLRSAPrivateKey.java
+++ b/src/main/java/org/conscrypt/OpenSSLRSAPrivateKey.java
@@ -89,7 +89,25 @@
       return new OpenSSLRSAPrivateKey(key, params);
     }
 
+    protected static OpenSSLKey wrapPlatformKey(RSAPrivateKey rsaPrivateKey)
+            throws InvalidKeyException {
+        OpenSSLKey wrapper = Platform.wrapRsaKey(rsaPrivateKey);
+        if (wrapper != null) {
+            return wrapper;
+        }
+        return new OpenSSLKey(NativeCrypto.getRSAPrivateKeyWrapper(rsaPrivateKey, rsaPrivateKey
+                .getModulus().toByteArray()));
+    }
+
     static OpenSSLKey getInstance(RSAPrivateKey rsaPrivateKey) throws InvalidKeyException {
+        /**
+         * If the key is not encodable (PKCS11-like key), then wrap it and use
+         * JNI upcalls to satisfy requests.
+         */
+        if (rsaPrivateKey.getFormat() == null) {
+            return wrapPlatformKey(rsaPrivateKey);
+        }
+
         final BigInteger modulus = rsaPrivateKey.getModulus();
         final BigInteger privateExponent = rsaPrivateKey.getPrivateExponent();
 
diff --git a/src/main/java/org/conscrypt/OpenSSLRandom.java b/src/main/java/org/conscrypt/OpenSSLRandom.java
index 1683bb8..c25f2ee 100644
--- a/src/main/java/org/conscrypt/OpenSSLRandom.java
+++ b/src/main/java/org/conscrypt/OpenSSLRandom.java
@@ -22,20 +22,58 @@
 public class OpenSSLRandom extends SecureRandomSpi implements Serializable {
     private static final long serialVersionUID = 8506210602917522860L;
 
+    private boolean mSeeded;
+
     @Override
     protected void engineSetSeed(byte[] seed) {
+        // NOTE: The contract of the SecureRandomSpi does not appear to prohibit self-seeding here
+        // (in addition to using the provided seed).
+        selfSeedIfNotSeeded();
         NativeCrypto.RAND_seed(seed);
     }
 
     @Override
     protected void engineNextBytes(byte[] bytes) {
+        selfSeedIfNotSeeded();
         NativeCrypto.RAND_bytes(bytes);
     }
 
     @Override
     protected byte[] engineGenerateSeed(int numBytes) {
+        selfSeedIfNotSeeded();
         byte[] output = new byte[numBytes];
         NativeCrypto.RAND_bytes(output);
         return output;
     }
+
+    /**
+     * Self-seeds this instance from the Linux RNG. Does nothing if this instance has already been
+     * seeded.
+     */
+    private void selfSeedIfNotSeeded() {
+        // NOTE: No need to worry about concurrent access to this field because the worst case is
+        // that the code below is executed multiple times (by different threads), which may only
+        // increase the entropy of the OpenSSL PRNG.
+        if (mSeeded) {
+            return;
+        }
+
+        seedOpenSSLPRNGFromLinuxRNG();
+        mSeeded = true;
+    }
+
+    /**
+     * Obtains a seed from the Linux RNG and mixes it into the OpenSSL PRNG (default RAND engine).
+     *
+     * <p>NOTE: This modifies the OpenSSL PRNG shared by all instances of OpenSSLRandom and other
+     * crypto primitives offered by or built on top of OpenSSL.
+     */
+    public static void seedOpenSSLPRNGFromLinuxRNG() {
+        int seedLengthInBytes = NativeCrypto.RAND_SEED_LENGTH_IN_BYTES;
+        int bytesRead = NativeCrypto.RAND_load_file("/dev/urandom", seedLengthInBytes);
+        if (bytesRead != seedLengthInBytes) {
+            throw new SecurityException("Failed to read sufficient bytes from /dev/urandom."
+                    + " Expected: " + seedLengthInBytes + ", actual: " + bytesRead);
+        }
+    }
 }
diff --git a/src/main/java/org/conscrypt/OpenSSLServerSocketFactoryImpl.java b/src/main/java/org/conscrypt/OpenSSLServerSocketFactoryImpl.java
index dbc3a19..25819ed 100644
--- a/src/main/java/org/conscrypt/OpenSSLServerSocketFactoryImpl.java
+++ b/src/main/java/org/conscrypt/OpenSSLServerSocketFactoryImpl.java
@@ -44,7 +44,7 @@
 
     @Override
     public String[] getDefaultCipherSuites() {
-        return NativeCrypto.getDefaultCipherSuites();
+        return sslParameters.getEnabledCipherSuites();
     }
 
     @Override
diff --git a/src/main/java/org/conscrypt/OpenSSLServerSocketImpl.java b/src/main/java/org/conscrypt/OpenSSLServerSocketImpl.java
index aa917c3..215da55 100644
--- a/src/main/java/org/conscrypt/OpenSSLServerSocketImpl.java
+++ b/src/main/java/org/conscrypt/OpenSSLServerSocketImpl.java
@@ -25,8 +25,6 @@
  */
 public class OpenSSLServerSocketImpl extends javax.net.ssl.SSLServerSocket {
     private final SSLParametersImpl sslParameters;
-    private String[] enabledProtocols = NativeCrypto.getSupportedProtocols();
-    private String[] enabledCipherSuites = NativeCrypto.getDefaultCipherSuites();
     private boolean channelIdEnabled;
 
     protected OpenSSLServerSocketImpl(SSLParametersImpl sslParameters) throws IOException {
@@ -81,7 +79,7 @@
      */
     @Override
     public String[] getEnabledProtocols() {
-        return enabledProtocols.clone();
+        return sslParameters.getEnabledProtocols();
     }
 
     /**
@@ -95,7 +93,7 @@
      */
     @Override
     public void setEnabledProtocols(String[] protocols) {
-        enabledProtocols = NativeCrypto.checkEnabledProtocols(protocols);
+        sslParameters.setEnabledProtocols(protocols);
     }
 
     @Override
@@ -105,7 +103,7 @@
 
     @Override
     public String[] getEnabledCipherSuites() {
-        return enabledCipherSuites.clone();
+        return sslParameters.getEnabledCipherSuites();
     }
 
     /**
@@ -132,7 +130,7 @@
      */
     @Override
     public void setEnabledCipherSuites(String[] suites) {
-        enabledCipherSuites = NativeCrypto.checkEnabledCipherSuites(suites);
+        sslParameters.setEnabledCipherSuites(suites);
     }
 
     @Override
@@ -167,9 +165,7 @@
 
     @Override
     public Socket accept() throws IOException {
-        OpenSSLSocketImpl socket = new OpenSSLSocketImpl(sslParameters,
-                                                         enabledProtocols.clone(),
-                                                         enabledCipherSuites.clone());
+        OpenSSLSocketImpl socket = new OpenSSLSocketImpl(sslParameters);
         socket.setChannelIdEnabled(channelIdEnabled);
         implAccept(socket);
         return socket;
diff --git a/src/main/java/org/conscrypt/OpenSSLSignature.java b/src/main/java/org/conscrypt/OpenSSLSignature.java
index 53b0df0..33e6b65 100644
--- a/src/main/java/org/conscrypt/OpenSSLSignature.java
+++ b/src/main/java/org/conscrypt/OpenSSLSignature.java
@@ -21,29 +21,19 @@
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.Signature;
 import java.security.SignatureException;
-import java.security.interfaces.DSAPrivateKey;
-import java.security.interfaces.DSAPublicKey;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAPrivateCrtKey;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
+import java.security.SignatureSpi;
 
 /**
  * Implements the subset of the JDK Signature interface needed for
  * signature verification using OpenSSL.
  */
-public class OpenSSLSignature extends Signature {
+public class OpenSSLSignature extends SignatureSpi {
     private static enum EngineType {
         RSA, DSA, EC,
     };
 
-    /**
-     * Holds a pointer to the native message digest context.
-     */
-    private long ctx;
+    private OpenSSLDigestContext ctx;
 
     /**
      * The current OpenSSL key we're operating on.
@@ -58,7 +48,7 @@
     /**
      * Holds the OpenSSL name of the algorithm (lower case, no dashes).
      */
-    private final String evpAlgorithm;
+    private final long evpAlgorithm;
 
     /**
      * Holds a dummy buffer for writing single bytes to the digest.
@@ -66,23 +56,33 @@
     private final byte[] singleByte = new byte[1];
 
     /**
+     * True when engine is initialized to signing.
+     */
+    private boolean signing;
+
+    /**
      * Creates a new OpenSSLSignature instance for the given algorithm name.
      *
      * @param algorithm OpenSSL name of the algorithm, e.g. "RSA-SHA1".
      */
-    private OpenSSLSignature(String algorithm, EngineType engineType)
+    private OpenSSLSignature(long algorithm, EngineType engineType)
             throws NoSuchAlgorithmException {
-        super(algorithm);
-
-        // We don't support MD2
-        if ("RSA-MD2".equals(algorithm)) {
-            throw new NoSuchAlgorithmException(algorithm);
-        }
-
         this.engineType = engineType;
         this.evpAlgorithm = algorithm;
     }
 
+    private final void resetContext() {
+        OpenSSLDigestContext ctxLocal = new OpenSSLDigestContext(NativeCrypto.EVP_MD_CTX_create());
+        NativeCrypto.EVP_MD_CTX_init(ctxLocal);
+        if (signing) {
+            enableDSASignatureNonceHardeningIfApplicable();
+            NativeCrypto.EVP_SignInit(ctxLocal, evpAlgorithm);
+        } else {
+            NativeCrypto.EVP_VerifyInit(ctxLocal, evpAlgorithm);
+        }
+        this.ctx = ctxLocal;
+    }
+
     @Override
     protected void engineUpdate(byte input) {
         singleByte[0] = input;
@@ -91,26 +91,11 @@
 
     @Override
     protected void engineUpdate(byte[] input, int offset, int len) {
-        if (state == SIGN) {
-            if (ctx == 0) {
-                try {
-                    ctx = NativeCrypto.EVP_SignInit(evpAlgorithm);
-                } catch (Exception ex) {
-                    throw new RuntimeException(ex);
-                }
-            }
-
-            NativeCrypto.EVP_SignUpdate(ctx, input, offset, len);
+        final OpenSSLDigestContext ctxLocal = ctx;
+        if (signing) {
+            NativeCrypto.EVP_SignUpdate(ctxLocal, input, offset, len);
         } else {
-            if (ctx == 0) {
-                try {
-                    ctx = NativeCrypto.EVP_VerifyInit(evpAlgorithm);
-                } catch (Exception ex) {
-                    throw new RuntimeException(ex);
-                }
-            }
-
-            NativeCrypto.EVP_VerifyUpdate(ctx, input, offset, len);
+            NativeCrypto.EVP_VerifyUpdate(ctxLocal, input, offset, len);
         }
     }
 
@@ -125,98 +110,64 @@
         switch (engineType) {
             case RSA:
                 if (pkeyType != NativeCrypto.EVP_PKEY_RSA) {
-                    throw new InvalidKeyException("Signature not initialized as RSA");
+                    throw new InvalidKeyException("Signature initialized as " + engineType
+                            + " (not RSA)");
                 }
                 break;
             case DSA:
                 if (pkeyType != NativeCrypto.EVP_PKEY_DSA) {
-                    throw new InvalidKeyException("Signature not initialized as DSA");
+                    throw new InvalidKeyException("Signature initialized as " + engineType
+                            + " (not DSA)");
                 }
                 break;
             case EC:
                 if (pkeyType != NativeCrypto.EVP_PKEY_EC) {
-                    throw new InvalidKeyException("Signature not initialized as EC");
+                    throw new InvalidKeyException("Signature initialized as " + engineType
+                            + " (not EC)");
                 }
                 break;
             default:
-                throw new InvalidKeyException("Need DSA or RSA or EC private key");
+                throw new InvalidKeyException("Key must be of type " + engineType);
         }
     }
 
+    private void initInternal(OpenSSLKey newKey, boolean signing) throws InvalidKeyException {
+        checkEngineType(newKey);
+        key = newKey;
+
+        this.signing = signing;
+        resetContext();
+    }
+
     @Override
     protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
-        destroyContextIfExists();
+        initInternal(OpenSSLKey.fromPrivateKey(privateKey), true);
+    }
 
-        if (privateKey instanceof OpenSSLKeyHolder) {
-            OpenSSLKey pkey = ((OpenSSLKeyHolder) privateKey).getOpenSSLKey();
-            checkEngineType(pkey);
-            key = pkey;
-        } else if (privateKey instanceof RSAPrivateCrtKey) {
-            if (engineType != EngineType.RSA) {
-                throw new InvalidKeyException("Signature not initialized as RSA");
-            }
-
-            RSAPrivateCrtKey rsaPrivateKey = (RSAPrivateCrtKey) privateKey;
-            key = OpenSSLRSAPrivateCrtKey.getInstance(rsaPrivateKey);
-        } else if (privateKey instanceof RSAPrivateKey) {
-            if (engineType != EngineType.RSA) {
-                throw new InvalidKeyException("Signature not initialized as RSA");
-            }
-
-            RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey;
-            key = OpenSSLRSAPrivateKey.getInstance(rsaPrivateKey);
-        } else if (privateKey instanceof DSAPrivateKey) {
-            if (engineType != EngineType.DSA) {
-                throw new InvalidKeyException("Signature not initialized as DSA");
-            }
-
-            DSAPrivateKey dsaPrivateKey = (DSAPrivateKey) privateKey;
-            key = OpenSSLDSAPrivateKey.getInstance(dsaPrivateKey);
-        } else if (privateKey instanceof ECPrivateKey) {
-            if (engineType != EngineType.EC) {
-                throw new InvalidKeyException("Signature not initialized as EC");
-            }
-
-            ECPrivateKey ecPrivateKey = (ECPrivateKey) privateKey;
-            key = OpenSSLECPrivateKey.getInstance(ecPrivateKey);
-        } else {
-            throw new InvalidKeyException("Need DSA or RSA or EC private key");
+    /**
+     * Enables a mitigation against private key leakage through DSA and ECDSA signatures when weak
+     * nonces (per-message k values) are used. To mitigate the issue, private key and message being
+     * signed is mixed into the randomly generated nonce (k).
+     *
+     * <p>Does nothing for signatures that are neither DSA nor ECDSA.
+     */
+    private void enableDSASignatureNonceHardeningIfApplicable() {
+        final OpenSSLKey key = this.key;
+        switch (engineType) {
+            case DSA:
+                NativeCrypto.set_DSA_flag_nonce_from_hash(key.getPkeyContext());
+                break;
+            case EC:
+                NativeCrypto.EC_KEY_set_nonce_from_hash(key.getPkeyContext(), true);
+                break;
+            default:
+              // Hardening not applicable
         }
     }
 
     @Override
     protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
-        // If we had an existing context, destroy it first.
-        destroyContextIfExists();
-
-        if (publicKey instanceof OpenSSLKeyHolder) {
-            OpenSSLKey pkey = ((OpenSSLKeyHolder) publicKey).getOpenSSLKey();
-            checkEngineType(pkey);
-            key = pkey;
-        } else if (publicKey instanceof RSAPublicKey) {
-            if (engineType != EngineType.RSA) {
-                throw new InvalidKeyException("Signature not initialized as RSA");
-            }
-
-            RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
-            key = OpenSSLRSAPublicKey.getInstance(rsaPublicKey);
-        } else if (publicKey instanceof DSAPublicKey) {
-            if (engineType != EngineType.DSA) {
-                throw new InvalidKeyException("Signature not initialized as DSA");
-            }
-
-            DSAPublicKey dsaPublicKey = (DSAPublicKey) publicKey;
-            key = OpenSSLDSAPublicKey.getInstance(dsaPublicKey);
-        } else if (publicKey instanceof ECPublicKey) {
-            if (engineType != EngineType.EC) {
-                throw new InvalidKeyException("Signature not initialized as EC");
-            }
-
-            ECPublicKey ecPublicKey = (ECPublicKey) publicKey;
-            key = OpenSSLECPublicKey.getInstance(ecPublicKey);
-        } else {
-            throw new InvalidKeyException("Need DSA or RSA or EC public key");
-        }
+        initInternal(OpenSSLKey.fromPublicKey(publicKey), false);
     }
 
     @Override
@@ -230,9 +181,11 @@
             throw new SignatureException("Need DSA or RSA or EC private key");
         }
 
+        final OpenSSLDigestContext ctxLocal = ctx;
         try {
             byte[] buffer = new byte[NativeCrypto.EVP_PKEY_size(key.getPkeyContext())];
-            int bytesWritten = NativeCrypto.EVP_SignFinal(ctx, buffer, 0, key.getPkeyContext());
+            int bytesWritten = NativeCrypto.EVP_SignFinal(ctxLocal, buffer, 0,
+                    key.getPkeyContext());
 
             byte[] signature = new byte[bytesWritten];
             System.arraycopy(buffer, 0, signature, 0, bytesWritten);
@@ -245,7 +198,7 @@
              * Java expects the digest context to be reset completely after sign
              * calls.
              */
-            destroyContextIfExists();
+            resetContext();
         }
     }
 
@@ -267,86 +220,80 @@
              * Java expects the digest context to be reset completely after
              * verify calls.
              */
-            destroyContextIfExists();
-        }
-    }
-
-    private void destroyContextIfExists() {
-        if (ctx != 0) {
-            NativeCrypto.EVP_MD_CTX_destroy(ctx);
-            ctx = 0;
-        }
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            if (ctx != 0) {
-                NativeCrypto.EVP_MD_CTX_destroy(ctx);
-            }
-        } finally {
-            super.finalize();
+            resetContext();
         }
     }
 
     public static final class MD5RSA extends OpenSSLSignature {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("RSA-MD5");
         public MD5RSA() throws NoSuchAlgorithmException {
-            super("RSA-MD5", EngineType.RSA);
+            super(EVP_MD, EngineType.RSA);
         }
     }
     public static final class SHA1RSA extends OpenSSLSignature {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("RSA-SHA1");
         public SHA1RSA() throws NoSuchAlgorithmException {
-            super("RSA-SHA1", EngineType.RSA);
+            super(EVP_MD, EngineType.RSA);
         }
     }
     public static final class SHA224RSA extends OpenSSLSignature {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("RSA-SHA224");
         public SHA224RSA() throws NoSuchAlgorithmException {
-            super("RSA-SHA224", EngineType.RSA);
+            super(EVP_MD, EngineType.RSA);
         }
     }
     public static final class SHA256RSA extends OpenSSLSignature {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("RSA-SHA256");
         public SHA256RSA() throws NoSuchAlgorithmException {
-            super("RSA-SHA256", EngineType.RSA);
+            super(EVP_MD, EngineType.RSA);
         }
     }
     public static final class SHA384RSA extends OpenSSLSignature {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("RSA-SHA384");
         public SHA384RSA() throws NoSuchAlgorithmException {
-            super("RSA-SHA384", EngineType.RSA);
+            super(EVP_MD, EngineType.RSA);
         }
     }
     public static final class SHA512RSA extends OpenSSLSignature {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("RSA-SHA512");
         public SHA512RSA() throws NoSuchAlgorithmException {
-            super("RSA-SHA512", EngineType.RSA);
+            super(EVP_MD, EngineType.RSA);
         }
     }
     public static final class SHA1DSA extends OpenSSLSignature {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("DSA-SHA1");
         public SHA1DSA() throws NoSuchAlgorithmException {
-            super("DSA-SHA1", EngineType.DSA);
+            super(EVP_MD, EngineType.DSA);
         }
     }
     public static final class SHA1ECDSA extends OpenSSLSignature {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("SHA1");
         public SHA1ECDSA() throws NoSuchAlgorithmException {
-            super("SHA1", EngineType.EC);
+            super(EVP_MD, EngineType.EC);
         }
     }
     public static final class SHA224ECDSA extends OpenSSLSignature {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("SHA224");
         public SHA224ECDSA() throws NoSuchAlgorithmException {
-            super("SHA224", EngineType.EC);
+            super(EVP_MD, EngineType.EC);
         }
     }
     public static final class SHA256ECDSA extends OpenSSLSignature {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("SHA256");
         public SHA256ECDSA() throws NoSuchAlgorithmException {
-            super("SHA256", EngineType.EC);
+            super(EVP_MD, EngineType.EC);
         }
     }
     public static final class SHA384ECDSA extends OpenSSLSignature {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("SHA384");
         public SHA384ECDSA() throws NoSuchAlgorithmException {
-            super("SHA384", EngineType.EC);
+            super(EVP_MD, EngineType.EC);
         }
     }
     public static final class SHA512ECDSA extends OpenSSLSignature {
+        private static final long EVP_MD = NativeCrypto.EVP_get_digestbyname("SHA512");
         public SHA512ECDSA() throws NoSuchAlgorithmException {
-            super("SHA512", EngineType.EC);
+            super(EVP_MD, EngineType.EC);
         }
     }
 }
diff --git a/src/main/java/org/conscrypt/OpenSSLSignatureRawRSA.java b/src/main/java/org/conscrypt/OpenSSLSignatureRawRSA.java
index 9c4e4ad..7d4faf0 100644
--- a/src/main/java/org/conscrypt/OpenSSLSignatureRawRSA.java
+++ b/src/main/java/org/conscrypt/OpenSSLSignatureRawRSA.java
@@ -18,11 +18,10 @@
 
 import java.security.InvalidKeyException;
 import java.security.InvalidParameterException;
-import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.Signature;
 import java.security.SignatureException;
+import java.security.SignatureSpi;
 import java.security.interfaces.RSAPrivateCrtKey;
 import java.security.interfaces.RSAPrivateKey;
 import java.security.interfaces.RSAPublicKey;
@@ -31,7 +30,7 @@
  * Implements the JDK Signature interface needed for RAW RSA signature
  * generation and verification using OpenSSL.
  */
-public class OpenSSLSignatureRawRSA extends Signature {
+public class OpenSSLSignatureRawRSA extends SignatureSpi {
     /**
      * The current OpenSSL key we're operating on.
      */
@@ -52,13 +51,6 @@
      */
     private boolean inputIsTooLong;
 
-    /**
-     * Creates a new OpenSSLSignature instance for the given algorithm name.
-     */
-    public OpenSSLSignatureRawRSA() throws NoSuchAlgorithmException {
-        super("NONEwithRSA");
-    }
-
     @Override
     protected void engineUpdate(byte input) {
         final int oldOffset = inputOffset++;
diff --git a/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java b/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java
index 9367e1f..53df874 100644
--- a/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java
+++ b/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java
@@ -47,7 +47,7 @@
 
     @Override
     public String[] getDefaultCipherSuites() {
-        return NativeCrypto.getDefaultCipherSuites();
+        return sslParameters.getEnabledCipherSuites();
     }
 
     @Override
diff --git a/src/main/java/org/conscrypt/OpenSSLSocketImpl.java b/src/main/java/org/conscrypt/OpenSSLSocketImpl.java
index d05322c..8303b00 100644
--- a/src/main/java/org/conscrypt/OpenSSLSocketImpl.java
+++ b/src/main/java/org/conscrypt/OpenSSLSocketImpl.java
@@ -16,6 +16,7 @@
 
 package org.conscrypt;
 
+import org.conscrypt.util.Arrays;
 import dalvik.system.BlockGuard;
 import dalvik.system.CloseGuard;
 import java.io.FileDescriptor;
@@ -30,26 +31,18 @@
 import java.security.SecureRandom;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
+import javax.crypto.SecretKey;
 import javax.net.ssl.HandshakeCompletedEvent;
 import javax.net.ssl.HandshakeCompletedListener;
 import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLParameters;
 import javax.net.ssl.SSLProtocolException;
 import javax.net.ssl.SSLSession;
+import javax.net.ssl.X509KeyManager;
 import javax.net.ssl.X509TrustManager;
 import javax.security.auth.x500.X500Principal;
-import libcore.io.ErrnoException;
-import libcore.io.Libcore;
-import libcore.io.Streams;
-import libcore.io.StructTimeval;
-
-import static libcore.io.OsConstants.SOL_SOCKET;
-import static libcore.io.OsConstants.SO_SNDTIMEO;
 
 /**
  * Implementation of the class OpenSSLSocketImpl based on OpenSSL.
@@ -63,7 +56,8 @@
  */
 public class OpenSSLSocketImpl
         extends javax.net.ssl.SSLSocket
-        implements NativeCrypto.SSLHandshakeCallbacks {
+        implements NativeCrypto.SSLHandshakeCallbacks, SSLParametersImpl.AliasChooser,
+        SSLParametersImpl.PSKCallbacks {
 
     private static final boolean DBG_STATE = false;
 
@@ -138,29 +132,19 @@
     private final SSLParametersImpl sslParameters;
     private final CloseGuard guard = CloseGuard.get();
 
-    private String[] enabledProtocols;
-    private String[] enabledCipherSuites;
-    private byte[] npnProtocols;
-    private byte[] alpnProtocols;
-    private boolean useSessionTickets;
-    private boolean useSni;
+    private ArrayList<HandshakeCompletedListener> listeners;
 
     /**
-     * Whether the TLS Channel ID extension is enabled. This field is
-     * server-side only.
+     * Private key for the TLS Channel ID extension. This field is client-side
+     * only. Set during startHandshake.
      */
-    private boolean channelIdEnabled;
-
-    /**
-     * Private key for the TLS Channel ID extension. This field is
-     * client-side only. Set during startHandshake.
-     */
-    private OpenSSLKey channelIdPrivateKey;
+    OpenSSLKey channelIdPrivateKey;
 
     /** Set during startHandshake. */
     private OpenSSLSessionImpl sslSession;
 
-    private ArrayList<HandshakeCompletedListener> listeners;
+    /** Used during handshake callbacks. */
+    private OpenSSLSessionImpl handshakeSession;
 
     /**
      * Local cache of timeout to avoid getsockopt on every read and
@@ -179,20 +163,6 @@
         this.wrappedPort = -1;
         this.autoClose = false;
         this.sslParameters = sslParameters;
-        this.enabledProtocols = NativeCrypto.getDefaultProtocols();
-        this.enabledCipherSuites = NativeCrypto.getDefaultCipherSuites();
-    }
-
-    protected OpenSSLSocketImpl(SSLParametersImpl sslParameters,
-                                String[] enabledProtocols,
-                                String[] enabledCipherSuites) throws IOException {
-        this.socket = this;
-        this.wrappedHost = null;
-        this.wrappedPort = -1;
-        this.autoClose = false;
-        this.sslParameters = sslParameters;
-        this.enabledProtocols = enabledProtocols;
-        this.enabledCipherSuites = enabledCipherSuites;
     }
 
     protected OpenSSLSocketImpl(String host, int port, SSLParametersImpl sslParameters)
@@ -203,8 +173,6 @@
         this.wrappedPort = -1;
         this.autoClose = false;
         this.sslParameters = sslParameters;
-        this.enabledProtocols = NativeCrypto.getDefaultProtocols();
-        this.enabledCipherSuites = NativeCrypto.getDefaultCipherSuites();
     }
 
     protected OpenSSLSocketImpl(InetAddress address, int port, SSLParametersImpl sslParameters)
@@ -215,8 +183,6 @@
         this.wrappedPort = -1;
         this.autoClose = false;
         this.sslParameters = sslParameters;
-        this.enabledProtocols = NativeCrypto.getDefaultProtocols();
-        this.enabledCipherSuites = NativeCrypto.getDefaultCipherSuites();
     }
 
 
@@ -229,8 +195,6 @@
         this.wrappedPort = -1;
         this.autoClose = false;
         this.sslParameters = sslParameters;
-        this.enabledProtocols = NativeCrypto.getDefaultProtocols();
-        this.enabledCipherSuites = NativeCrypto.getDefaultCipherSuites();
     }
 
     protected OpenSSLSocketImpl(InetAddress address, int port,
@@ -242,8 +206,6 @@
         this.wrappedPort = -1;
         this.autoClose = false;
         this.sslParameters = sslParameters;
-        this.enabledProtocols = NativeCrypto.getDefaultProtocols();
-        this.enabledCipherSuites = NativeCrypto.getDefaultCipherSuites();
     }
 
     /**
@@ -257,55 +219,12 @@
         this.wrappedPort = port;
         this.autoClose = autoClose;
         this.sslParameters = sslParameters;
-        this.enabledProtocols = NativeCrypto.getDefaultProtocols();
-        this.enabledCipherSuites = NativeCrypto.getDefaultCipherSuites();
 
         // this.timeout is not set intentionally.
         // OpenSSLSocketImplWrapper.getSoTimeout will delegate timeout
         // to wrapped socket
     }
 
-    /**
-     * Gets the suitable session reference from the session cache container.
-     */
-    private OpenSSLSessionImpl getCachedClientSession(ClientSessionContext sessionContext) {
-        String hostname = getPeerHostName();
-        if (hostname == null) {
-            return null;
-        }
-        int port = getPeerPort();
-        OpenSSLSessionImpl session = (OpenSSLSessionImpl) sessionContext.getSession(hostname, port);
-        if (session == null) {
-            return null;
-        }
-
-        String protocol = session.getProtocol();
-        boolean protocolFound = false;
-        for (String enabledProtocol : enabledProtocols) {
-            if (protocol.equals(enabledProtocol)) {
-                protocolFound = true;
-                break;
-            }
-        }
-        if (!protocolFound) {
-            return null;
-        }
-
-        String cipherSuite = session.getCipherSuite();
-        boolean cipherSuiteFound = false;
-        for (String enabledCipherSuite : enabledCipherSuites) {
-            if (cipherSuite.equals(enabledCipherSuite)) {
-                cipherSuiteFound = true;
-                break;
-            }
-        }
-        if (!cipherSuiteFound) {
-            return null;
-        }
-
-        return session;
-    }
-
     private void checkOpen() throws SocketException {
         if (isClosed()) {
             throw new SocketException("Socket is closed");
@@ -343,117 +262,26 @@
 
         final boolean client = sslParameters.getUseClientMode();
 
-        final long sslCtxNativePointer = (client) ?
-                sslParameters.getClientSessionContext().sslCtxNativePointer :
-                sslParameters.getServerSessionContext().sslCtxNativePointer;
-
         sslNativePointer = 0;
         boolean releaseResources = true;
         try {
+            final AbstractSessionContext sessionContext = sslParameters.getSessionContext();
+            final long sslCtxNativePointer = sessionContext.sslCtxNativePointer;
             sslNativePointer = NativeCrypto.SSL_new(sslCtxNativePointer);
             guard.open("close");
 
-            if (npnProtocols != null) {
-                NativeCrypto.SSL_CTX_enable_npn(sslCtxNativePointer);
-            }
-
-            if (client && alpnProtocols != null) {
-                NativeCrypto.SSL_CTX_set_alpn_protos(sslCtxNativePointer, alpnProtocols);
-            }
-
-            NativeCrypto.setEnabledProtocols(sslNativePointer, enabledProtocols);
-            NativeCrypto.setEnabledCipherSuites(sslNativePointer, enabledCipherSuites);
-            if (useSessionTickets) {
-                NativeCrypto.SSL_clear_options(sslNativePointer, NativeCrypto.SSL_OP_NO_TICKET);
-            }
-            if (useSni) {
-                NativeCrypto.SSL_set_tlsext_host_name(sslNativePointer, getPeerHostName());
-            }
-
-            // BEAST attack mitigation (1/n-1 record splitting for CBC cipher suites with TLSv1 and
-            // SSLv3).
-            NativeCrypto.SSL_set_mode(
-                    sslNativePointer, NativeCrypto.SSL_MODE_CBC_RECORD_SPLITTING);
-
-            boolean enableSessionCreation = sslParameters.getEnableSessionCreation();
+            boolean enableSessionCreation = getEnableSessionCreation();
             if (!enableSessionCreation) {
                 NativeCrypto.SSL_set_session_creation_enabled(sslNativePointer,
-                                                              enableSessionCreation);
+                        enableSessionCreation);
             }
 
-            AbstractSessionContext sessionContext;
-            OpenSSLSessionImpl sessionToReuse;
-            if (client) {
-                // look for client session to reuse
-                ClientSessionContext clientSessionContext = sslParameters.getClientSessionContext();
-                sessionContext = clientSessionContext;
-                sessionToReuse = getCachedClientSession(clientSessionContext);
-                if (sessionToReuse != null) {
-                    NativeCrypto.SSL_set_session(sslNativePointer,
-                                                 sessionToReuse.sslSessionNativePointer);
-                }
-            } else {
-                sessionContext = sslParameters.getServerSessionContext();
-                sessionToReuse = null;
-            }
-
-            // setup server certificates and private keys.
-            // clients will receive a call back to request certificates.
-            if (!client) {
-                Set<String> keyTypes = new HashSet<String>();
-                for (long sslCipherNativePointer : NativeCrypto.SSL_get_ciphers(sslNativePointer)) {
-                    String keyType = getServerKeyType(sslCipherNativePointer);
-                    if (keyType != null) {
-                        keyTypes.add(keyType);
-                    }
-                }
-                for (String keyType : keyTypes) {
-                    try {
-                        setCertificate(sslParameters.getKeyManager().chooseServerAlias(keyType,
-                                                                                       null,
-                                                                                       this));
-                    } catch (CertificateEncodingException e) {
-                        throw new IOException(e);
-                    }
-                }
-            }
-
-            // setup peer certificate verification
-            if (client) {
-                // TODO support for anonymous cipher would require us to
-                // conditionally use SSL_VERIFY_NONE
-            } else {
-                // needing client auth takes priority...
-                boolean certRequested;
-                if (sslParameters.getNeedClientAuth()) {
-                    NativeCrypto.SSL_set_verify(sslNativePointer,
-                                                NativeCrypto.SSL_VERIFY_PEER
-                                                | NativeCrypto.SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
-                    certRequested = true;
-                // ... over just wanting it...
-                } else if (sslParameters.getWantClientAuth()) {
-                    NativeCrypto.SSL_set_verify(sslNativePointer,
-                                                NativeCrypto.SSL_VERIFY_PEER);
-                    certRequested = true;
-                // ... and it defaults properly so don't call SSL_set_verify in the common case.
-                } else {
-                    certRequested = false;
-                }
-
-                if (certRequested) {
-                    X509TrustManager trustManager = sslParameters.getTrustManager();
-                    X509Certificate[] issuers = trustManager.getAcceptedIssuers();
-                    if (issuers != null && issuers.length != 0) {
-                        byte[][] issuersBytes;
-                        try {
-                            issuersBytes = encodeIssuerX509Principals(issuers);
-                        } catch (CertificateEncodingException e) {
-                            throw new IOException("Problem encoding principals", e);
-                        }
-                        NativeCrypto.SSL_set_client_CA_list(sslNativePointer, issuersBytes);
-                    }
-                }
-            }
+            final OpenSSLSessionImpl sessionToReuse = sslParameters.getSessionToReuse(
+                    sslNativePointer, getPeerHostName(), getPeerPort());
+            sslParameters.setSSLParameters(sslCtxNativePointer, sslNativePointer, this, this,
+                    getPeerHostName());
+            sslParameters.setCertificateValidation(sslNativePointer);
+            sslParameters.setTlsChannelId(sslNativePointer, channelIdPrivateKey);
 
             // Temporarily use a different timeout for the handshake process
             int savedReadTimeoutMilliseconds = getSoTimeout();
@@ -463,21 +291,6 @@
                 setSoWriteTimeout(handshakeTimeoutMilliseconds);
             }
 
-            // TLS Channel ID
-            if (channelIdEnabled) {
-                if (client) {
-                    // Client-side TLS Channel ID
-                    if (channelIdPrivateKey == null) {
-                        throw new SSLHandshakeException("Invalid TLS channel ID key specified");
-                    }
-                    NativeCrypto.SSL_set1_tls_channel_id(sslNativePointer,
-                            channelIdPrivateKey.getPkeyContext());
-                } else {
-                    // Server-side TLS Channel ID
-                    NativeCrypto.SSL_enable_tls_channel_id(sslNativePointer);
-                }
-            }
-
             synchronized (stateLock) {
                 if (state == STATE_CLOSED) {
                     return;
@@ -487,8 +300,8 @@
             long sslSessionNativePointer;
             try {
                 sslSessionNativePointer = NativeCrypto.SSL_do_handshake(sslNativePointer,
-                        socket.getFileDescriptor$(), this, getSoTimeout(), client, npnProtocols,
-                        client ? null : alpnProtocols);
+                        Platform.getFileDescriptor(socket), this, getSoTimeout(), client,
+                        sslParameters.npnProtocols, client ? null : sslParameters.alpnProtocols);
             } catch (CertificateException e) {
                 SSLHandshakeException wrapper = new SSLHandshakeException(e.getMessage());
                 wrapper.initCause(e);
@@ -507,6 +320,15 @@
                     }
                 }
 
+                // Write CCS errors to EventLog
+                String message = e.getMessage();
+                // Must match error string of SSL_R_UNEXPECTED_CCS
+                if (message.contains("unexpected CCS")) {
+                    String logMessage = String.format("ssl_unexpected_ccs: host=%s",
+                            getPeerHostName());
+                    Platform.logEvent(logMessage);
+                }
+
                 throw e;
             }
 
@@ -519,27 +341,8 @@
                 }
             }
 
-            byte[] sessionId = NativeCrypto.SSL_SESSION_session_id(sslSessionNativePointer);
-            if (sessionToReuse != null && Arrays.equals(sessionToReuse.getId(), sessionId)) {
-                this.sslSession = sessionToReuse;
-                sslSession.lastAccessedTime = System.currentTimeMillis();
-                NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
-            } else {
-                if (!enableSessionCreation) {
-                    // Should have been prevented by NativeCrypto.SSL_set_session_creation_enabled
-                    throw new IllegalStateException("SSL Session may not be created");
-                }
-                X509Certificate[] localCertificates
-                        = createCertChain(NativeCrypto.SSL_get_certificate(sslNativePointer));
-                X509Certificate[] peerCertificates
-                        = createCertChain(NativeCrypto.SSL_get_peer_cert_chain(sslNativePointer));
-                this.sslSession = new OpenSSLSessionImpl(sslSessionNativePointer, localCertificates,
-                        peerCertificates, getPeerHostName(), getPeerPort(), sessionContext);
-                // if not, putSession later in handshakeCompleted() callback
-                if (handshakeCompleted) {
-                    sessionContext.putSession(sslSession);
-                }
-            }
+            sslSession = sslParameters.setupSession(sslSessionNativePointer, sslNativePointer,
+                    sessionToReuse, getPeerHostName(), getPeerPort(), handshakeCompleted);
 
             // Restore the original timeout now that the handshake is complete
             if (handshakeTimeoutMilliseconds >= 0) {
@@ -568,7 +371,8 @@
                 }
             }
         } catch (SSLProtocolException e) {
-            throw new SSLHandshakeException(e);
+            throw (SSLHandshakeException) new SSLHandshakeException("Handshake failed")
+                    .initCause(e);
         } finally {
             // on exceptional exit, treat the socket as closed
             if (releaseResources) {
@@ -591,15 +395,6 @@
         }
     }
 
-    private static byte[][] encodeIssuerX509Principals(X509Certificate[] certificates)
-            throws CertificateEncodingException {
-        byte[][] principalBytes = new byte[certificates.length][];
-        for (int i = 0; i < certificates.length; i++) {
-            principalBytes[i] = certificates[i].getIssuerX500Principal().getEncoded();
-        }
-        return principalBytes;
-    }
-
     String getPeerHostName() {
         if (wrappedHost != null) {
             return wrappedHost;
@@ -615,90 +410,33 @@
         return wrappedHost == null ? super.getPort() : wrappedPort;
     }
 
-    /**
-     * Return a possibly null array of X509Certificates given the
-     * possibly null array of DER encoded bytes.
-     */
-    private static OpenSSLX509Certificate[] createCertChain(long[] certificateRefs)
-            throws IOException {
-        if (certificateRefs == null) {
-            return null;
-        }
-        OpenSSLX509Certificate[] certificates = new OpenSSLX509Certificate[certificateRefs.length];
-        for (int i = 0; i < certificateRefs.length; i++) {
-            certificates[i] = new OpenSSLX509Certificate(certificateRefs[i]);
-        }
-        return certificates;
-    }
-
-    private void setCertificate(String alias) throws CertificateEncodingException, SSLException {
-        if (alias == null) {
-            return;
-        }
-        PrivateKey privateKey = sslParameters.getKeyManager().getPrivateKey(alias);
-        if (privateKey == null) {
-            return;
-        }
-        X509Certificate[] certificates = sslParameters.getKeyManager().getCertificateChain(alias);
-        if (certificates == null) {
-            return;
-        }
-
-        /*
-         * Make sure we keep a reference to the OpenSSLX509Certificate by using
-         * this array. Otherwise, if they're not OpenSSLX509Certificate
-         * instances originally, they may be garbage collected before we complete
-         * our JNI calls.
-         */
-        OpenSSLX509Certificate[] openSslCerts = new OpenSSLX509Certificate[certificates.length];
-        long[] x509refs = new long[certificates.length];
-        for (int i = 0; i < certificates.length; i++) {
-            OpenSSLX509Certificate openSslCert = OpenSSLX509Certificate
-                    .fromCertificate(certificates[i]);
-            openSslCerts[i] = openSslCert;
-            x509refs[i] = openSslCert.getContext();
-        }
-
-        // Note that OpenSSL says to use SSL_use_certificate before SSL_use_PrivateKey.
-        NativeCrypto.SSL_use_certificate(sslNativePointer, x509refs);
-
-        try {
-            final OpenSSLKey key = OpenSSLKey.fromPrivateKey(privateKey);
-            NativeCrypto.SSL_use_PrivateKey(sslNativePointer, key.getPkeyContext());
-        } catch (InvalidKeyException e) {
-            throw new SSLException(e);
-        }
-
-        // checks the last installed private key and certificate,
-        // so need to do this once per loop iteration
-        NativeCrypto.SSL_check_private_key(sslNativePointer);
-    }
-
     @Override
     @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks / client_cert_cb
     public void clientCertificateRequested(byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals)
             throws CertificateEncodingException, SSLException {
+        sslParameters.chooseClientCertificate(keyTypeBytes, asn1DerEncodedPrincipals,
+                sslNativePointer, this);
+    }
 
-        String[] keyTypes = new String[keyTypeBytes.length];
-        for (int i = 0; i < keyTypeBytes.length; i++) {
-            keyTypes[i] = getClientKeyType(keyTypeBytes[i]);
-        }
+    @Override
+    @SuppressWarnings("unused") // used by native psk_client_callback
+    public int clientPSKKeyRequested(String identityHint, byte[] identity, byte[] key) {
+        return sslParameters.clientPSKKeyRequested(identityHint, identity, key, this);
+    }
 
-        X500Principal[] issuers;
-        if (asn1DerEncodedPrincipals == null) {
-            issuers = null;
-        } else {
-            issuers = new X500Principal[asn1DerEncodedPrincipals.length];
-            for (int i = 0; i < asn1DerEncodedPrincipals.length; i++) {
-                issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
-            }
-        }
-        setCertificate(sslParameters.getKeyManager().chooseClientAlias(keyTypes, issuers, this));
+    @Override
+    @SuppressWarnings("unused") // used by native psk_server_callback
+    public int serverPSKKeyRequested(String identityHint, String identity, byte[] key) {
+        return sslParameters.serverPSKKeyRequested(identityHint, identity, key, this);
     }
 
     @Override
     @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks / info_callback
-    public void handshakeCompleted() {
+    public void onSSLStateChange(long sslSessionNativePtr, int type, int val) {
+        if (type != NativeCrypto.SSL_CB_HANDSHAKE_DONE) {
+            return;
+        }
+
         synchronized (stateLock) {
             if (state == STATE_HANDSHAKE_STARTED) {
                 // If sslSession is null, the handshake was completed during
@@ -763,9 +501,13 @@
 
     @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks
     @Override
-    public void verifyCertificateChain(long[] certRefs, String authMethod)
+    public void verifyCertificateChain(long sslSessionNativePtr, long[] certRefs, String authMethod)
             throws CertificateException {
         try {
+            X509TrustManager x509tm = sslParameters.getX509TrustManager();
+            if (x509tm == null) {
+                throw new CertificateException("No X.509 TrustManager");
+            }
             if (certRefs == null || certRefs.length == 0) {
                 throw new SSLException("Peer sent no certificate");
             }
@@ -773,25 +515,25 @@
             for (int i = 0; i < certRefs.length; i++) {
                 peerCertChain[i] = new OpenSSLX509Certificate(certRefs[i]);
             }
+
+            // Used for verifyCertificateChain callback
+            handshakeSession = new OpenSSLSessionImpl(sslSessionNativePtr, null, peerCertChain,
+                    getPeerHostName(), getPeerPort(), null);
+
             boolean client = sslParameters.getUseClientMode();
             if (client) {
-                X509TrustManager x509tm = sslParameters.getTrustManager();
-                if (x509tm instanceof TrustManagerImpl) {
-                    TrustManagerImpl tm = (TrustManagerImpl) x509tm;
-                    tm.checkServerTrusted(peerCertChain, authMethod, getPeerHostName());
-                } else {
-                    x509tm.checkServerTrusted(peerCertChain, authMethod);
-                }
+                Platform.checkServerTrusted(x509tm, peerCertChain, authMethod, getPeerHostName());
             } else {
                 String authType = peerCertChain[0].getPublicKey().getAlgorithm();
-                sslParameters.getTrustManager().checkClientTrusted(peerCertChain,
-                                                                   authType);
+                x509tm.checkClientTrusted(peerCertChain, authType);
             }
-
         } catch (CertificateException e) {
             throw e;
         } catch (Exception e) {
             throw new CertificateException(e);
+        } finally {
+            // Clear this before notifying handshake completed listeners
+            handshakeSession = null;
         }
     }
 
@@ -901,7 +643,9 @@
          */
         @Override
         public int read() throws IOException {
-            return Streams.readSingleByte(this);
+            byte[] buffer = new byte[1];
+            int result = read(buffer, 0, 1);
+            return (result != -1) ? buffer[0] & 0xff : -1;
         }
 
         /**
@@ -927,7 +671,7 @@
                     if (DBG_STATE) assertReadableOrWriteableState();
                 }
 
-                return NativeCrypto.SSL_read(sslNativePointer, socket.getFileDescriptor$(),
+                return NativeCrypto.SSL_read(sslNativePointer, Platform.getFileDescriptor(socket),
                         OpenSSLSocketImpl.this, buf, offset, byteCount, getSoTimeout());
             }
         }
@@ -966,7 +710,9 @@
          */
         @Override
         public void write(int oneByte) throws IOException {
-            Streams.writeSingleByte(this, oneByte);
+            byte[] buffer = new byte[1];
+            buffer[0] = (byte) (oneByte & 0xff);
+            write(buffer);
         }
 
         /**
@@ -991,7 +737,7 @@
                     if (DBG_STATE) assertReadableOrWriteableState();
                 }
 
-                NativeCrypto.SSL_write(sslNativePointer, socket.getFileDescriptor$(),
+                NativeCrypto.SSL_write(sslNativePointer, Platform.getFileDescriptor(socket),
                         OpenSSLSocketImpl.this, buf, offset, byteCount, writeTimeoutMilliseconds);
             }
         }
@@ -1017,7 +763,7 @@
             } catch (IOException e) {
                 // return an invalid session with
                 // invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
-                return SSLSessionImpl.getNullSession();
+                return SSLNullSession.getNullSession();
             }
         }
         return sslSession;
@@ -1068,12 +814,12 @@
 
     @Override
     public String[] getEnabledCipherSuites() {
-        return enabledCipherSuites.clone();
+        return sslParameters.getEnabledCipherSuites();
     }
 
     @Override
     public void setEnabledCipherSuites(String[] suites) {
-        enabledCipherSuites = NativeCrypto.checkEnabledCipherSuites(suites);
+        sslParameters.setEnabledCipherSuites(suites);
     }
 
     @Override
@@ -1083,12 +829,12 @@
 
     @Override
     public String[] getEnabledProtocols() {
-        return enabledProtocols.clone();
+        return sslParameters.getEnabledProtocols();
     }
 
     @Override
     public void setEnabledProtocols(String[] protocols) {
-        enabledProtocols = NativeCrypto.checkEnabledProtocols(protocols);
+        sslParameters.setEnabledProtocols(protocols);
     }
 
     /**
@@ -1097,7 +843,7 @@
      * @param useSessionTickets True to enable session tickets
      */
     public void setUseSessionTickets(boolean useSessionTickets) {
-        this.useSessionTickets = useSessionTickets;
+        sslParameters.useSessionTickets = useSessionTickets;
     }
 
     /**
@@ -1106,7 +852,7 @@
      * @param hostname the desired SNI hostname, or null to disable
      */
     public void setHostname(String hostname) {
-        useSni = hostname != null;
+        sslParameters.setUseSni(hostname != null);
         wrappedHost = hostname;
     }
 
@@ -1131,7 +877,7 @@
                                 + " begun.");
             }
         }
-        this.channelIdEnabled = enabled;
+        sslParameters.channelIdEnabled = enabled;
     }
 
     /**
@@ -1184,12 +930,12 @@
         }
 
         if (privateKey == null) {
-            this.channelIdEnabled = false;
-            this.channelIdPrivateKey = null;
+            sslParameters.channelIdEnabled = false;
+            channelIdPrivateKey = null;
         } else {
-            this.channelIdEnabled = true;
+            sslParameters.channelIdEnabled = true;
             try {
-                this.channelIdPrivateKey = OpenSSLKey.fromPrivateKey(privateKey);
+                channelIdPrivateKey = OpenSSLKey.fromPrivateKey(privateKey);
             } catch (InvalidKeyException e) {
                 // Will have error in startHandshake
             }
@@ -1259,12 +1005,7 @@
     public void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException {
         this.writeTimeoutMilliseconds = writeTimeoutMilliseconds;
 
-        StructTimeval tv = StructTimeval.fromMillis(writeTimeoutMilliseconds);
-        try {
-            Libcore.os.setsockoptTimeval(getFileDescriptor$(), SOL_SOCKET, SO_SNDTIMEO, tv);
-        } catch (ErrnoException errnoException) {
-            throw errnoException.rethrowAsSocketException();
-        }
+        Platform.setSocketTimeout(this, writeTimeoutMilliseconds);
     }
 
     /**
@@ -1347,7 +1088,7 @@
     private void shutdownAndFreeSslNative() throws IOException {
         try {
             BlockGuard.getThreadPolicy().onNetwork();
-            NativeCrypto.SSL_shutdown(sslNativePointer, socket.getFileDescriptor$(),
+            NativeCrypto.SSL_shutdown(sslNativePointer, Platform.getFileDescriptor(socket),
                     this);
         } catch (IOException ignored) {
             /*
@@ -1411,12 +1152,12 @@
         }
     }
 
-    @Override
+    /* @Override */
     public FileDescriptor getFileDescriptor$() {
         if (socket == this) {
-            return super.getFileDescriptor$();
+            return Platform.getFileDescriptorFromSSLSocket(this);
         } else {
-            return socket.getFileDescriptor$();
+            return Platform.getFileDescriptor(socket);
         }
     }
 
@@ -1449,7 +1190,7 @@
         if (npnProtocols != null && npnProtocols.length == 0) {
             throw new IllegalArgumentException("npnProtocols.length == 0");
         }
-        this.npnProtocols = npnProtocols;
+        sslParameters.npnProtocols = npnProtocols;
     }
 
     /**
@@ -1466,97 +1207,32 @@
         if (alpnProtocols != null && alpnProtocols.length == 0) {
             throw new IllegalArgumentException("alpnProtocols.length == 0");
         }
-        this.alpnProtocols = alpnProtocols;
+        sslParameters.alpnProtocols = alpnProtocols;
     }
 
-    /** Key type: RSA. */
-    private static final String KEY_TYPE_RSA = "RSA";
-
-    /** Key type: DSA. */
-    private static final String KEY_TYPE_DSA = "DSA";
-
-    /** Key type: Diffie-Hellman with RSA signature. */
-    private static final String KEY_TYPE_DH_RSA = "DH_RSA";
-
-    /** Key type: Diffie-Hellman with DSA signature. */
-    private static final String KEY_TYPE_DH_DSA = "DH_DSA";
-
-    /** Key type: Elliptic Curve. */
-    private static final String KEY_TYPE_EC = "EC";
-
-    /** Key type: Eliiptic Curve with ECDSA signature. */
-    private static final String KEY_TYPE_EC_EC = "EC_EC";
-
-    /** Key type: Eliiptic Curve with RSA signature. */
-    private static final String KEY_TYPE_EC_RSA = "EC_RSA";
-
-    /**
-     * Returns key type constant suitable for calling X509KeyManager.chooseServerAlias or
-     * X509ExtendedKeyManager.chooseEngineServerAlias. Returns {@code null} for anonymous key
-     * exchanges.
-     */
-    private static String getServerKeyType(long sslCipherNative) throws SSLException {
-        int algorithm_mkey = NativeCrypto.get_SSL_CIPHER_algorithm_mkey(sslCipherNative);
-        int algorithm_auth = NativeCrypto.get_SSL_CIPHER_algorithm_auth(sslCipherNative);
-        switch (algorithm_mkey) {
-            case NativeCrypto.SSL_kRSA:
-                return KEY_TYPE_RSA;
-            case NativeCrypto.SSL_kEDH:
-                switch (algorithm_auth) {
-                    case NativeCrypto.SSL_aDSS:
-                        return KEY_TYPE_DSA;
-                    case NativeCrypto.SSL_aRSA:
-                        return KEY_TYPE_RSA;
-                    case NativeCrypto.SSL_aNULL:
-                        return null;
-                }
-                break;
-            case NativeCrypto.SSL_kECDHr:
-                return KEY_TYPE_EC_RSA;
-            case NativeCrypto.SSL_kECDHe:
-                return KEY_TYPE_EC_EC;
-            case NativeCrypto.SSL_kEECDH:
-                switch (algorithm_auth) {
-                    case NativeCrypto.SSL_aECDSA:
-                        return KEY_TYPE_EC_EC;
-                    case NativeCrypto.SSL_aRSA:
-                        return KEY_TYPE_RSA;
-                    case NativeCrypto.SSL_aNULL:
-                        return null;
-                }
-                break;
-        }
-
-        throw new SSLException("Unsupported key exchange. "
-                + "mkey: 0x" + Long.toHexString(algorithm_mkey & 0xffffffffL)
-                + ", auth: 0x" + Long.toHexString(algorithm_auth & 0xffffffffL));
+    @Override
+    public String chooseServerAlias(X509KeyManager keyManager, String keyType) {
+        return keyManager.chooseServerAlias(keyType, null, this);
     }
 
-    /**
-     * Similar to getServerKeyType, but returns value given TLS
-     * ClientCertificateType byte values from a CertificateRequest
-     * message for use with X509KeyManager.chooseClientAlias or
-     * X509ExtendedKeyManager.chooseEngineClientAlias.
-     */
-    public static String getClientKeyType(byte keyType) {
-        // See also http://www.ietf.org/assignments/tls-parameters/tls-parameters.xml
-        switch (keyType) {
-            case NativeCrypto.TLS_CT_RSA_SIGN:
-                return KEY_TYPE_RSA; // RFC rsa_sign
-            case NativeCrypto.TLS_CT_DSS_SIGN:
-                return KEY_TYPE_DSA; // RFC dss_sign
-            case NativeCrypto.TLS_CT_RSA_FIXED_DH:
-                return KEY_TYPE_DH_RSA; // RFC rsa_fixed_dh
-            case NativeCrypto.TLS_CT_DSS_FIXED_DH:
-                return KEY_TYPE_DH_DSA; // RFC dss_fixed_dh
-            case NativeCrypto.TLS_CT_ECDSA_SIGN:
-                return KEY_TYPE_EC; // RFC ecdsa_sign
-            case NativeCrypto.TLS_CT_RSA_FIXED_ECDH:
-                return KEY_TYPE_EC_RSA; // RFC rsa_fixed_ecdh
-            case NativeCrypto.TLS_CT_ECDSA_FIXED_ECDH:
-                return KEY_TYPE_EC_EC; // RFC ecdsa_fixed_ecdh
-            default:
-                return null;
-        }
+    @Override
+    public String chooseClientAlias(X509KeyManager keyManager, X500Principal[] issuers,
+            String[] keyTypes) {
+        return keyManager.chooseClientAlias(keyTypes, null, this);
+    }
+
+    @Override
+    public String chooseServerPSKIdentityHint(PSKKeyManager keyManager) {
+        return keyManager.chooseServerKeyIdentityHint(this);
+    }
+
+    @Override
+    public String chooseClientPSKIdentity(PSKKeyManager keyManager, String identityHint) {
+        return keyManager.chooseClientKeyIdentity(identityHint, this);
+    }
+
+    @Override
+    public SecretKey getPSKKey(PSKKeyManager keyManager, String identityHint, String identity) {
+        return keyManager.getKey(identityHint, identity, this);
     }
 }
diff --git a/src/main/java/org/conscrypt/OpenSSLX509CRL.java b/src/main/java/org/conscrypt/OpenSSLX509CRL.java
index 56b99cc..58571e7 100644
--- a/src/main/java/org/conscrypt/OpenSSLX509CRL.java
+++ b/src/main/java/org/conscrypt/OpenSSLX509CRL.java
@@ -63,7 +63,7 @@
         } catch (Exception e) {
             throw new ParsingException(e);
         } finally {
-            NativeCrypto.BIO_free(bis.getBioContext());
+            bis.release();
         }
     }
 
@@ -77,7 +77,7 @@
         } catch (Exception e) {
             throw new ParsingException(e);
         } finally {
-            NativeCrypto.BIO_free(bis.getBioContext());
+            bis.release();
         }
 
         final List<OpenSSLX509CRL> certs = new ArrayList<OpenSSLX509CRL>(certRefs.length);
@@ -102,7 +102,7 @@
         } catch (Exception e) {
             throw new ParsingException(e);
         } finally {
-            NativeCrypto.BIO_free(bis.getBioContext());
+            bis.release();
         }
     }
 
@@ -117,7 +117,7 @@
         } catch (Exception e) {
             throw new ParsingException(e);
         } finally {
-            NativeCrypto.BIO_free(bis.getBioContext());
+            bis.release();
         }
 
         final List<OpenSSLX509CRL> certs = new ArrayList<OpenSSLX509CRL>(certRefs.length);
@@ -373,7 +373,7 @@
             NativeCrypto.X509_CRL_print(bioCtx, mContext);
             return os.toString();
         } finally {
-            NativeCrypto.BIO_free(bioCtx);
+            NativeCrypto.BIO_free_all(bioCtx);
         }
     }
 
diff --git a/src/main/java/org/conscrypt/OpenSSLX509CRLEntry.java b/src/main/java/org/conscrypt/OpenSSLX509CRLEntry.java
index 470cc98..c5b0bac 100644
--- a/src/main/java/org/conscrypt/OpenSSLX509CRLEntry.java
+++ b/src/main/java/org/conscrypt/OpenSSLX509CRLEntry.java
@@ -129,7 +129,7 @@
             NativeCrypto.X509_REVOKED_print(bioCtx, mContext);
             return os.toString();
         } finally {
-            NativeCrypto.BIO_free(bioCtx);
+            NativeCrypto.BIO_free_all(bioCtx);
         }
     }
 }
diff --git a/src/main/java/org/conscrypt/OpenSSLX509CertPath.java b/src/main/java/org/conscrypt/OpenSSLX509CertPath.java
index 57568b8..6cea051 100644
--- a/src/main/java/org/conscrypt/OpenSSLX509CertPath.java
+++ b/src/main/java/org/conscrypt/OpenSSLX509CertPath.java
@@ -32,7 +32,9 @@
 import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException;
 
 public class OpenSSLX509CertPath extends CertPath {
-    private static final byte[] PKCS7_MARKER = "-----BEGIN PKCS7".getBytes();
+    private static final byte[] PKCS7_MARKER = new byte[] {
+            '-', '-', '-', '-', '-', 'B', 'E', 'G', 'I', 'N', ' ', 'P', 'K', 'C', 'S', '7'
+    };
 
     private static final int PUSHBACK_SIZE = 64;
 
@@ -153,7 +155,7 @@
             }
             throw new CertificateException(e);
         } finally {
-            NativeCrypto.BIO_free(bis.getBioContext());
+            bis.release();
         }
 
         if (certRefs == null) {
diff --git a/src/main/java/org/conscrypt/OpenSSLX509Certificate.java b/src/main/java/org/conscrypt/OpenSSLX509Certificate.java
index 142978a..e3d8b01 100644
--- a/src/main/java/org/conscrypt/OpenSSLX509Certificate.java
+++ b/src/main/java/org/conscrypt/OpenSSLX509Certificate.java
@@ -72,7 +72,7 @@
         } catch (Exception e) {
             throw new ParsingException(e);
         } finally {
-            NativeCrypto.BIO_free(bis.getBioContext());
+            bis.release();
         }
     }
 
@@ -95,7 +95,7 @@
         } catch (Exception e) {
             throw new ParsingException(e);
         } finally {
-            NativeCrypto.BIO_free(bis.getBioContext());
+            bis.release();
         }
 
         if (certRefs == null) {
@@ -127,7 +127,7 @@
         } catch (Exception e) {
             throw new ParsingException(e);
         } finally {
-            NativeCrypto.BIO_free(bis.getBioContext());
+            bis.release();
         }
     }
 
@@ -143,7 +143,7 @@
         } catch (Exception e) {
             throw new ParsingException(e);
         } finally {
-            NativeCrypto.BIO_free(bis.getBioContext());
+            bis.release();
         }
 
         final List<OpenSSLX509Certificate> certs = new ArrayList<OpenSSLX509Certificate>(
@@ -226,11 +226,13 @@
     public void checkValidity(Date date) throws CertificateExpiredException,
             CertificateNotYetValidException {
         if (getNotBefore().compareTo(date) > 0) {
-            throw new CertificateNotYetValidException();
+            throw new CertificateNotYetValidException("Certificate not valid until "
+                    + getNotBefore().toString() + " (compared to " + date.toString() + ")");
         }
 
         if (getNotAfter().compareTo(date) < 0) {
-            throw new CertificateExpiredException();
+            throw new CertificateExpiredException("Certificate expired at "
+                    + getNotAfter().toString() + " (compared to " + date.toString() + ")");
         }
     }
 
@@ -401,7 +403,7 @@
             NativeCrypto.X509_print_ex(bioCtx, mContext, 0, 0);
             return os.toString();
         } finally {
-            NativeCrypto.BIO_free(bioCtx);
+            NativeCrypto.BIO_free_all(bioCtx);
         }
     }
 
diff --git a/src/main/java/org/conscrypt/OpenSSLX509CertificateFactory.java b/src/main/java/org/conscrypt/OpenSSLX509CertificateFactory.java
index 75fdedb..bae6c8e 100644
--- a/src/main/java/org/conscrypt/OpenSSLX509CertificateFactory.java
+++ b/src/main/java/org/conscrypt/OpenSSLX509CertificateFactory.java
@@ -34,7 +34,9 @@
 import java.util.List;
 
 public class OpenSSLX509CertificateFactory extends CertificateFactorySpi {
-    private static final byte[] PKCS7_MARKER = "-----BEGIN PKCS7".getBytes();
+    private static final byte[] PKCS7_MARKER = new byte[] {
+            '-', '-', '-', '-', '-', 'B', 'E', 'G', 'I', 'N', ' ', 'P', 'K', 'C', 'S', '7'
+    };
 
     private static final int PUSHBACK_SIZE = 64;
 
diff --git a/src/main/java/org/conscrypt/PRF.java b/src/main/java/org/conscrypt/PRF.java
deleted file mode 100644
index afbbb45..0000000
--- a/src/main/java/org/conscrypt/PRF.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- *  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.GeneralSecurityException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-import javax.crypto.Mac;
-import javax.crypto.spec.SecretKeySpec;
-import javax.net.ssl.SSLException;
-
-/**
- * This class provides functionality for computation
- * of PRF values for TLS (http://www.ietf.org/rfc/rfc2246.txt)
- * and SSL v3 (http://wp.netscape.com/eng/ssl3) protocols.
- */
-public class PRF {
-    private static Logger.Stream logger = Logger.getStream("prf");
-
-    private static Mac md5_mac;
-    private static Mac sha_mac;
-    protected static MessageDigest md5;
-    protected static MessageDigest sha;
-    private static int md5_mac_length;
-    private static int sha_mac_length;
-
-    static private void init() {
-        try {
-            md5_mac = Mac.getInstance("HmacMD5");
-            sha_mac = Mac.getInstance("HmacSHA1");
-        } catch (NoSuchAlgorithmException e) {
-            throw new AlertException(AlertProtocol.INTERNAL_ERROR,
-                    new SSLException(
-                "There is no provider of HmacSHA1 or HmacMD5 "
-                + "algorithms installed in the system"));
-        }
-        md5_mac_length = md5_mac.getMacLength();
-        sha_mac_length = sha_mac.getMacLength();
-        try {
-            md5 = MessageDigest.getInstance("MD5");
-            sha = MessageDigest.getInstance("SHA-1");
-        } catch (Exception e) {
-            throw new AlertException(AlertProtocol.INTERNAL_ERROR,
-                    new SSLException(
-                    "Could not initialize the Digest Algorithms."));
-        }
-    }
-
-    /**
-     * Computes the value of SSLv3 pseudo random function.
-     * @param   out:    the buffer to fill up with the value of the function.
-     * @param   secret: the buffer containing the secret value to generate prf.
-     * @param   seed:   the seed to be used.
-     */
-    static synchronized void computePRF_SSLv3(byte[] out, byte[] secret, byte[] seed) {
-        if (sha == null) {
-            init();
-        }
-        int pos = 0;
-        int iteration = 1;
-        byte[] digest;
-        while (pos < out.length) {
-            byte[] pref = new byte[iteration];
-            Arrays.fill(pref, (byte) (64 + iteration++));
-            sha.update(pref);
-            sha.update(secret);
-            sha.update(seed);
-            md5.update(secret);
-            md5.update(sha.digest());
-            digest = md5.digest(); // length == 16
-            if (pos + 16 > out.length) {
-                System.arraycopy(digest, 0, out, pos, out.length - pos);
-                pos = out.length;
-            } else {
-                System.arraycopy(digest, 0, out, pos, 16);
-                pos += 16;
-            }
-        }
-    }
-
-    /**
-     * Computes the value of TLS pseudo random function.
-     * @param   out:    the buffer to fill up with the value of the function.
-     * @param   secret: the buffer containing the secret value to generate prf.
-     * @param   str_bytes:  the label bytes to be used.
-     * @param   seed:   the seed to be used.
-     */
-    synchronized static void computePRF(byte[] out, byte[] secret,
-            byte[] str_byts, byte[] seed) throws GeneralSecurityException {
-        if (sha_mac == null) {
-            init();
-        }
-        // Do concatenation of the label with the seed:
-        // (metterings show that is is faster to concatenate the arrays
-        // and to call HMAC.update on cancatenation, than twice call for
-        // each of the part, i.e.:
-        // time(HMAC.update(label+seed))
-        //          < time(HMAC.update(label)) + time(HMAC.update(seed))
-        // but it takes more memmory (approximaty on 4%)
-        /*
-        byte[] tmp_seed = new byte[seed.length + str_byts.length];
-        System.arraycopy(str_byts, 0, tmp_seed, 0, str_byts.length);
-        System.arraycopy(seed, 0, tmp_seed, str_byts.length, seed.length);
-        seed = tmp_seed;
-        */
-        SecretKeySpec keyMd5;
-        SecretKeySpec keySha1;
-        if ((secret == null) || (secret.length == 0)) {
-            secret = new byte[8];
-            keyMd5 = new SecretKeySpec(secret, "HmacMD5");
-            keySha1 = new SecretKeySpec(secret, "HmacSHA1");
-        } else {
-            int length = secret.length >> 1; // division by 2
-            int offset = secret.length & 1;  // remainder
-            keyMd5 = new SecretKeySpec(secret, 0, length + offset,
-                    "HmacMD5");
-            keySha1 = new SecretKeySpec(secret, length, length
-                    + offset, "HmacSHA1");
-        }
-
-        //byte[] str_byts = label.getBytes();
-
-        if (logger != null) {
-            logger.println("secret["+secret.length+"]: ");
-            logger.printAsHex(16, "", " ", secret);
-            logger.println("label["+str_byts.length+"]: ");
-            logger.printAsHex(16, "", " ", str_byts);
-            logger.println("seed["+seed.length+"]: ");
-            logger.printAsHex(16, "", " ", seed);
-            logger.println("MD5 key:");
-            logger.printAsHex(16, "", " ", keyMd5.getEncoded());
-            logger.println("SHA1 key:");
-            logger.printAsHex(16, "", " ", keySha1.getEncoded());
-        }
-
-        md5_mac.init(keyMd5);
-        sha_mac.init(keySha1);
-
-        int pos = 0;
-        md5_mac.update(str_byts);
-        byte[] hash = md5_mac.doFinal(seed); // A(1)
-        while (pos < out.length) {
-            md5_mac.update(hash);
-            md5_mac.update(str_byts);
-            md5_mac.update(seed);
-            if (pos + md5_mac_length < out.length) {
-                md5_mac.doFinal(out, pos);
-                pos += md5_mac_length;
-            } else {
-                System.arraycopy(md5_mac.doFinal(), 0, out,
-                        pos, out.length - pos);
-                break;
-            }
-            // make A(i)
-            hash = md5_mac.doFinal(hash);
-        }
-        if (logger != null) {
-            logger.println("P_MD5:");
-            logger.printAsHex(md5_mac_length, "", " ", out);
-        }
-
-        pos = 0;
-        sha_mac.update(str_byts);
-        hash = sha_mac.doFinal(seed); // A(1)
-        byte[] sha1hash;
-        while (pos < out.length) {
-            sha_mac.update(hash);
-            sha_mac.update(str_byts);
-            sha1hash = sha_mac.doFinal(seed);
-            for (int i = 0; (i < sha_mac_length) & (pos < out.length); i++) {
-                out[pos++] ^= sha1hash[i];
-            }
-            // make A(i)
-            hash = sha_mac.doFinal(hash);
-        }
-
-        if (logger != null) {
-            logger.println("PRF:");
-            logger.printAsHex(sha_mac_length, "", " ", out);
-        }
-    }
-}
diff --git a/src/main/java/org/conscrypt/PSKKeyManager.java b/src/main/java/org/conscrypt/PSKKeyManager.java
new file mode 100644
index 0000000..d32b49d
--- /dev/null
+++ b/src/main/java/org/conscrypt/PSKKeyManager.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 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.net.Socket;
+import javax.crypto.SecretKey;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLEngine;
+
+/**
+ * Provider of key material for pre-shared key (PSK) key exchange used in TLS-PSK cipher suites.
+ *
+ * <h3>Overview of TLS-PSK</h3>
+ *
+ * <p>TLS-PSK is a set of TLS/SSL cipher suites which rely on a symmetric pre-shared key (PSK) to
+ * secure the TLS/SSL connection and mutually authenticate its peers. These cipher suites may be
+ * a more natural fit compared to conventional public key based cipher suites in some scenarios
+ * where communication between peers is bootstrapped via a separate step (for example, a pairing
+ * step) and requires both peers to authenticate each other. In such scenarios a symmetric key (PSK)
+ * can be exchanged during the bootstrapping step, removing the need to generate and exchange public
+ * key pairs and X.509 certificates.</p>
+ *
+ * <p>When a TLS-PSK cipher suite is used, both peers have to use the same key for the TLS/SSL
+ * handshake to succeed. Thus, both peers are implicitly authenticated by a successful handshake.
+ * This removes the need to use a {@code TrustManager} in conjunction with this {@code KeyManager}.
+ * </p>
+ *
+ * <h3>Supporting multiple keys</h3>
+ *
+ * <p>A peer may have multiple keys to choose from. To help choose the right key, during the
+ * handshake the server can provide a <em>PSK identity hint</em> to the client, and the client can
+ * provide a <em>PSK identity</em> to the server. The contents of these two pieces of information
+ * are specific to application-level protocols.</p>
+ *
+ * <p><em>NOTE: Both the PSK identity hint and the PSK identity are transmitted in cleartext.
+ * Moreover, these data are received and processed prior to peer having been authenticated. Thus,
+ * they must not contain or leak key material or other sensitive information, and should be
+ * treated (e.g., parsed) with caution, as untrusted data.</em></p>
+ *
+ * <p>The high-level flow leading to peers choosing a key during TLS/SSL handshake is as follows:
+ * <ol>
+ * <li>Server receives a handshake request from client.
+ * <li>Server replies, optionally providing a PSK identity hint to client.</li>
+ * <li>Client chooses the key.</li>
+ * <li>Client provides a PSK identity of the chosen key to server.</li>
+ * <li>Server chooses the key.</li>
+ * </ol></p>
+ *
+ * <p>In the flow above, either peer can signal that they do not have a suitable key, in which case
+ * the the handshake will be aborted immediately. This may enable a network attacker who does not
+ * know the key to learn which PSK identity hints or PSK identities are supported. If this is a
+ * concern then a randomly generated key should be used in the scenario where no key is available.
+ * This will lead to the handshake aborting later, due to key mismatch -- same as in the scenario
+ * where a key is available -- making it appear to the attacker that all PSK identity hints and PSK
+ * identities are supported.</p>
+ *
+ * <h3>Maximum sizes</h3>
+ *
+ * <p>The maximum supported sizes are as follows:
+ * <ul>
+ * <li>256 bytes for keys (see {@link #MAX_KEY_LENGTH_BYTES}),</li>
+ * <li>128 bytes for PSK identity and PSK identity hint (in modified UTF-8 representation) (see
+ * {@link #MAX_IDENTITY_LENGTH_BYTES} and {@link #MAX_IDENTITY_HINT_LENGTH_BYTES}).</li>
+ * </ul></p>
+ *
+ * <h3>Example</h3>
+ * 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
+ * PSKKeyManager myPskKeyManager = ...;
+ *
+ * SSLContext sslContext = SSLContext.getInstance("TLS");
+ * sslContext.init(
+ *         new KeyManager[] &#123;myPskKeyManager&#125;,
+ *         new TrustManager[0], // No TrustManagers needed for TLS-PSK
+ *         null // Use the default source of entropy
+ *         );
+ *
+ * SSLSocket sslSocket = (SSLSocket) sslContext.getSocketFactory().createSocket(...);
+ * }</pre>
+ */
+public interface PSKKeyManager extends KeyManager {
+
+    /**
+     * Maximum supported length (in bytes) for PSK identity hint (in modified UTF-8 representation).
+     */
+    int MAX_IDENTITY_HINT_LENGTH_BYTES = 128;
+
+    /** Maximum supported length (in bytes) for PSK identity (in modified UTF-8 representation). */
+    int MAX_IDENTITY_LENGTH_BYTES = 128;
+
+    /** Maximum supported length (in bytes) for PSK key. */
+    int MAX_KEY_LENGTH_BYTES = 256;
+
+    /**
+     * Gets the PSK identity hint to report to the client to help agree on the PSK for the provided
+     * socket.
+     *
+     * @return PSK identity hint to be provided to the client or {@code null} to provide no hint.
+     */
+    String chooseServerKeyIdentityHint(Socket socket);
+
+    /**
+     * Gets the PSK identity hint to report to the client to help agree on the PSK for the provided
+     * engine.
+     *
+     * @return PSK identity hint to be provided to the client or {@code null} to provide no hint.
+     */
+    String chooseServerKeyIdentityHint(SSLEngine engine);
+
+    /**
+     * Gets the PSK identity to report to the server to help agree on the PSK for the provided
+     * socket.
+     *
+     * @param identityHint identity hint provided by the server or {@code null} if none provided.
+     *
+     * @return PSK identity to provide to the server. {@code null} is permitted but will be
+     *         converted into an empty string.
+     */
+    String chooseClientKeyIdentity(String identityHint, Socket socket);
+
+    /**
+     * Gets the PSK identity to report to the server to help agree on the PSK for the provided
+     * engine.
+     *
+     * @param identityHint identity hint provided by the server or {@code null} if none provided.
+     *
+     * @return PSK identity to provide to the server. {@code null} is permitted but will be
+     *         converted into an empty string.
+     */
+    String chooseClientKeyIdentity(String identityHint, SSLEngine engine);
+
+    /**
+     * Gets the PSK to use for the provided socket.
+     *
+     * @param identityHint identity hint provided by the server to help select the key or
+     *        {@code null} if none provided.
+     * @param identity identity provided by the client to help select the key.
+     *
+     * @return key or {@code null} to signal to peer that no suitable key is available and to abort
+     *         the handshake.
+     */
+    SecretKey getKey(String identityHint, String identity, Socket socket);
+
+    /**
+     * Gets the PSK to use for the provided engine.
+     *
+     * @param identityHint identity hint provided by the server to help select the key or
+     *        {@code null} if none provided.
+     * @param identity identity provided by the client to help select the key.
+     *
+     * @return key or {@code null} to signal to peer that no suitable key is available and to abort
+     *         the handshake.
+     */
+    SecretKey getKey(String identityHint, String identity, SSLEngine engine);
+}
\ No newline at end of file
diff --git a/src/main/java/org/conscrypt/Platform.java b/src/main/java/org/conscrypt/Platform.java
deleted file mode 100644
index 3a577ce..0000000
--- a/src/main/java/org/conscrypt/Platform.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 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 org.apache.harmony.security.utils.AlgNameMapper;
-import org.apache.harmony.security.utils.AlgNameMapperSource;
-
-class Platform {
-    private static class NoPreloadHolder {
-        public static final Platform MAPPER = new Platform();
-    }
-
-    /**
-     * Runs all the setup for the platform that only needs to run once.
-     */
-    public static void setup() {
-        NoPreloadHolder.MAPPER.ping();
-    }
-
-    /**
-     * Just a placeholder to make sure the class is initialized.
-     */
-    private void ping() {
-    }
-
-    private Platform() {
-        AlgNameMapper.setSource(new OpenSSLMapper());
-    }
-
-    private static class OpenSSLMapper implements AlgNameMapperSource {
-        @Override
-        public String mapNameToOid(String algName) {
-            return NativeCrypto.OBJ_txt2nid_oid(algName);
-        }
-
-        @Override
-        public String mapOidToName(String oid) {
-            return NativeCrypto.OBJ_txt2nid_longName(oid);
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/org/conscrypt/ProtocolVersion.java b/src/main/java/org/conscrypt/ProtocolVersion.java
deleted file mode 100644
index 4e2555c..0000000
--- a/src/main/java/org/conscrypt/ProtocolVersion.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- *  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.util.Hashtable;
-
-/**
- *
- * Represents Protocol Version
- */
-public class ProtocolVersion {
-    /**
-     * Protocols supported by this provider implementation
-     */
-    public static final String[] supportedProtocols = new String[] { "TLSv1",
-            "SSLv3" };
-
-    private static Hashtable<String, ProtocolVersion> protocolsByName = new Hashtable<String, ProtocolVersion>(4);
-
-    /**
-     *
-     * Returns true if protocol version is supported
-     *
-     * @param version
-     */
-    public static boolean isSupported(byte[] version) {
-        if (version[0] != 3 || (version[1] != 0 && version[1] != 1)) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Returns ProtocolVersion
-     *
-     * @param version
-     * @return
-     */
-    public static ProtocolVersion getByVersion(byte[] version) {
-        if (version[0] == 3) {
-            if (version[1] == 1) {
-                return TLSv1;
-            }
-            if (version[1] == 0) {
-                return SSLv3;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns true if provider supports protocol version
-     *
-     * @param name
-     * @return
-     */
-    public static boolean isSupported(String name) {
-        return protocolsByName.containsKey(name);
-    }
-
-    /**
-     * Returns ProtocolVersion
-     *
-     * @param name
-     * @return
-     */
-    public static ProtocolVersion getByName(String name) {
-        return protocolsByName.get(name);
-    }
-
-    /**
-     * Highest protocol version supported by provider implementation
-     *
-     * @param protocols
-     * @return
-     */
-    public static ProtocolVersion getLatestVersion(String[] protocols) {
-        if (protocols == null || protocols.length == 0) {
-            return null;
-        }
-        ProtocolVersion latest = getByName(protocols[0]);
-        ProtocolVersion current;
-        for (int i = 1; i < protocols.length; i++) {
-            current = getByName(protocols[i]);
-            if (current == null) {
-                continue;
-            }
-            if ((latest == null)
-                    || (latest.version[0] < current.version[0])
-                    || (latest.version[0] == current.version[0] && latest.version[1] < current.version[1])) {
-                latest = current;
-            }
-        }
-        return latest;
-
-    }
-
-    /**
-     * SSL 3.0 protocol version
-     */
-    public static final ProtocolVersion SSLv3 = new ProtocolVersion("SSLv3",
-            new byte[] { 3, 0 });
-
-    /**
-     * TLS 1.0 protocol version
-     */
-    public static final ProtocolVersion TLSv1 = new ProtocolVersion("TLSv1",
-            new byte[] { 3, 1 });
-
-    static {
-        protocolsByName.put(SSLv3.name, SSLv3);
-        protocolsByName.put(TLSv1.name, TLSv1);
-        protocolsByName.put("SSL", SSLv3);
-        protocolsByName.put("TLS", TLSv1);
-    }
-
-    /**
-     * Protocol name
-     */
-    public final String name;
-
-    /**
-     * Protocol version as byte array
-     */
-    public final byte[] version;
-
-    private ProtocolVersion(String name, byte[] version) {
-        this.name = name;
-        this.version = version;
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/org/conscrypt/SSLBufferedInput.java b/src/main/java/org/conscrypt/SSLBufferedInput.java
deleted file mode 100644
index d2834d3..0000000
--- a/src/main/java/org/conscrypt/SSLBufferedInput.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- *  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.io.IOException;
-import java.nio.ByteBuffer;
-
-/**
- * This is a wrapper input stream for ByteBuffer data source.
- * Among with the read functionality it provides info
- * about number of cunsumed bytes from the source ByteBuffer.
- * The source ByteBuffer object can be reseted.
- * So one instance of this wrapper can be reused for several
- * ByteBuffer data sources.
- */
-public class SSLBufferedInput extends SSLInputStream {
-
-    private ByteBuffer in;
-    private int bytik;
-    private int consumed = 0;
-
-    /**
-     * Constructor
-     */
-    protected SSLBufferedInput() {}
-
-    /**
-     * Sets the buffer as a data source
-     */
-    protected void setSourceBuffer(ByteBuffer in) {
-        consumed = 0;
-        this.in = in;
-    }
-
-    @Override
-    public int available() throws IOException {
-        // in assumption that the buffer has been set
-        return in.remaining();
-    }
-
-    /**
-     * Returns the number of consumed bytes.
-     */
-    protected int consumed() {
-        return consumed;
-    }
-
-    /**
-     * Reads the following byte value. If there are no bytes in the source
-     * buffer, method throws java.nio.BufferUnderflowException.
-     */
-    @Override
-    public int read() throws IOException {
-        // TODO: implement optimized read(int)
-        // and read(byte[], int, int) methods
-        bytik = in.get() & 0x00FF;
-        consumed ++;
-        return bytik;
-    }
-}
diff --git a/src/main/java/org/conscrypt/SSLEngineAppData.java b/src/main/java/org/conscrypt/SSLEngineAppData.java
deleted file mode 100644
index d1b87a2..0000000
--- a/src/main/java/org/conscrypt/SSLEngineAppData.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- *  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.nio.ByteBuffer;
-import javax.net.ssl.SSLException;
-
-/**
- * This class is used to retrieve the application data
- * arrived for the SSLEngine.
- */
-public class SSLEngineAppData implements Appendable {
-
-    /**
-     * Buffer containing received application data.
-     */
-    byte[] buffer;
-
-    /**
-     * Constructor
-     */
-    protected SSLEngineAppData() {}
-
-    /**
-     * Stores received data. The source data is not cloned,
-     * just the array reference is remembered into the buffer field.
-     */
-    @Override
-    public void append(byte[] src) {
-        if (buffer != null) {
-            throw new AlertException(
-                AlertProtocol.INTERNAL_ERROR,
-                new SSLException("Attempt to override the data"));
-        }
-        buffer = src;
-    }
-
-    /**
-     * Places the data from the buffer into the array of destination
-     * ByteBuffer objects.
-     */
-    protected int placeTo(ByteBuffer[] dsts, int offset, int length) {
-        if (buffer == null) {
-            return 0;
-        }
-        int pos = 0;
-        int len = buffer.length;
-        int rem;
-        // write data to the buffers
-        for (int i=offset; i<offset+length; i++) {
-            rem = dsts[i].remaining();
-            // TODO: optimization work - use hasArray, array(), arraycopy
-            if (len - pos < rem) {
-                // can fully write remaining data into buffer
-                dsts[i].put(buffer, pos, len - pos);
-                pos = len;
-                // data was written, exit
-                break;
-            }
-            // write chunk of data
-            dsts[i].put(buffer, pos, rem);
-            pos += rem;
-        }
-        if (pos != len) {
-            // The data did not feet into the buffers,
-            // it should not happen, because the destination buffers
-            // had been checked for the space before record unwrapping.
-            // But if it so, we should allert about internal error.
-            throw new AlertException(
-                AlertProtocol.INTERNAL_ERROR,
-                new SSLException(
-                    "The received application data could not be fully written"
-                    + "into the destination buffers"));
-        }
-        buffer = null;
-        return len;
-    }
-}
-
diff --git a/src/main/java/org/conscrypt/SSLEngineDataStream.java b/src/main/java/org/conscrypt/SSLEngineDataStream.java
deleted file mode 100644
index 8dfdb9f..0000000
--- a/src/main/java/org/conscrypt/SSLEngineDataStream.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- *  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.nio.ByteBuffer;
-
-/**
- * This class provides the DataStream functionality
- * implemented over the array of ByteBuffer instances.
- * Among with the data chunks read functionality
- * it provides the info about amount of consumed data.
- * The source ByteBuffer objects can be replaced by other.
- * So one instance of this wrapper can be reused for several
- * data sources.
- */
-public class SSLEngineDataStream implements DataStream {
-
-    private ByteBuffer[] srcs;
-    private int offset;
-    private int limit;
-
-    private int available;
-    private int consumed;
-
-    protected SSLEngineDataStream() {}
-
-    protected void setSourceBuffers(ByteBuffer[] srcs, int offset, int length) {
-        this.srcs = srcs;
-        this.offset = offset;
-        this.limit = offset+length;
-        this.consumed = 0;
-        this.available = 0;
-        for (int i=offset; i<limit; i++) {
-            if (srcs[i] == null) {
-                throw new IllegalStateException(
-                        "Some of the input parameters are null");
-            }
-            available += srcs[i].remaining();
-        }
-    }
-
-    public int available() {
-        return available;
-    }
-
-    @Override
-    public boolean hasData() {
-        return available > 0;
-    }
-
-    @Override
-    public byte[] getData(int length) {
-        // TODO: optimization work:
-        // use ByteBuffer.get(byte[],int,int)
-        // and ByteBuffer.hasArray() methods
-        int len = (length < available) ? length : available;
-        available -= len;
-        consumed += len;
-        byte[] res = new byte[len];
-        int pos = 0;
-        loop:
-        for (; offset<limit; offset++) {
-            while (srcs[offset].hasRemaining()) {
-                res[pos++] = srcs[offset].get();
-                len --;
-                if (len == 0) {
-                    break loop;
-                }
-            }
-        }
-        return res;
-    }
-
-    protected int consumed() {
-        return consumed;
-    }
-}
-
diff --git a/src/main/java/org/conscrypt/SSLEngineImpl.java b/src/main/java/org/conscrypt/SSLEngineImpl.java
deleted file mode 100644
index 3ed9980..0000000
--- a/src/main/java/org/conscrypt/SSLEngineImpl.java
+++ /dev/null
@@ -1,753 +0,0 @@
-/*
- *  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.io.IOException;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.nio.ReadOnlyBufferException;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLEngineResult;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLSession;
-
-/**
- * Implementation of SSLEngine.
- * @see javax.net.ssl.SSLEngine class documentation for more information.
- */
-public class SSLEngineImpl extends SSLEngine {
-
-    // indicates if peer mode was set
-    private boolean peer_mode_was_set = false;
-    // indicates if handshake has been started
-    private boolean handshake_started = false;
-    // indicates if inbound operations finished
-    private boolean isInboundDone = false;
-    // indicates if outbound operations finished
-    private boolean isOutboundDone = false;
-    // indicates if close_notify alert had been sent to another peer
-    private boolean close_notify_was_sent = false;
-    // indicates if close_notify alert had been received from another peer
-    private boolean close_notify_was_received = false;
-    // indicates if engine was closed (it means that
-    // all the works on it are done, except (probably) some finalizing work)
-    private boolean engine_was_closed = false;
-    // indicates if engine was shutted down (it means that
-    // all cleaning work had been done and the engine is not operable)
-    private boolean engine_was_shutteddown = false;
-
-    // record protocol to be used
-    protected SSLRecordProtocol recordProtocol;
-    // input stream for record protocol
-    private SSLBufferedInput recProtIS;
-    // handshake protocol to be used
-    private HandshakeProtocol handshakeProtocol;
-    // alert protocol to be used
-    private AlertProtocol alertProtocol;
-    // place where application data will be stored
-    private SSLEngineAppData appData;
-    // outcoming application data stream
-    private SSLEngineDataStream dataStream = new SSLEngineDataStream();
-    // active session object
-    private SSLSessionImpl session;
-
-    // peer configuration parameters
-    protected SSLParametersImpl sslParameters;
-
-    // in case of emergency situations when data could not be
-    // placed in destination buffers it will be stored in this
-    // fields
-    private byte[] remaining_wrapped_data = null;
-    private byte[] remaining_hsh_data = null;
-
-    // logger
-    private Logger.Stream logger = Logger.getStream("engine");
-
-    protected SSLEngineImpl(SSLParametersImpl sslParameters) {
-        this.sslParameters = sslParameters;
-    }
-
-    protected SSLEngineImpl(String host, int port, SSLParametersImpl sslParameters) {
-        super(host, port);
-        this.sslParameters = sslParameters;
-    }
-
-    /**
-     * Starts the handshake.
-     * @throws  SSLException
-     * @see javax.net.ssl.SSLEngine#beginHandshake() method documentation
-     * for more information
-     */
-    @Override
-    public void beginHandshake() throws SSLException {
-        if (engine_was_closed) {
-            throw new SSLException("Engine has already been closed.");
-        }
-        if (!peer_mode_was_set) {
-            throw new IllegalStateException("Client/Server mode was not set");
-        }
-        if (!handshake_started) {
-            handshake_started = true;
-            if (getUseClientMode()) {
-                handshakeProtocol = new ClientHandshakeImpl(this);
-            } else {
-                handshakeProtocol = new ServerHandshakeImpl(this);
-            }
-            appData = new SSLEngineAppData();
-            alertProtocol = new AlertProtocol();
-            recProtIS = new SSLBufferedInput();
-            recordProtocol = new SSLRecordProtocol(handshakeProtocol,
-                    alertProtocol, recProtIS, appData);
-        }
-        handshakeProtocol.start();
-    }
-
-    /**
-     * Closes inbound operations of this engine
-     * @throws  SSLException
-     * @see javax.net.ssl.SSLEngine#closeInbound() method documentation
-     * for more information
-     */
-    @Override
-    public void closeInbound() throws SSLException {
-        if (logger != null) {
-            logger.println("closeInbound() "+isInboundDone);
-        }
-        if (isInboundDone) {
-            return;
-        }
-        isInboundDone = true;
-        engine_was_closed = true;
-        if (handshake_started) {
-            if (!close_notify_was_received) {
-                if (session != null) {
-                    session.invalidate();
-                }
-                alertProtocol.alert(AlertProtocol.FATAL,
-                        AlertProtocol.INTERNAL_ERROR);
-                throw new SSLException("Inbound is closed before close_notify "
-                        + "alert has been received.");
-            }
-        } else {
-            // engine is closing before initial handshake has been made
-            shutdown();
-        }
-    }
-
-    /**
-     * Closes outbound operations of this engine
-     * @see javax.net.ssl.SSLEngine#closeOutbound() method documentation
-     * for more information
-     */
-    @Override
-    public void closeOutbound() {
-        if (logger != null) {
-            logger.println("closeOutbound() "+isOutboundDone);
-        }
-        if (isOutboundDone) {
-            return;
-        }
-        isOutboundDone = true;
-        if (handshake_started) {
-            // initial handshake had been started
-            alertProtocol.alert(AlertProtocol.WARNING,
-                    AlertProtocol.CLOSE_NOTIFY);
-            close_notify_was_sent = true;
-        } else {
-            // engine is closing before initial handshake has been made
-            shutdown();
-        }
-        engine_was_closed = true;
-    }
-
-    /**
-     * Returns handshake's delegated tasks to be run
-     * @return the delegated task to be executed.
-     * @see javax.net.ssl.SSLEngine#getDelegatedTask() method documentation
-     * for more information
-     */
-    @Override
-    public Runnable getDelegatedTask() {
-        return handshakeProtocol.getTask();
-    }
-
-    /**
-     * Returns names of supported cipher suites.
-     * @return array of strings containing the names of supported cipher suites
-     * @see javax.net.ssl.SSLEngine#getSupportedCipherSuites() method
-     * documentation for more information
-     */
-    @Override
-    public String[] getSupportedCipherSuites() {
-        return CipherSuite.getSupportedCipherSuiteNames();
-    }
-
-    // --------------- SSLParameters based methods ---------------------
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#getEnabledCipherSuites() method
-     * documentation for more information
-     */
-    @Override
-    public String[] getEnabledCipherSuites() {
-        return sslParameters.getEnabledCipherSuites();
-    }
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#setEnabledCipherSuites(String[]) method
-     * documentation for more information
-     */
-    @Override
-    public void setEnabledCipherSuites(String[] suites) {
-        sslParameters.setEnabledCipherSuites(suites);
-    }
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#getSupportedProtocols() method
-     * documentation for more information
-     */
-    @Override
-    public String[] getSupportedProtocols() {
-        return ProtocolVersion.supportedProtocols.clone();
-    }
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#getEnabledProtocols() method
-     * documentation for more information
-     */
-    @Override
-    public String[] getEnabledProtocols() {
-        return sslParameters.getEnabledProtocols();
-    }
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#setEnabledProtocols(String[]) method
-     * documentation for more information
-     */
-    @Override
-    public void setEnabledProtocols(String[] protocols) {
-        sslParameters.setEnabledProtocols(protocols);
-    }
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#setUseClientMode(boolean) method
-     * documentation for more information
-     */
-    @Override
-    public void setUseClientMode(boolean mode) {
-        if (handshake_started) {
-            throw new IllegalArgumentException(
-            "Could not change the mode after the initial handshake has begun.");
-        }
-        sslParameters.setUseClientMode(mode);
-        peer_mode_was_set = true;
-    }
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#getUseClientMode() method
-     * documentation for more information
-     */
-    @Override
-    public boolean getUseClientMode() {
-        return sslParameters.getUseClientMode();
-    }
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#setNeedClientAuth(boolean) method
-     * documentation for more information
-     */
-    @Override
-    public void setNeedClientAuth(boolean need) {
-        sslParameters.setNeedClientAuth(need);
-    }
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#getNeedClientAuth() method
-     * documentation for more information
-     */
-    @Override
-    public boolean getNeedClientAuth() {
-        return sslParameters.getNeedClientAuth();
-    }
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#setWantClientAuth(boolean) method
-     * documentation for more information
-     */
-    @Override
-    public void setWantClientAuth(boolean want) {
-        sslParameters.setWantClientAuth(want);
-    }
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#getWantClientAuth() method
-     * documentation for more information
-     */
-    @Override
-    public boolean getWantClientAuth() {
-        return sslParameters.getWantClientAuth();
-    }
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#setEnableSessionCreation(boolean) method
-     * documentation for more information
-     */
-    @Override
-    public void setEnableSessionCreation(boolean flag) {
-        sslParameters.setEnableSessionCreation(flag);
-    }
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#getEnableSessionCreation() method
-     * documentation for more information
-     */
-    @Override
-    public boolean getEnableSessionCreation() {
-        return sslParameters.getEnableSessionCreation();
-    }
-
-    // -----------------------------------------------------------------
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#getHandshakeStatus() method
-     * documentation for more information
-     */
-    @Override
-    public SSLEngineResult.HandshakeStatus getHandshakeStatus() {
-        if (!handshake_started || engine_was_shutteddown) {
-            // initial handshake has not been started yet
-            return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
-        }
-        if (alertProtocol.hasAlert()) {
-            // need to send an alert
-            return SSLEngineResult.HandshakeStatus.NEED_WRAP;
-        }
-        if (close_notify_was_sent && !close_notify_was_received) {
-            // waiting for "close_notify" response
-            return SSLEngineResult.HandshakeStatus.NEED_UNWRAP;
-        }
-        return handshakeProtocol.getStatus();
-    }
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#getSession() method
-     * documentation for more information
-     */
-    @Override
-    public SSLSession getSession() {
-        if (session != null) {
-            return session;
-        }
-        return SSLSessionImpl.getNullSession();
-    }
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#isInboundDone() method
-     * documentation for more information
-     */
-    @Override
-    public boolean isInboundDone() {
-        return isInboundDone || engine_was_closed;
-    }
-
-    /**
-     * This method works according to the specification of implemented class.
-     * @see javax.net.ssl.SSLEngine#isOutboundDone() method
-     * documentation for more information
-     */
-    @Override
-    public boolean isOutboundDone() {
-        return isOutboundDone;
-    }
-
-    /**
-     * Decodes one complete SSL/TLS record provided in the source buffer.
-     * If decoded record contained application data, this data will
-     * be placed in the destination buffers.
-     * For more information about TLS record fragmentation see
-     * TLS v 1 specification (http://www.ietf.org/rfc/rfc2246.txt) p 6.2.
-     * @param src source buffer containing SSL/TLS record.
-     * @param dsts destination buffers to place received application data.
-     * @see javax.net.ssl.SSLEngine#unwrap(ByteBuffer,ByteBuffer[],int,int)
-     * method documentation for more information
-     */
-    @Override
-    public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts,
-                                int offset, int length) throws SSLException {
-        if (engine_was_shutteddown) {
-            return new SSLEngineResult(SSLEngineResult.Status.CLOSED,
-                    SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, 0, 0);
-        }
-        if ((src == null) || (dsts == null)) {
-            throw new IllegalStateException(
-                    "Some of the input parameters are null");
-        }
-
-        if (!handshake_started) {
-            beginHandshake();
-        }
-
-        SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
-        // If is is initial handshake or connection closure stage,
-        // check if this call was made in spite of handshake status
-        if ((session == null || engine_was_closed) && (
-                    handshakeStatus.equals(
-                        SSLEngineResult.HandshakeStatus.NEED_WRAP) ||
-                    handshakeStatus.equals(
-                        SSLEngineResult.HandshakeStatus.NEED_TASK))) {
-            return new SSLEngineResult(
-                    getEngineStatus(), handshakeStatus, 0, 0);
-        }
-
-        if (src.remaining() < recordProtocol.getMinRecordSize()) {
-            return new SSLEngineResult(
-                    SSLEngineResult.Status.BUFFER_UNDERFLOW,
-                    getHandshakeStatus(), 0, 0);
-        }
-
-        try {
-            src.mark();
-            // check the destination buffers and count their capacity
-            int capacity = 0;
-            for (int i=offset; i<offset+length; i++) {
-                if (dsts[i] == null) {
-                    throw new IllegalStateException(
-                            "Some of the input parameters are null");
-                }
-                if (dsts[i].isReadOnly()) {
-                    throw new ReadOnlyBufferException();
-                }
-                capacity += dsts[i].remaining();
-            }
-            if (capacity < recordProtocol.getDataSize(src.remaining())) {
-                return new SSLEngineResult(
-                        SSLEngineResult.Status.BUFFER_OVERFLOW,
-                        getHandshakeStatus(), 0, 0);
-            }
-            recProtIS.setSourceBuffer(src);
-            // unwrap the record contained in source buffer, pass it
-            // to appropriate client protocol (alert, handshake, or app)
-            // and retrieve the type of unwrapped data
-            int type = recordProtocol.unwrap();
-            // process the data and return the result
-            switch (type) {
-                case ContentType.HANDSHAKE:
-                case ContentType.CHANGE_CIPHER_SPEC:
-                    if (handshakeProtocol.getStatus().equals(
-                            SSLEngineResult.HandshakeStatus.FINISHED)) {
-                        session = recordProtocol.getSession();
-                    }
-                    break;
-                case ContentType.APPLICATION_DATA:
-                    break;
-                case ContentType.ALERT:
-                    if (alertProtocol.isFatalAlert()) {
-                        alertProtocol.setProcessed();
-                        if (session != null) {
-                            session.invalidate();
-                        }
-                        String description = "Fatal alert received "
-                            + alertProtocol.getAlertDescription();
-                        shutdown();
-                        throw new SSLException(description);
-                    } else {
-                        if (logger != null) {
-                            logger.println("Warning allert has been received: "
-                                + alertProtocol.getAlertDescription());
-                        }
-                        switch(alertProtocol.getDescriptionCode()) {
-                            case AlertProtocol.CLOSE_NOTIFY:
-                                alertProtocol.setProcessed();
-                                close_notify_was_received = true;
-                                if (!close_notify_was_sent) {
-                                    closeOutbound();
-                                    closeInbound();
-                                } else {
-                                    closeInbound();
-                                    shutdown();
-                                }
-                                break;
-                            case AlertProtocol.NO_RENEGOTIATION:
-                                alertProtocol.setProcessed();
-                                if (session == null) {
-                                    // message received during the initial
-                                    // handshake
-                                    throw new AlertException(
-                                        AlertProtocol.HANDSHAKE_FAILURE,
-                                        new SSLHandshakeException(
-                                            "Received no_renegotiation "
-                                            + "during the initial handshake"));
-                                } else {
-                                    // just stop the handshake
-                                    handshakeProtocol.stop();
-                                }
-                                break;
-                            default:
-                                alertProtocol.setProcessed();
-                        }
-                    }
-                    break;
-            }
-            return new SSLEngineResult(getEngineStatus(), getHandshakeStatus(),
-                    recProtIS.consumed(),
-                    // place the app. data (if any) into the dest. buffers
-                    // and get the number of produced bytes:
-                    appData.placeTo(dsts, offset, length));
-        } catch (BufferUnderflowException e) {
-            // there was not enought data ource buffer to make complete packet
-            src.reset();
-            return new SSLEngineResult(SSLEngineResult.Status.BUFFER_UNDERFLOW,
-                    getHandshakeStatus(), 0, 0);
-        } catch (AlertException e) {
-            // fatal alert occured
-            alertProtocol.alert(AlertProtocol.FATAL, e.getDescriptionCode());
-            engine_was_closed = true;
-            src.reset();
-            if (session != null) {
-                session.invalidate();
-            }
-            // shutdown work will be made after the alert will be sent
-            // to another peer (by wrap method)
-            throw e.getReason();
-        } catch (SSLException e) {
-            throw e;
-        } catch (IOException e) {
-            alertProtocol.alert(AlertProtocol.FATAL,
-                    AlertProtocol.INTERNAL_ERROR);
-            engine_was_closed = true;
-            // shutdown work will be made after the alert will be sent
-            // to another peer (by wrap method)
-            throw new SSLException(e.getMessage());
-        }
-    }
-
-    /**
-     * Encodes the application data into SSL/TLS record. If handshake status
-     * of the engine differs from NOT_HANDSHAKING the operation can work
-     * without consuming of the source data.
-     * For more information about TLS record fragmentation see
-     * TLS v 1 specification (http://www.ietf.org/rfc/rfc2246.txt) p 6.2.
-     * @param srcs the source buffers with application data to be encoded
-     * into SSL/TLS record.
-     * @param offset the offset in the destination buffers array pointing to
-     * the first buffer with the source data.
-     * @param len specifies the maximum number of buffers to be procesed.
-     * @param dst the destination buffer where encoded data will be placed.
-     * @see javax.net.ssl.SSLEngine#wrap(ByteBuffer[],int,int,ByteBuffer) method
-     * documentation for more information
-     */
-    @Override
-    public SSLEngineResult wrap(ByteBuffer[] srcs, int offset,
-                            int len, ByteBuffer dst) throws SSLException {
-        if (engine_was_shutteddown) {
-            return new SSLEngineResult(SSLEngineResult.Status.CLOSED,
-                    SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING, 0, 0);
-        }
-        if ((srcs == null) || (dst == null)) {
-            throw new IllegalStateException(
-                    "Some of the input parameters are null");
-        }
-        if (dst.isReadOnly()) {
-            throw new ReadOnlyBufferException();
-        }
-
-        if (!handshake_started) {
-            beginHandshake();
-        }
-
-        SSLEngineResult.HandshakeStatus handshakeStatus = getHandshakeStatus();
-        // If it is an initial handshake or connection closure stage,
-        // check if this call was made in spite of handshake status
-        if ((session == null || engine_was_closed) && (
-                handshakeStatus.equals(
-                        SSLEngineResult.HandshakeStatus.NEED_UNWRAP) ||
-                handshakeStatus.equals(
-                        SSLEngineResult.HandshakeStatus.NEED_TASK))) {
-            return new SSLEngineResult(
-                    getEngineStatus(), handshakeStatus, 0, 0);
-        }
-
-        int capacity = dst.remaining();
-        int produced = 0;
-
-        if (alertProtocol.hasAlert()) {
-            // we have an alert to be sent
-            if (capacity < recordProtocol.getRecordSize(2)) {
-                return new SSLEngineResult(
-                        SSLEngineResult.Status.BUFFER_OVERFLOW,
-                        handshakeStatus, 0, 0);
-            }
-            byte[] alert_data = alertProtocol.wrap();
-            // place the alert record into destination
-            dst.put(alert_data);
-            if (alertProtocol.isFatalAlert()) {
-                alertProtocol.setProcessed();
-                if (session != null) {
-                    session.invalidate();
-                }
-                // fatal alert has been sent, so shut down the engine
-                shutdown();
-                return new SSLEngineResult(
-                        SSLEngineResult.Status.CLOSED,
-                        SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING,
-                        0, alert_data.length);
-            } else {
-                alertProtocol.setProcessed();
-                // check if the works on this engine have been done
-                if (close_notify_was_sent && close_notify_was_received) {
-                    shutdown();
-                    return new SSLEngineResult(SSLEngineResult.Status.CLOSED,
-                            SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING,
-                            0, alert_data.length);
-                }
-                return new SSLEngineResult(
-                        getEngineStatus(),
-                        getHandshakeStatus(),
-                        0, alert_data.length);
-            }
-        }
-
-        if (capacity < recordProtocol.getMinRecordSize()) {
-            if (logger != null) {
-                logger.println("Capacity of the destination("
-                        +capacity+") < MIN_PACKET_SIZE("
-                        +recordProtocol.getMinRecordSize()+")");
-            }
-            return new SSLEngineResult(SSLEngineResult.Status.BUFFER_OVERFLOW,
-                        handshakeStatus, 0, 0);
-        }
-
-        try {
-            if (!handshakeStatus.equals(
-                        SSLEngineResult.HandshakeStatus.NEED_WRAP)) {
-                // so we wraps application data
-                dataStream.setSourceBuffers(srcs, offset, len);
-                if ((capacity < SSLRecordProtocol.MAX_SSL_PACKET_SIZE) &&
-                    (capacity < recordProtocol.getRecordSize(
-                                                 dataStream.available()))) {
-                    if (logger != null) {
-                        logger.println("The destination buffer("
-                                +capacity+") can not take the resulting packet("
-                                + recordProtocol.getRecordSize(
-                                    dataStream.available())+")");
-                    }
-                    return new SSLEngineResult(
-                            SSLEngineResult.Status.BUFFER_OVERFLOW,
-                            handshakeStatus, 0, 0);
-                }
-                if (remaining_wrapped_data == null) {
-                    remaining_wrapped_data =
-                        recordProtocol.wrap(ContentType.APPLICATION_DATA,
-                                dataStream);
-                }
-                if (capacity < remaining_wrapped_data.length) {
-                    // It should newer happen because we checked the destination
-                    // buffer size, but there is a possibility
-                    // (if dest buffer was filled outside)
-                    // so we just remember the data into remaining_wrapped_data
-                    // and will enclose it during the the next call
-                    return new SSLEngineResult(
-                            SSLEngineResult.Status.BUFFER_OVERFLOW,
-                            handshakeStatus, dataStream.consumed(), 0);
-                } else {
-                    dst.put(remaining_wrapped_data);
-                    produced = remaining_wrapped_data.length;
-                    remaining_wrapped_data = null;
-                    return new SSLEngineResult(getEngineStatus(),
-                            handshakeStatus, dataStream.consumed(), produced);
-                }
-            } else {
-                if (remaining_hsh_data == null) {
-                    remaining_hsh_data = handshakeProtocol.wrap();
-                }
-                if (capacity < remaining_hsh_data.length) {
-                    // It should newer happen because we checked the destination
-                    // buffer size, but there is a possibility
-                    // (if dest buffer was filled outside)
-                    // so we just remember the data into remaining_hsh_data
-                    // and will enclose it during the the next call
-                    return new SSLEngineResult(
-                            SSLEngineResult.Status.BUFFER_OVERFLOW,
-                            handshakeStatus, 0, 0);
-                } else {
-                    dst.put(remaining_hsh_data);
-                    produced = remaining_hsh_data.length;
-                    remaining_hsh_data = null;
-
-                    handshakeStatus = handshakeProtocol.getStatus();
-                    if (handshakeStatus.equals(
-                            SSLEngineResult.HandshakeStatus.FINISHED)) {
-                        session = recordProtocol.getSession();
-                    }
-                }
-                return new SSLEngineResult(
-                        getEngineStatus(), getHandshakeStatus(), 0, produced);
-            }
-        } catch (AlertException e) {
-            // fatal alert occured
-            alertProtocol.alert(AlertProtocol.FATAL, e.getDescriptionCode());
-            engine_was_closed = true;
-            if (session != null) {
-                session.invalidate();
-            }
-            // shutdown work will be made after the alert will be sent
-            // to another peer (by wrap method)
-            throw e.getReason();
-        }
-    }
-
-    // Shutdownes the engine and makes all cleanup work.
-    private void shutdown() {
-        engine_was_closed = true;
-        engine_was_shutteddown = true;
-        isOutboundDone = true;
-        isInboundDone = true;
-        if (handshake_started) {
-            alertProtocol.shutdown();
-            alertProtocol = null;
-            handshakeProtocol.shutdown();
-            handshakeProtocol = null;
-            recordProtocol.shutdown();
-            recordProtocol = null;
-        }
-    }
-
-
-    private SSLEngineResult.Status getEngineStatus() {
-        return (engine_was_closed)
-            ? SSLEngineResult.Status.CLOSED
-            : SSLEngineResult.Status.OK;
-    }
-}
diff --git a/src/main/java/org/conscrypt/SSLInputStream.java b/src/main/java/org/conscrypt/SSLInputStream.java
deleted file mode 100644
index c8a5eca..0000000
--- a/src/main/java/org/conscrypt/SSLInputStream.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- *  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.io.IOException;
-import java.io.InputStream;
-
-/**
- * This class is a base for all input stream classes used
- * in protocol implementation. It extends an InputStream with
- * some additional read methods allowing to read TLS specific
- * data types such as uint8, uint32 etc (see TLS v 1 specification
- * at http://www.ietf.org/rfc/rfc2246.txt).
- */
-public abstract class SSLInputStream extends InputStream {
-
-    @Override
-    public abstract int available() throws IOException;
-
-    /**
-     * Reads the following byte value. Note that in the case of
-     * reaching of the end of the data this methods throws the
-     * exception, not return -1. The type of exception depends
-     * on implementation. It was done for simplifying and speeding
-     * up of processing of such cases.
-     * @see org.conscrypt.SSLStreamedInput#read()
-     * @see org.conscrypt.SSLBufferedInput#read()
-     * @see org.conscrypt.HandshakeIODataStream#read()
-     */
-    @Override
-    public abstract int read() throws IOException;
-
-    /**
-     * Reads and returns uint8 value.
-     */
-    public int readUint8() throws IOException {
-        return read() & 0x00FF;
-    }
-
-    /**
-     * Reads and returns uint16 value.
-     */
-    public int readUint16() throws IOException {
-        return (read() << 8) | (read() & 0x00FF);
-    }
-
-    /**
-     * Reads and returns uint24 value.
-     */
-    public int readUint24() throws IOException {
-        return (read() << 16) | (read() << 8) | (read() & 0x00FF);
-    }
-
-    /**
-     * Reads and returns uint32 value.
-     */
-    public long readUint32() throws IOException {
-        return (read() << 24) | (read() << 16)
-              | (read() << 8) | (read() & 0x00FF);
-    }
-
-    /**
-     * Reads and returns uint64 value.
-     */
-    public long readUint64() throws IOException {
-        long hi = readUint32();
-        long lo = readUint32();
-        return (hi << 32) | lo;
-    }
-
-    /**
-     * Returns the vector of opaque values of specified length;
-     * @param length - the length of the vector to be read.
-     * @return the read data
-     * @throws IOException if read operation could not be finished.
-     */
-    public byte[] read(int length) throws IOException {
-        byte[] res = new byte[length];
-        for (int i=0; i<length; i++) {
-            res[i] = (byte) read();
-        }
-        return res;
-    }
-
-    @Override
-    public int read(byte[] b, int off, int len) throws IOException {
-        int read_b;
-        int i = 0;
-        do {
-            if ((read_b = read()) == -1) {
-                return (i == 0) ? -1 : i;
-            }
-            b[off+i] = (byte) read_b;
-            i++;
-        } while ((available() != 0) && (i<len));
-        return i;
-    }
-}
diff --git a/src/main/java/org/conscrypt/SSLNullSession.java b/src/main/java/org/conscrypt/SSLNullSession.java
new file mode 100644
index 0000000..7fbf072
--- /dev/null
+++ b/src/main/java/org/conscrypt/SSLNullSession.java
@@ -0,0 +1,180 @@
+/*
+ *  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 org.conscrypt.util.EmptyArray;
+
+import java.security.Principal;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.util.HashMap;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionBindingEvent;
+import javax.net.ssl.SSLSessionBindingListener;
+import javax.net.ssl.SSLSessionContext;
+
+public final class SSLNullSession implements SSLSession, Cloneable {
+
+    /*
+     * Holds default instances so class preloading doesn't create an instance of
+     * it.
+     */
+    private static class DefaultHolder {
+        public static final SSLNullSession NULL_SESSION = new SSLNullSession();
+    }
+
+    private final HashMap<String, Object> values = new HashMap<String, Object>();
+
+    long creationTime;
+    long lastAccessedTime;
+
+    public static SSLSession getNullSession() {
+        return DefaultHolder.NULL_SESSION;
+    }
+
+    public SSLNullSession() {
+        creationTime = System.currentTimeMillis();
+        lastAccessedTime = creationTime;
+    }
+
+    @Override
+    public int getApplicationBufferSize() {
+        return SSLRecordProtocol.MAX_DATA_LENGTH;
+    }
+
+    @Override
+    public String getCipherSuite() {
+        return "SSL_NULL_WITH_NULL_NULL";
+    }
+
+    @Override
+    public long getCreationTime() {
+        return creationTime;
+    }
+
+    @Override
+    public byte[] getId() {
+        return EmptyArray.BYTE;
+    }
+
+    @Override
+    public long getLastAccessedTime() {
+        return lastAccessedTime;
+    }
+
+    @Override
+    public Certificate[] getLocalCertificates() {
+        return null;
+    }
+
+    @Override
+    public Principal getLocalPrincipal() {
+        return null;
+    }
+
+    @Override
+    public int getPacketBufferSize() {
+        return SSLRecordProtocol.MAX_SSL_PACKET_SIZE;
+    }
+
+    @Override
+    public javax.security.cert.X509Certificate[] getPeerCertificateChain()
+            throws SSLPeerUnverifiedException {
+        throw new SSLPeerUnverifiedException("No peer certificate");
+    }
+
+    @Override
+    public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
+        throw new SSLPeerUnverifiedException("No peer certificate");
+    }
+
+    @Override
+    public String getPeerHost() {
+        return null;
+    }
+
+    @Override
+    public int getPeerPort() {
+        return -1;
+    }
+
+    @Override
+    public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+        throw new SSLPeerUnverifiedException("No peer certificate");
+    }
+
+    @Override
+    public String getProtocol() {
+        return "NONE";
+    }
+
+    @Override
+    public SSLSessionContext getSessionContext() {
+        return null;
+    }
+
+    @Override
+    public Object getValue(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("name == null");
+        }
+        return values.get(name);
+    }
+
+    @Override
+    public String[] getValueNames() {
+        return values.keySet().toArray(new String[values.size()]);
+    }
+
+    @Override
+    public void invalidate() {
+    }
+
+    @Override
+    public boolean isValid() {
+        return false;
+    }
+
+    @Override
+    public void putValue(String name, Object value) {
+        if (name == null || value == null) {
+            throw new IllegalArgumentException("name == null || value == null");
+        }
+        Object old = values.put(name, value);
+        if (value instanceof SSLSessionBindingListener) {
+            ((SSLSessionBindingListener) value).valueBound(new SSLSessionBindingEvent(this, name));
+        }
+        if (old instanceof SSLSessionBindingListener) {
+            ((SSLSessionBindingListener) old).valueUnbound(new SSLSessionBindingEvent(this, name));
+        }
+
+    }
+
+    @Override
+    public void removeValue(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("name == null");
+        }
+        Object old = values.remove(name);
+        if (old instanceof SSLSessionBindingListener) {
+            SSLSessionBindingListener listener = (SSLSessionBindingListener) old;
+            listener.valueUnbound(new SSLSessionBindingEvent(this, name));
+        }
+    }
+}
diff --git a/src/main/java/org/conscrypt/SSLParametersImpl.java b/src/main/java/org/conscrypt/SSLParametersImpl.java
index 21b8ae2..1201b05 100644
--- a/src/main/java/org/conscrypt/SSLParametersImpl.java
+++ b/src/main/java/org/conscrypt/SSLParametersImpl.java
@@ -17,19 +17,33 @@
 
 package org.conscrypt;
 
+import org.conscrypt.util.EmptyArray;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidKeyException;
 import java.security.KeyManagementException;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
 import java.security.SecureRandom;
 import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
 import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import javax.crypto.SecretKey;
 import javax.net.ssl.KeyManager;
 import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509ExtendedKeyManager;
 import javax.net.ssl.X509KeyManager;
 import javax.net.ssl.X509TrustManager;
+import javax.security.auth.x500.X500Principal;
 
 /**
  * The instances of this class encapsulate all the info
@@ -41,10 +55,10 @@
  */
 public class SSLParametersImpl implements Cloneable {
 
-    // default source of authentication keys
-    private static volatile X509KeyManager defaultKeyManager;
-    // default source of authentication trust decisions
-    private static volatile X509TrustManager defaultTrustManager;
+    // default source of X.509 certificate based authentication keys
+    private static volatile X509KeyManager defaultX509KeyManager;
+    // default source of X.509 certificate based authentication trust decisions
+    private static volatile X509TrustManager defaultX509TrustManager;
     // default source of random numbers
     private static volatile SecureRandom defaultSecureRandom;
     // default SSL parameters
@@ -56,20 +70,19 @@
     // server session context contains the set of reusable
     // server-side SSL sessions
     private final ServerSessionContext serverSessionContext;
-    // source of authentication keys
-    private X509KeyManager keyManager;
-    // source of authentication trust decisions
-    private X509TrustManager trustManager;
+    // source of X.509 certificate based authentication keys or null if not provided
+    private final X509KeyManager x509KeyManager;
+    // source of Pre-Shared Key (PSK) authentication keys or null if not provided.
+    private final PSKKeyManager pskKeyManager;
+    // source of X.509 certificate based authentication trust decisions or null if not provided
+    private final X509TrustManager x509TrustManager;
     // source of random numbers
     private SecureRandom secureRandom;
 
-    // cipher suites available for SSL connection
-    private CipherSuite[] enabledCipherSuites;
-    // string representations of available cipher suites
-    private String[] enabledCipherSuiteNames = null;
-
-    // protocols available for SSL connection
-    private String[] enabledProtocols = ProtocolVersion.supportedProtocols;
+    // protocols enabled for SSL connection
+    private String[] enabledProtocols;
+    // cipher suites enabled for SSL connection
+    private String[] enabledCipherSuites;
 
     // if the peer with this parameters tuned to work in client mode
     private boolean client_mode = true;
@@ -79,13 +92,18 @@
     private boolean want_client_auth = false;
     // if the peer with this parameters allowed to cteate new SSL session
     private boolean enable_session_creation = true;
+    private String endpointIdentificationAlgorithm;
 
-    protected CipherSuite[] getEnabledCipherSuitesMember() {
-        if (enabledCipherSuites == null) {
-            this.enabledCipherSuites = CipherSuite.DEFAULT_CIPHER_SUITES;
-        }
-        return enabledCipherSuites;
-    }
+    byte[] npnProtocols;
+    byte[] alpnProtocols;
+    boolean useSessionTickets;
+    boolean useSni;
+
+    /**
+     * Whether the TLS Channel ID extension is enabled. This field is
+     * server-side only.
+     */
+    boolean channelIdEnabled;
 
     /**
      * Initializes the parameters. Naturally this constructor is used
@@ -102,41 +120,36 @@
         this.serverSessionContext = serverSessionContext;
         this.clientSessionContext = clientSessionContext;
 
-        // It's not described by the spec of SSLContext what should happen
-        // if the arrays of length 0 are specified. This implementation
-        // behave as for null arrays (i.e. use installed security providers)
-
-        // initialize keyManager
-        if ((kms == null) || (kms.length == 0)) {
-            keyManager = getDefaultKeyManager();
+        // initialize key managers
+        if (kms == null) {
+            x509KeyManager = getDefaultX509KeyManager();
+            // There's no default PSK key manager
+            pskKeyManager = null;
         } else {
-            keyManager = findX509KeyManager(kms);
+            x509KeyManager = findFirstX509KeyManager(kms);
+            pskKeyManager = findFirstPSKKeyManager(kms);
         }
 
-        // initialize trustManager
-        if ((tms == null) || (tms.length == 0)) {
-            trustManager = getDefaultTrustManager();
+        // initialize x509TrustManager
+        if (tms == null) {
+            x509TrustManager = getDefaultX509TrustManager();
         } else {
-            trustManager = findX509TrustManager(tms);
+            x509TrustManager = findFirstX509TrustManager(tms);
         }
+
         // initialize secure random
-        // BEGIN android-removed
-        // if (sr == null) {
-        //     if (defaultSecureRandom == null) {
-        //         defaultSecureRandom = new SecureRandom();
-        //     }
-        //     secureRandom = defaultSecureRandom;
-        // } else {
-        //     secureRandom = sr;
-        // }
-        // END android-removed
-        // BEGIN android-added
         // We simply use the SecureRandom passed in by the caller. If it's
         // null, we don't replace it by a new instance. The native code below
         // then directly accesses /dev/urandom. Not the most elegant solution,
         // but faster than going through the SecureRandom object.
         secureRandom = sr;
-        // END android-added
+
+        // initialize the list of cipher suites and protocols enabled by default
+        enabledProtocols = getDefaultProtocols();
+        boolean x509CipherSuitesNeeded = (x509KeyManager != null) || (x509TrustManager != null);
+        boolean pskCipherSuitesNeeded = pskKeyManager != null;
+        enabledCipherSuites = getDefaultCipherSuites(
+                x509CipherSuitesNeeded, pskCipherSuitesNeeded);
     }
 
     protected static SSLParametersImpl getDefault() throws KeyManagementException {
@@ -153,6 +166,13 @@
     }
 
     /**
+     * Returns the appropriate session context.
+     */
+    public AbstractSessionContext getSessionContext() {
+        return client_mode ? clientSessionContext : serverSessionContext;
+    }
+
+    /**
      * @return server session context
      */
     protected ServerSessionContext getServerSessionContext() {
@@ -167,17 +187,24 @@
     }
 
     /**
-     * @return key manager
+     * @return X.509 key manager or {@code null} for none.
      */
-    protected X509KeyManager getKeyManager() {
-        return keyManager;
+    protected X509KeyManager getX509KeyManager() {
+        return x509KeyManager;
     }
 
     /**
-     * @return trust manager
+     * @return Pre-Shared Key (PSK) key manager or {@code null} for none.
      */
-    protected X509TrustManager getTrustManager() {
-        return trustManager;
+    protected PSKKeyManager getPSKKeyManager() {
+        return pskKeyManager;
+    }
+
+    /**
+     * @return X.509 trust manager or {@code null} for none.
+     */
+    protected X509TrustManager getX509TrustManager() {
+        return x509TrustManager;
     }
 
     /**
@@ -207,38 +234,14 @@
      * @return the names of enabled cipher suites
      */
     protected String[] getEnabledCipherSuites() {
-        if (enabledCipherSuiteNames == null) {
-            CipherSuite[] enabledCipherSuites = getEnabledCipherSuitesMember();
-            enabledCipherSuiteNames = new String[enabledCipherSuites.length];
-            for (int i = 0; i< enabledCipherSuites.length; i++) {
-                enabledCipherSuiteNames[i] = enabledCipherSuites[i].getName();
-            }
-        }
-        return enabledCipherSuiteNames.clone();
+        return enabledCipherSuites.clone();
     }
 
     /**
-     * Sets the set of available cipher suites for use in SSL connection.
-     * @param   suites: String[]
-     * @return
+     * Sets the enabled cipher suites after filtering through OpenSSL.
      */
-    protected void setEnabledCipherSuites(String[] suites) {
-        if (suites == null) {
-            throw new IllegalArgumentException("suites == null");
-        }
-        CipherSuite[] cipherSuites = new CipherSuite[suites.length];
-        for (int i=0; i<suites.length; i++) {
-            String suite = suites[i];
-            if (suite == null) {
-                throw new IllegalArgumentException("suites[" + i + "] == null");
-            }
-            cipherSuites[i] = CipherSuite.getByName(suite);
-            if (cipherSuites[i] == null || !cipherSuites[i].supported) {
-                throw new IllegalArgumentException(suite + " is not supported.");
-            }
-        }
-        enabledCipherSuites = cipherSuites;
-        enabledCipherSuiteNames = suites;
+    protected void setEnabledCipherSuites(String[] cipherSuites) {
+        enabledCipherSuites = NativeCrypto.checkEnabledCipherSuites(cipherSuites).clone();
     }
 
     /**
@@ -253,19 +256,7 @@
      * @param protocols String[]
      */
     protected void setEnabledProtocols(String[] protocols) {
-        if (protocols == null) {
-            throw new IllegalArgumentException("protocols == null");
-        }
-        for (int i=0; i<protocols.length; i++) {
-            String protocol = protocols[i];
-            if (protocol == null) {
-                throw new IllegalArgumentException("protocols[" + i + "] == null");
-            }
-            if (!ProtocolVersion.isSupported(protocol)) {
-                throw new IllegalArgumentException("Protocol " + protocol + " is not supported.");
-            }
-        }
-        enabledProtocols = protocols;
+        enabledProtocols = NativeCrypto.checkEnabledProtocols(protocols).clone();
     }
 
     /**
@@ -313,7 +304,6 @@
     /**
      * Returns the value indicating if the peer with this parameters
      * tuned to request client authentication
-     * @return
      */
     protected boolean getWantClientAuth() {
         return want_client_auth;
@@ -336,6 +326,421 @@
     }
 
     /**
+     * Whether connections using this SSL connection should use the TLS
+     * extension Server Name Indication (SNI).
+     */
+    protected void setUseSni(boolean flag) {
+        useSni = flag;
+    }
+
+    /**
+     * Returns whether connections using this SSL connection should use the TLS
+     * extension Server Name Indication (SNI).
+     */
+    protected boolean getUseSni() {
+        return useSni;
+    }
+
+    static byte[][] encodeIssuerX509Principals(X509Certificate[] certificates)
+            throws CertificateEncodingException {
+        byte[][] principalBytes = new byte[certificates.length][];
+        for (int i = 0; i < certificates.length; i++) {
+            principalBytes[i] = certificates[i].getIssuerX500Principal().getEncoded();
+        }
+        return principalBytes;
+    }
+
+    /**
+     * Return a possibly null array of X509Certificates given the possibly null
+     * array of DER encoded bytes.
+     */
+    private static OpenSSLX509Certificate[] createCertChain(long[] certificateRefs)
+            throws IOException {
+        if (certificateRefs == null) {
+            return null;
+        }
+        OpenSSLX509Certificate[] certificates = new OpenSSLX509Certificate[certificateRefs.length];
+        for (int i = 0; i < certificateRefs.length; i++) {
+            certificates[i] = new OpenSSLX509Certificate(certificateRefs[i]);
+        }
+        return certificates;
+    }
+
+    OpenSSLSessionImpl getSessionToReuse(long sslNativePointer, String hostname, int port)
+            throws SSLException {
+        final OpenSSLSessionImpl sessionToReuse;
+        if (client_mode) {
+            // look for client session to reuse
+            sessionToReuse = getCachedClientSession(clientSessionContext, hostname, port);
+            if (sessionToReuse != null) {
+                NativeCrypto.SSL_set_session(sslNativePointer,
+                        sessionToReuse.sslSessionNativePointer);
+            }
+        } else {
+            sessionToReuse = null;
+        }
+        return sessionToReuse;
+    }
+
+    void setTlsChannelId(long sslNativePointer, OpenSSLKey channelIdPrivateKey)
+            throws SSLHandshakeException, SSLException {
+        // TLS Channel ID
+        if (channelIdEnabled) {
+            if (client_mode) {
+                // Client-side TLS Channel ID
+                if (channelIdPrivateKey == null) {
+                    throw new SSLHandshakeException("Invalid TLS channel ID key specified");
+                }
+                NativeCrypto.SSL_set1_tls_channel_id(sslNativePointer,
+                        channelIdPrivateKey.getPkeyContext());
+            } else {
+                // Server-side TLS Channel ID
+                NativeCrypto.SSL_enable_tls_channel_id(sslNativePointer);
+            }
+        }
+    }
+
+    void setCertificate(long sslNativePointer, String alias) throws CertificateEncodingException,
+            SSLException {
+        if (alias == null) {
+            return;
+        }
+        X509KeyManager keyManager = getX509KeyManager();
+        if (keyManager == null) {
+            return;
+        }
+        PrivateKey privateKey = keyManager.getPrivateKey(alias);
+        if (privateKey == null) {
+            return;
+        }
+        X509Certificate[] certificates = keyManager.getCertificateChain(alias);
+        if (certificates == null) {
+            return;
+        }
+
+        /*
+         * Make sure we keep a reference to the OpenSSLX509Certificate by using
+         * this array. Otherwise, if they're not OpenSSLX509Certificate
+         * instances originally, they may be garbage collected before we
+         * complete our JNI calls.
+         */
+        OpenSSLX509Certificate[] openSslCerts = new OpenSSLX509Certificate[certificates.length];
+        long[] x509refs = new long[certificates.length];
+        for (int i = 0; i < certificates.length; i++) {
+            OpenSSLX509Certificate openSslCert = OpenSSLX509Certificate
+                    .fromCertificate(certificates[i]);
+            openSslCerts[i] = openSslCert;
+            x509refs[i] = openSslCert.getContext();
+        }
+
+        // Note that OpenSSL says to use SSL_use_certificate before
+        // SSL_use_PrivateKey.
+        NativeCrypto.SSL_use_certificate(sslNativePointer, x509refs);
+
+        try {
+            final OpenSSLKey key = OpenSSLKey.fromPrivateKey(privateKey);
+            NativeCrypto.SSL_use_PrivateKey(sslNativePointer, key.getPkeyContext());
+        } catch (InvalidKeyException e) {
+            throw new SSLException(e);
+        }
+
+        // checks the last installed private key and certificate,
+        // so need to do this once per loop iteration
+        NativeCrypto.SSL_check_private_key(sslNativePointer);
+    }
+
+    void setSSLParameters(long sslCtxNativePointer, long sslNativePointer, AliasChooser chooser,
+            PSKCallbacks pskCallbacks, String hostname) throws SSLException, IOException {
+        if (npnProtocols != null) {
+            NativeCrypto.SSL_CTX_enable_npn(sslCtxNativePointer);
+        }
+
+        if (client_mode && alpnProtocols != null) {
+            NativeCrypto.SSL_set_alpn_protos(sslNativePointer, alpnProtocols);
+        }
+
+        NativeCrypto.setEnabledProtocols(sslNativePointer, enabledProtocols);
+        NativeCrypto.setEnabledCipherSuites(sslNativePointer, enabledCipherSuites);
+
+        // setup server certificates and private keys.
+        // clients will receive a call back to request certificates.
+        if (!client_mode) {
+            Set<String> keyTypes = new HashSet<String>();
+            for (long sslCipherNativePointer : NativeCrypto.SSL_get_ciphers(sslNativePointer)) {
+                String keyType = getServerX509KeyType(sslCipherNativePointer);
+                if (keyType != null) {
+                    keyTypes.add(keyType);
+                }
+            }
+            X509KeyManager keyManager = getX509KeyManager();
+            if (keyManager != null) {
+                for (String keyType : keyTypes) {
+                    try {
+                        setCertificate(sslNativePointer,
+                                chooser.chooseServerAlias(x509KeyManager, keyType));
+                    } catch (CertificateEncodingException e) {
+                        throw new IOException(e);
+                    }
+                }
+            }
+        }
+
+        // Enable Pre-Shared Key (PSK) key exchange if requested
+        PSKKeyManager pskKeyManager = getPSKKeyManager();
+        if (pskKeyManager != null) {
+            boolean pskEnabled = false;
+            for (String enabledCipherSuite : enabledCipherSuites) {
+                if ((enabledCipherSuite != null) && (enabledCipherSuite.contains("PSK"))) {
+                    pskEnabled = true;
+                    break;
+                }
+            }
+            if (pskEnabled) {
+                if (client_mode) {
+                    NativeCrypto.set_SSL_psk_client_callback_enabled(sslNativePointer, true);
+                } else {
+                    NativeCrypto.set_SSL_psk_server_callback_enabled(sslNativePointer, true);
+                    String identityHint = pskCallbacks.chooseServerPSKIdentityHint(pskKeyManager);
+                    NativeCrypto.SSL_use_psk_identity_hint(sslNativePointer, identityHint);
+                }
+            }
+        }
+
+        if (useSessionTickets) {
+            NativeCrypto.SSL_clear_options(sslNativePointer, NativeCrypto.SSL_OP_NO_TICKET);
+        }
+        if (useSni) {
+            NativeCrypto.SSL_set_tlsext_host_name(sslNativePointer, hostname);
+        }
+
+        // BEAST attack mitigation (1/n-1 record splitting for CBC cipher suites
+        // with TLSv1 and SSLv3).
+        NativeCrypto.SSL_set_mode(sslNativePointer, NativeCrypto.SSL_MODE_CBC_RECORD_SPLITTING);
+
+        boolean enableSessionCreation = getEnableSessionCreation();
+        if (!enableSessionCreation) {
+            NativeCrypto.SSL_set_session_creation_enabled(sslNativePointer, enableSessionCreation);
+        }
+    }
+
+    void setCertificateValidation(long sslNativePointer) throws IOException {
+        // setup peer certificate verification
+        if (!client_mode) {
+            // needing client auth takes priority...
+            boolean certRequested;
+            if (getNeedClientAuth()) {
+                NativeCrypto.SSL_set_verify(sslNativePointer,
+                                            NativeCrypto.SSL_VERIFY_PEER
+                                            | NativeCrypto.SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
+                certRequested = true;
+            // ... over just wanting it...
+            } else if (getWantClientAuth()) {
+                NativeCrypto.SSL_set_verify(sslNativePointer, NativeCrypto.SSL_VERIFY_PEER);
+                certRequested = true;
+            // ... and we must disable verification if we don't want client auth.
+            } else {
+                NativeCrypto.SSL_set_verify(sslNativePointer, NativeCrypto.SSL_VERIFY_NONE);
+                certRequested = false;
+            }
+
+            if (certRequested) {
+                X509TrustManager trustManager = getX509TrustManager();
+                X509Certificate[] issuers = trustManager.getAcceptedIssuers();
+                if (issuers != null && issuers.length != 0) {
+                    byte[][] issuersBytes;
+                    try {
+                        issuersBytes = encodeIssuerX509Principals(issuers);
+                    } catch (CertificateEncodingException e) {
+                        throw new IOException("Problem encoding principals", e);
+                    }
+                    NativeCrypto.SSL_set_client_CA_list(sslNativePointer, issuersBytes);
+                }
+            }
+        }
+    }
+
+    OpenSSLSessionImpl setupSession(long sslSessionNativePointer, long sslNativePointer,
+            final OpenSSLSessionImpl sessionToReuse, String hostname, int port,
+            boolean handshakeCompleted) throws IOException {
+        OpenSSLSessionImpl sslSession = null;
+        byte[] sessionId = NativeCrypto.SSL_SESSION_session_id(sslSessionNativePointer);
+        if (sessionToReuse != null && Arrays.equals(sessionToReuse.getId(), sessionId)) {
+            sslSession = sessionToReuse;
+            sslSession.lastAccessedTime = System.currentTimeMillis();
+            NativeCrypto.SSL_SESSION_free(sslSessionNativePointer);
+        } else {
+            if (!getEnableSessionCreation()) {
+                // Should have been prevented by
+                // NativeCrypto.SSL_set_session_creation_enabled
+                throw new IllegalStateException("SSL Session may not be created");
+            }
+            X509Certificate[] localCertificates = createCertChain(NativeCrypto
+                    .SSL_get_certificate(sslNativePointer));
+            X509Certificate[] peerCertificates = createCertChain(NativeCrypto
+                    .SSL_get_peer_cert_chain(sslNativePointer));
+            sslSession = new OpenSSLSessionImpl(sslSessionNativePointer, localCertificates,
+                    peerCertificates, hostname, port, getSessionContext());
+            // if not, putSession later in handshakeCompleted() callback
+            if (handshakeCompleted) {
+                getSessionContext().putSession(sslSession);
+            }
+        }
+        return sslSession;
+    }
+
+    void chooseClientCertificate(byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals,
+            long sslNativePointer, AliasChooser chooser) throws SSLException,
+            CertificateEncodingException {
+        String[] keyTypes = new String[keyTypeBytes.length];
+        for (int i = 0; i < keyTypeBytes.length; i++) {
+            keyTypes[i] = getClientKeyType(keyTypeBytes[i]);
+        }
+
+        X500Principal[] issuers;
+        if (asn1DerEncodedPrincipals == null) {
+            issuers = null;
+        } else {
+            issuers = new X500Principal[asn1DerEncodedPrincipals.length];
+            for (int i = 0; i < asn1DerEncodedPrincipals.length; i++) {
+                issuers[i] = new X500Principal(asn1DerEncodedPrincipals[i]);
+            }
+        }
+        X509KeyManager keyManager = getX509KeyManager();
+        String alias = (keyManager != null) ? chooser.chooseClientAlias(keyManager, issuers,
+                keyTypes) : null;
+        setCertificate(sslNativePointer, alias);
+    }
+
+    /**
+     * @see NativeCrypto.SSLHandshakeCallbacks#clientPSKKeyRequested(String, byte[], byte[])
+     */
+    int clientPSKKeyRequested(
+            String identityHint, byte[] identityBytesOut, byte[] key, PSKCallbacks pskCallbacks) {
+        PSKKeyManager pskKeyManager = getPSKKeyManager();
+        if (pskKeyManager == null) {
+            return 0;
+        }
+
+        String identity = pskCallbacks.chooseClientPSKIdentity(pskKeyManager, identityHint);
+        // Store identity in NULL-terminated modified UTF-8 representation into ientityBytesOut
+        byte[] identityBytes;
+        if (identity == null) {
+            identity = "";
+            identityBytes = EmptyArray.BYTE;
+        } else if (identity.isEmpty()) {
+            identityBytes = EmptyArray.BYTE;
+        } else {
+            try {
+                identityBytes = identity.getBytes("UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                throw new RuntimeException("UTF-8 encoding not supported", e);
+            }
+        }
+        if (identityBytes.length + 1 > identityBytesOut.length) {
+            // Insufficient space in the output buffer
+            return 0;
+        }
+        if (identityBytes.length > 0) {
+            System.arraycopy(identityBytes, 0, identityBytesOut, 0, identityBytes.length);
+        }
+        identityBytesOut[identityBytes.length] = 0;
+
+        SecretKey secretKey = pskCallbacks.getPSKKey(pskKeyManager, identityHint, identity);
+        byte[] secretKeyBytes = secretKey.getEncoded();
+        if (secretKeyBytes == null) {
+            return 0;
+        } else if (secretKeyBytes.length > key.length) {
+            // Insufficient space in the output buffer
+            return 0;
+        }
+        System.arraycopy(secretKeyBytes, 0, key, 0, secretKeyBytes.length);
+        return secretKeyBytes.length;
+    }
+
+    /**
+     * @see NativeCrypto.SSLHandshakeCallbacks#serverPSKKeyRequested(String, String, byte[])
+     */
+    int serverPSKKeyRequested(
+            String identityHint, String identity, byte[] key, PSKCallbacks pskCallbacks) {
+        PSKKeyManager pskKeyManager = getPSKKeyManager();
+        if (pskKeyManager == null) {
+            return 0;
+        }
+        SecretKey secretKey = pskCallbacks.getPSKKey(pskKeyManager, identityHint, identity);
+        byte[] secretKeyBytes = secretKey.getEncoded();
+        if (secretKeyBytes == null) {
+            return 0;
+        } else if (secretKeyBytes.length > key.length) {
+            return 0;
+        }
+        System.arraycopy(secretKeyBytes, 0, key, 0, secretKeyBytes.length);
+        return secretKeyBytes.length;
+    }
+
+    /**
+     * Gets the suitable session reference from the session cache container.
+     */
+    OpenSSLSessionImpl getCachedClientSession(ClientSessionContext sessionContext, String hostName,
+            int port) {
+        if (hostName == null) {
+            return null;
+        }
+        OpenSSLSessionImpl session = (OpenSSLSessionImpl) sessionContext.getSession(hostName, port);
+        if (session == null) {
+            return null;
+        }
+
+        String protocol = session.getProtocol();
+        boolean protocolFound = false;
+        for (String enabledProtocol : enabledProtocols) {
+            if (protocol.equals(enabledProtocol)) {
+                protocolFound = true;
+                break;
+            }
+        }
+        if (!protocolFound) {
+            return null;
+        }
+
+        String cipherSuite = session.getCipherSuite();
+        boolean cipherSuiteFound = false;
+        for (String enabledCipherSuite : enabledCipherSuites) {
+            if (cipherSuite.equals(enabledCipherSuite)) {
+                cipherSuiteFound = true;
+                break;
+            }
+        }
+        if (!cipherSuiteFound) {
+            return null;
+        }
+
+        return session;
+    }
+
+    /**
+     * For abstracting the X509KeyManager calls between
+     * {@link X509KeyManager#chooseClientAlias(String[], java.security.Principal[], java.net.Socket)}
+     * and
+     * {@link X509ExtendedKeyManager#chooseEngineClientAlias(String[], java.security.Principal[], javax.net.ssl.SSLEngine)}
+     */
+    public interface AliasChooser {
+        String chooseClientAlias(X509KeyManager keyManager, X500Principal[] issuers,
+                String[] keyTypes);
+
+        String chooseServerAlias(X509KeyManager keyManager, String keyType);
+    }
+
+    /**
+     * For abstracting the {@code PSKKeyManager} calls between those taking an {@code SSLSocket} and
+     * those taking an {@code SSLEngine}.
+     */
+    public interface PSKCallbacks {
+        String chooseServerPSKIdentityHint(PSKKeyManager keyManager);
+        String chooseClientPSKIdentity(PSKKeyManager keyManager, String identityHint);
+        SecretKey getPSKKey(PSKKeyManager keyManager, String identityHint, String identity);
+    }
+
+    /**
      * Returns the clone of this object.
      * @return the clone.
      */
@@ -348,21 +753,26 @@
         }
     }
 
-    private static X509KeyManager getDefaultKeyManager() throws KeyManagementException {
-        X509KeyManager result = defaultKeyManager;
+    private static X509KeyManager getDefaultX509KeyManager() throws KeyManagementException {
+        X509KeyManager result = defaultX509KeyManager;
         if (result == null) {
             // single-check idiom
-            defaultKeyManager = result = createDefaultKeyManager();
+            defaultX509KeyManager = result = createDefaultX509KeyManager();
         }
         return result;
     }
-    private static X509KeyManager createDefaultKeyManager() throws KeyManagementException {
+    private static X509KeyManager createDefaultX509KeyManager() throws KeyManagementException {
         try {
             String algorithm = KeyManagerFactory.getDefaultAlgorithm();
             KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
             kmf.init(null, null);
             KeyManager[] kms = kmf.getKeyManagers();
-            return findX509KeyManager(kms);
+            X509KeyManager result = findFirstX509KeyManager(kms);
+            if (result == null) {
+                throw new KeyManagementException("No X509KeyManager among default KeyManagers: "
+                        + Arrays.toString(kms));
+            }
+            return result;
         } catch (NoSuchAlgorithmException e) {
             throw new KeyManagementException(e);
         } catch (KeyStoreException e) {
@@ -371,35 +781,63 @@
             throw new KeyManagementException(e);
         }
     }
-    private static X509KeyManager findX509KeyManager(KeyManager[] kms) throws KeyManagementException {
+
+    /**
+     * Finds the first {@link X509KeyManager} element in the provided array.
+     *
+     * @return the first {@code X509KeyManager} or {@code null} if not found.
+     */
+    private static X509KeyManager findFirstX509KeyManager(KeyManager[] kms) {
         for (KeyManager km : kms) {
             if (km instanceof X509KeyManager) {
                 return (X509KeyManager)km;
             }
         }
-        throw new KeyManagementException("Failed to find an X509KeyManager in " + Arrays.toString(kms));
+        return null;
     }
 
     /**
-     * Gets the default trust manager.
+     * Finds the first {@link PSKKeyManager} element in the provided array.
+     *
+     * @return the first {@code PSKKeyManager} or {@code null} if not found.
+     */
+    private static PSKKeyManager findFirstPSKKeyManager(KeyManager[] kms) {
+        for (KeyManager km : kms) {
+            if (km instanceof PSKKeyManager) {
+                return (PSKKeyManager)km;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets the default X.509 trust manager.
      *
      * TODO: Move this to a published API under dalvik.system.
      */
-    public static X509TrustManager getDefaultTrustManager() throws KeyManagementException {
-        X509TrustManager result = defaultTrustManager;
+    public static X509TrustManager getDefaultX509TrustManager()
+            throws KeyManagementException {
+        X509TrustManager result = defaultX509TrustManager;
         if (result == null) {
             // single-check idiom
-            defaultTrustManager = result = createDefaultTrustManager();
+            defaultX509TrustManager = result = createDefaultX509TrustManager();
         }
         return result;
     }
-    private static X509TrustManager createDefaultTrustManager() throws KeyManagementException {
+
+    private static X509TrustManager createDefaultX509TrustManager()
+            throws KeyManagementException {
         try {
             String algorithm = TrustManagerFactory.getDefaultAlgorithm();
             TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
             tmf.init((KeyStore) null);
             TrustManager[] tms = tmf.getTrustManagers();
-            X509TrustManager trustManager = findX509TrustManager(tms);
+            X509TrustManager trustManager = findFirstX509TrustManager(tms);
+            if (trustManager == null) {
+                throw new KeyManagementException(
+                        "No X509TrustManager in among default TrustManagers: "
+                                + Arrays.toString(tms));
+            }
             return trustManager;
         } catch (NoSuchAlgorithmException e) {
             throw new KeyManagementException(e);
@@ -407,12 +845,172 @@
             throw new KeyManagementException(e);
         }
     }
-    private static X509TrustManager findX509TrustManager(TrustManager[] tms) throws KeyManagementException {
+
+    /**
+     * Finds the first {@link X509TrustManager} element in the provided array.
+     *
+     * @return the first {@code X509TrustManager} or {@code null} if not found.
+     */
+    private static X509TrustManager findFirstX509TrustManager(TrustManager[] tms) {
         for (TrustManager tm : tms) {
             if (tm instanceof X509TrustManager) {
-                return (X509TrustManager)tm;
+                return (X509TrustManager) tm;
             }
         }
-        throw new KeyManagementException("Failed to find an X509TrustManager in " +  Arrays.toString(tms));
+        return null;
+    }
+
+    public String getEndpointIdentificationAlgorithm() {
+        return endpointIdentificationAlgorithm;
+    }
+
+    public void setEndpointIdentificationAlgorithm(String endpointIdentificationAlgorithm) {
+        this.endpointIdentificationAlgorithm = endpointIdentificationAlgorithm;
+    }
+
+    /** Key type: RSA. */
+    private static final String KEY_TYPE_RSA = "RSA";
+
+    /** Key type: DSA. */
+    private static final String KEY_TYPE_DSA = "DSA";
+
+    /** Key type: Diffie-Hellman with RSA signature. */
+    private static final String KEY_TYPE_DH_RSA = "DH_RSA";
+
+    /** Key type: Diffie-Hellman with DSA signature. */
+    private static final String KEY_TYPE_DH_DSA = "DH_DSA";
+
+    /** Key type: Elliptic Curve. */
+    private static final String KEY_TYPE_EC = "EC";
+
+    /** Key type: Eliiptic Curve with ECDSA signature. */
+    private static final String KEY_TYPE_EC_EC = "EC_EC";
+
+    /** Key type: Eliiptic Curve with RSA signature. */
+    private static final String KEY_TYPE_EC_RSA = "EC_RSA";
+
+    /**
+     * Returns key type constant suitable for calling X509KeyManager.chooseServerAlias or
+     * X509ExtendedKeyManager.chooseEngineServerAlias. Returns {@code null} for key exchanges that
+     * do not use X.509 for server authentication.
+     */
+    private static String getServerX509KeyType(long sslCipherNative) throws SSLException {
+        int algorithm_mkey = NativeCrypto.get_SSL_CIPHER_algorithm_mkey(sslCipherNative);
+        int algorithm_auth = NativeCrypto.get_SSL_CIPHER_algorithm_auth(sslCipherNative);
+        switch (algorithm_mkey) {
+            case NativeCrypto.SSL_kRSA:
+                return KEY_TYPE_RSA;
+            case NativeCrypto.SSL_kEDH:
+                switch (algorithm_auth) {
+                    case NativeCrypto.SSL_aDSS:
+                        return KEY_TYPE_DSA;
+                    case NativeCrypto.SSL_aRSA:
+                        return KEY_TYPE_RSA;
+                    case NativeCrypto.SSL_aNULL:
+                        return null;
+                }
+                break;
+            case NativeCrypto.SSL_kECDHr:
+                return KEY_TYPE_EC_RSA;
+            case NativeCrypto.SSL_kECDHe:
+                return KEY_TYPE_EC_EC;
+            case NativeCrypto.SSL_kEECDH:
+                switch (algorithm_auth) {
+                    case NativeCrypto.SSL_aECDSA:
+                        return KEY_TYPE_EC_EC;
+                    case NativeCrypto.SSL_aRSA:
+                        return KEY_TYPE_RSA;
+                    case NativeCrypto.SSL_aPSK:
+                        return null;
+                    case NativeCrypto.SSL_aNULL:
+                        return null;
+                }
+                break;
+            case NativeCrypto.SSL_kPSK:
+                return null;
+        }
+
+        throw new SSLException("Unsupported key exchange. "
+                + "mkey: 0x" + Long.toHexString(algorithm_mkey & 0xffffffffL)
+                + ", auth: 0x" + Long.toHexString(algorithm_auth & 0xffffffffL));
+    }
+
+    /**
+     * Similar to getServerKeyType, but returns value given TLS
+     * ClientCertificateType byte values from a CertificateRequest
+     * message for use with X509KeyManager.chooseClientAlias or
+     * X509ExtendedKeyManager.chooseEngineClientAlias.
+     */
+    public static String getClientKeyType(byte keyType) {
+        // See also http://www.ietf.org/assignments/tls-parameters/tls-parameters.xml
+        switch (keyType) {
+            case NativeCrypto.TLS_CT_RSA_SIGN:
+                return KEY_TYPE_RSA; // RFC rsa_sign
+            case NativeCrypto.TLS_CT_DSS_SIGN:
+                return KEY_TYPE_DSA; // RFC dss_sign
+            case NativeCrypto.TLS_CT_RSA_FIXED_DH:
+                return KEY_TYPE_DH_RSA; // RFC rsa_fixed_dh
+            case NativeCrypto.TLS_CT_DSS_FIXED_DH:
+                return KEY_TYPE_DH_DSA; // RFC dss_fixed_dh
+            case NativeCrypto.TLS_CT_ECDSA_SIGN:
+                return KEY_TYPE_EC; // RFC ecdsa_sign
+            case NativeCrypto.TLS_CT_RSA_FIXED_ECDH:
+                return KEY_TYPE_EC_RSA; // RFC rsa_fixed_ecdh
+            case NativeCrypto.TLS_CT_ECDSA_FIXED_ECDH:
+                return KEY_TYPE_EC_EC; // RFC ecdsa_fixed_ecdh
+            default:
+                return null;
+        }
+    }
+
+    private static String[] getDefaultCipherSuites(
+            boolean x509CipherSuitesNeeded,
+            boolean pskCipherSuitesNeeded) {
+        if (x509CipherSuitesNeeded) {
+            // X.509 based cipher suites need to be listed.
+            if (pskCipherSuitesNeeded) {
+                // Both X.509 and PSK based cipher suites need to be listed. Because TLS-PSK is not
+                // normally used, we assume that when PSK cipher suites are requested here they
+                // should be preferred over other cipher suites. Thus, we give PSK cipher suites
+                // higher priority than X.509 cipher suites.
+                // NOTE: There are cipher suites that use both X.509 and PSK (e.g., those based on
+                // RSA_PSK key exchange). However, these cipher suites are not currently supported.
+                return concat(
+                        NativeCrypto.DEFAULT_PSK_CIPHER_SUITES,
+                        NativeCrypto.DEFAULT_X509_CIPHER_SUITES,
+                        new String[] {NativeCrypto.TLS_EMPTY_RENEGOTIATION_INFO_SCSV});
+            } else {
+                // Only X.509 cipher suites need to be listed.
+                return concat(
+                        NativeCrypto.DEFAULT_X509_CIPHER_SUITES,
+                        new String[] {NativeCrypto.TLS_EMPTY_RENEGOTIATION_INFO_SCSV});
+            }
+        } else if (pskCipherSuitesNeeded) {
+            // Only PSK cipher suites need to be listed.
+            return concat(
+                    NativeCrypto.DEFAULT_PSK_CIPHER_SUITES,
+                    new String[] {NativeCrypto.TLS_EMPTY_RENEGOTIATION_INFO_SCSV});
+        } else {
+            // Neither X.509 nor PSK cipher suites need to be listed.
+            return new String[] {NativeCrypto.TLS_EMPTY_RENEGOTIATION_INFO_SCSV};
+        }
+    }
+
+    private static String[] getDefaultProtocols() {
+        return NativeCrypto.DEFAULT_PROTOCOLS.clone();
+    }
+
+    private static String[] concat(String[]... arrays) {
+        int resultLength = 0;
+        for (String[] array : arrays) {
+            resultLength += array.length;
+        }
+        String[] result = new String[resultLength];
+        int resultOffset = 0;
+        for (String[] array : arrays) {
+            System.arraycopy(array, 0, result, resultOffset, array.length);
+            resultOffset += array.length;
+        }
+        return result;
     }
 }
diff --git a/src/main/java/org/conscrypt/SSLRecordProtocol.java b/src/main/java/org/conscrypt/SSLRecordProtocol.java
index b8a9ba2..515a522 100644
--- a/src/main/java/org/conscrypt/SSLRecordProtocol.java
+++ b/src/main/java/org/conscrypt/SSLRecordProtocol.java
@@ -17,485 +17,35 @@
 
 package org.conscrypt;
 
-import java.io.IOException;
-import javax.net.ssl.SSLProtocolException;
 
 /**
- * This class performs functionality dedicated to SSL record layer.
- * It unpacks and routes income data to the appropriate
- * client protocol (handshake, alert, application data protocols)
- * and packages outcome data into SSL/TLS records.
- * Initially created object has null connection state and does not
- * perform any cryptography computations over the income/outcome data.
- * After handshake protocol agreed upon security parameters they are placed
- * into SSLSessionImpl object and available for record protocol as
- * pending session. The order of setting up of the pending session
- * as an active session differs for client and server modes.
- * So for client mode the parameters are provided by handshake protocol
- * during retrieving of change_cipher_spec message to be sent (by calling of
- * getChangeCipherSpecMesage method).
- * For server side mode record protocol retrieves the parameters from
- * handshake protocol after receiving of client's change_cipher_spec message.
- * After the pending session has been set up as a current session,
- * new connection state object is created and used for encryption/decryption
- * of the messages.
- * Among with base functionality this class provides the information about
- * constrains on the data length, and information about correspondence
- * of plain and encrypted data lengths.
- * For more information on TLS v1 see http://www.ietf.org/rfc/rfc2246.txt,
- * on SSL v3 see http://wp.netscape.com/eng/ssl3,
- * on SSL v2 see http://wp.netscape.com/eng/security/SSL_2.html.
+ * This class contains some SSL constants.
  */
 public class SSLRecordProtocol {
-
-    /**
-     * Maximum length of allowed plain data fragment
-     * as specified by TLS specification.
-     */
-    protected static final int MAX_DATA_LENGTH = 16384; // 2^14
-    /**
-     * Maximum length of allowed compressed data fragment
-     * as specified by TLS specification.
-     */
-    protected static final int MAX_COMPRESSED_DATA_LENGTH
-                                    = MAX_DATA_LENGTH + 1024;
-    /**
-     * Maximum length of allowed ciphered data fragment
-     * as specified by TLS specification.
-     */
-    protected static final int MAX_CIPHERED_DATA_LENGTH
-                                    = MAX_COMPRESSED_DATA_LENGTH + 1024;
-    /**
-     * Maximum length of ssl record. It is counted as:
-     * type(1) + version(2) + length(2) + MAX_CIPHERED_DATA_LENGTH
-     */
-    protected static final int MAX_SSL_PACKET_SIZE
-                                    = MAX_CIPHERED_DATA_LENGTH + 5;
-    // the SSL session used for connection
-    private SSLSessionImpl session;
-    // protocol version of the connection
-    private byte[] version;
-    // input stream of record protocol
-    private SSLInputStream in;
-    // handshake protocol object to which handshaking data will be transmitted
-    private HandshakeProtocol handshakeProtocol;
-    // alert protocol to indicate alerts occurred/received
-    private AlertProtocol alertProtocol;
-    // application data object to which application data will be transmitted
-    private Appendable appData;
-    // connection state holding object
-    private ConnectionState
-        activeReadState, activeWriteState, pendingConnectionState;
-
-    // logger
-    private Logger.Stream logger = Logger.getStream("record");
-
-    // flag indicating if session object has been changed after
-    // handshake phase (to distinguish session pending state)
-    private boolean sessionWasChanged = false;
-
-    // change cipher spec message content
-    private static final byte[] change_cipher_spec_byte = new byte[] {1};
-
-    /**
-     * Creates an instance of record protocol and tunes
-     * up the client protocols to use ut.
-     * @param   handshakeProtocol:  HandshakeProtocol
-     * @param   alertProtocol:  AlertProtocol
-     * @param   in: SSLInputStream
-     * @param   appData:    Appendable
-     */
-    protected SSLRecordProtocol(HandshakeProtocol handshakeProtocol,
-            AlertProtocol alertProtocol,
-            SSLInputStream in,
-            Appendable appData) {
-        this.handshakeProtocol = handshakeProtocol;
-        this.handshakeProtocol.setRecordProtocol(this);
-        this.alertProtocol = alertProtocol;
-        this.alertProtocol.setRecordProtocol(this);
-        this.in = in;
-        this.appData = appData;
+    private SSLRecordProtocol() {
     }
 
     /**
-     * Returns the session obtained during the handshake negotiation.
-     * If the handshake process was not completed, method returns null.
-     * @return the session in effect.
+     * Maximum length of allowed plain data fragment as specified by TLS
+     * specification.
      */
-    protected SSLSessionImpl getSession() {
-        return session;
-    }
+    static final int MAX_DATA_LENGTH = 16384; // 2^14
 
     /**
-     * Returns the minimum possible length of the SSL record.
-     * @return
+     * Maximum length of allowed compressed data fragment as specified by TLS
+     * specification.
      */
-    protected int getMinRecordSize() {
-        return (activeReadState == null)
-            ? 6 // type + version + length + 1 byte of data
-            : 5 + activeReadState.getMinFragmentSize();
-    }
+    static final int MAX_COMPRESSED_DATA_LENGTH = MAX_DATA_LENGTH + 1024;
 
     /**
-     * Returns the record length for the specified incoming data length.
-     * If actual resulting record length is greater than
-     * MAX_CIPHERED_DATA_LENGTH, MAX_CIPHERED_DATA_LENGTH is returned.
+     * Maximum length of allowed ciphered data fragment as specified by TLS
+     * specification.
      */
-    protected int getRecordSize(int data_size) {
-        if (activeWriteState == null) {
-            return 5+data_size; // type + version + length + data_size
-        } else {
-            int res = 5 + activeWriteState.getFragmentSize(data_size);
-            return (res > MAX_CIPHERED_DATA_LENGTH)
-                ? MAX_CIPHERED_DATA_LENGTH // so the source data should be
-                                           // split into several packets
-                : res;
-        }
-    }
+    static final int MAX_CIPHERED_DATA_LENGTH = MAX_COMPRESSED_DATA_LENGTH + 1024;
 
     /**
-     * Returns the upper bound of length of data containing in the record with
-     * specified length.
-     * If the provided record_size is greater or equal to
-     * MAX_CIPHERED_DATA_LENGTH the returned value will be
-     * MAX_DATA_LENGTH
-     * counted as for data with
-     * MAX_CIPHERED_DATA_LENGTH length.
+     * Maximum length of ssl record. It is counted as: type(1) + version(2) +
+     * length(2) + MAX_CIPHERED_DATA_LENGTH
      */
-    protected int getDataSize(int record_size) {
-        record_size -= 5; // - (type + version + length + data_size)
-        if (record_size > MAX_CIPHERED_DATA_LENGTH) {
-            // the data of such size consists of the several packets
-            return MAX_DATA_LENGTH;
-        }
-        if (activeReadState == null) {
-            return record_size;
-        }
-        return activeReadState.getContentSize(record_size);
-    }
-
-    /**
-     * Depending on the Connection State (Session) encrypts and compress the
-     * provided data, and packs it into TLSCiphertext structure.
-     *
-     * @param content_type the SSL/TLS content type
-     * @param dataStream the stream to read from
-     * @return ssl packet created over the current connection state
-     */
-    protected byte[] wrap(byte content_type, DataStream dataStream) {
-        if (content_type == ContentType.APPLICATION_DATA
-                && session != null
-                && (session.protocol == ProtocolVersion.SSLv3
-                    || session.protocol == ProtocolVersion.TLSv1)
-                && session.cipherSuite.isInitialRecordSplit()) {
-            byte[] first = wrap(content_type, dataStream, 1);
-            if (!dataStream.hasData()) {
-                return first;
-            }
-
-            byte[] second = wrap(content_type, dataStream, MAX_DATA_LENGTH);
-            byte[] output = new byte[first.length + second.length];
-            System.arraycopy(first, 0, output, 0, first.length);
-            System.arraycopy(second, 0, output, first.length, second.length);
-
-            return output;
-        } else {
-            return wrap(content_type, dataStream, MAX_DATA_LENGTH);
-        }
-    }
-
-    private byte[] wrap(byte content_type, DataStream dataStream, int max_len) {
-        byte[] fragment = dataStream.getData(max_len);
-        return wrap(content_type, fragment, 0, fragment.length);
-    }
-
-    /**
-     * Depending on the Connection State (Session) encrypts and compress
-     * the provided data, and packs it into TLSCiphertext structure.
-     * @param   content_type: int
-     * @param   fragment: byte[]
-     * @return  ssl packet created over the current connection state
-     */
-    protected byte[] wrap(byte content_type,
-                       byte[] fragment, int offset, int len) {
-        if (logger != null) {
-            logger.println("SSLRecordProtocol.wrap: TLSPlaintext.fragment["
-                    +len+"]:");
-            logger.print(fragment, offset, len);
-        }
-        if (len > MAX_DATA_LENGTH) {
-            throw new AlertException(
-                AlertProtocol.INTERNAL_ERROR,
-                new SSLProtocolException(
-                    "The provided chunk of data is too big: " + len
-                    + " > MAX_DATA_LENGTH == "+MAX_DATA_LENGTH));
-        }
-        byte[] ciphered_fragment = fragment;
-        if (activeWriteState != null) {
-            ciphered_fragment =
-                activeWriteState.encrypt(content_type, fragment, offset, len);
-            if (ciphered_fragment.length > MAX_CIPHERED_DATA_LENGTH) {
-                throw new AlertException(
-                    AlertProtocol.INTERNAL_ERROR,
-                    new SSLProtocolException(
-                        "The ciphered data increased more than on 1024 bytes"));
-            }
-            if (logger != null) {
-                logger.println("SSLRecordProtocol.wrap: TLSCiphertext.fragment["
-                        +ciphered_fragment.length+"]:");
-                logger.print(ciphered_fragment);
-            }
-        }
-        return packetize(content_type, version, ciphered_fragment);
-    }
-
-    private byte[] packetize(byte type, byte[] version, byte[] fragment) {
-        byte[] buff = new byte[5+fragment.length];
-        buff[0] = type;
-        if (version != null) {
-            buff[1] = version[0];
-            buff[2] = version[1];
-        } else {
-            buff[1] = 3;
-            buff[2] = 1;
-        }
-        buff[3] = (byte) ((0x00FF00 & fragment.length) >> 8);
-        buff[4] = (byte) (0x0000FF & fragment.length);
-        System.arraycopy(fragment, 0, buff, 5, fragment.length);
-        return buff;
-    }
-
-    /**
-     * Set the ssl session to be used after sending the changeCipherSpec message
-     * @param   session:    SSLSessionImpl
-     */
-    private void setSession(SSLSessionImpl session) {
-        if (!sessionWasChanged) {
-            // session was not changed for current handshake process
-            if (logger != null) {
-                logger.println("SSLRecordProtocol.setSession: Set pending session");
-                logger.println("  cipher name: " + session.getCipherSuite());
-            }
-            this.session = session;
-            // create new connection state
-            pendingConnectionState = ((version == null) || (version[1] == 1))
-                ? (ConnectionState) new ConnectionStateTLS(getSession())
-                : (ConnectionState) new ConnectionStateSSLv3(getSession());
-            sessionWasChanged = true;
-        } else {
-            // wait for rehandshaking's session
-            sessionWasChanged = false;
-        }
-    }
-
-    /**
-     * Returns the change cipher spec message to be sent to another peer.
-     * The pending connection state will be built on the base of provided
-     * session object
-     * The calling of this method triggers pending write connection state to
-     * be active.
-     * @return ssl record containing the "change cipher spec" message.
-     */
-    protected byte[] getChangeCipherSpecMesage(SSLSessionImpl session) {
-        // make change_cipher_spec_message:
-        byte[] change_cipher_spec_message;
-        if (activeWriteState == null) {
-            change_cipher_spec_message = new byte[] {
-                    ContentType.CHANGE_CIPHER_SPEC, version[0],
-                        version[1], 0, 1, 1
-                };
-        } else {
-            change_cipher_spec_message =
-                packetize(ContentType.CHANGE_CIPHER_SPEC, version,
-                        activeWriteState.encrypt(ContentType.CHANGE_CIPHER_SPEC,
-                            change_cipher_spec_byte, 0, 1));
-        }
-        setSession(session);
-        activeWriteState = pendingConnectionState;
-        if (logger != null) {
-            logger.println("SSLRecordProtocol.getChangeCipherSpecMesage");
-            logger.println("activeWriteState = pendingConnectionState");
-            logger.print(change_cipher_spec_message);
-        }
-        return change_cipher_spec_message;
-    }
-
-    /**
-     * Retrieves the fragment field of TLSCiphertext, and than
-     * depending on the established Connection State
-     * decrypts and decompresses it. The following structure is expected
-     * on the input at the moment of the call:
-     *
-     *  struct {
-     *      ContentType type;
-     *      ProtocolVersion version;
-     *      uint16 length;
-     *      select (CipherSpec.cipher_type) {
-     *          case stream: GenericStreamCipher;
-     *          case block: GenericBlockCipher;
-     *      } fragment;
-     *  } TLSCiphertext;
-     *
-     * (as specified by RFC 2246, TLS v1 Protocol specification)
-     *
-     * In addition this method can recognize SSLv2 hello message which
-     * are often used to establish the SSL/TLS session.
-     *
-     * @throws IOException if some io errors have been occurred
-     * @throws EndOfSourceException if underlying input stream
-     *                              has ran out of data.
-     * @throws EndOfBufferException if there was not enough data
-     *                              to build complete ssl packet.
-     * @return the type of unwrapped message.
-     */
-    protected int unwrap() throws IOException {
-        if (logger != null) {
-            logger.println("SSLRecordProtocol.unwrap: BEGIN [");
-        }
-        int type = in.readUint8();
-        if ((type < ContentType.CHANGE_CIPHER_SPEC)
-                || (type > ContentType.APPLICATION_DATA)) {
-            if (logger != null) {
-                logger.println("Non v3.1 message type:" + type);
-            }
-            if (type >= 0x80) {
-                // it is probably SSL v2 client_hello message
-                // (see SSL v2 spec at:
-                // http://wp.netscape.com/eng/security/SSL_2.html)
-                int length = (type & 0x7f) << 8 | in.read();
-                byte[] fragment = in.read(length);
-                handshakeProtocol.unwrapSSLv2(fragment);
-                if (logger != null) {
-                    logger.println(
-                            "SSLRecordProtocol:unwrap ] END, SSLv2 type");
-                }
-                return ContentType.HANDSHAKE;
-            }
-            throw new AlertException(AlertProtocol.UNEXPECTED_MESSAGE,
-                    new SSLProtocolException(
-                        "Unexpected message type has been received: "+type));
-        }
-        if (logger != null) {
-            logger.println("Got the message of type: " + type);
-        }
-        if (version != null) {
-            if ((in.read() != version[0])
-                    || (in.read() != version[1])) {
-                throw new AlertException(AlertProtocol.UNEXPECTED_MESSAGE,
-                        new SSLProtocolException(
-                            "Unexpected message type has been received: " +
-                            type));
-            }
-        } else {
-            in.skip(2); // just skip the version number
-        }
-        int length = in.readUint16();
-        if (logger != null) {
-            logger.println("TLSCiphertext.fragment["+length+"]: ...");
-        }
-        if (length > MAX_CIPHERED_DATA_LENGTH) {
-            throw new AlertException(AlertProtocol.RECORD_OVERFLOW,
-                    new SSLProtocolException(
-                        "Received message is too big."));
-        }
-        byte[] fragment = in.read(length);
-        if (logger != null) {
-            logger.print(fragment);
-        }
-        if (activeReadState != null) {
-            fragment = activeReadState.decrypt((byte) type, fragment);
-            if (logger != null) {
-                logger.println("TLSPlaintext.fragment:");
-                logger.print(fragment);
-            }
-        }
-        if (fragment.length > MAX_DATA_LENGTH) {
-            throw new AlertException(AlertProtocol.DECOMPRESSION_FAILURE,
-                    new SSLProtocolException(
-                        "Decompressed plain data is too big."));
-        }
-        switch (type) {
-            case ContentType.CHANGE_CIPHER_SPEC:
-                // notify handshake protocol:
-                handshakeProtocol.receiveChangeCipherSpec();
-                setSession(handshakeProtocol.getSession());
-                // change cipher spec message has been received, so:
-                if (logger != null) {
-                    logger.println("activeReadState = pendingConnectionState");
-                }
-                activeReadState = pendingConnectionState;
-                break;
-            case ContentType.ALERT:
-                alert(fragment[0], fragment[1]);
-                break;
-            case ContentType.HANDSHAKE:
-                handshakeProtocol.unwrap(fragment);
-                break;
-            case ContentType.APPLICATION_DATA:
-                if (logger != null) {
-                    logger.println(
-                            "TLSCiphertext.unwrap: APP DATA["+length+"]:");
-                    logger.println(new String(fragment));
-                }
-                appData.append(fragment);
-                break;
-            default:
-                throw new AlertException(AlertProtocol.UNEXPECTED_MESSAGE,
-                        new SSLProtocolException(
-                            "Unexpected message type has been received: " +
-                            type));
-        }
-        if (logger != null) {
-            logger.println("SSLRecordProtocol:unwrap ] END, type: " + type);
-        }
-        return type;
-    }
-
-    /**
-     * Passes the alert information to the alert protocol.
-     * @param   level:  byte
-     * @param   description:    byte
-     */
-    protected void alert(byte level, byte description) {
-        if (logger != null) {
-            logger.println("SSLRecordProtocol.allert: "+level+" "+description);
-        }
-        alertProtocol.alert(level, description);
-    }
-
-    /**
-     * Sets up the SSL version used in this connection.
-     * This method is calling from the handshake protocol after
-     * it becomes known which protocol version will be used.
-     * @param   ver:    byte[]
-     * @return
-     */
-    protected void setVersion(byte[] ver) {
-        this.version = ver;
-    }
-
-    /**
-     * Shuts down the protocol. It will be impossible to use the instance
-     * after the calling of this method.
-     */
-    protected void shutdown() {
-        session = null;
-        version = null;
-        in = null;
-        handshakeProtocol = null;
-        alertProtocol = null;
-        appData = null;
-        if (pendingConnectionState != null) {
-            pendingConnectionState.shutdown();
-        }
-        pendingConnectionState = null;
-        if (activeReadState != null) {
-            activeReadState.shutdown();
-        }
-        activeReadState = null;
-        if (activeReadState != null) {
-            activeReadState.shutdown();
-        }
-        activeWriteState = null;
-    }
+    static final int MAX_SSL_PACKET_SIZE = MAX_CIPHERED_DATA_LENGTH + 5;
 }
diff --git a/src/main/java/org/conscrypt/SSLSessionImpl.java b/src/main/java/org/conscrypt/SSLSessionImpl.java
deleted file mode 100644
index 63fa828..0000000
--- a/src/main/java/org/conscrypt/SSLSessionImpl.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- *  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.Principal;
-import java.security.SecureRandom;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-import java.util.HashMap;
-import java.util.Map;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSessionBindingEvent;
-import javax.net.ssl.SSLSessionBindingListener;
-import javax.net.ssl.SSLSessionContext;
-import org.conscrypt.util.EmptyArray;
-
-public final class SSLSessionImpl implements SSLSession, Cloneable  {
-
-    /*
-     * Holds default instances so class preloading doesn't create an instance of
-     * it.
-     */
-    private static class DefaultHolder {
-        public static final SSLSessionImpl NULL_SESSION = new SSLSessionImpl(null);
-    }
-
-    private long creationTime;
-    private boolean isValid = true;
-    private final Map<String, Object> values = new HashMap<String, Object>();
-
-    byte[] id;
-    long lastAccessedTime;
-    ProtocolVersion protocol;
-    CipherSuite cipherSuite;
-    SSLSessionContext context;
-    X509Certificate[] localCertificates;
-    X509Certificate[] peerCertificates;
-    private String peerHost;
-    private int peerPort = -1;
-    byte[] master_secret;
-    byte[] clientRandom;
-    byte[] serverRandom;
-    final boolean isServer;
-
-    public static SSLSessionImpl getNullSession() {
-        return DefaultHolder.NULL_SESSION;
-    }
-
-    public SSLSessionImpl(CipherSuite cipher_suite, SecureRandom secureRandom) {
-        creationTime = System.currentTimeMillis();
-        lastAccessedTime = creationTime;
-        if (cipher_suite == null) {
-            this.cipherSuite = CipherSuite.SSL_NULL_WITH_NULL_NULL;
-            id = EmptyArray.BYTE;
-            isServer = false;
-            isValid = false;
-        } else {
-            this.cipherSuite = cipher_suite;
-            id = new byte[32];
-            secureRandom.nextBytes(id);
-            long time = creationTime / 1000;
-            id[28] = (byte) ((time & 0xFF000000) >>> 24);
-            id[29] = (byte) ((time & 0x00FF0000) >>> 16);
-            id[30] = (byte) ((time & 0x0000FF00) >>> 8);
-            id[31] = (byte) ((time & 0x000000FF));
-            isServer = true;
-        }
-
-    }
-
-    public SSLSessionImpl(SecureRandom secureRandom) {
-        this(null, secureRandom);
-    }
-
-    @Override
-    public int getApplicationBufferSize() {
-        return SSLRecordProtocol.MAX_DATA_LENGTH;
-    }
-
-    @Override
-    public String getCipherSuite() {
-        return cipherSuite.getName();
-    }
-
-    @Override
-    public long getCreationTime() {
-        return creationTime;
-    }
-
-    @Override
-    public byte[] getId() {
-        return id;
-    }
-
-    @Override
-    public long getLastAccessedTime() {
-        return lastAccessedTime;
-    }
-
-    @Override
-    public Certificate[] getLocalCertificates() {
-        return localCertificates;
-    }
-
-    @Override
-    public Principal getLocalPrincipal() {
-        if (localCertificates != null && localCertificates.length > 0) {
-            return localCertificates[0].getSubjectX500Principal();
-        }
-        return null;
-    }
-
-    @Override
-    public int getPacketBufferSize() {
-        return SSLRecordProtocol.MAX_SSL_PACKET_SIZE;
-    }
-
-    @Override
-    public javax.security.cert.X509Certificate[] getPeerCertificateChain()
-            throws SSLPeerUnverifiedException {
-        if (peerCertificates == null) {
-            throw new SSLPeerUnverifiedException("No peer certificate");
-        }
-        javax.security.cert.X509Certificate[] certs = new javax.security.cert.X509Certificate[peerCertificates.length];
-        for (int i = 0; i < certs.length; i++) {
-            try {
-                certs[i] = javax.security.cert.X509Certificate.getInstance(peerCertificates[i]
-                        .getEncoded());
-            } catch (javax.security.cert.CertificateException ignored) {
-            } catch (CertificateEncodingException ignored) {
-            }
-        }
-        return certs;
-    }
-
-    @Override
-    public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
-        if (peerCertificates == null) {
-            throw new SSLPeerUnverifiedException("No peer certificate");
-        }
-        return peerCertificates;
-    }
-
-    @Override
-    public String getPeerHost() {
-        return peerHost;
-    }
-
-    @Override
-    public int getPeerPort() {
-        return peerPort;
-    }
-
-    @Override
-    public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
-        if (peerCertificates == null) {
-            throw new SSLPeerUnverifiedException("No peer certificate");
-        }
-        return peerCertificates[0].getSubjectX500Principal();
-    }
-
-    @Override
-    public String getProtocol() {
-        return (protocol == null) ? "NONE" : protocol.name;
-    }
-
-    @Override
-    public SSLSessionContext getSessionContext() {
-        return context;
-    }
-
-    @Override
-    public Object getValue(String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("name == null");
-        }
-        return values.get(name);
-    }
-
-    @Override
-    public String[] getValueNames() {
-        return values.keySet().toArray(new String[values.size()]);
-    }
-
-    @Override
-    public void invalidate() {
-        isValid = false;
-        context = null;
-    }
-
-    @Override
-    public boolean isValid() {
-        if (isValid && context != null && context.getSessionTimeout() != 0
-                && lastAccessedTime + context.getSessionTimeout() > System.currentTimeMillis()) {
-            isValid = false;
-        }
-        return isValid;
-    }
-
-    @Override
-    public void putValue(String name, Object value) {
-        if (name == null || value == null) {
-            throw new IllegalArgumentException("name == null || value == null");
-        }
-        Object old = values.put(name, value);
-        if (value instanceof SSLSessionBindingListener) {
-            ((SSLSessionBindingListener) value).valueBound(new SSLSessionBindingEvent(this, name));
-        }
-        if (old instanceof SSLSessionBindingListener) {
-            ((SSLSessionBindingListener) old).valueUnbound(new SSLSessionBindingEvent(this, name));
-        }
-
-    }
-
-    @Override
-    public void removeValue(String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("name == null");
-        }
-        Object old = values.remove(name);
-        if (old instanceof SSLSessionBindingListener) {
-            SSLSessionBindingListener listener = (SSLSessionBindingListener) old;
-            listener.valueUnbound(new SSLSessionBindingEvent(this, name));
-        }
-    }
-
-    @Override
-    public Object clone() {
-        try {
-            return super.clone();
-        } catch (CloneNotSupportedException e) {
-            throw new AssertionError(e);
-        }
-    }
-
-    void setPeer(String peerHost, int peerPort) {
-        this.peerHost = peerHost;
-        this.peerPort = peerPort;
-    }
-}
diff --git a/src/main/java/org/conscrypt/SSLStreamedInput.java b/src/main/java/org/conscrypt/SSLStreamedInput.java
deleted file mode 100644
index 4c8a885..0000000
--- a/src/main/java/org/conscrypt/SSLStreamedInput.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- *  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.io.IOException;
-import java.io.InputStream;
-
-/**
- * This class acts like a filtered input stream: it takes
- * the bytes from another InputStream.
- */
-public class SSLStreamedInput extends SSLInputStream {
-
-    private InputStream in;
-
-    public SSLStreamedInput(InputStream in) {
-        this.in = in;
-    }
-
-    @Override
-    public int available() throws IOException {
-        return in.available();
-    }
-
-    /**
-     * Read an opaque value from the stream.
-     * @return the value read from the underlying stream.
-     * @throws IOException if the data could not be read from
-     * the underlying stream
-     * @throws org.conscrypt.EndOfSourceException if the end of the underlying
-     * stream has been reached.
-     */
-    @Override
-    public int read() throws IOException {
-        int res = in.read();
-        if (res < 0) {
-            throw new EndOfSourceException();
-        }
-        return res;
-    }
-}
-
diff --git a/src/main/java/org/conscrypt/SSLv3Constants.java b/src/main/java/org/conscrypt/SSLv3Constants.java
deleted file mode 100644
index ea6482f..0000000
--- a/src/main/java/org/conscrypt/SSLv3Constants.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- *  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;
-
-/**
- *
- * Contains SSL 3.0 constants
- * @see <a href="http://wp.netscape.com/eng/ssl3">SSL 3.0 Spec.</a>
- */
-public class SSLv3Constants {
-
-    /**
-     * Client is a sender. Used in hash calculating for finished message.
-     * @see <a href="http://wp.netscape.com/eng/ssl3">SSL 3.0 Spec., 5.6.9
-     * Finished</a>
-     */
-    static final byte[] client = new byte[] { 0x43, 0x4C, 0x4E, 0x54 };
-
-    /**
-     * Server is a sender. Used in hash calculating for finished message.
-     * @see <a href="http://wp.netscape.com/eng/ssl3">SSL 3.0 Spec., 5.6.9
-     * Finished</a>
-     */
-    static final byte[] server = new byte[] { 0x53, 0x52, 0x56, 0x52 };
-
-    /**
-     * pad_1 for MD5
-     * @see <a href="http://wp.netscape.com/eng/ssl3">SSL 3.0 Spec., 5.2.3.1
-     * Null or standard stream cipher</a>
-     */
-    static final byte[] MD5pad1 = new byte[] { 0x36, 0x36, 0x36, 0x36,
-            0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
-            0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
-            0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
-            0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36 };
-
-    /**
-     * pad_1 for SHA
-     * @see <a href="http://wp.netscape.com/eng/ssl3">SSL 3.0 Spec., 5.2.3.1
-     * Null or standard stream cipher</a>
-     */
-    static final byte[] SHApad1 = new byte[] { 0x36, 0x36, 0x36, 0x36,
-            0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
-            0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
-            0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
-            0x36, 0x36, 0x36 };
-
-    /**
-     * pad_2 for MD5
-     * @see <a href="http://wp.netscape.com/eng/ssl3">SSL 3.0 Spec., 5.2.3.1
-     * Null or standard stream cipher</a>
-     */
-    static final byte[] MD5pad2 = new byte[] { 0x5C, 0x5C, 0x5C, 0x5C,
-            0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C,
-            0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C,
-            0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C,
-            0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C };
-
-    /**
-     * pad_2 for SHA
-     * @see <a href="http://wp.netscape.com/eng/ssl3">SSL 3.0 Spec., 5.2.3.1
-     * Null or standard stream cipher</a>
-     */
-    static final byte[] SHApad2 = new byte[] { 0x5C, 0x5C, 0x5C, 0x5C,
-            0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C,
-            0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C,
-            0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C, 0x5C,
-            0x5C, 0x5C, 0x5C };
-}
diff --git a/src/main/java/org/conscrypt/ServerHandshakeImpl.java b/src/main/java/org/conscrypt/ServerHandshakeImpl.java
deleted file mode 100644
index afbac56..0000000
--- a/src/main/java/org/conscrypt/ServerHandshakeImpl.java
+++ /dev/null
@@ -1,644 +0,0 @@
-/*
- *  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.io.IOException;
-import java.math.BigInteger;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.RSAPublicKey;
-import java.util.Arrays;
-import javax.crypto.Cipher;
-import javax.crypto.KeyAgreement;
-import javax.crypto.interfaces.DHPublicKey;
-import javax.crypto.spec.DHParameterSpec;
-import javax.crypto.spec.DHPublicKeySpec;
-import javax.net.ssl.X509ExtendedKeyManager;
-import javax.net.ssl.X509KeyManager;
-import javax.net.ssl.X509TrustManager;
-
-/**
- * Server side handshake protocol implementation.
- * Handshake protocol operates on top of the Record Protocol.
- * It responsible for negotiating a session.
- *
- * The implementation processes inbound client handshake messages,
- * creates and sends respond messages. Outbound messages are supplied
- * to Record Protocol. Detected errors are reported to the Alert protocol.
- *
- * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.
- * Handshake protocol.</a>
- *
- */
-public class ServerHandshakeImpl extends HandshakeProtocol {
-
-    // private key used in key exchange
-    private PrivateKey privKey;
-
-    /**
-     * Creates Server Handshake Implementation
-     *
-     * @param owner
-     */
-    public ServerHandshakeImpl(SSLEngineImpl owner) {
-        super(owner);
-        status = NEED_UNWRAP;
-    }
-
-    /**
-     * Start session negotiation
-     */
-    @Override
-    public void start() {
-        if (session == null) { // initial handshake
-            status = NEED_UNWRAP;
-            return; // wait client hello
-        }
-        if (clientHello != null && this.status != FINISHED) {
-            // current negotiation has not completed
-            return; // ignore
-        }
-
-        // renegotiation
-        sendHelloRequest();
-        status = NEED_UNWRAP;
-    }
-
-    /**
-     * Proceses inbound handshake messages
-     * @param bytes
-     */
-    @Override
-    public void unwrap(byte[] bytes) {
-
-        io_stream.append(bytes);
-        while (io_stream.available() > 0) {
-            int handshakeType;
-            int length;
-            io_stream.mark();
-            try {
-                handshakeType = io_stream.read();
-                length = io_stream.readUint24();
-                if (io_stream.available() < length) {
-                    io_stream.reset();
-                    return;
-                }
-
-                switch (handshakeType) {
-                case 1: // CLIENT_HELLO
-                    if (clientHello != null && this.status != FINISHED) {
-                            // Client hello has been received during handshake
-                            unexpectedMessage();
-                            return;
-                    }
-                    // if protocol planed to send Hello Request message
-                    // - cancel this demand.
-                    needSendHelloRequest = false;
-                    clientHello = new ClientHello(io_stream, length);
-                    if (nonBlocking) {
-                        delegatedTasks.add(new DelegatedTask(new Runnable() {
-                            @Override
-                            public void run() {
-                                processClientHello();
-                            }
-                        }, this));
-                        return;
-                    }
-                    processClientHello();
-                    break;
-
-                case 11: //    CLIENT CERTIFICATE
-                    if (isResuming || certificateRequest == null
-                            || serverHelloDone == null || clientCert != null) {
-                        unexpectedMessage();
-                        return;
-                    }
-                    clientCert = new CertificateMessage(io_stream, length);
-                    if (clientCert.certs.length == 0) {
-                        if (parameters.getNeedClientAuth()) {
-                            fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
-                                       "HANDSHAKE FAILURE: no client certificate received");
-                        }
-                    } else {
-                        String authType = clientCert.getAuthType();
-                        try {
-                            parameters.getTrustManager().checkClientTrusted(
-                                    clientCert.certs, authType);
-                        } catch (CertificateException e) {
-                            fatalAlert(AlertProtocol.BAD_CERTIFICATE,
-                                       "Untrusted Client Certificate ", e);
-                        }
-                        session.peerCertificates = clientCert.certs;
-                    }
-                    break;
-
-                case 15: // CERTIFICATE_VERIFY
-                    if (isResuming
-                            || clientKeyExchange == null
-                            || clientCert == null
-                            || clientKeyExchange.isEmpty() //client certificate
-                                                           // contains fixed DH
-                                                           // parameters
-                            || certificateVerify != null
-                            || changeCipherSpecReceived) {
-                        unexpectedMessage();
-                        return;
-                    }
-                    certificateVerify = new CertificateVerify(io_stream, length);
-
-                    String authType = clientCert.getAuthType();
-                    DigitalSignature ds = new DigitalSignature(authType);
-                    ds.init(clientCert.certs[0]);
-                    byte[] md5_hash = null;
-                    byte[] sha_hash = null;
-
-                    if ("RSA".equals(authType)) {
-                        md5_hash = io_stream.getDigestMD5withoutLast();
-                        sha_hash = io_stream.getDigestSHAwithoutLast();
-                    } else if ("DSA".equals(authType)) {
-                        sha_hash = io_stream.getDigestSHAwithoutLast();
-                    // The Signature should be empty in case of anonymous signature algorithm:
-                    // } else if ("DH".equals(authType)) {
-                    }
-                    ds.setMD5(md5_hash);
-                    ds.setSHA(sha_hash);
-                    if (!ds.verifySignature(certificateVerify.signedHash)) {
-                        fatalAlert(AlertProtocol.DECRYPT_ERROR,
-                                   "DECRYPT ERROR: CERTIFICATE_VERIFY incorrect signature");
-                    }
-                    break;
-                case 16: // CLIENT_KEY_EXCHANGE
-                    if (isResuming
-                            || serverHelloDone == null
-                            || clientKeyExchange != null
-                            || (clientCert == null && parameters.getNeedClientAuth())) {
-                        unexpectedMessage();
-                        return;
-                    }
-                    if (session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA
-                            || session.cipherSuite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA_EXPORT) {
-                        clientKeyExchange = new ClientKeyExchange(io_stream,
-                                length, serverHello.server_version[1] == 1,
-                                true);
-                        Cipher c = null;
-                        try {
-                            c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
-                            c.init(Cipher.UNWRAP_MODE, privKey);
-                            preMasterSecret = c.unwrap(clientKeyExchange.exchange_keys,
-                                                       "preMasterSecret",
-                                                       Cipher.SECRET_KEY).getEncoded();
-                            // check preMasterSecret:
-                            if (preMasterSecret.length != 48
-                                    || preMasterSecret[0] != clientHello.client_version[0]
-                                    || preMasterSecret[1] != clientHello.client_version[1]) {
-                                // incorrect preMasterSecret
-                                // prevent an attack (see TLS 1.0 spec., 7.4.7.1.)
-                                preMasterSecret = new byte[48];
-                                parameters.getSecureRandom().nextBytes(
-                                        preMasterSecret);
-                            }
-                        } catch (Exception e) {
-                            fatalAlert(AlertProtocol.INTERNAL_ERROR,
-                                       "INTERNAL ERROR", e);
-                        }
-                    } else { // diffie hellman key exchange
-                        clientKeyExchange = new ClientKeyExchange(io_stream,
-                                length, serverHello.server_version[1] == 1,
-                                false);
-                        if (clientKeyExchange.isEmpty()) {
-                            // TODO check that client cert. DH params
-                            // matched server cert. DH params
-
-                            // client cert. contains fixed DH parameters
-                            preMasterSecret = ((DHPublicKey) clientCert.certs[0].getPublicKey()).getY().toByteArray();
-                        } else {
-                            try {
-                                KeyFactory kf = KeyFactory.getInstance("DH");
-                                KeyAgreement agreement = KeyAgreement.getInstance("DH");
-                                PublicKey clientPublic = kf.generatePublic(new DHPublicKeySpec(
-                                                new BigInteger(
-                                                        1,
-                                                        clientKeyExchange.exchange_keys),
-                                                serverKeyExchange.par1,
-                                                serverKeyExchange.par2));
-                                agreement.init(privKey);
-                                agreement.doPhase(clientPublic, true);
-                                preMasterSecret = agreement.generateSecret();
-                            } catch (Exception e) {
-                                fatalAlert(AlertProtocol.INTERNAL_ERROR,
-                                           "INTERNAL ERROR", e);
-                                return;
-                            }
-                        }
-                    }
-
-                    computerMasterSecret();
-                    break;
-
-                case 20: // FINISHED
-                    if (!isResuming && !changeCipherSpecReceived) {
-                        unexpectedMessage();
-                        return;
-                    }
-
-                    clientFinished = new Finished(io_stream, length);
-                    verifyFinished(clientFinished.getData());
-                    session.context = parameters.getServerSessionContext();
-                    parameters.getServerSessionContext().putSession(session);
-                    if (!isResuming) {
-                        sendChangeCipherSpec();
-                    } else {
-                        session.lastAccessedTime = System.currentTimeMillis();
-                        status = FINISHED;
-                    }
-                    break;
-                default:
-                    unexpectedMessage();
-                    return;
-                }
-            } catch (IOException e) {
-                // io stream dosn't contain complete handshake message
-                io_stream.reset();
-                return;
-            }
-        }
-    }
-    /**
-     * Processes SSLv2 Hello message
-     * @ see TLS 1.0 spec., E.1. Version 2 client hello
-     * @param bytes
-     */
-    @Override
-    public void unwrapSSLv2(byte[] bytes) {
-        io_stream.append(bytes);
-        io_stream.mark();
-        try {
-            clientHello = new ClientHello(io_stream);
-        } catch (IOException e) {
-            io_stream.reset();
-            return;
-        }
-        if (nonBlocking) {
-            delegatedTasks.add(new DelegatedTask(new Runnable() {
-                @Override
-                public void run() {
-                    processClientHello();
-                }
-            }, this));
-            return;
-        }
-        processClientHello();
-    }
-
-    /**
-     *
-     * Processes Client Hello message.
-     * Server responds to client hello message with server hello
-     * and (if necessary) server certificate, server key exchange,
-     * certificate request, and server hello done messages.
-     */
-    void processClientHello() {
-        CipherSuite cipher_suite;
-
-        // check that clientHello contains CompressionMethod.null
-        checkCompression: {
-            for (int i = 0; i < clientHello.compression_methods.length; i++) {
-                if (clientHello.compression_methods[i] == 0) {
-                    break checkCompression;
-                }
-            }
-            fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
-                       "HANDSHAKE FAILURE. Incorrect client hello message");
-        }
-
-        byte[] server_version = clientHello.client_version;
-        if (!ProtocolVersion.isSupported(clientHello.client_version)) {
-            if (clientHello.client_version[0] >= 3) {
-                // Protocol from the future, admit that the newest thing we know is TLSv1
-                server_version = ProtocolVersion.TLSv1.version;
-            } else {
-                fatalAlert(AlertProtocol.PROTOCOL_VERSION,
-                           "PROTOCOL VERSION. Unsupported client version "
-                           + clientHello.client_version[0]
-                           + clientHello.client_version[1]);
-            }
-        }
-
-        isResuming = false;
-        FIND: if (clientHello.session_id.length != 0) {
-            // client wishes to reuse session
-
-            SSLSessionImpl sessionToResume;
-            boolean reuseCurrent = false;
-
-            // reuse current session
-            if (session != null
-                    && Arrays.equals(session.id, clientHello.session_id)) {
-                if (session.isValid()) {
-                    isResuming = true;
-                    break FIND;
-                }
-                reuseCurrent = true;
-            }
-
-            // find session in cash
-            sessionToResume = findSessionToResume(clientHello.session_id);
-            if (sessionToResume == null || !sessionToResume.isValid()) {
-                if (!parameters.getEnableSessionCreation()) {
-                    if (reuseCurrent) {
-                        // we can continue current session
-                        sendWarningAlert(AlertProtocol.NO_RENEGOTIATION);
-                        status = NOT_HANDSHAKING;
-                        clearMessages();
-                        return;
-                    }
-                    // throw AlertException
-                    fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "SSL Session may not be created");
-                }
-                session = null;
-            } else {
-                session = (SSLSessionImpl)sessionToResume.clone();
-                isResuming = true;
-            }
-        }
-
-        if (isResuming) {
-            cipher_suite = session.cipherSuite;
-            // clientHello.cipher_suites must include at least cipher_suite from the session
-            checkCipherSuite: {
-                for (int i = 0; i < clientHello.cipher_suites.length; i++) {
-                    if (cipher_suite.equals(clientHello.cipher_suites[i])) {
-                        break checkCipherSuite;
-                    }
-                }
-                fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
-                           "HANDSHAKE FAILURE. Incorrect client hello message");
-            }
-        } else {
-            cipher_suite = selectSuite(clientHello.cipher_suites);
-            if (cipher_suite == null) {
-                fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "HANDSHAKE FAILURE. NO COMMON SUITE");
-            }
-            if (!parameters.getEnableSessionCreation()) {
-                fatalAlert(AlertProtocol.HANDSHAKE_FAILURE,
-                           "SSL Session may not be created");
-            }
-            session = new SSLSessionImpl(cipher_suite, parameters.getSecureRandom());
-            session.setPeer(engineOwner.getPeerHost(), engineOwner.getPeerPort());
-        }
-
-        recordProtocol.setVersion(server_version);
-        session.protocol = ProtocolVersion.getByVersion(server_version);
-        session.clientRandom = clientHello.random;
-
-        // create server hello message
-        serverHello = new ServerHello(parameters.getSecureRandom(),
-                server_version,
-                session.getId(), cipher_suite, (byte) 0); //CompressionMethod.null
-        session.serverRandom = serverHello.random;
-        send(serverHello);
-        if (isResuming) {
-            sendChangeCipherSpec();
-            return;
-        }
-
-        //    create and send server certificate message if needed
-        if (!cipher_suite.isAnonymous()) { // need to send server certificate
-            X509Certificate[] certs = null;
-            String certType = cipher_suite.getServerKeyType();
-            if (certType == null) {
-                fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "NO CERT TYPE FOR " + cipher_suite.getName());
-            }
-            // obtain certificates from key manager
-            String alias = null;
-            X509KeyManager km = parameters.getKeyManager();
-            if (km instanceof X509ExtendedKeyManager) {
-                X509ExtendedKeyManager ekm = (X509ExtendedKeyManager)km;
-                alias = ekm.chooseEngineServerAlias(certType, null,
-                        this.engineOwner);
-                if (alias != null) {
-                    certs = ekm.getCertificateChain(alias);
-                }
-            } else {
-                alias = km.chooseServerAlias(certType, null, null);
-                if (alias != null) {
-                    certs = km.getCertificateChain(alias);
-                }
-            }
-
-            if (certs == null) {
-                fatalAlert(AlertProtocol.HANDSHAKE_FAILURE, "NO SERVER CERTIFICATE FOUND");
-                return;
-            }
-            session.localCertificates = certs;
-            serverCert = new CertificateMessage(certs);
-            privKey = km.getPrivateKey(alias);
-            send(serverCert);
-        }
-
-        // create and send server key exchange message if needed
-        RSAPublicKey rsakey = null;
-        DHPublicKeySpec dhkeySpec = null;
-        byte[] hash = null;
-        BigInteger p = null;
-        BigInteger g = null;
-
-        KeyPairGenerator kpg = null;
-
-        try {
-            if (cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA_EXPORT) {
-                PublicKey pk = serverCert.certs[0].getPublicKey();
-                if (getRSAKeyLength(pk) > 512) {
-                    // key is longer than 512 bits
-                    kpg = KeyPairGenerator.getInstance("RSA");
-                    kpg.initialize(512);
-                }
-            } else if (cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_DSS
-                    || cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_DSS_EXPORT
-                    || cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_RSA
-                    || cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_DHE_RSA_EXPORT
-                    || cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_DH_anon
-                    || cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_DH_anon_EXPORT) {
-                kpg = KeyPairGenerator.getInstance("DH");
-                p = new BigInteger(1, DHParameters.getPrime());
-                g = new BigInteger("2");
-                DHParameterSpec spec = new DHParameterSpec(p, g);
-                kpg.initialize(spec);
-            }
-        } catch (Exception e) {
-            fatalAlert(AlertProtocol.INTERNAL_ERROR, "INTERNAL ERROR", e);
-        }
-
-        if (kpg != null) {
-            // need to send server key exchange message
-            DigitalSignature ds = new DigitalSignature(cipher_suite.authType);
-            KeyPair kp = null;
-            try {
-                kp = kpg.genKeyPair();
-                if (cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA_EXPORT) {
-                    rsakey = (RSAPublicKey) kp.getPublic();
-                } else {
-                    DHPublicKey dhkey = (DHPublicKey) kp.getPublic();
-                    KeyFactory kf = KeyFactory.getInstance("DH");
-                    dhkeySpec = kf.getKeySpec(dhkey, DHPublicKeySpec.class);
-                }
-                if (!cipher_suite.isAnonymous()) { // calculate signed_params
-
-                    // init by private key which correspond to
-                    // server certificate
-                    ds.init(privKey);
-
-                    // use emphemeral key for key exchange
-                    privKey = kp.getPrivate();
-                    ds.update(clientHello.getRandom());
-                    ds.update(serverHello.getRandom());
-
-//FIXME 1_byte==0x00
-                    if (cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA_EXPORT) {
-                        ServerKeyExchange.updateSignatureRsa(ds, rsakey.getModulus(),
-                                rsakey.getPublicExponent());
-                    } else {
-                        ServerKeyExchange.updateSignatureDh(ds, dhkeySpec.getP(), dhkeySpec.getG(),
-                                dhkeySpec.getY());
-                    }
-                    hash = ds.sign();
-                } else {
-                    privKey = kp.getPrivate(); // use emphemeral key for key exchange
-                }
-            } catch (Exception e) {
-                fatalAlert(AlertProtocol.INTERNAL_ERROR, "INTERNAL ERROR", e);
-            }
-
-            if (cipher_suite.keyExchange == CipherSuite.KEY_EXCHANGE_RSA_EXPORT) {
-                serverKeyExchange = new ServerKeyExchange(rsakey.getModulus(),
-                        rsakey.getPublicExponent(), null, hash);
-            } else {
-                serverKeyExchange = new ServerKeyExchange(p,
-                        g, dhkeySpec.getY(), hash);
-            }
-            send(serverKeyExchange);
-        }
-
-        // CERTIFICATE_REQUEST
-        certRequest: if (parameters.getWantClientAuth()
-                || parameters.getNeedClientAuth()) {
-            X509Certificate[] accepted;
-            try {
-                X509TrustManager tm = parameters.getTrustManager();
-                accepted = tm.getAcceptedIssuers();
-            } catch (ClassCastException e) {
-                // don't send certificateRequest
-                break certRequest;
-            }
-            byte[] requestedClientCertTypes = { CipherSuite.TLS_CT_RSA_SIGN,
-                                                CipherSuite.TLS_CT_DSS_SIGN };
-            certificateRequest = new CertificateRequest(
-                    requestedClientCertTypes, accepted);
-            send(certificateRequest);
-        }
-
-        // SERVER_HELLO_DONE
-        serverHelloDone = new ServerHelloDone();
-        send(serverHelloDone);
-        status = NEED_UNWRAP;
-    }
-
-    /**
-     * Creates and sends finished message
-     */
-    @Override
-    protected void makeFinished() {
-        byte[] verify_data;
-        boolean isTLS = (serverHello.server_version[1] == 1); // TLS 1.0 protocol
-        if (isTLS) {
-            verify_data = new byte[12];
-            computerVerifyDataTLS("server finished", verify_data);
-        } else { // SSL 3.0 protocol (http://wp.netscape.com/eng/ssl3)
-            verify_data = new byte[36];
-            computerVerifyDataSSLv3(SSLv3Constants.server, verify_data);
-        }
-        serverFinished = new Finished(verify_data);
-        send(serverFinished);
-        if (isResuming) {
-            if (isTLS) {
-                computerReferenceVerifyDataTLS("client finished");
-            } else {
-                computerReferenceVerifyDataSSLv3(SSLv3Constants.client);
-            }
-            status = NEED_UNWRAP;
-        } else {
-            session.lastAccessedTime = System.currentTimeMillis();
-            status = FINISHED;
-        }
-    }
-
-    // find sesssion in the session hash
-    private SSLSessionImpl findSessionToResume(byte[] session_id) {
-        return (SSLSessionImpl)parameters.getServerSessionContext().getSession(session_id);
-    }
-
-    // find appropriate cipher_suite in the client suites
-    private CipherSuite selectSuite(CipherSuite[] clientSuites) {
-        for (CipherSuite clientSuite : clientSuites) {
-            if (!clientSuite.supported) {
-                continue;
-            }
-            for (CipherSuite enabledCipherSuite : parameters.getEnabledCipherSuitesMember()) {
-                if (clientSuite.equals(enabledCipherSuite)) {
-                    return clientSuite;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Processes inbound ChangeCipherSpec message
-     */
-    @Override
-    public void receiveChangeCipherSpec() {
-        if (isResuming) {
-            if (serverFinished == null) {
-                unexpectedMessage();
-            } else {
-                changeCipherSpecReceived = true;
-            }
-        } else {
-            if ((parameters.getNeedClientAuth() && clientCert == null)
-                    || clientKeyExchange == null
-                    || (clientCert != null && clientCert.certs.length > 0
-                            && !clientKeyExchange.isEmpty()
-                            && certificateVerify == null)) {
-                unexpectedMessage();
-            } else {
-                changeCipherSpecReceived = true;
-            }
-            if (serverHello.server_version[1] == 1) {
-                computerReferenceVerifyDataTLS("client finished");
-            } else {
-                computerReferenceVerifyDataSSLv3(SSLv3Constants.client);
-            }
-        }
-    }
-
-}
diff --git a/src/main/java/org/conscrypt/ServerHello.java b/src/main/java/org/conscrypt/ServerHello.java
deleted file mode 100644
index 3cc3b46..0000000
--- a/src/main/java/org/conscrypt/ServerHello.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- *  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.io.IOException;
-import java.security.SecureRandom;
-import libcore.io.Streams;
-
-/**
- *
- * Represents server hello message.
- * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.1.3.
- * Server hello.</a>
- */
-public class ServerHello extends Message {
-
-    /**
-     * Server version
-     */
-    byte[] server_version = new byte[2];
-
-    /**
-     * Random bytes
-     */
-    byte[] random = new byte[32];
-
-    /**
-     * Session id
-     */
-    byte[] session_id;
-
-    /**
-     * Selected cipher suite
-     */
-    CipherSuite cipher_suite;
-
-    /**
-     * Selected compression method
-     */
-    byte compression_method;
-
-    /**
-     * Creates outbound message
-     * @param sr
-     * @param server_version
-     * @param session_id
-     * @param cipher_suite
-     * @param compression_method
-     */
-    public ServerHello(SecureRandom sr, byte[] server_version,
-            byte[] session_id, CipherSuite cipher_suite, byte compression_method) {
-        long gmt_unix_time = new java.util.Date().getTime() / 1000;
-        sr.nextBytes(random);
-        random[0] = (byte) ((gmt_unix_time & 0xFF000000) >>> 24);
-        random[1] = (byte) ((gmt_unix_time & 0xFF0000) >>> 16);
-        random[2] = (byte) ((gmt_unix_time & 0xFF00) >>> 8);
-        random[3] = (byte) (gmt_unix_time & 0xFF);
-        this.session_id = session_id;
-        this.cipher_suite = cipher_suite;
-        this.compression_method = compression_method;
-        this.server_version = server_version;
-        length = 38 + session_id.length;
-    }
-
-    /**
-     * Creates inbound message
-     * @param in
-     * @param length
-     * @throws IOException
-     */
-    public ServerHello(HandshakeIODataStream in, int length) throws IOException {
-
-        server_version[0] = (byte) in.read();
-        server_version[1] = (byte) in.read();
-        Streams.readFully(in, random);
-        int size = in.readUint8();
-        session_id = new byte[size];
-        in.read(session_id, 0, size);
-        byte b0 = (byte) in.read();
-        byte b1 = (byte) in.read();
-        cipher_suite = CipherSuite.getByCode(b0, b1);
-        compression_method = (byte) in.read();
-        this.length = 38 + session_id.length;
-        if (this.length != length) {
-            fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect ServerHello");
-        }
-
-    }
-
-    /**
-     * Sends message
-     * @param out
-     */
-    @Override
-    public void send(HandshakeIODataStream out) {
-        out.write(server_version);
-        out.write(random);
-        out.writeUint8(session_id.length);
-        out.write(session_id);
-        out.write(cipher_suite.toBytes());
-        out.write(compression_method);
-        length = 38 + session_id.length;
-    }
-
-    /**
-     * Returns server random
-     * @return
-     */
-    public byte[] getRandom() {
-        return random;
-    }
-
-    /**
-     * Returns message type
-     * @return
-     */
-    @Override
-    public int getType() {
-        return Handshake.SERVER_HELLO;
-    }
-}
diff --git a/src/main/java/org/conscrypt/ServerHelloDone.java b/src/main/java/org/conscrypt/ServerHelloDone.java
deleted file mode 100644
index 3e40f9a..0000000
--- a/src/main/java/org/conscrypt/ServerHelloDone.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- *  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.io.IOException;
-
-/**
- *
- * Represents server hello done message
- * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.5.
- * Server hello done</a>
- *
- */
-public class ServerHelloDone extends Message {
-
-    /**
-     * Creates outbound message
-     *
-     */
-    public ServerHelloDone() {
-    }
-
-    /**
-     * Creates inbound message
-     * @param in
-     * @param length
-     * @throws IOException
-     */
-    public ServerHelloDone(HandshakeIODataStream in, int length)
-            throws IOException {
-        if (length != 0) {
-            fatalAlert(AlertProtocol.DECODE_ERROR, "DECODE ERROR: incorrect ServerHelloDone");
-        }
-    }
-
-    /**
-     * Sends message
-     * @param out
-     */
-    @Override
-    public void send(HandshakeIODataStream out) {
-    }
-
-    /**
-     * Returns message length
-     * @return
-     */
-    @Override
-    public int length() {
-        return 0;
-    }
-
-    /**
-     * Returns message type
-     * @return
-     */
-    @Override
-    public int getType() {
-        return Handshake.SERVER_HELLO_DONE;
-    }
-}
diff --git a/src/main/java/org/conscrypt/ServerKeyExchange.java b/src/main/java/org/conscrypt/ServerKeyExchange.java
deleted file mode 100644
index d949850..0000000
--- a/src/main/java/org/conscrypt/ServerKeyExchange.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- *  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.io.IOException;
-import java.math.BigInteger;
-import java.security.KeyFactory;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.RSAPublicKeySpec;
-
-/**
- *
- * Represents server key exchange message.
- * @see <a href="http://www.ietf.org/rfc/rfc2246.txt">TLS 1.0 spec., 7.4.3.
- * Server key exchange message.</a>
- *
- */
-public class ServerKeyExchange extends Message {
-
-                           //          ServerRSAParams        ServerDHParams
-    final BigInteger par1; //            rsa_modulus               dh_p
-    final byte[] bytes1;
-
-    final BigInteger par2; //            rsa_exponent              dh_g
-    final byte[] bytes2;
-
-    final BigInteger par3; //                                      dh_Ys
-    final byte[] bytes3;
-
-    /**
-     * Signature
-     */
-    final byte[] hash;
-
-    private RSAPublicKey key;
-
-    /**
-     * Creates outbound message
-     * @param par1 rsa_modulus or dh_p
-     * @param par2 rsa_exponent or dh_g
-     * @param par3 dh_Ys for ServerDHParams; should be null for ServerRSAParams
-     * @param hash should be null for anonymous SignatureAlgorithm
-     */
-    public ServerKeyExchange(BigInteger par1, BigInteger par2, BigInteger par3,
-            byte[] hash) {
-        this.par1 = par1;
-        this.par2 = par2;
-        this.par3 = par3;
-        this.hash = hash;
-
-        bytes1 = toUnsignedByteArray(this.par1);
-
-        bytes2 = toUnsignedByteArray(this.par2);
-
-        length = 4 + bytes1.length + bytes2.length;
-        if (hash != null) {
-            length += 2 + hash.length;
-        }
-        if (par3 == null) {
-            bytes3 = null;
-            return;
-        }
-        bytes3 = toUnsignedByteArray(this.par3);
-        length += 2 + bytes3.length;
-    }
-
-    /**
-     * Remove first byte if 0. Needed because BigInteger.toByteArray() sometimes
-     * returns a zero prefix.
-     */
-    public static byte[] toUnsignedByteArray(BigInteger bi) {
-        if (bi == null) {
-            return null;
-        }
-        byte[] bb = bi.toByteArray();
-        // bb is not null, and has at least 1 byte - ZERO is represented as [0]
-        if (bb[0] == 0) {
-            byte[] noZero = new byte[bb.length - 1];
-            System.arraycopy(bb, 1, noZero, 0, noZero.length);
-            return noZero;
-        } else {
-            return bb;
-        }
-    }
-
-    public static void updateSignatureRsa(DigitalSignature ds, BigInteger modulus,
-            BigInteger publicExponent) {
-        byte[] tmp;
-        byte[] tmpLength = new byte[2];
-        tmp = ServerKeyExchange.toUnsignedByteArray(modulus);
-        tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8);
-        tmpLength[1] = (byte) (tmp.length & 0xFF);
-        ds.update(tmpLength);
-        ds.update(tmp);
-        tmp = ServerKeyExchange.toUnsignedByteArray(publicExponent);
-        tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8);
-        tmpLength[1] = (byte) (tmp.length & 0xFF);
-        ds.update(tmpLength);
-        ds.update(tmp);
-    }
-
-    public static void updateSignatureDh(DigitalSignature ds, BigInteger p, BigInteger g,
-            BigInteger y) {
-        byte[] tmp;
-        byte[] tmpLength = new byte[2];
-        tmp = ServerKeyExchange.toUnsignedByteArray(p);
-        tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8);
-        tmpLength[1] = (byte) (tmp.length & 0xFF);
-        ds.update(tmpLength);
-        ds.update(tmp);
-        tmp = ServerKeyExchange.toUnsignedByteArray(g);
-        tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8);
-        tmpLength[1] = (byte) (tmp.length & 0xFF);
-        ds.update(tmpLength);
-        ds.update(tmp);
-        tmp = ServerKeyExchange.toUnsignedByteArray(y);
-        tmpLength[0] = (byte) ((tmp.length & 0xFF00) >>> 8);
-        tmpLength[1] = (byte) (tmp.length & 0xFF);
-        ds.update(tmpLength);
-        ds.update(tmp);
-    }
-
-    public boolean verifySignature(DigitalSignature ds) {
-        if (par3 != null) {
-            updateSignatureDh(ds, par1, par2, par3);
-        } else {
-            updateSignatureRsa(ds, par1, par2);
-        }
-        return ds.verifySignature(hash);
-    }
-
-    /**
-     * Will return {@code true} if the signature is {@code null} since this is
-     * considered anonymous.
-     */
-    public boolean isAnonymous() {
-        return hash == null;
-    }
-
-    /**
-     * Creates inbound message
-     * @param in
-     * @param length
-     * @param keyExchange
-     * @throws IOException
-     */
-    public ServerKeyExchange(HandshakeIODataStream in, int length,
-            int keyExchange) throws IOException {
-
-        int size = in.readUint16();
-        bytes1 = in.read(size);
-        par1 = new BigInteger(1, bytes1);
-        this.length = 2 + bytes1.length;
-        size = in.readUint16();
-        bytes2 = in.read(size);
-        par2 = new BigInteger(1, bytes2);
-        this.length += 2 + bytes2.length;
-        if (keyExchange != CipherSuite.KEY_EXCHANGE_RSA_EXPORT) {
-            size = in.readUint16();
-            bytes3 = in.read(size);
-            par3 = new BigInteger(1, bytes3);
-            this.length += 2 + bytes3.length;
-        } else {
-            par3 = null;
-            bytes3 = null;
-        }
-        if (keyExchange != CipherSuite.KEY_EXCHANGE_DH_anon_EXPORT
-                && keyExchange != CipherSuite.KEY_EXCHANGE_DH_anon) {
-            size = in.readUint16();
-            hash = in.read(size);
-            this.length += 2 + hash.length;
-        } else {
-            hash = null;
-        }
-        if (this.length != length) {
-            fatalAlert(AlertProtocol.DECODE_ERROR,
-                    "DECODE ERROR: incorrect ServerKeyExchange");
-        }
-    }
-
-    /**
-     * Sends message
-     * @param out
-     */
-    @Override
-    public void send(HandshakeIODataStream out) {
-        out.writeUint16(bytes1.length);
-        out.write(bytes1);
-        out.writeUint16(bytes2.length);
-        out.write(bytes2);
-        if (bytes3 != null) {
-            out.writeUint16(bytes3.length);
-            out.write(bytes3);
-        }
-        if (hash != null) {
-            out.writeUint16(hash.length);
-            out.write(hash);
-        }
-    }
-
-    /**
-     * Returns RSAPublicKey generated using ServerRSAParams
-     * (rsa_modulus and rsa_exponent).
-     *
-     * @return
-     */
-    public RSAPublicKey getRSAPublicKey() {
-        if (key != null) {
-            return key;
-        }
-        try {
-            KeyFactory kf = KeyFactory.getInstance("RSA");
-            key = (RSAPublicKey) kf.generatePublic(new RSAPublicKeySpec(par1,
-                    par2));
-        } catch (Exception e) {
-            return null;
-        }
-        return key;
-    }
-
-    /**
-     * Returns message type
-     * @return
-     */
-    @Override
-    public int getType() {
-        return Handshake.SERVER_KEY_EXCHANGE;
-    }
-
-}
diff --git a/src/main/java/org/conscrypt/util/Arrays.java b/src/main/java/org/conscrypt/util/Arrays.java
new file mode 100644
index 0000000..dbfcb13
--- /dev/null
+++ b/src/main/java/org/conscrypt/util/Arrays.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 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.util;
+
+/**
+ * Compatibility utility for Arrays.
+ */
+public final class Arrays {
+    private Arrays() {
+    }
+
+    /**
+     * Checks that the range described by {@code offset} and {@code count}
+     * doesn't exceed {@code arrayLength}.
+     *
+     * @hide
+     */
+    public static final void checkOffsetAndCount(int arrayLength, int offset, int count) {
+        if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) {
+            throw new ArrayIndexOutOfBoundsException("length=" + arrayLength + "; regionStart="
+                    + offset + "; regionLength=" + count);
+        }
+    }
+}
diff --git a/src/main/native/org_conscrypt_NativeCrypto.cpp b/src/main/native/org_conscrypt_NativeCrypto.cpp
index 077e96c..8d0ac72 100644
--- a/src/main/native/org_conscrypt_NativeCrypto.cpp
+++ b/src/main/native/org_conscrypt_NativeCrypto.cpp
@@ -21,17 +21,24 @@
 #define TO_STRING1(x) #x
 #define TO_STRING(x) TO_STRING1(x)
 #ifndef JNI_JARJAR_PREFIX
-#define CONSCRYPT_UNBUNDLED
-#define JNI_JARJAR_PREFIX
+    #ifndef CONSCRYPT_NOT_UNBUNDLED
+        #define CONSCRYPT_UNBUNDLED
+    #endif
+    #define JNI_JARJAR_PREFIX
 #endif
 
 #define LOG_TAG "NativeCrypto"
 
 #include <arpa/inet.h>
 #include <fcntl.h>
+#include <pthread.h>
 #include <sys/socket.h>
 #include <unistd.h>
 
+#ifdef CONSCRYPT_UNBUNDLED
+#include <dlfcn.h>
+#endif
+
 #include <jni.h>
 
 #include <openssl/asn1t.h>
@@ -43,19 +50,46 @@
 #include <openssl/rsa.h>
 #include <openssl/ssl.h>
 #include <openssl/x509v3.h>
+#include "crypto/ecdsa/ecs_locl.h"
 
-#include "AsynchronousSocketCloseMonitor.h"
+#ifndef CONSCRYPT_UNBUNDLED
+/* If we're compiled unbundled from Android system image, we use the
+ * CompatibilityCloseMonitor
+ */
+#include "AsynchronousCloseMonitor.h"
+#endif
+
+#ifndef CONSCRYPT_UNBUNDLED
 #include "cutils/log.h"
+#else
+#include <android/log.h>
+#define ALOGD(...) \
+        __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);
+#define ALOGE(...) \
+        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__);
+#define ALOGV(...) \
+        __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__);
+#endif
+
+#ifndef CONSCRYPT_UNBUNDLED
 #include "JNIHelp.h"
 #include "JniConstants.h"
 #include "JniException.h"
-#include "NetFd.h"
+#else
+#define NATIVE_METHOD(className, functionName, signature) \
+  { #functionName, signature, reinterpret_cast<void*>(className ## _ ## functionName) }
+#define REGISTER_NATIVE_METHODS(jni_class_name) \
+  RegisterNativeMethods(env, jni_class_name, gMethods, arraysize(gMethods))
+#endif
+
 #include "ScopedLocalRef.h"
 #include "ScopedPrimitiveArray.h"
 #include "ScopedUtfChars.h"
 #include "UniquePtr.h"
+#include "NetFd.h"
 
 #undef WITH_JNI_TRACE
+#undef WITH_JNI_TRACE_MD
 #undef WITH_JNI_TRACE_DATA
 
 /*
@@ -84,11 +118,19 @@
 #else
 #define JNI_TRACE(...) ((void)0)
 #endif
+#ifdef WITH_JNI_TRACE_MD
+#define JNI_TRACE_MD(...) \
+        ((void)ALOG(LOG_INFO, LOG_TAG "-jni", __VA_ARGS__));
+#else
+#define JNI_TRACE_MD(...) ((void)0)
+#endif
 // don't overwhelm logcat
 #define WITH_JNI_TRACE_DATA_CHUNK_SIZE 512
 
 static JavaVM* gJavaVM;
-static jclass openSslOutputStreamClass;
+static jclass cryptoUpcallsClass;
+static jclass openSslInputStreamClass;
+static jclass openSslNativeReferenceClass;
 
 static jclass byteArrayClass;
 static jclass calendarClass;
@@ -99,6 +141,8 @@
 static jclass outputStreamClass;
 static jclass stringClass;
 
+static jfieldID openSslNativeReference_context;
+
 static jmethodID calendar_setMethod;
 static jmethodID inputStream_readMethod;
 static jmethodID integer_valueOfMethod;
@@ -115,7 +159,7 @@
 
 struct BIO_Delete {
     void operator()(BIO* p) const {
-        BIO_free(p);
+        BIO_free_all(p);
     }
 };
 typedef UniquePtr<BIO, BIO_Delete> Unique_BIO;
@@ -510,6 +554,14 @@
 /**
  * Throws a javax.net.ssl.SSLException with the given string as a message.
  */
+static void throwSSLHandshakeExceptionStr(JNIEnv* env, const char* message) {
+    JNI_TRACE("throwSSLExceptionStr %s", message);
+    jniThrowException(env, "javax/net/ssl/SSLHandshakeException", message);
+}
+
+/**
+ * Throws a javax.net.ssl.SSLException with the given string as a message.
+ */
 static void throwSSLExceptionStr(JNIEnv* env, const char* message) {
     JNI_TRACE("throwSSLExceptionStr %s", message);
     jniThrowException(env, "javax/net/ssl/SSLException", message);
@@ -533,8 +585,8 @@
  * SSL_ERROR_NONE to probe with ERR_get_error
  * @param message null-ok; general error message
  */
-static void throwSSLExceptionWithSslErrors(
-        JNIEnv* env, SSL* ssl, int sslErrorCode, const char* message) {
+static void throwSSLExceptionWithSslErrors(JNIEnv* env, SSL* ssl, int sslErrorCode,
+        const char* message, void (*actualThrow)(JNIEnv*, const char*) = throwSSLExceptionStr) {
 
     if (message == NULL) {
         message = "SSL error";
@@ -582,7 +634,7 @@
     char* str;
     if (asprintf(&str, "%s: ssl=%p: %s", message, ssl, sslErrorStr) <= 0) {
         // problem with asprintf, just throw argument message, log everything
-        throwSSLExceptionStr(env, message);
+        actualThrow(env, message);
         ALOGV("%s: ssl=%p: %s", message, ssl, sslErrorStr);
         freeOpenSslErrorState();
         return;
@@ -638,7 +690,7 @@
     if (sslErrorCode == SSL_ERROR_SSL) {
         throwSSLProtocolExceptionStr(env, allocStr);
     } else {
-        throwSSLExceptionStr(env, allocStr);
+        actualThrow(env, allocStr);
     }
 
     ALOGV("%s", allocStr);
@@ -697,6 +749,16 @@
     return ssl_cipher;
 }
 
+template<typename T>
+static T* fromContextObject(JNIEnv* env, jobject contextObject) {
+    T* ref = reinterpret_cast<T*>(env->GetLongField(contextObject, openSslNativeReference_context));
+    if (ref == NULL) {
+        JNI_TRACE("ctx == null");
+        jniThrowNullPointerException(env, "ctx == null");
+    }
+    return ref;
+}
+
 /**
  * Converts a Java byte[] two's complement to an OpenSSL BIGNUM. This will
  * allocate the BIGNUM if *dest == NULL. Returns true on success. If the
@@ -890,6 +952,38 @@
     return x509;
 }
 
+/*
+ * Sets the read and write BIO for an SSL connection and removes it when it goes out of scope.
+ * We hang on to BIO with a JNI GlobalRef and we want to remove them as soon as possible.
+ */
+class ScopedSslBio {
+public:
+    ScopedSslBio(SSL *ssl, BIO* rbio, BIO* wbio) : ssl_(ssl) {
+        SSL_set_bio(ssl_, rbio, wbio);
+        CRYPTO_add(&rbio->references,1,CRYPTO_LOCK_BIO);
+        CRYPTO_add(&wbio->references,1,CRYPTO_LOCK_BIO);
+    }
+
+    ~ScopedSslBio() {
+        SSL_set_bio(ssl_, NULL, NULL);
+    }
+
+private:
+    SSL* const ssl_;
+};
+
+/**
+ * Obtains the current thread's JNIEnv
+ */
+static JNIEnv* getJNIEnv() {
+    JNIEnv* env;
+    if (gJavaVM->AttachCurrentThread(&env, NULL) < 0) {
+        ALOGE("Could not attach JavaVM to find current JNIEnv");
+        return NULL;
+    }
+    return env;
+}
+
 /**
  * BIO for InputStream
  */
@@ -897,12 +991,12 @@
 public:
     BIO_Stream(jobject stream) :
             mEof(false) {
-        JNIEnv* env = getEnv();
+        JNIEnv* env = getJNIEnv();
         mStream = env->NewGlobalRef(stream);
     }
 
     ~BIO_Stream() {
-        JNIEnv* env = getEnv();
+        JNIEnv* env = getJNIEnv();
 
         env->DeleteGlobalRef(mStream);
     }
@@ -913,11 +1007,16 @@
     }
 
     int flush() {
-        JNIEnv* env = getEnv();
+        JNIEnv* env = getJNIEnv();
         if (env == NULL) {
             return -1;
         }
 
+        if (env->ExceptionCheck()) {
+            JNI_TRACE("BIO_Stream::flush called with pending exception");
+            return -1;
+        }
+
         env->CallVoidMethod(mStream, outputStream_flushMethod);
         if (env->ExceptionCheck()) {
             return -1;
@@ -935,16 +1034,6 @@
         mEof = eof;
     }
 
-    JNIEnv* getEnv() {
-        JNIEnv* env;
-
-        if (gJavaVM->AttachCurrentThread(&env, NULL) < 0) {
-            return NULL;
-        }
-
-        return env;
-    }
-
 private:
     jobject mStream;
     bool mEof;
@@ -973,12 +1062,17 @@
 
 private:
     int read_internal(char *buf, int len, jmethodID method) {
-        JNIEnv* env = getEnv();
+        JNIEnv* env = getJNIEnv();
         if (env == NULL) {
             JNI_TRACE("BIO_InputStream::read could not get JNIEnv");
             return -1;
         }
 
+        if (env->ExceptionCheck()) {
+            JNI_TRACE("BIO_InputStream::read called with pending exception");
+            return -1;
+        }
+
         ScopedLocalRef<jbyteArray> javaBytes(env, env->NewByteArray(len));
         if (javaBytes.get() == NULL) {
             JNI_TRACE("BIO_InputStream::read failed call to NewByteArray");
@@ -1014,12 +1108,17 @@
     }
 
     int write(const char *buf, int len) {
-        JNIEnv* env = getEnv();
+        JNIEnv* env = getJNIEnv();
         if (env == NULL) {
             JNI_TRACE("BIO_OutputStream::write => could not get JNIEnv");
             return -1;
         }
 
+        if (env->ExceptionCheck()) {
+            JNI_TRACE("BIO_OutputStream::write => called with pending exception");
+            return -1;
+        }
+
         ScopedLocalRef<jbyteArray> javaBytes(env, env->NewByteArray(len));
         if (javaBytes.get() == NULL) {
             JNI_TRACE("BIO_OutputStream::write => failed call to NewByteArray");
@@ -1062,11 +1161,19 @@
 }
 
 static int bio_stream_read(BIO *b, char *buf, int len) {
+    BIO_clear_retry_flags(b);
     BIO_InputStream* stream = static_cast<BIO_InputStream*>(b->ptr);
-    return stream->read(buf, len);
+    int ret = stream->read(buf, len);
+    if (ret == 0) {
+        // EOF is indicated by -1 with a BIO flag.
+        BIO_set_retry_read(b);
+        return -1;
+    }
+    return ret;
 }
 
 static int bio_stream_write(BIO *b, const char *buf, int len) {
+    BIO_clear_retry_flags(b);
     BIO_OutputStream* stream = static_cast<BIO_OutputStream*>(b->ptr);
     return stream->write(buf, len);
 }
@@ -1111,6 +1218,497 @@
         NULL, /* no bio_callback_ctrl */
 };
 
+static jbyteArray rawSignDigestWithPrivateKey(JNIEnv* env, jobject privateKey,
+        const char* message, size_t message_len) {
+    ScopedLocalRef<jbyteArray> messageArray(env, env->NewByteArray(message_len));
+    if (env->ExceptionCheck()) {
+        JNI_TRACE("rawSignDigestWithPrivateKey(%p) => threw exception", privateKey);
+        return NULL;
+    }
+
+    {
+        ScopedByteArrayRW messageBytes(env, messageArray.get());
+        if (messageBytes.get() == NULL) {
+            JNI_TRACE("rawSignDigestWithPrivateKey(%p) => using byte array failed", privateKey);
+            return NULL;
+        }
+
+        memcpy(messageBytes.get(), message, message_len);
+    }
+
+    jmethodID rawSignMethod = env->GetStaticMethodID(cryptoUpcallsClass,
+            "rawSignDigestWithPrivateKey", "(Ljava/security/PrivateKey;[B)[B");
+    if (rawSignMethod == NULL) {
+        ALOGE("Could not find rawSignDigestWithPrivateKey");
+        return NULL;
+    }
+
+    return reinterpret_cast<jbyteArray>(env->CallStaticObjectMethod(
+            cryptoUpcallsClass, rawSignMethod, privateKey, messageArray.get()));
+}
+
+static jbyteArray rawCipherWithPrivateKey(JNIEnv* env, jobject privateKey, jboolean encrypt,
+        const char* ciphertext, size_t ciphertext_len) {
+    ScopedLocalRef<jbyteArray> ciphertextArray(env, env->NewByteArray(ciphertext_len));
+    if (env->ExceptionCheck()) {
+        JNI_TRACE("rawCipherWithPrivateKey(%p) => threw exception", privateKey);
+        return NULL;
+    }
+
+    {
+        ScopedByteArrayRW ciphertextBytes(env, ciphertextArray.get());
+        if (ciphertextBytes.get() == NULL) {
+            JNI_TRACE("rawCipherWithPrivateKey(%p) => using byte array failed", privateKey);
+            return NULL;
+        }
+
+        memcpy(ciphertextBytes.get(), ciphertext, ciphertext_len);
+    }
+
+    jmethodID rawCipherMethod = env->GetStaticMethodID(cryptoUpcallsClass,
+            "rawCipherWithPrivateKey", "(Ljava/security/PrivateKey;Z[B)[B");
+    if (rawCipherMethod == NULL) {
+        ALOGE("Could not find rawCipherWithPrivateKey");
+        return NULL;
+    }
+
+    return reinterpret_cast<jbyteArray>(env->CallStaticObjectMethod(
+            cryptoUpcallsClass, rawCipherMethod, privateKey, encrypt, ciphertextArray.get()));
+}
+
+// *********************************************
+// From keystore_openssl.cpp in Chromium source.
+// *********************************************
+
+// Custom RSA_METHOD that uses the platform APIs.
+// Note that for now, only signing through RSA_sign() is really supported.
+// all other method pointers are either stubs returning errors, or no-ops.
+// See <openssl/rsa.h> for exact declaration of RSA_METHOD.
+
+int RsaMethodPubEnc(int /* flen */,
+                    const unsigned char* /* from */,
+                    unsigned char* /* to */,
+                    RSA* /* rsa */,
+                    int /* padding */) {
+    RSAerr(RSA_F_RSA_PUBLIC_ENCRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED);
+    return -1;
+}
+
+int RsaMethodPubDec(int flen,
+                    const unsigned char* from,
+                    unsigned char* to,
+                    RSA* rsa,
+                    int padding) {
+    RSAerr(RSA_F_RSA_PUBLIC_DECRYPT, RSA_R_RSA_OPERATIONS_NOT_SUPPORTED);
+    return -1;
+}
+
+// See RSA_eay_private_encrypt in
+// third_party/openssl/openssl/crypto/rsa/rsa_eay.c for the default
+// implementation of this function.
+int RsaMethodPrivEnc(int flen,
+                     const unsigned char *from,
+                     unsigned char *to,
+                     RSA *rsa,
+                     int padding) {
+    if (padding != RSA_PKCS1_PADDING) {
+        // TODO(davidben): If we need to, we can implement RSA_NO_PADDING
+        // by using javax.crypto.Cipher and picking either the
+        // "RSA/ECB/NoPadding" or "RSA/ECB/PKCS1Padding" transformation as
+        // appropriate. I believe support for both of these was added in
+        // the same Android version as the "NONEwithRSA"
+        // java.security.Signature algorithm, so the same version checks
+        // for GetRsaLegacyKey should work.
+        RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
+        return -1;
+    }
+
+    // Retrieve private key JNI reference.
+    jobject private_key = reinterpret_cast<jobject>(RSA_get_app_data(rsa));
+    if (!private_key) {
+        ALOGE("Null JNI reference passed to RsaMethodPrivEnc!");
+        RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, ERR_R_INTERNAL_ERROR);
+        return -1;
+    }
+
+    JNIEnv* env = getJNIEnv();
+    if (env == NULL) {
+        return -1;
+    }
+
+    // For RSA keys, this function behaves as RSA_private_encrypt with
+    // PKCS#1 padding.
+    ScopedLocalRef<jbyteArray> signature(
+            env, rawSignDigestWithPrivateKey(env, private_key,
+                                         reinterpret_cast<const char*>(from), flen));
+    if (signature.get() == NULL) {
+        ALOGE("Could not sign message in RsaMethodPrivEnc!");
+        RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, ERR_R_INTERNAL_ERROR);
+        return -1;
+    }
+
+    ScopedByteArrayRO signatureBytes(env, signature.get());
+    size_t expected_size = static_cast<size_t>(RSA_size(rsa));
+    if (signatureBytes.size() > expected_size) {
+        ALOGE("RSA Signature size mismatch, actual: %zd, expected <= %zd", signatureBytes.size(),
+              expected_size);
+        RSAerr(RSA_F_RSA_PRIVATE_ENCRYPT, ERR_R_INTERNAL_ERROR);
+        return -1;
+    }
+
+    // Copy result to OpenSSL-provided buffer. rawSignDigestWithPrivateKey
+    // should pad with leading 0s, but if it doesn't, pad the result.
+    size_t zero_pad = expected_size - signatureBytes.size();
+    memset(to, 0, zero_pad);
+    memcpy(to + zero_pad, signatureBytes.get(), signatureBytes.size());
+
+    return expected_size;
+}
+
+int RsaMethodPrivDec(int flen,
+                     const unsigned char* from,
+                     unsigned char* to,
+                     RSA* rsa,
+                     int padding) {
+    if (padding != RSA_PKCS1_PADDING) {
+        RSAerr(RSA_F_RSA_PRIVATE_DECRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
+        return -1;
+    }
+
+    // Retrieve private key JNI reference.
+    jobject private_key = reinterpret_cast<jobject>(RSA_get_app_data(rsa));
+    if (!private_key) {
+        ALOGE("Null JNI reference passed to RsaMethodPrivDec!");
+        RSAerr(RSA_F_RSA_PRIVATE_DECRYPT, ERR_R_INTERNAL_ERROR);
+        return -1;
+    }
+
+    JNIEnv* env = getJNIEnv();
+    if (env == NULL) {
+        return -1;
+    }
+
+    // For RSA keys, this function behaves as RSA_private_decrypt with
+    // PKCS#1 padding.
+    ScopedLocalRef<jbyteArray> cleartext(env, rawCipherWithPrivateKey(env, private_key, false,
+                                         reinterpret_cast<const char*>(from), flen));
+    if (cleartext.get() == NULL) {
+        ALOGE("Could not decrypt message in RsaMethodPrivDec!");
+        RSAerr(RSA_F_RSA_PRIVATE_DECRYPT, ERR_R_INTERNAL_ERROR);
+        return -1;
+    }
+
+    ScopedByteArrayRO cleartextBytes(env, cleartext.get());
+    size_t expected_size = static_cast<size_t>(RSA_size(rsa));
+    if (cleartextBytes.size() > expected_size) {
+        ALOGE("RSA ciphertext size mismatch, actual: %zd, expected <= %zd", cleartextBytes.size(),
+              expected_size);
+        RSAerr(RSA_F_RSA_PRIVATE_DECRYPT, ERR_R_INTERNAL_ERROR);
+        return -1;
+    }
+
+    // Copy result to OpenSSL-provided buffer.
+    memcpy(to, cleartextBytes.get(), cleartextBytes.size());
+
+    return cleartextBytes.size();
+}
+
+int RsaMethodInit(RSA*) {
+    return 0;
+}
+
+int RsaMethodFinish(RSA* rsa) {
+    // Ensure the global JNI reference created with this wrapper is
+    // properly destroyed with it.
+    jobject key = reinterpret_cast<jobject>(RSA_get_app_data(rsa));
+    if (key != NULL) {
+        RSA_set_app_data(rsa, NULL);
+        JNIEnv* env = getJNIEnv();
+        env->DeleteGlobalRef(key);
+    }
+    // Actual return value is ignored by OpenSSL. There are no docs
+    // explaining what this is supposed to be.
+    return 0;
+}
+
+const RSA_METHOD android_rsa_method = {
+        /* .name = */ "Android signing-only RSA method",
+        /* .rsa_pub_enc = */ RsaMethodPubEnc,
+        /* .rsa_pub_dec = */ RsaMethodPubDec,
+        /* .rsa_priv_enc = */ RsaMethodPrivEnc,
+        /* .rsa_priv_dec = */ RsaMethodPrivDec,
+        /* .rsa_mod_exp = */ NULL,
+        /* .bn_mod_exp = */ NULL,
+        /* .init = */ RsaMethodInit,
+        /* .finish = */ RsaMethodFinish,
+        // This flag is necessary to tell OpenSSL to avoid checking the content
+        // (i.e. internal fields) of the private key. Otherwise, it will complain
+        // it's not valid for the certificate.
+        /* .flags = */ RSA_METHOD_FLAG_NO_CHECK,
+        /* .app_data = */ NULL,
+        /* .rsa_sign = */ NULL,
+        /* .rsa_verify = */ NULL,
+        /* .rsa_keygen = */ NULL,
+};
+
+// Custom DSA_METHOD that uses the platform APIs.
+// Note that for now, only signing through DSA_sign() is really supported.
+// all other method pointers are either stubs returning errors, or no-ops.
+// See <openssl/dsa.h> for exact declaration of DSA_METHOD.
+//
+// Note: There is no DSA_set_app_data() and DSA_get_app_data() functions,
+//       but RSA_set_app_data() is defined as a simple macro that calls
+//       RSA_set_ex_data() with a hard-coded index of 0, so this code
+//       does the same thing here.
+
+DSA_SIG* DsaMethodDoSign(const unsigned char* dgst,
+                         int dlen,
+                         DSA* dsa) {
+    // Extract the JNI reference to the PrivateKey object.
+    jobject private_key = reinterpret_cast<jobject>(DSA_get_ex_data(dsa, 0));
+    if (private_key == NULL) return NULL;
+
+    JNIEnv* env = getJNIEnv();
+    if (env == NULL) {
+        return NULL;
+    }
+
+    // Sign the message with it, calling platform APIs.
+    ScopedLocalRef<jbyteArray> signature(
+            env, rawSignDigestWithPrivateKey(env, private_key, reinterpret_cast<const char*>(dgst),
+                                             dlen));
+    if (signature.get() == NULL) {
+        ALOGE("Could not sign message in DsaMethodDoSign!");
+        return NULL;
+    }
+
+    ScopedByteArrayRO signatureBytes(env, signature.get());
+    // Note: With DSA, the actual signature might be smaller than DSA_size().
+    size_t max_expected_size = static_cast<size_t>(DSA_size(dsa));
+    if (signatureBytes.size() > max_expected_size) {
+        ALOGE("DSA Signature size mismatch, actual: %zd, expected <= %zd", signatureBytes.size(),
+              max_expected_size);
+        return NULL;
+    }
+
+    // Convert the signature into a DSA_SIG object.
+    const unsigned char* sigbuf = reinterpret_cast<const unsigned char*>(signatureBytes.get());
+    int siglen = static_cast<size_t>(signatureBytes.size());
+    DSA_SIG* dsa_sig = d2i_DSA_SIG(NULL, &sigbuf, siglen);
+    return dsa_sig;
+}
+
+int DsaMethodSignSetup(DSA* /* dsa */,
+                       BN_CTX* /* ctx_in */,
+                       BIGNUM** /* kinvp */,
+                       BIGNUM** /* rp */,
+                       const unsigned char* /* dgst */,
+                       int /* dlen */) {
+    DSAerr(DSA_F_DSA_SIGN_SETUP, DSA_R_INVALID_DIGEST_TYPE);
+    return -1;
+}
+
+int DsaMethodDoVerify(const unsigned char* /* dgst */,
+                      int /* dgst_len */,
+                      DSA_SIG* /* sig */,
+                      DSA* /* dsa */) {
+    DSAerr(DSA_F_DSA_DO_VERIFY, DSA_R_INVALID_DIGEST_TYPE);
+    return -1;
+}
+
+int DsaMethodFinish(DSA* dsa) {
+    // Free the global JNI reference that was created with this
+    // wrapper key.
+    jobject key = reinterpret_cast<jobject>(DSA_get_ex_data(dsa, 0));
+    if (key != NULL) {
+        DSA_set_ex_data(dsa, 0, NULL);
+        JNIEnv* env = getJNIEnv();
+        env->DeleteGlobalRef(key);
+    }
+    // Actual return value is ignored by OpenSSL. There are no docs
+    // explaining what this is supposed to be.
+    return 0;
+}
+
+const DSA_METHOD android_dsa_method = {
+        /* .name = */ "Android signing-only DSA method",
+        /* .dsa_do_sign = */ DsaMethodDoSign,
+        /* .dsa_sign_setup = */ DsaMethodSignSetup,
+        /* .dsa_do_verify = */ DsaMethodDoVerify,
+        /* .dsa_mod_exp = */ NULL,
+        /* .bn_mod_exp = */ NULL,
+        /* .init = */ NULL,  // nothing to do here.
+        /* .finish = */ DsaMethodFinish,
+        /* .flags = */ 0,
+        /* .app_data = */ NULL,
+        /* .dsa_paramgem = */ NULL,
+        /* .dsa_keygen = */ NULL};
+
+// Used to ensure that the global JNI reference associated with a custom
+// EC_KEY + ECDSA_METHOD wrapper is released when its EX_DATA is destroyed
+// (this function is called when EVP_PKEY_free() is called on the wrapper).
+void ExDataFree(void* /* parent */,
+                void* ptr,
+                CRYPTO_EX_DATA* ad,
+                int idx,
+                long /* argl */,
+                void* /* argp */) {
+    jobject private_key = reinterpret_cast<jobject>(ptr);
+    if (private_key == NULL) return;
+
+    CRYPTO_set_ex_data(ad, idx, NULL);
+    JNIEnv* env = getJNIEnv();
+    env->DeleteGlobalRef(private_key);
+}
+
+int ExDataDup(CRYPTO_EX_DATA* /* to */,
+              CRYPTO_EX_DATA* /* from */,
+              void* /* from_d */,
+              int /* idx */,
+              long /* argl */,
+              void* /* argp */) {
+    // This callback shall never be called with the current OpenSSL
+    // implementation (the library only ever duplicates EX_DATA items
+    // for SSL and BIO objects). But provide this to catch regressions
+    // in the future.
+    // Return value is currently ignored by OpenSSL.
+    return 0;
+}
+
+class EcdsaExDataIndex {
+  public:
+    int ex_data_index() { return ex_data_index_; }
+
+    static EcdsaExDataIndex& Instance() {
+        static EcdsaExDataIndex singleton;
+        return singleton;
+    }
+
+  private:
+    EcdsaExDataIndex() {
+        ex_data_index_ = ECDSA_get_ex_new_index(0, NULL, NULL, ExDataDup, ExDataFree);
+    }
+    EcdsaExDataIndex(EcdsaExDataIndex const&);
+    ~EcdsaExDataIndex() {}
+    EcdsaExDataIndex& operator=(EcdsaExDataIndex const&);
+
+    int ex_data_index_;
+};
+
+// Returns the index of the custom EX_DATA used to store the JNI reference.
+int EcdsaGetExDataIndex(void) {
+    EcdsaExDataIndex& exData = EcdsaExDataIndex::Instance();
+    return exData.ex_data_index();
+}
+
+ECDSA_SIG* EcdsaMethodDoSign(const unsigned char* dgst, int dgst_len, const BIGNUM* /* inv */,
+                             const BIGNUM* /* rp */, EC_KEY* eckey) {
+    // Retrieve private key JNI reference.
+    jobject private_key =
+            reinterpret_cast<jobject>(ECDSA_get_ex_data(eckey, EcdsaGetExDataIndex()));
+    if (!private_key) {
+        ALOGE("Null JNI reference passed to EcdsaMethodDoSign!");
+        return NULL;
+    }
+    JNIEnv* env = getJNIEnv();
+    if (env == NULL) {
+        return NULL;
+    }
+
+    // Sign message with it through JNI.
+    ScopedLocalRef<jbyteArray> signature(
+            env, rawSignDigestWithPrivateKey(env, private_key, reinterpret_cast<const char*>(dgst),
+                                             dgst_len));
+    if (signature.get() == NULL) {
+        ALOGE("Could not sign message in EcdsaMethodDoSign!");
+        return NULL;
+    }
+
+    ScopedByteArrayRO signatureBytes(env, signature.get());
+    // Note: With ECDSA, the actual signature may be smaller than
+    // ECDSA_size().
+    size_t max_expected_size = static_cast<size_t>(ECDSA_size(eckey));
+    if (signatureBytes.size() > max_expected_size) {
+        ALOGE("ECDSA Signature size mismatch, actual: %zd, expected <= %zd", signatureBytes.size(),
+              max_expected_size);
+        return NULL;
+    }
+
+    // Convert signature to ECDSA_SIG object
+    const unsigned char* sigbuf = reinterpret_cast<const unsigned char*>(signatureBytes.get());
+    long siglen = static_cast<long>(signatureBytes.size());
+    return d2i_ECDSA_SIG(NULL, &sigbuf, siglen);
+}
+
+int EcdsaMethodSignSetup(EC_KEY* /* eckey */,
+                         BN_CTX* /* ctx */,
+                         BIGNUM** /* kinv */,
+                         BIGNUM** /* r */,
+                         const unsigned char*,
+                         int) {
+    ECDSAerr(ECDSA_F_ECDSA_SIGN_SETUP, ECDSA_R_ERR_EC_LIB);
+    return -1;
+}
+
+int EcdsaMethodDoVerify(const unsigned char* /* dgst */,
+                        int /* dgst_len */,
+                        const ECDSA_SIG* /* sig */,
+                        EC_KEY* /* eckey */) {
+    ECDSAerr(ECDSA_F_ECDSA_DO_VERIFY, ECDSA_R_ERR_EC_LIB);
+    return -1;
+}
+
+const ECDSA_METHOD android_ecdsa_method = {
+        /* .name = */ "Android signing-only ECDSA method",
+        /* .ecdsa_do_sign = */ EcdsaMethodDoSign,
+        /* .ecdsa_sign_setup = */ EcdsaMethodSignSetup,
+        /* .ecdsa_do_verify = */ EcdsaMethodDoVerify,
+        /* .flags = */ 0,
+        /* .app_data = */ NULL,
+};
+
+#ifdef CONSCRYPT_UNBUNDLED
+/*
+ * This is a big hack; don't learn from this. Basically what happened is we do
+ * not have an API way to insert ourselves into the AsynchronousCloseMonitor
+ * that's compiled into the native libraries for libcore when we're unbundled.
+ * So we try to look up the symbol from the main library to find it.
+ */
+typedef void (*acm_ctor_func)(void*, int);
+typedef void (*acm_dtor_func)(void*);
+static acm_ctor_func async_close_monitor_ctor = NULL;
+static acm_dtor_func async_close_monitor_dtor = NULL;
+
+class CompatibilityCloseMonitor {
+public:
+    CompatibilityCloseMonitor(int fd) {
+        if (async_close_monitor_ctor != NULL) {
+            async_close_monitor_ctor(objBuffer, fd);
+        }
+    }
+
+    ~CompatibilityCloseMonitor() {
+        if (async_close_monitor_dtor != NULL) {
+            async_close_monitor_dtor(objBuffer);
+        }
+    }
+private:
+    char objBuffer[256];
+#if 0
+    static_assert(sizeof(objBuffer) > 2*sizeof(AsynchronousCloseMonitor),
+                  "CompatibilityCloseMonitor must be larger than the actual object");
+#endif
+};
+
+static void findAsynchronousCloseMonitorFuncs() {
+    void *lib = dlopen("libjavacore.so", RTLD_NOW);
+    if (lib != NULL) {
+        async_close_monitor_ctor = (acm_ctor_func) dlsym(lib, "_ZN24AsynchronousCloseMonitorC1Ei");
+        async_close_monitor_dtor = (acm_dtor_func) dlsym(lib, "_ZN24AsynchronousCloseMonitorD1Ev");
+    }
+}
+#endif
+
 /**
  * Copied from libnativehelper NetworkUtilites.cpp
  */
@@ -1376,6 +1974,64 @@
     return ret;
 }
 
+static jlong NativeCrypto_EVP_PKEY_new_DH(JNIEnv* env, jclass,
+                                               jbyteArray p, jbyteArray g,
+                                               jbyteArray pub_key, jbyteArray priv_key) {
+    JNI_TRACE("EVP_PKEY_new_DH(p=%p, g=%p, pub_key=%p, priv_key=%p)",
+              p, g, pub_key, priv_key);
+
+    Unique_DH dh(DH_new());
+    if (dh.get() == NULL) {
+        jniThrowRuntimeException(env, "DH_new failed");
+        return 0;
+    }
+
+    if (!arrayToBignum(env, p, &dh->p)) {
+        return 0;
+    }
+
+    if (!arrayToBignum(env, g, &dh->g)) {
+        return 0;
+    }
+
+    if (pub_key != NULL && !arrayToBignum(env, pub_key, &dh->pub_key)) {
+        return 0;
+    }
+
+    if (priv_key != NULL && !arrayToBignum(env, priv_key, &dh->priv_key)) {
+        return 0;
+    }
+
+    if (dh->p == NULL || dh->g == NULL
+            || (pub_key != NULL && dh->pub_key == NULL)
+            || (priv_key != NULL && dh->priv_key == NULL)) {
+        jniThrowRuntimeException(env, "Unable to convert BigInteger to BIGNUM");
+        return 0;
+    }
+
+    /* The public key can be recovered if the private key is available. */
+    if (dh->pub_key == NULL && dh->priv_key != NULL) {
+        if (!DH_generate_key(dh.get())) {
+            jniThrowRuntimeException(env, "EVP_PKEY_new_DH failed during pub_key generation");
+            return 0;
+        }
+    }
+
+    Unique_EVP_PKEY pkey(EVP_PKEY_new());
+    if (pkey.get() == NULL) {
+        jniThrowRuntimeException(env, "EVP_PKEY_new failed");
+        return 0;
+    }
+    if (EVP_PKEY_assign_DH(pkey.get(), dh.get()) != 1) {
+        jniThrowRuntimeException(env, "EVP_PKEY_assign_DH failed");
+        return 0;
+    }
+    OWNERSHIP_TRANSFERRED(dh);
+    JNI_TRACE("EVP_PKEY_new_DH(p=%p, g=%p, pub_key=%p, priv_key=%p) => %p",
+              p, g, pub_key, priv_key, pkey.get());
+    return reinterpret_cast<jlong>(pkey.release());
+}
+
 /**
  * public static native int EVP_PKEY_new_DSA(byte[] p, byte[] q, byte[] g,
  *                                           byte[] pub_key, byte[] priv_key);
@@ -1836,6 +2492,112 @@
     return reinterpret_cast<uintptr_t>(pkey.release());
 }
 
+static jlong NativeCrypto_getRSAPrivateKeyWrapper(JNIEnv* env, jclass, jobject javaKey,
+        jbyteArray modulusBytes) {
+    JNI_TRACE("getRSAPrivateKeyWrapper(%p, %p)", javaKey, modulus);
+
+    Unique_RSA rsa(RSA_new());
+    if (rsa.get() == NULL) {
+        jniThrowOutOfMemory(env, "Unable to allocate RSA key");
+        return 0;
+    }
+
+    RSA_set_method(rsa.get(), &android_rsa_method);
+
+    if (!arrayToBignum(env, modulusBytes, &rsa->n)) {
+        return 0;
+    }
+
+    RSA_set_app_data(rsa.get(), env->NewGlobalRef(javaKey));
+
+    Unique_EVP_PKEY pkey(EVP_PKEY_new());
+    if (pkey.get() == NULL) {
+        JNI_TRACE("getRSAPrivateKeyWrapper failed");
+        jniThrowRuntimeException(env, "NativeCrypto_getRSAPrivateKeyWrapper failed");
+        freeOpenSslErrorState();
+        return 0;
+    }
+
+    if (EVP_PKEY_assign_RSA(pkey.get(), rsa.get()) != 1) {
+        jniThrowRuntimeException(env, "getRSAPrivateKeyWrapper failed");
+        return 0;
+    }
+    OWNERSHIP_TRANSFERRED(rsa);
+    return reinterpret_cast<uintptr_t>(pkey.release());
+}
+
+static jlong NativeCrypto_getDSAPrivateKeyWrapper(JNIEnv* env, jclass, jobject javaKey,
+        jbyteArray qBytes) {
+    JNI_TRACE("getDSAPrivateKeyWrapper(%p, %p)", javaKey, modulus);
+
+    Unique_DSA dsa(DSA_new());
+    if (dsa.get() == NULL) {
+        jniThrowOutOfMemory(env, "Unable to allocate DSA key");
+        return 0;
+    }
+
+    if (!arrayToBignum(env, qBytes, &dsa->q)) {
+        return 0;
+    }
+
+    DSA_set_method(dsa.get(), &android_dsa_method);
+    DSA_set_ex_data(dsa.get(), 0, env->NewGlobalRef(javaKey));
+
+    Unique_EVP_PKEY pkey(EVP_PKEY_new());
+    if (pkey.get() == NULL) {
+        JNI_TRACE("getDSAPrivateKeyWrapper failed");
+        jniThrowRuntimeException(env, "NativeCrypto_getDSAPrivateKeyWrapper failed");
+        freeOpenSslErrorState();
+        return 0;
+    }
+
+    if (EVP_PKEY_assign_DSA(pkey.get(), dsa.get()) != 1) {
+        jniThrowRuntimeException(env, "getDSAPrivateKeyWrapper failed");
+        return 0;
+    }
+    OWNERSHIP_TRANSFERRED(dsa);
+    return reinterpret_cast<uintptr_t>(pkey.release());
+}
+
+static jlong NativeCrypto_getECPrivateKeyWrapper(JNIEnv* env, jclass, jobject javaKey, jlong groupRef) {
+    JNI_TRACE("getECPrivateKeyWrapper(%p, %p)", javaKey, modulus);
+
+    Unique_EC_KEY ecKey(EC_KEY_new());
+    if (ecKey.get() == NULL) {
+        jniThrowOutOfMemory(env, "Unable to allocate EC key");
+        return 0;
+    }
+
+    const EC_GROUP* group = reinterpret_cast<const EC_GROUP*>(groupRef);
+    JNI_TRACE("EC_GROUP_get_curve_name(%p)", group);
+
+    if (group == NULL) {
+        JNI_TRACE("EC_GROUP_get_curve_name => group == NULL");
+        jniThrowNullPointerException(env, "group == NULL");
+        return 0;
+    }
+
+    EC_KEY_set_group(ecKey.get(), group);
+
+    ECDSA_set_method(ecKey.get(), &android_ecdsa_method);
+    ECDSA_set_ex_data(ecKey.get(), EcdsaGetExDataIndex(), env->NewGlobalRef(javaKey));
+
+    Unique_EVP_PKEY pkey(EVP_PKEY_new());
+    if (pkey.get() == NULL) {
+        JNI_TRACE("getDSAPrivateKeyWrapper failed");
+        jniThrowRuntimeException(env, "NativeCrypto_getDSAPrivateKeyWrapper failed");
+        freeOpenSslErrorState();
+        return 0;
+    }
+
+    if (EVP_PKEY_assign_EC_KEY(pkey.get(), ecKey.get()) != 1) {
+        jniThrowRuntimeException(env, "getECPrivateKeyWrapper failed");
+        return 0;
+    }
+    OWNERSHIP_TRANSFERRED(ecKey);
+    return reinterpret_cast<uintptr_t>(pkey.release());
+}
+
 /*
  * public static native int RSA_generate_key(int modulusBits, byte[] publicExponent);
  */
@@ -1880,6 +2642,11 @@
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("RSA_size(%p)", pkey);
 
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+        return 0;
+    }
+
     Unique_RSA rsa(EVP_PKEY_get1_RSA(pkey));
     if (rsa.get() == NULL) {
         jniThrowRuntimeException(env, "RSA_size failed");
@@ -1898,6 +2665,11 @@
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("%s(%d, %p, %p, %p)", caller, flen, fromJavaBytes, toJavaBytes, pkey);
 
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+        return -1;
+    }
+
     Unique_RSA rsa(EVP_PKEY_get1_RSA(pkey));
     if (rsa.get() == NULL) {
         return -1;
@@ -2160,6 +2932,11 @@
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("get_DSA_params(%p)", pkey);
 
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+        return NULL;
+    }
+
     Unique_DSA dsa(EVP_PKEY_get1_DSA(pkey));
     if (dsa.get() == NULL) {
         throwExceptionIfNecessary(env, "get_DSA_params failed");
@@ -2214,6 +2991,139 @@
     return joa;
 }
 
+static void NativeCrypto_set_DSA_flag_nonce_from_hash(JNIEnv* env, jclass, jlong pkeyRef)
+{
+    EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
+    JNI_TRACE("set_DSA_flag_nonce_from_hash(%p)", pkey);
+
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+        return;
+    }
+
+    Unique_DSA dsa(EVP_PKEY_get1_DSA(pkey));
+    if (dsa.get() == NULL) {
+        throwExceptionIfNecessary(env, "set_DSA_flag_nonce_from_hash failed");
+        return;
+    }
+
+    dsa->flags |= DSA_FLAG_NONCE_FROM_HASH;
+}
+
+static jlong NativeCrypto_DH_generate_parameters_ex(JNIEnv* env, jclass, jint primeBits, jlong generator) {
+    JNI_TRACE("DH_generate_parameters_ex(%d, %d)", primeBits, generator);
+
+    Unique_DH dh(DH_new());
+    if (dh.get() == NULL) {
+        JNI_TRACE("DH_generate_parameters_ex failed");
+        jniThrowOutOfMemory(env, "Unable to allocate DH key");
+        freeOpenSslErrorState();
+        return 0;
+    }
+
+    JNI_TRACE("DH_generate_parameters_ex generating parameters");
+
+    if (!DH_generate_parameters_ex(dh.get(), primeBits, generator, NULL)) {
+        JNI_TRACE("DH_generate_parameters_ex => param generation failed");
+        throwExceptionIfNecessary(env, "NativeCrypto_DH_generate_parameters_ex failed");
+        return 0;
+    }
+
+    Unique_EVP_PKEY pkey(EVP_PKEY_new());
+    if (pkey.get() == NULL) {
+        JNI_TRACE("DH_generate_parameters_ex failed");
+        jniThrowRuntimeException(env, "NativeCrypto_DH_generate_parameters_ex failed");
+        freeOpenSslErrorState();
+        return 0;
+    }
+
+    if (EVP_PKEY_assign_DH(pkey.get(), dh.get()) != 1) {
+        JNI_TRACE("DH_generate_parameters_ex failed");
+        throwExceptionIfNecessary(env, "NativeCrypto_DH_generate_parameters_ex failed");
+        return 0;
+    }
+
+    OWNERSHIP_TRANSFERRED(dh);
+    JNI_TRACE("DH_generate_parameters_ex(n=%d, g=%d) => %p", primeBits, generator, pkey.get());
+    return reinterpret_cast<uintptr_t>(pkey.release());
+}
+
+static void NativeCrypto_DH_generate_key(JNIEnv* env, jclass, jlong pkeyRef) {
+    JNI_TRACE("DH_generate_key(%p)", pkeyRef);
+    EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
+
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+    }
+
+    Unique_DH dh(EVP_PKEY_get1_DH(pkey));
+    if (dh.get() == NULL) {
+        JNI_TRACE("DH_generate_key failed");
+        throwExceptionIfNecessary(env, "Unable to get DH key");
+        freeOpenSslErrorState();
+    }
+
+    if (!DH_generate_key(dh.get())) {
+        JNI_TRACE("DH_generate_key failed");
+        throwExceptionIfNecessary(env, "NativeCrypto_DH_generate_key failed");
+    }
+}
+
+static jobjectArray NativeCrypto_get_DH_params(JNIEnv* env, jclass, jlong pkeyRef) {
+    EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
+    JNI_TRACE("get_DH_params(%p)", pkey);
+
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+        return NULL;
+    }
+
+    Unique_DH dh(EVP_PKEY_get1_DH(pkey));
+    if (dh.get() == NULL) {
+        throwExceptionIfNecessary(env, "get_DH_params failed");
+        return 0;
+    }
+
+    jobjectArray joa = env->NewObjectArray(4, byteArrayClass, NULL);
+    if (joa == NULL) {
+        return NULL;
+    }
+
+    if (dh->p != NULL) {
+        jbyteArray p = bignumToArray(env, dh->p, "p");
+        if (env->ExceptionCheck()) {
+            return NULL;
+        }
+        env->SetObjectArrayElement(joa, 0, p);
+    }
+
+    if (dh->g != NULL) {
+        jbyteArray g = bignumToArray(env, dh->g, "g");
+        if (env->ExceptionCheck()) {
+            return NULL;
+        }
+        env->SetObjectArrayElement(joa, 1, g);
+    }
+
+    if (dh->pub_key != NULL) {
+        jbyteArray pub_key = bignumToArray(env, dh->pub_key, "pub_key");
+        if (env->ExceptionCheck()) {
+            return NULL;
+        }
+        env->SetObjectArrayElement(joa, 2, pub_key);
+    }
+
+    if (dh->priv_key != NULL) {
+        jbyteArray priv_key = bignumToArray(env, dh->priv_key, "priv_key");
+        if (env->ExceptionCheck()) {
+            return NULL;
+        }
+        env->SetObjectArrayElement(joa, 3, priv_key);
+    }
+
+    return joa;
+}
+
 #define EC_CURVE_GFP 1
 #define EC_CURVE_GF2M 2
 
@@ -2821,6 +3731,11 @@
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("EC_KEY_get_private_key(%p)", pkey);
 
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+        return NULL;
+    }
+
     Unique_EC_KEY eckey(EVP_PKEY_get1_EC_KEY(pkey));
     if (eckey.get() == NULL) {
         throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY");
@@ -2844,6 +3759,11 @@
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("EC_KEY_get_public_key(%p)", pkey);
 
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+        return 0;
+    }
+
     Unique_EC_KEY eckey(EVP_PKEY_get1_EC_KEY(pkey));
     if (eckey.get() == NULL) {
         throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY");
@@ -2862,6 +3782,26 @@
     return reinterpret_cast<uintptr_t>(dup.release());
 }
 
+static void NativeCrypto_EC_KEY_set_nonce_from_hash(JNIEnv* env, jclass, jlong pkeyRef,
+        jboolean enabled)
+{
+    EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
+    JNI_TRACE("EC_KEY_set_nonce_from_hash(%p, %d)", pkey, enabled ? 1 : 0);
+
+    if (pkey == NULL) {
+        jniThrowNullPointerException(env, "pkey == null");
+        return;
+    }
+
+    Unique_EC_KEY eckey(EVP_PKEY_get1_EC_KEY(pkey));
+    if (eckey.get() == NULL) {
+        throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY");
+        return;
+    }
+
+    EC_KEY_set_nonce_from_hash(eckey.get(), enabled ? 1 : 0);
+}
+
 static jint NativeCrypto_ECDH_compute_key(JNIEnv* env, jclass,
      jbyteArray outArray, jint outOffset, jlong pubkeyRef, jlong privkeyRef)
 {
@@ -2881,6 +3821,11 @@
         return -1;
     }
 
+    if (pubPkey == NULL) {
+        jniThrowNullPointerException(env, "pubPkey == null");
+        return -1;
+    }
+
     Unique_EC_KEY pubkey(EVP_PKEY_get1_EC_KEY(pubPkey));
     if (pubkey.get() == NULL) {
         JNI_TRACE("ECDH_compute_key(%p) => can't get public key", pubPkey);
@@ -2895,6 +3840,11 @@
         return -1;
     }
 
+    if (privPkey == NULL) {
+        jniThrowNullPointerException(env, "privPkey == null");
+        return -1;
+    }
+
     Unique_EC_KEY privkey(EVP_PKEY_get1_EC_KEY(privPkey));
     if (privkey.get() == NULL) {
         throwExceptionIfNecessary(env, "EVP_PKEY_get1_EC_KEY private");
@@ -2917,7 +3867,7 @@
 }
 
 static jlong NativeCrypto_EVP_MD_CTX_create(JNIEnv* env, jclass) {
-    JNI_TRACE("EVP_MD_CTX_create()");
+    JNI_TRACE_MD("EVP_MD_CTX_create()");
 
     Unique_EVP_MD_CTX ctx(EVP_MD_CTX_create());
     if (ctx.get() == NULL) {
@@ -2925,13 +3875,13 @@
         return 0;
     }
 
-    JNI_TRACE("EVP_MD_CTX_create() => %p", ctx.get());
+    JNI_TRACE_MD("EVP_MD_CTX_create() => %p", ctx.get());
     return reinterpret_cast<uintptr_t>(ctx.release());
 }
 
-static void NativeCrypto_EVP_MD_CTX_init(JNIEnv*, jclass, jlong ctxRef) {
-    EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
-    JNI_TRACE("NativeCrypto_EVP_MD_CTX_init(%p)", ctx);
+static void NativeCrypto_EVP_MD_CTX_init(JNIEnv* env, jclass, jobject ctxRef) {
+    EVP_MD_CTX* ctx = fromContextObject<EVP_MD_CTX>(env, ctxRef);
+    JNI_TRACE_MD("EVP_MD_CTX_init(%p)", ctx);
 
     if (ctx != NULL) {
         EVP_MD_CTX_init(ctx);
@@ -2940,50 +3890,45 @@
 
 static void NativeCrypto_EVP_MD_CTX_destroy(JNIEnv*, jclass, jlong ctxRef) {
     EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
-    JNI_TRACE("NativeCrypto_EVP_MD_CTX_destroy(%p)", ctx);
+    JNI_TRACE_MD("EVP_MD_CTX_destroy(%p)", ctx);
 
     if (ctx != NULL) {
         EVP_MD_CTX_destroy(ctx);
     }
 }
 
-static jlong NativeCrypto_EVP_MD_CTX_copy(JNIEnv* env, jclass, jlong ctxRef) {
-    EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
-    JNI_TRACE("NativeCrypto_EVP_MD_CTX_copy(%p)", ctx);
+static jint NativeCrypto_EVP_MD_CTX_copy(JNIEnv* env, jclass, jobject dstCtxRef, jobject srcCtxRef) {
+    EVP_MD_CTX* dst_ctx = fromContextObject<EVP_MD_CTX>(env, dstCtxRef);
+    const EVP_MD_CTX* src_ctx = fromContextObject<EVP_MD_CTX>(env, srcCtxRef);
+    JNI_TRACE_MD("EVP_MD_CTX_copy(%p. %p)", dst_ctx, src_ctx);
 
-    if (ctx == NULL) {
-        jniThrowNullPointerException(env, "ctx == null");
+    if (src_ctx == NULL) {
+        return 0;
+    } else if (dst_ctx == NULL) {
         return 0;
     }
 
-    EVP_MD_CTX* copy = EVP_MD_CTX_create();
-    if (copy == NULL) {
-        jniThrowOutOfMemory(env, "Unable to allocate copy of EVP_MD_CTX");
-        return 0;
-    }
-
-    EVP_MD_CTX_init(copy);
-    int result = EVP_MD_CTX_copy_ex(copy, ctx);
+    int result = EVP_MD_CTX_copy_ex(dst_ctx, src_ctx);
     if (result == 0) {
-        EVP_MD_CTX_destroy(copy);
         jniThrowRuntimeException(env, "Unable to copy EVP_MD_CTX");
         freeOpenSslErrorState();
-        return 0;
     }
 
-    JNI_TRACE("NativeCrypto_EVP_MD_CTX_copy(%p) => %p", ctx, copy);
-    return reinterpret_cast<uintptr_t>(copy);
+    JNI_TRACE_MD("EVP_MD_CTX_copy(%p, %p) => %d", dst_ctx, src_ctx, result);
+    return result;
 }
 
 /*
  * public static native int EVP_DigestFinal(long, byte[], int)
  */
-static jint NativeCrypto_EVP_DigestFinal(JNIEnv* env, jclass, jlong ctxRef,
-                                         jbyteArray hash, jint offset) {
-    EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
-    JNI_TRACE("NativeCrypto_EVP_DigestFinal(%p, %p, %d)", ctx, hash, offset);
+static jint NativeCrypto_EVP_DigestFinal(JNIEnv* env, jclass, jobject ctxRef, jbyteArray hash,
+        jint offset) {
+    EVP_MD_CTX* ctx = fromContextObject<EVP_MD_CTX>(env, ctxRef);
+    JNI_TRACE_MD("EVP_DigestFinal(%p, %p, %d)", ctx, hash, offset);
 
-    if (ctx == NULL || hash == NULL) {
+    if (ctx == NULL) {
+        return -1;
+    } else if (hash == NULL) {
         jniThrowNullPointerException(env, "ctx == null || hash == null");
         return -1;
     }
@@ -2993,45 +3938,52 @@
         return -1;
     }
     unsigned int bytesWritten = -1;
-    int ok = EVP_DigestFinal(ctx,
+    int ok = EVP_DigestFinal_ex(ctx,
                              reinterpret_cast<unsigned char*>(hashBytes.get() + offset),
                              &bytesWritten);
     if (ok == 0) {
-        throwExceptionIfNecessary(env, "NativeCrypto_EVP_DigestFinal");
+        throwExceptionIfNecessary(env, "EVP_DigestFinal");
     }
-    EVP_MD_CTX_destroy(ctx);
 
-    JNI_TRACE("NativeCrypto_EVP_DigestFinal(%p, %p, %d) => %d", ctx, hash, offset, bytesWritten);
+    JNI_TRACE_MD("EVP_DigestFinal(%p, %p, %d) => %d", ctx, hash, offset, bytesWritten);
     return bytesWritten;
 }
 
-/*
- * public static native int EVP_DigestInit(long)
- */
-static jlong NativeCrypto_EVP_DigestInit(JNIEnv* env, jclass, jlong evpMdRef) {
+static jint evpInit(JNIEnv* env, jobject evpMdCtxRef, jlong evpMdRef, const char* jniName,
+        int (*init_func)(EVP_MD_CTX*, const EVP_MD*, ENGINE*)) {
+    EVP_MD_CTX* ctx = fromContextObject<EVP_MD_CTX>(env, evpMdCtxRef);
     const EVP_MD* evp_md = reinterpret_cast<const EVP_MD*>(evpMdRef);
-    JNI_TRACE("NativeCrypto_EVP_DigestInit(%p)", evp_md);
+    JNI_TRACE_MD("%s(%p, %p)", jniName, ctx, evp_md);
 
-    if (evp_md == NULL) {
-        jniThrowNullPointerException(env, NULL);
+    if (ctx == NULL) {
+        return 0;
+    } else if (evp_md == NULL) {
+        jniThrowNullPointerException(env, "evp_md == null");
         return 0;
     }
 
-    Unique_EVP_MD_CTX ctx(EVP_MD_CTX_create());
-    if (ctx.get() == NULL) {
-        jniThrowOutOfMemory(env, "Unable to allocate EVP_MD_CTX");
-        return 0;
-    }
-    JNI_TRACE("NativeCrypto_EVP_DigestInit ctx=%p", ctx.get());
-
-    int ok = EVP_DigestInit(ctx.get(), evp_md);
+    int ok = init_func(ctx, evp_md, NULL);
     if (ok == 0) {
-        bool exception = throwExceptionIfNecessary(env, "NativeCrypto_EVP_DigestInit");
+        bool exception = throwExceptionIfNecessary(env, jniName);
         if (exception) {
+            JNI_TRACE("%s(%p) => threw exception", jniName, evp_md);
             return 0;
         }
     }
-    return reinterpret_cast<uintptr_t>(ctx.release());
+    JNI_TRACE_MD("%s(%p, %p) => %d", jniName, ctx, evp_md, ok);
+    return ok;
+}
+
+static jint NativeCrypto_EVP_DigestInit(JNIEnv* env, jclass, jobject evpMdCtxRef, jlong evpMdRef) {
+    return evpInit(env, evpMdCtxRef, evpMdRef, "EVP_DigestInit", EVP_DigestInit_ex);
+}
+
+static jint NativeCrypto_EVP_SignInit(JNIEnv* env, jclass, jobject evpMdCtxRef, jlong evpMdRef) {
+    return evpInit(env, evpMdCtxRef, evpMdRef, "EVP_SignInit", EVP_DigestInit_ex);
+}
+
+static jint NativeCrypto_EVP_VerifyInit(JNIEnv* env, jclass, jobject evpMdCtxRef, jlong evpMdRef) {
+    return evpInit(env, evpMdCtxRef, evpMdRef, "EVP_VerifyInit", EVP_DigestInit_ex);
 }
 
 /*
@@ -3095,45 +4047,14 @@
     return result;
 }
 
-/*
- * public static native void EVP_DigestUpdate(long, byte[], int, int)
- */
-static void NativeCrypto_EVP_DigestUpdate(JNIEnv* env, jclass, jlong ctxRef,
-                                          jbyteArray buffer, jint offset, jint length) {
-    EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
-    JNI_TRACE("NativeCrypto_EVP_DigestUpdate(%p, %p, %d, %d)", ctx, buffer, offset, length);
-
-    if (offset < 0 || length < 0) {
-        jniThrowException(env, "java/lang/IndexOutOfBoundsException", NULL);
-        return;
-    }
-
-    if (ctx == NULL || buffer == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return;
-    }
-
-    ScopedByteArrayRO bufferBytes(env, buffer);
-    if (bufferBytes.get() == NULL) {
-        return;
-    }
-    int ok = EVP_DigestUpdate(ctx,
-                              reinterpret_cast<const unsigned char*>(bufferBytes.get() + offset),
-                              length);
-    if (ok == 0) {
-        throwExceptionIfNecessary(env, "NativeCrypto_EVP_DigestUpdate");
-    }
-}
-
-static void NativeCrypto_EVP_DigestSignInit(JNIEnv* env, jclass, jlong evpMdCtxRef,
+static void NativeCrypto_EVP_DigestSignInit(JNIEnv* env, jclass, jobject evpMdCtxRef,
         const jlong evpMdRef, jlong pkeyRef) {
-    EVP_MD_CTX* mdCtx = reinterpret_cast<EVP_MD_CTX*>(evpMdCtxRef);
+    EVP_MD_CTX* mdCtx = fromContextObject<EVP_MD_CTX>(env, evpMdCtxRef);
     const EVP_MD* md = reinterpret_cast<const EVP_MD*>(evpMdRef);
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("EVP_DigestSignInit(%p, %p, %p)", mdCtx, md, pkey);
 
     if (mdCtx == NULL) {
-         jniThrowNullPointerException(env, "mdCtx == null");
          return;
     }
 
@@ -3156,14 +4077,14 @@
     JNI_TRACE("EVP_DigestSignInit(%p, %p, %p) => success", mdCtx, md, pkey);
 }
 
-static void NativeCrypto_EVP_DigestSignUpdate(JNIEnv* env, jclass, jint evpMdCtxRef,
-        jbyteArray inJavaBytes, jint inOffset, jint inLength)
+static void evpUpdate(JNIEnv* env, jobject evpMdCtxRef, jbyteArray inJavaBytes, jint inOffset,
+        jint inLength, const char *jniName, int (*update_func)(EVP_MD_CTX*, const void *,
+        size_t))
 {
-    EVP_MD_CTX* mdCtx = reinterpret_cast<EVP_MD_CTX*>(evpMdCtxRef);
-    JNI_TRACE("EVP_DigestSignUpdate(%p, %p, %d, %d)", mdCtx, inJavaBytes, inOffset, inLength);
+    EVP_MD_CTX* mdCtx = fromContextObject<EVP_MD_CTX>(env, evpMdCtxRef);
+    JNI_TRACE_MD("%s(%p, %p, %d, %d)", jniName, mdCtx, inJavaBytes, inOffset, inLength);
 
     if (mdCtx == NULL) {
-         jniThrowNullPointerException(env, "mdCtx == null");
          return;
     }
 
@@ -3173,33 +4094,49 @@
     }
 
     if (inOffset < 0 || size_t(inOffset) > inBytes.size()) {
-        jniThrowException(env, "java/lang/IndexOutOfBoundsException", "inOffset");
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", "inOffset");
         return;
     }
 
     const ssize_t inEnd = inOffset + inLength;
-    if (inEnd < 0 || size_t(inEnd) >= inBytes.size()) {
-        jniThrowException(env, "java/lang/IndexOutOfBoundsException", "inLength");
+    if (inLength < 0 || inEnd < 0 || size_t(inEnd) > inBytes.size()) {
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", "inLength");
         return;
     }
 
     const unsigned char *tmp = reinterpret_cast<const unsigned char *>(inBytes.get());
-    if (!EVP_DigestSignUpdate(mdCtx, tmp + inOffset, inLength)) {
-        JNI_TRACE("ctx=%p EVP_DigestSignUpdate => threw exception", mdCtx);
-        throwExceptionIfNecessary(env, "EVP_DigestSignUpdate");
+    if (!update_func(mdCtx, tmp + inOffset, inLength)) {
+        JNI_TRACE("ctx=%p %s => threw exception", mdCtx, jniName);
+        throwExceptionIfNecessary(env, jniName);
     }
 
-    JNI_TRACE("EVP_DigestSignUpdate(%p, %p, %d, %d) => success", mdCtx, inJavaBytes, inOffset,
-            inLength);
+    JNI_TRACE_MD("%s(%p, %p, %d, %d) => success", jniName, mdCtx, inJavaBytes, inOffset, inLength);
 }
 
-static jbyteArray NativeCrypto_EVP_DigestSignFinal(JNIEnv* env, jclass, jlong evpMdCtxRef)
+static void NativeCrypto_EVP_DigestUpdate(JNIEnv* env, jclass, jobject evpMdCtxRef,
+        jbyteArray inJavaBytes, jint inOffset, jint inLength) {
+    evpUpdate(env, evpMdCtxRef, inJavaBytes, inOffset, inLength, "EVP_DigestUpdate",
+            EVP_DigestUpdate);
+}
+
+static void NativeCrypto_EVP_DigestSignUpdate(JNIEnv* env, jclass, jobject evpMdCtxRef,
+        jbyteArray inJavaBytes, jint inOffset, jint inLength) {
+    evpUpdate(env, evpMdCtxRef, inJavaBytes, inOffset, inLength, "EVP_DigestSignUpdate",
+            EVP_DigestUpdate);
+}
+
+static void NativeCrypto_EVP_SignUpdate(JNIEnv* env, jclass, jobject evpMdCtxRef,
+        jbyteArray inJavaBytes, jint inOffset, jint inLength) {
+    evpUpdate(env, evpMdCtxRef, inJavaBytes, inOffset, inLength, "EVP_SignUpdate",
+            EVP_DigestUpdate);
+}
+
+static jbyteArray NativeCrypto_EVP_DigestSignFinal(JNIEnv* env, jclass, jobject evpMdCtxRef)
 {
-    EVP_MD_CTX* mdCtx = reinterpret_cast<EVP_MD_CTX*>(evpMdCtxRef);
+    EVP_MD_CTX* mdCtx = fromContextObject<EVP_MD_CTX>(env, evpMdCtxRef);
     JNI_TRACE("EVP_DigestSignFinal(%p)", mdCtx);
 
     if (mdCtx == NULL) {
-         jniThrowNullPointerException(env, "mdCtx == null");
          return NULL;
     }
 
@@ -3229,82 +4166,18 @@
     return outJavaBytes.release();
 }
 
-static jlong NativeCrypto_EVP_SignInit(JNIEnv* env, jclass, jstring algorithm) {
-    JNI_TRACE("NativeCrypto_EVP_SignInit(%p)", algorithm);
-
-    if (algorithm == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return 0;
-    }
-
-    Unique_EVP_MD_CTX ctx(EVP_MD_CTX_create());
-    if (ctx.get() == NULL) {
-        jniThrowOutOfMemory(env, "Unable to allocate EVP_MD_CTX");
-        return 0;
-    }
-    JNI_TRACE("NativeCrypto_EVP_SignInit ctx=%p", ctx.get());
-
-    ScopedUtfChars algorithmChars(env, algorithm);
-    if (algorithmChars.c_str() == NULL) {
-        return 0;
-    }
-    JNI_TRACE("NativeCrypto_EVP_SignInit algorithmChars=%s", algorithmChars.c_str());
-
-    const EVP_MD* digest = EVP_get_digestbynid(OBJ_txt2nid(algorithmChars.c_str()));
-    if (digest == NULL) {
-        JNI_TRACE("NativeCrypto_EVP_SignInit(%s) => hash not found", algorithmChars.c_str());
-        throwExceptionIfNecessary(env, "Hash algorithm not found");
-        return 0;
-    }
-
-    int ok = EVP_SignInit(ctx.get(), digest);
-    if (ok == 0) {
-        bool exception = throwExceptionIfNecessary(env, "NativeCrypto_EVP_SignInit");
-        if (exception) {
-            JNI_TRACE("NativeCrypto_EVP_SignInit(%s) => threw exception", algorithmChars.c_str());
-            return 0;
-        }
-    }
-
-    JNI_TRACE("NativeCrypto_EVP_SignInit(%s) => %p", algorithmChars.c_str(), ctx.get());
-    return reinterpret_cast<uintptr_t>(ctx.release());
-}
-
-/*
- * public static native void EVP_SignUpdate(long, byte[], int, int)
- */
-static void NativeCrypto_EVP_SignUpdate(JNIEnv* env, jclass, jlong ctxRef,
-                                          jbyteArray buffer, jint offset, jint length) {
-    EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
-    JNI_TRACE("NativeCrypto_EVP_SignUpdate(%p, %p, %d, %d)", ctx, buffer, offset, length);
-
-    if (ctx == NULL || buffer == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return;
-    }
-
-    ScopedByteArrayRO bufferBytes(env, buffer);
-    if (bufferBytes.get() == NULL) {
-        return;
-    }
-    int ok = EVP_SignUpdate(ctx,
-                            reinterpret_cast<const unsigned char*>(bufferBytes.get() + offset),
-                            length);
-    if (ok == 0) {
-        throwExceptionIfNecessary(env, "NativeCrypto_EVP_SignUpdate");
-    }
-}
-
 /*
  * public static native int EVP_SignFinal(long, byte[], int, long)
  */
-static jint NativeCrypto_EVP_SignFinal(JNIEnv* env, jclass, jlong ctxRef, jbyteArray signature,
+static jint NativeCrypto_EVP_SignFinal(JNIEnv* env, jclass, jobject ctxRef, jbyteArray signature,
         jint offset, jlong pkeyRef) {
-    EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
+    EVP_MD_CTX* ctx = fromContextObject<EVP_MD_CTX>(env, ctxRef);
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("NativeCrypto_EVP_SignFinal(%p, %p, %d, %p)", ctx, signature, offset, pkey);
 
-    if (ctx == NULL || pkey == NULL) {
+    if (ctx == NULL) {
+        return -1;
+    } else if (pkey == NULL) {
         jniThrowNullPointerException(env, NULL);
         return -1;
     }
@@ -3328,62 +4201,34 @@
 }
 
 /*
- * public static native int EVP_VerifyInit(java.lang.String)
- */
-static jlong NativeCrypto_EVP_VerifyInit(JNIEnv* env, jclass, jstring algorithm) {
-    JNI_TRACE("NativeCrypto_EVP_VerifyInit(%p)", algorithm);
-
-    if (algorithm == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return 0;
-    }
-
-    Unique_EVP_MD_CTX ctx(EVP_MD_CTX_create());
-    if (ctx.get() == NULL) {
-        jniThrowOutOfMemory(env, "Unable to allocate EVP_MD_CTX");
-        return 0;
-    }
-    JNI_TRACE("NativeCrypto_EVP_VerifyInit ctx=%p", ctx.get());
-
-    ScopedUtfChars algorithmChars(env, algorithm);
-    if (algorithmChars.c_str() == NULL) {
-        return 0;
-    }
-    JNI_TRACE("NativeCrypto_EVP_VerifyInit algorithmChars=%s", algorithmChars.c_str());
-
-    const EVP_MD* digest = EVP_get_digestbynid(OBJ_txt2nid(algorithmChars.c_str()));
-    if (digest == NULL) {
-        jniThrowRuntimeException(env, "Hash algorithm not found");
-        return 0;
-    }
-
-    int ok = EVP_VerifyInit(ctx.get(), digest);
-    if (ok == 0) {
-        bool exception = throwExceptionIfNecessary(env, "NativeCrypto_EVP_VerifyInit");
-        if (exception) {
-            return 0;
-        }
-    }
-    return reinterpret_cast<uintptr_t>(ctx.release());
-}
-
-/*
  * public static native void EVP_VerifyUpdate(long, byte[], int, int)
  */
-static void NativeCrypto_EVP_VerifyUpdate(JNIEnv* env, jclass, jlong ctxRef,
+static void NativeCrypto_EVP_VerifyUpdate(JNIEnv* env, jclass, jobject ctxRef,
                                           jbyteArray buffer, jint offset, jint length) {
-    EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
+    EVP_MD_CTX* ctx = fromContextObject<EVP_MD_CTX>(env, ctxRef);
     JNI_TRACE("NativeCrypto_EVP_VerifyUpdate(%p, %p, %d, %d)", ctx, buffer, offset, length);
 
-    if (ctx == NULL || buffer == NULL) {
+    if (ctx == NULL) {
+        return;
+    } else if (buffer == NULL) {
         jniThrowNullPointerException(env, NULL);
         return;
     }
 
+    if (offset < 0 || length < 0) {
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
+        return;
+    }
+
     ScopedByteArrayRO bufferBytes(env, buffer);
     if (bufferBytes.get() == NULL) {
         return;
     }
+    if (bufferBytes.size() < static_cast<size_t>(offset + length)) {
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
+        return;
+    }
+
     int ok = EVP_VerifyUpdate(ctx,
                               reinterpret_cast<const unsigned char*>(bufferBytes.get() + offset),
                               length);
@@ -3395,14 +4240,16 @@
 /*
  * public static native int EVP_VerifyFinal(long, byte[], int, int, long)
  */
-static jint NativeCrypto_EVP_VerifyFinal(JNIEnv* env, jclass, jlong ctxRef, jbyteArray buffer,
+static jint NativeCrypto_EVP_VerifyFinal(JNIEnv* env, jclass, jobject ctxRef, jbyteArray buffer,
                                         jint offset, jint length, jlong pkeyRef) {
-    EVP_MD_CTX* ctx = reinterpret_cast<EVP_MD_CTX*>(ctxRef);
+    EVP_MD_CTX* ctx = fromContextObject<EVP_MD_CTX>(env, ctxRef);
     EVP_PKEY* pkey = reinterpret_cast<EVP_PKEY*>(pkeyRef);
     JNI_TRACE("NativeCrypto_EVP_VerifyFinal(%p, %p, %d, %d, %p)",
               ctx, buffer, offset, length, pkey);
 
-    if (ctx == NULL || buffer == NULL || pkey == NULL) {
+    if (ctx == NULL) {
+        return -1;
+    } else if (buffer == NULL || pkey == NULL) {
         jniThrowNullPointerException(env, NULL);
         return -1;
     }
@@ -3522,7 +4369,7 @@
     }
     const size_t inSize = inBytes.size();
     if (size_t(inOffset + inLength) > inSize) {
-        jniThrowException(env, "java/lang/IndexOutOfBoundsException",
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException",
                 "in.length < (inSize + inOffset)");
         return 0;
     }
@@ -3533,7 +4380,7 @@
     }
     const size_t outSize = outBytes.size();
     if (size_t(outOffset + inLength) > outSize) {
-        jniThrowException(env, "java/lang/IndexOutOfBoundsException",
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException",
                 "out.length < inSize + outOffset + blockSize - 1");
         return 0;
     }
@@ -3867,14 +4714,14 @@
     }
 
     if (offset < 0 || length < 0) {
-        jniThrowException(env, "java/lang/IndexOutOfBoundsException", "offset < 0 || length < 0");
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", "offset < 0 || length < 0");
         JNI_TRACE("BIO_write(%p, %p, %d, %d) => IOOB", bio, inputJavaBytes, offset, length);
         return;
     }
 
     int inputSize = env->GetArrayLength(inputJavaBytes);
     if (inputSize < offset + length) {
-        jniThrowException(env, "java/lang/IndexOutOfBoundsException",
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException",
                 "input.length < offset + length");
         JNI_TRACE("BIO_write(%p, %p, %d, %d) => IOOB", bio, inputJavaBytes, offset, length);
         return;
@@ -3897,16 +4744,16 @@
     JNI_TRACE("BIO_write(%p, %p, %d, %d) => success", bio, inputJavaBytes, offset, length);
 }
 
-static void NativeCrypto_BIO_free(JNIEnv* env, jclass, jlong bioRef) {
+static void NativeCrypto_BIO_free_all(JNIEnv* env, jclass, jlong bioRef) {
     BIO* bio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(bioRef));
-    JNI_TRACE("BIO_free(%p)", bio);
+    JNI_TRACE("BIO_free_all(%p)", bio);
 
     if (bio == NULL) {
         jniThrowNullPointerException(env, "bio == null");
         return;
     }
 
-    BIO_free(bio);
+    BIO_free_all(bio);
 }
 
 static jstring X509_NAME_to_jstring(JNIEnv* env, X509_NAME* name, unsigned long flags) {
@@ -5150,6 +5997,7 @@
         return 0;
     }
 
+    JNI_TRACE("X509_get_pubkey(%p) => %p", x509, pkey.get());
     return reinterpret_cast<uintptr_t>(pkey.release());
 }
 
@@ -5674,9 +6522,13 @@
      */
     bool setCallbackState(JNIEnv* e, jobject shc, jobject fd, jbyteArray npnProtocols,
             jbyteArray alpnProtocols) {
-        NetFd netFd(e, fd);
-        if (netFd.isClosed()) {
-            return false;
+        UniquePtr<NetFd> netFd;
+        if (fd != NULL) {
+            netFd.reset(new NetFd(e, fd));
+            if (netFd->isClosed()) {
+                JNI_TRACE("appData=%p setCallbackState => netFd->isClosed() == true", this);
+                return false;
+            }
         }
         env = e;
         sslHandshakeCallbacks = shc;
@@ -5684,6 +6536,7 @@
             npnProtocolsData = e->GetByteArrayElements(npnProtocols, NULL);
             if (npnProtocolsData == NULL) {
                 clearCallbackState();
+                JNI_TRACE("appData=%p setCallbackState => npnProtocolsData == NULL", this);
                 return false;
             }
             npnProtocolsArray = npnProtocols;
@@ -5693,6 +6546,7 @@
             alpnProtocolsData = e->GetByteArrayElements(alpnProtocols, NULL);
             if (alpnProtocolsData == NULL) {
                 clearCallbackState();
+                JNI_TRACE("appData=%p setCallbackState => alpnProtocolsData == NULL", this);
                 return false;
             }
             alpnProtocolsArray = alpnProtocols;
@@ -5784,7 +6638,11 @@
             ptv = NULL;
         }
 
-        AsynchronousSocketCloseMonitor monitor(intFd);
+#ifndef CONSCRYPT_UNBUNDLED
+        AsynchronousCloseMonitor monitor(intFd);
+#else
+        CompatibilityCloseMonitor monitor(intFd);
+#endif
         result = select(maxFd + 1, &rfds, &wfds, NULL, ptv);
         JNI_TRACE("sslSelect %s fd=%d appData=%p timeout_millis=%d => %d",
                   (type == SSL_ERROR_WANT_READ) ? "READ" : "WRITE",
@@ -5874,7 +6732,7 @@
 
     jclass cls = env->GetObjectClass(sslHandshakeCallbacks);
     jmethodID methodID
-        = env->GetMethodID(cls, "verifyCertificateChain", "([JLjava/lang/String;)V");
+        = env->GetMethodID(cls, "verifyCertificateChain", "(J[JLjava/lang/String;)V");
 
     jlongArray refArray = getCertificateRefs(env, x509_store_ctx->untrusted);
 
@@ -5882,7 +6740,9 @@
     JNI_TRACE("ssl=%p cert_verify_callback calling verifyCertificateChain authMethod=%s",
               ssl, authMethod);
     jstring authMethodString = env->NewStringUTF(authMethod);
-    env->CallVoidMethod(sslHandshakeCallbacks, methodID, refArray, authMethodString);
+    env->CallVoidMethod(sslHandshakeCallbacks, methodID,
+            static_cast<jlong>(reinterpret_cast<uintptr_t>(SSL_get1_session(ssl))), refArray,
+            authMethodString);
 
     int result = (env->ExceptionCheck()) ? 0 : 1;
     JNI_TRACE("ssl=%p cert_verify_callback => %d", ssl, result);
@@ -5894,12 +6754,12 @@
  * for SSL_MODE_HANDSHAKE_CUTTHROUGH support, since SSL_do_handshake
  * returns before the handshake is completed in this case.
  */
-static void info_callback(const SSL* ssl, int where, int ret __attribute__ ((unused))) {
+static void info_callback(const SSL* ssl, int where, int ret) {
     JNI_TRACE("ssl=%p info_callback where=0x%x ret=%d", ssl, where, ret);
 #ifdef WITH_JNI_TRACE
     info_callback_LOG(ssl, where, ret);
 #endif
-    if (!(where & SSL_CB_HANDSHAKE_DONE)) {
+    if (!(where & SSL_CB_HANDSHAKE_DONE) && !(where & SSL_CB_HANDSHAKE_START)) {
         JNI_TRACE("ssl=%p info_callback ignored", ssl);
         return;
     }
@@ -5919,10 +6779,10 @@
     jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks;
 
     jclass cls = env->GetObjectClass(sslHandshakeCallbacks);
-    jmethodID methodID = env->GetMethodID(cls, "handshakeCompleted", "()V");
+    jmethodID methodID = env->GetMethodID(cls, "onSSLStateChange", "(JII)V");
 
-    JNI_TRACE("ssl=%p info_callback calling handshakeCompleted", ssl);
-    env->CallVoidMethod(sslHandshakeCallbacks, methodID);
+    JNI_TRACE("ssl=%p info_callback calling onSSLStateChange", ssl);
+    env->CallVoidMethod(sslHandshakeCallbacks, methodID, reinterpret_cast<jlong>(ssl), where, ret);
 
     if (env->ExceptionCheck()) {
         JNI_TRACE("ssl=%p info_callback exception", ssl);
@@ -6019,6 +6879,137 @@
     return result;
 }
 
+/**
+ * Pre-Shared Key (PSK) client callback.
+ */
+static unsigned int psk_client_callback(SSL* ssl, const char *hint,
+        char *identity, unsigned int max_identity_len,
+        unsigned char *psk, unsigned int max_psk_len) {
+    JNI_TRACE("ssl=%p psk_client_callback", ssl);
+
+    AppData* appData = toAppData(ssl);
+    JNIEnv* env = appData->env;
+    if (env == NULL) {
+        ALOGE("AppData->env missing in psk_client_callback");
+        JNI_TRACE("ssl=%p psk_client_callback env error", ssl);
+        return 0;
+    }
+    if (env->ExceptionCheck()) {
+        JNI_TRACE("ssl=%p psk_client_callback already pending exception", ssl);
+        return 0;
+    }
+
+    jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks;
+    jclass cls = env->GetObjectClass(sslHandshakeCallbacks);
+    jmethodID methodID =
+            env->GetMethodID(cls, "clientPSKKeyRequested", "(Ljava/lang/String;[B[B)I");
+    JNI_TRACE("ssl=%p psk_client_callback calling clientPSKKeyRequested", ssl);
+    ScopedLocalRef<jstring> identityHintJava(
+            env,
+            (hint != NULL) ? env->NewStringUTF(hint) : NULL);
+    ScopedLocalRef<jbyteArray> identityJava(env, env->NewByteArray(max_identity_len));
+    if (identityJava.get() == NULL) {
+        JNI_TRACE("ssl=%p psk_client_callback failed to allocate identity bufffer", ssl);
+        return 0;
+    }
+    ScopedLocalRef<jbyteArray> keyJava(env, env->NewByteArray(max_psk_len));
+    if (keyJava.get() == NULL) {
+        JNI_TRACE("ssl=%p psk_client_callback failed to allocate key bufffer", ssl);
+        return 0;
+    }
+    jint keyLen = env->CallIntMethod(sslHandshakeCallbacks, methodID,
+            identityHintJava.get(), identityJava.get(), keyJava.get());
+    if (env->ExceptionCheck()) {
+        JNI_TRACE("ssl=%p psk_client_callback exception", ssl);
+        return 0;
+    }
+    if (keyLen <= 0) {
+        JNI_TRACE("ssl=%p psk_client_callback failed to get key", ssl);
+        return 0;
+    } else if ((unsigned int) keyLen > max_psk_len) {
+        JNI_TRACE("ssl=%p psk_client_callback got key which is too long", ssl);
+        return 0;
+    }
+    ScopedByteArrayRO keyJavaRo(env, keyJava.get());
+    if (keyJavaRo.get() == NULL) {
+        JNI_TRACE("ssl=%p psk_client_callback failed to get key bytes", ssl);
+        return 0;
+    }
+    memcpy(psk, keyJavaRo.get(), keyLen);
+
+    ScopedByteArrayRO identityJavaRo(env, identityJava.get());
+    if (identityJavaRo.get() == NULL) {
+        JNI_TRACE("ssl=%p psk_client_callback failed to get identity bytes", ssl);
+        return 0;
+    }
+    memcpy(identity, identityJavaRo.get(), max_identity_len);
+
+    JNI_TRACE("ssl=%p psk_client_callback completed", ssl);
+    return keyLen;
+}
+
+/**
+ * Pre-Shared Key (PSK) server callback.
+ */
+static unsigned int psk_server_callback(SSL* ssl, const char *identity,
+        unsigned char *psk, unsigned int max_psk_len) {
+    JNI_TRACE("ssl=%p psk_server_callback", ssl);
+
+    AppData* appData = toAppData(ssl);
+    JNIEnv* env = appData->env;
+    if (env == NULL) {
+        ALOGE("AppData->env missing in psk_server_callback");
+        JNI_TRACE("ssl=%p psk_server_callback env error", ssl);
+        return 0;
+    }
+    if (env->ExceptionCheck()) {
+        JNI_TRACE("ssl=%p psk_server_callback already pending exception", ssl);
+        return 0;
+    }
+
+    jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks;
+    jclass cls = env->GetObjectClass(sslHandshakeCallbacks);
+    jmethodID methodID = env->GetMethodID(
+            cls, "serverPSKKeyRequested", "(Ljava/lang/String;Ljava/lang/String;[B)I");
+    JNI_TRACE("ssl=%p psk_server_callback calling serverPSKKeyRequested", ssl);
+    const char* identityHint = SSL_get_psk_identity_hint(ssl);
+    // identityHint = NULL;
+    // identity = NULL;
+    ScopedLocalRef<jstring> identityHintJava(
+            env,
+            (identityHint != NULL) ? env->NewStringUTF(identityHint) : NULL);
+    ScopedLocalRef<jstring> identityJava(
+            env,
+            (identity != NULL) ? env->NewStringUTF(identity) : NULL);
+    ScopedLocalRef<jbyteArray> keyJava(env, env->NewByteArray(max_psk_len));
+    if (keyJava.get() == NULL) {
+        JNI_TRACE("ssl=%p psk_server_callback failed to allocate key bufffer", ssl);
+        return 0;
+    }
+    jint keyLen = env->CallIntMethod(sslHandshakeCallbacks, methodID,
+            identityHintJava.get(), identityJava.get(), keyJava.get());
+    if (env->ExceptionCheck()) {
+        JNI_TRACE("ssl=%p psk_server_callback exception", ssl);
+        return 0;
+    }
+    if (keyLen <= 0) {
+        JNI_TRACE("ssl=%p psk_server_callback failed to get key", ssl);
+        return 0;
+    } else if ((unsigned int) keyLen > max_psk_len) {
+        JNI_TRACE("ssl=%p psk_server_callback got key which is too long", ssl);
+        return 0;
+    }
+    ScopedByteArrayRO keyJavaRo(env, keyJava.get());
+    if (keyJavaRo.get() == NULL) {
+        JNI_TRACE("ssl=%p psk_server_callback failed to get key bytes", ssl);
+        return 0;
+    }
+    memcpy(psk, keyJavaRo.get(), keyLen);
+
+    JNI_TRACE("ssl=%p psk_server_callback completed", ssl);
+    return keyLen;
+}
+
 static RSA* rsaGenerateKey(int keylength) {
     Unique_BIGNUM bn(BN_new());
     if (bn.get() == NULL) {
@@ -6171,6 +7162,9 @@
     SSL_CTX_set_tmp_dh_callback(sslCtx.get(), tmp_dh_callback);
     SSL_CTX_set_tmp_ecdh_callback(sslCtx.get(), tmp_ecdh_callback);
 
+    // When TLS Channel ID extension is used, use the new version of it.
+    sslCtx.get()->tlsext_channel_id_enabled_new = 1;
+
     JNI_TRACE("NativeCrypto_SSL_CTX_new => %p", sslCtx.get());
     return (jlong) sslCtx.release();
 }
@@ -6238,17 +7232,28 @@
         return 0;
     }
 
-    /* Java code in class OpenSSLSocketImpl does the verification. Meaning of
-     * SSL_VERIFY_NONE flag in client mode: if not using an anonymous cipher
-     * (by default disabled), the server will send a certificate which will
-     * be checked. The result of the certificate verification process can be
-     * checked after the TLS/SSL handshake using the SSL_get_verify_result(3)
-     * function. The handshake will be continued regardless of the
-     * verification result.
+    /*
+     * Create our special application data.
      */
-    SSL_set_verify(ssl.get(), SSL_VERIFY_NONE, NULL);
+    AppData* appData = AppData::create();
+    if (appData == NULL) {
+        throwSSLExceptionStr(env, "Unable to create application data");
+        freeOpenSslErrorState();
+        JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new appData => 0", ssl_ctx);
+        return 0;
+    }
+    SSL_set_app_data(ssl.get(), reinterpret_cast<char*>(appData));
 
-    JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new => ssl=%p", ssl_ctx, ssl.get());
+    /*
+     * Java code in class OpenSSLSocketImpl does the verification. Since
+     * the callbacks do all the verification of the chain, this flag
+     * simply controls whether to send protocol-level alerts or not.
+     * SSL_VERIFY_NONE means don't send alerts and anything else means send
+     * alerts.
+     */
+    SSL_set_verify(ssl.get(), SSL_VERIFY_PEER, NULL);
+
+    JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new => ssl=%p appData=%p", ssl_ctx, ssl.get(), appData);
     return (jlong) ssl.release();
 }
 
@@ -6627,6 +7632,62 @@
     return result;
 }
 
+
+static void NativeCrypto_SSL_use_psk_identity_hint(JNIEnv* env, jclass,
+        jlong ssl_address, jstring identityHintJava)
+{
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_use_psk_identity_hint identityHint=%p",
+            ssl, identityHintJava);
+    if (ssl == NULL)  {
+        return;
+    }
+
+    int ret;
+    if (identityHintJava == NULL) {
+        ret = SSL_use_psk_identity_hint(ssl, NULL);
+    } else {
+        ScopedUtfChars identityHint(env, identityHintJava);
+        if (identityHint.c_str() == NULL) {
+            throwSSLExceptionStr(env, "Failed to obtain identityHint bytes");
+            return;
+        }
+        ret = SSL_use_psk_identity_hint(ssl, identityHint.c_str());
+    }
+
+    if (ret != 1) {
+        int sslErrorCode = SSL_get_error(ssl, ret);
+        throwSSLExceptionWithSslErrors(env, ssl, sslErrorCode, "Failed to set PSK identity hint");
+        SSL_clear(ssl);
+    }
+}
+
+static void NativeCrypto_set_SSL_psk_client_callback_enabled(JNIEnv* env, jclass,
+        jlong ssl_address, jboolean enabled)
+{
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_set_SSL_psk_client_callback_enabled(%d)",
+            ssl, enabled);
+    if (ssl == NULL)  {
+        return;
+    }
+
+    SSL_set_psk_client_callback(ssl, (enabled) ? psk_client_callback : NULL);
+}
+
+static void NativeCrypto_set_SSL_psk_server_callback_enabled(JNIEnv* env, jclass,
+        jlong ssl_address, jboolean enabled)
+{
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_set_SSL_psk_server_callback_enabled(%d)",
+            ssl, enabled);
+    if (ssl == NULL)  {
+        return;
+    }
+
+    SSL_set_psk_server_callback(ssl, (enabled) ? psk_server_callback : NULL);
+}
+
 static jlongArray NativeCrypto_SSL_get_ciphers(JNIEnv* env, jclass, jlong ssl_address)
 {
     SSL* ssl = to_SSL(env, ssl_address, true);
@@ -6725,6 +7786,24 @@
     }
 }
 
+static void NativeCrypto_SSL_set_accept_state(JNIEnv* env, jclass, jlong sslRef) {
+    SSL* ssl = to_SSL(env, sslRef, true);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_set_accept_state", ssl);
+    if (ssl == NULL) {
+      return;
+    }
+    SSL_set_accept_state(ssl);
+}
+
+static void NativeCrypto_SSL_set_connect_state(JNIEnv* env, jclass, jlong sslRef) {
+    SSL* ssl = to_SSL(env, sslRef, true);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_set_connect_state", ssl);
+    if (ssl == NULL) {
+      return;
+    }
+    SSL_set_connect_state(ssl);
+}
+
 /**
  * Sets certificate expectations, especially for server to request client auth
  */
@@ -6828,7 +7907,7 @@
         unsigned char **out, unsigned char *outLength,
         const unsigned char *primary, const unsigned int primaryLength,
         const unsigned char *secondary, const unsigned int secondaryLength) {
-    if (primary != NULL) {
+    if (primary != NULL && secondary != NULL) {
         JNI_TRACE("primary=%p, length=%d", primary, primaryLength);
 
         int status = SSL_select_next_proto(out, outLength, primary, primaryLength, secondary,
@@ -6951,30 +8030,30 @@
     return result;
 }
 
-static int NativeCrypto_SSL_CTX_set_alpn_protos(JNIEnv* env, jclass, jlong ssl_ctx_address,
+static int NativeCrypto_SSL_set_alpn_protos(JNIEnv* env, jclass, jlong ssl_address,
         jbyteArray protos) {
-    SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true);
-    if (ssl_ctx == NULL) {
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    if (ssl == NULL) {
         return 0;
     }
 
-    JNI_TRACE("ssl_ctx=%p SSL_CTX_set_alpn_protos protos=%p", ssl_ctx, protos);
+    JNI_TRACE("ssl=%p SSL_set_alpn_protos protos=%p", ssl, protos);
 
     if (protos == NULL) {
-        JNI_TRACE("ssl_ctx=%p SSL_CTX_set_alpn_protos protos=NULL", ssl_ctx);
+        JNI_TRACE("ssl=%p SSL_set_alpn_protos protos=NULL", ssl);
         return 1;
     }
 
     ScopedByteArrayRO protosBytes(env, protos);
     if (protosBytes.get() == NULL) {
-        JNI_TRACE("ssl_ctx=%p SSL_CTX_set_alpn_protos protos=%p => protosBytes == NULL", ssl_ctx,
+        JNI_TRACE("ssl=%p SSL_set_alpn_protos protos=%p => protosBytes == NULL", ssl,
                 protos);
         return 0;
     }
 
     const unsigned char *tmp = reinterpret_cast<const unsigned char*>(protosBytes.get());
-    int ret = SSL_CTX_set_alpn_protos(ssl_ctx, tmp, protosBytes.size());
-    JNI_TRACE("ssl_ctx=%p SSL_CTX_set_alpn_protos protos=%p => ret=%d", ssl_ctx, protos, ret);
+    int ret = SSL_set_alpn_protos(ssl, tmp, protosBytes.size());
+    JNI_TRACE("ssl=%p SSL_set_alpn_protos protos=%p => ret=%d", ssl, protos, ret);
     return ret;
 }
 
@@ -7062,6 +8141,99 @@
 /**
  * Perform SSL handshake
  */
+static jlong NativeCrypto_SSL_do_handshake_bio(JNIEnv* env, jclass, jlong ssl_address,
+        jlong rbioRef, jlong wbioRef, jobject shc, jboolean client_mode, jbyteArray npnProtocols,
+        jbyteArray alpnProtocols) {
+    SSL* ssl = to_SSL(env, ssl_address, true);
+    BIO* rbio = reinterpret_cast<BIO*>(rbioRef);
+    BIO* wbio = reinterpret_cast<BIO*>(wbioRef);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake_bio rbio=%p wbio=%p shc=%p client_mode=%d npn=%p",
+              ssl, rbio, wbio, shc, client_mode, npnProtocols);
+    if (ssl == NULL) {
+        return 0;
+    }
+    if (shc == NULL) {
+        jniThrowNullPointerException(env, "sslHandshakeCallbacks == null");
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake_bio sslHandshakeCallbacks == null => 0", ssl);
+        return 0;
+    }
+
+    if (rbio == NULL || wbio == NULL) {
+        jniThrowNullPointerException(env, "rbio == null || wbio == null");
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake_bio => rbio == null || wbio == NULL", ssl);
+        return 0;
+    }
+
+    ScopedSslBio sslBio(ssl, rbio, wbio);
+
+    AppData* appData = toAppData(ssl);
+    if (appData == NULL) {
+        throwSSLExceptionStr(env, "Unable to retrieve application data");
+        SSL_clear(ssl);
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake appData => 0", ssl);
+        return 0;
+    }
+
+    if (!client_mode && alpnProtocols != NULL) {
+        SSL_CTX_set_alpn_select_cb(SSL_get_SSL_CTX(ssl), alpn_select_callback, NULL);
+    }
+
+    int ret = 0;
+    errno = 0;
+
+    if (!appData->setCallbackState(env, shc, NULL, npnProtocols, alpnProtocols)) {
+        SSL_clear(ssl);
+        freeOpenSslErrorState();
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake_bio setCallbackState => 0", ssl);
+        return 0;
+    }
+    ret = SSL_do_handshake(ssl);
+    appData->clearCallbackState();
+    // cert_verify_callback threw exception
+    if (env->ExceptionCheck()) {
+        SSL_clear(ssl);
+        freeOpenSslErrorState();
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake_bio exception => 0", ssl);
+        return 0;
+    }
+
+    if (ret <= 0) { // error. See SSL_do_handshake(3SSL) man page.
+        // error case
+        int sslError = SSL_get_error(ssl, ret);
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake_bio ret=%d errno=%d sslError=%d",
+                  ssl, ret, errno, sslError);
+
+        /*
+         * If SSL_do_handshake doesn't succeed due to the socket being
+         * either unreadable or unwritable, we need to exit to allow
+         * the SSLEngine code to wrap or unwrap.
+         */
+        if (sslError == SSL_ERROR_NONE || (sslError == SSL_ERROR_SYSCALL && errno == 0)) {
+            throwSSLHandshakeExceptionStr(env, "Connection closed by peer");
+            SSL_clear(ssl);
+            freeOpenSslErrorState();
+        } else if (sslError != SSL_ERROR_WANT_READ && sslError != SSL_ERROR_WANT_WRITE) {
+            throwSSLExceptionWithSslErrors(env, ssl, sslError, "SSL handshake terminated",
+                    throwSSLHandshakeExceptionStr);
+            SSL_clear(ssl);
+            freeOpenSslErrorState();
+        }
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake_bio error => 0", ssl);
+        return 0;
+    }
+
+    // success. handshake completed
+    SSL_SESSION* ssl_session = SSL_get1_session(ssl);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake_bio => ssl_session=%p", ssl, ssl_session);
+#ifdef WITH_JNI_TRACE_KEYS
+    debug_print_session_key(ssl_session);
+#endif
+    return reinterpret_cast<uintptr_t>(ssl_session);
+}
+
+/**
+ * Perform SSL handshake
+ */
 static jlong NativeCrypto_SSL_do_handshake(JNIEnv* env, jclass, jlong ssl_address, jobject fdObject,
         jobject shc, jint timeout_millis, jboolean client_mode, jbyteArray npnProtocols,
         jbyteArray alpnProtocols) {
@@ -7112,20 +8284,14 @@
         return 0;
     }
 
-    /*
-     * Create our special application data.
-     */
-    AppData* appData = AppData::create();
+    AppData* appData = toAppData(ssl);
     if (appData == NULL) {
-        throwSSLExceptionStr(env, "Unable to create application data");
+        throwSSLExceptionStr(env, "Unable to retrieve application data");
         SSL_clear(ssl);
         JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake appData => 0", ssl);
         return 0;
     }
 
-    SSL_set_app_data(ssl, reinterpret_cast<char*>(appData));
-    JNI_TRACE("ssl=%p AppData::create => %p", ssl, appData);
-
     if (client_mode) {
         SSL_set_connect_state(ssl);
     } else {
@@ -7185,7 +8351,8 @@
                 return 0;
             }
             if (selectResult == -1) {
-                throwSSLExceptionWithSslErrors(env, ssl, SSL_ERROR_SYSCALL, "handshake error");
+                throwSSLExceptionWithSslErrors(env, ssl, SSL_ERROR_SYSCALL, "handshake error",
+                        throwSSLHandshakeExceptionStr);
                 SSL_clear(ssl);
                 JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake selectResult == -1 => 0", ssl);
                 return 0;
@@ -7212,9 +8379,10 @@
          */
         int sslError = SSL_get_error(ssl, ret);
         if (sslError == SSL_ERROR_NONE || (sslError == SSL_ERROR_SYSCALL && errno == 0)) {
-            throwSSLExceptionStr(env, "Connection closed by peer");
+            throwSSLHandshakeExceptionStr(env, "Connection closed by peer");
         } else {
-            throwSSLExceptionWithSslErrors(env, ssl, sslError, "SSL handshake terminated");
+            throwSSLExceptionWithSslErrors(env, ssl, sslError, "SSL handshake terminated",
+                    throwSSLHandshakeExceptionStr);
         }
         SSL_clear(ssl);
         JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake clean error => 0", ssl);
@@ -7228,7 +8396,8 @@
          * at this point.
          */
         int sslError = SSL_get_error(ssl, ret);
-        throwSSLExceptionWithSslErrors(env, ssl, sslError, "SSL handshake aborted");
+        throwSSLExceptionWithSslErrors(env, ssl, sslError, "SSL handshake aborted",
+                throwSSLHandshakeExceptionStr);
         SSL_clear(ssl);
         JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake unclean error => 0", ssl);
         return 0;
@@ -7355,18 +8524,6 @@
     return refArray;
 }
 
-/**
- * Helper function which does the actual reading. The Java layer guarantees that
- * at most one thread will enter this function at any given time.
- *
- * @param ssl non-null; the SSL context
- * @param buf non-null; buffer to read into
- * @param len length of the buffer, in bytes
- * @param sslReturnCode original SSL return code
- * @param sslErrorCode filled in with the SSL error code in case of error
- * @return number of bytes read on success, -1 if the connection was
- * cleanly shut down, or THROW_SSLEXCEPTION if an exception should be thrown.
- */
 static int sslRead(JNIEnv* env, SSL* ssl, jobject fdObject, jobject shc, char* buf, jint len,
                    int* sslReturnCode, int* sslErrorCode, int read_timeout_millis) {
     JNI_TRACE("ssl=%p sslRead buf=%p len=%d", ssl, buf, len);
@@ -7376,9 +8533,11 @@
         return 0;
     }
 
-    BIO* bio = SSL_get_rbio(ssl);
+    BIO* rbio = SSL_get_rbio(ssl);
+    BIO* wbio = SSL_get_wbio(ssl);
 
     AppData* appData = toAppData(ssl);
+    JNI_TRACE("ssl=%p sslRead appData=%p", ssl, appData);
     if (appData == NULL) {
         return THROW_SSLEXCEPTION;
     }
@@ -7390,7 +8549,15 @@
             return -1;
         }
 
-        unsigned int bytesMoved = BIO_number_read(bio) + BIO_number_written(bio);
+        if (!SSL_is_init_finished(ssl) && !SSL_cutthrough_complete(ssl) &&
+               !SSL_renegotiate_pending(ssl)) {
+            JNI_TRACE("ssl=%p sslRead => init is not finished (state=0x%x)", ssl,
+                    SSL_get_state(ssl));
+            MUTEX_UNLOCK(appData->mutex);
+            return THROW_SSLEXCEPTION;
+        }
+
+        unsigned int bytesMoved = BIO_number_read(rbio) + BIO_number_written(wbio);
 
         if (!appData->setCallbackState(env, shc, fdObject, NULL, NULL)) {
             MUTEX_UNLOCK(appData->mutex);
@@ -7423,7 +8590,7 @@
         // If we have been successful in moving data around, check whether it
         // might make sense to wake up other blocked threads, so they can give
         // it a try, too.
-        if (BIO_number_read(bio) + BIO_number_written(bio) != bytesMoved
+        if (BIO_number_read(rbio) + BIO_number_written(wbio) != bytesMoved
                 && appData->waitingThreads > 0) {
             sslNotify(appData);
         }
@@ -7496,6 +8663,133 @@
     return -1;
 }
 
+static jint NativeCrypto_SSL_read_BIO(JNIEnv* env, jclass, jlong sslRef, jbyteArray destJava,
+        jint destOffset, jint destLength, jlong sourceBioRef, jlong sinkBioRef, jobject shc) {
+    SSL* ssl = to_SSL(env, sslRef, true);
+    BIO* rbio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(sourceBioRef));
+    BIO* wbio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(sinkBioRef));
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_read_BIO dest=%p sourceBio=%p sinkBio=%p shc=%p",
+              ssl, destJava, rbio, wbio, shc);
+    if (ssl == NULL) {
+        return 0;
+    }
+    if (rbio == NULL || wbio == NULL) {
+        jniThrowNullPointerException(env, "rbio == null || wbio == null");
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_read_BIO => rbio == null || wbio == null", ssl);
+        return -1;
+    }
+    if (shc == NULL) {
+        jniThrowNullPointerException(env, "sslHandshakeCallbacks == null");
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_read_BIO => sslHandshakeCallbacks == null", ssl);
+        return -1;
+    }
+
+    ScopedByteArrayRW dest(env, destJava);
+    if (dest.get() == NULL) {
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_read_BIO => threw exception", ssl);
+        return -1;
+    }
+    if (destOffset < 0 || destOffset > ssize_t(dest.size()) || destLength < 0
+            || destLength > (ssize_t) dest.size() - destOffset) {
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_read_BIO => destOffset=%d, destLength=%d, size=%zd",
+                  destOffset, destLength, dest.size());
+        jniThrowException(env, "java/lang/ArrayIndexOutOfBoundsException", NULL);
+        return -1;
+    }
+
+    AppData* appData = toAppData(ssl);
+    if (appData == NULL) {
+        throwSSLExceptionStr(env, "Unable to retrieve application data");
+        SSL_clear(ssl);
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_read_BIO => appData == NULL", ssl);
+        return -1;
+    }
+
+    errno = 0;
+
+    if (MUTEX_LOCK(appData->mutex) == -1) {
+        return -1;
+    }
+
+    if (!appData->setCallbackState(env, shc, NULL, NULL, NULL)) {
+        MUTEX_UNLOCK(appData->mutex);
+        throwSSLExceptionStr(env, "Unable to set callback state");
+        SSL_clear(ssl);
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_read_BIO => set callback state failed", ssl);
+        return -1;
+    }
+
+    ScopedSslBio sslBio(ssl, rbio, wbio);
+
+    int result = SSL_read(ssl, dest.get() + destOffset, destLength);
+    appData->clearCallbackState();
+    // callbacks can happen if server requests renegotiation
+    if (env->ExceptionCheck()) {
+        SSL_clear(ssl);
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_read_BIO => threw exception", ssl);
+        return THROWN_EXCEPTION;
+    }
+    int sslError = SSL_ERROR_NONE;
+    if (result <= 0) {
+        sslError = SSL_get_error(ssl, result);
+    }
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_read_BIO SSL_read result=%d sslError=%d", ssl, result, sslError);
+#ifdef WITH_JNI_TRACE_DATA
+    for (int i = 0; i < result; i+= WITH_JNI_TRACE_DATA_CHUNK_SIZE) {
+        int n = result - i;
+        if (n > WITH_JNI_TRACE_DATA_CHUNK_SIZE) {
+            n = WITH_JNI_TRACE_DATA_CHUNK_SIZE;
+        }
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_read_BIO data: %d:\n%.*s", ssl, n, n, buf+i);
+    }
+#endif
+
+    MUTEX_UNLOCK(appData->mutex);
+
+    switch (sslError) {
+        // Successfully read at least one byte.
+        case SSL_ERROR_NONE:
+            break;
+
+        // Read zero bytes. End of stream reached.
+        case SSL_ERROR_ZERO_RETURN:
+            result = -1;
+            break;
+
+        // Need to wait for availability of underlying layer, then retry.
+        case SSL_ERROR_WANT_READ:
+        case SSL_ERROR_WANT_WRITE:
+            result = 0;
+            break;
+
+        // A problem occurred during a system call, but this is not
+        // necessarily an error.
+        case SSL_ERROR_SYSCALL: {
+            // Connection closed without proper shutdown. Tell caller we
+            // have reached end-of-stream.
+            if (result == 0) {
+                result = -1;
+                break;
+            } else if (errno == EINTR) {
+                // System call has been interrupted. Simply retry.
+                result = 0;
+                break;
+            }
+
+            // Note that for all other system call errors we fall through
+            // to the default case, which results in an Exception.
+        }
+
+        // Everything else is basically an error.
+        default: {
+            throwSSLExceptionWithSslErrors(env, ssl, sslError, "Read error");
+            return -1;
+        }
+    }
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_read_BIO => %d", ssl, result);
+    return result;
+}
+
 /**
  * OpenSSL read function (2): read into buffer at offset n chunks.
  * Returns 1 (success) or value <= 0 (failure).
@@ -7557,18 +8851,6 @@
     return result;
 }
 
-/**
- * Helper function which does the actual writing. The Java layer guarantees that
- * at most one thread will enter this function at any given time.
- *
- * @param ssl non-null; the SSL context
- * @param buf non-null; buffer to write
- * @param len length of the buffer, in bytes
- * @param sslReturnCode original SSL return code
- * @param sslErrorCode filled in with the SSL error code in case of error
- * @return number of bytes read on success, -1 if the connection was
- * cleanly shut down, or THROW_SSLEXCEPTION if an exception should be thrown.
- */
 static int sslWrite(JNIEnv* env, SSL* ssl, jobject fdObject, jobject shc, const char* buf, jint len,
                     int* sslReturnCode, int* sslErrorCode, int write_timeout_millis) {
     JNI_TRACE("ssl=%p sslWrite buf=%p len=%d write_timeout_millis=%d",
@@ -7579,9 +8861,11 @@
         return 0;
     }
 
-    BIO* bio = SSL_get_wbio(ssl);
+    BIO* rbio = SSL_get_rbio(ssl);
+    BIO* wbio = SSL_get_wbio(ssl);
 
     AppData* appData = toAppData(ssl);
+    JNI_TRACE("ssl=%p sslWrite appData=%p", ssl, appData);
     if (appData == NULL) {
         return THROW_SSLEXCEPTION;
     }
@@ -7595,7 +8879,15 @@
             return -1;
         }
 
-        unsigned int bytesMoved = BIO_number_read(bio) + BIO_number_written(bio);
+        if (!SSL_is_init_finished(ssl) && !SSL_cutthrough_complete(ssl) &&
+               !SSL_renegotiate_pending(ssl)) {
+            JNI_TRACE("ssl=%p sslWrite => init is not finished (state=0x%x)", ssl,
+                    SSL_get_state(ssl));
+            MUTEX_UNLOCK(appData->mutex);
+            return THROW_SSLEXCEPTION;
+        }
+
+        unsigned int bytesMoved = BIO_number_read(rbio) + BIO_number_written(wbio);
 
         if (!appData->setCallbackState(env, shc, fdObject, NULL, NULL)) {
             MUTEX_UNLOCK(appData->mutex);
@@ -7630,7 +8922,7 @@
         // If we have been successful in moving data around, check whether it
         // might make sense to wake up other blocked threads, so they can give
         // it a try, too.
-        if (BIO_number_read(bio) + BIO_number_written(bio) != bytesMoved
+        if (BIO_number_read(rbio) + BIO_number_written(wbio) != bytesMoved
                 && appData->waitingThreads > 0) {
             sslNotify(appData);
         }
@@ -7711,6 +9003,127 @@
 /**
  * OpenSSL write function (2): write into buffer at offset n chunks.
  */
+static int NativeCrypto_SSL_write_BIO(JNIEnv* env, jclass, jlong sslRef, jbyteArray sourceJava, jint len,
+        jlong sinkBioRef, jobject shc) {
+    SSL* ssl = to_SSL(env, sslRef, true);
+    BIO* wbio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(sinkBioRef));
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_write_BIO source=%p len=%d wbio=%p shc=%p",
+              ssl, sourceJava, len, wbio, shc);
+    if (ssl == NULL) {
+        return -1;
+    }
+    if (wbio == NULL) {
+        jniThrowNullPointerException(env, "wbio == null");
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_write_BIO => wbio == null", ssl);
+        return -1;
+    }
+    if (shc == NULL) {
+        jniThrowNullPointerException(env, "sslHandshakeCallbacks == null");
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_write_BIO => sslHandshakeCallbacks == null", ssl);
+        return -1;
+    }
+
+    AppData* appData = toAppData(ssl);
+    if (appData == NULL) {
+        throwSSLExceptionStr(env, "Unable to retrieve application data");
+        SSL_clear(ssl);
+        freeOpenSslErrorState();
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_write_BIO appData => NULL", ssl);
+        return -1;
+    }
+
+    errno = 0;
+
+    if (MUTEX_LOCK(appData->mutex) == -1) {
+        return 0;
+    }
+
+    if (!appData->setCallbackState(env, shc, NULL, NULL, NULL)) {
+        MUTEX_UNLOCK(appData->mutex);
+        throwSSLExceptionStr(env, "Unable to set appdata callback");
+        SSL_clear(ssl);
+        freeOpenSslErrorState();
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_write_BIO => appData can't set callback", ssl);
+        return -1;
+    }
+
+    ScopedByteArrayRO source(env, sourceJava);
+    if (source.get() == NULL) {
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_write_BIO => threw exception", ssl);
+        return -1;
+    }
+
+    Unique_BIO nullBio(BIO_new(BIO_s_null()));
+    ScopedSslBio sslBio(ssl, nullBio.get(), wbio);
+
+    int result = SSL_write(ssl, reinterpret_cast<const char*>(source.get()), len);
+    appData->clearCallbackState();
+    // callbacks can happen if server requests renegotiation
+    if (env->ExceptionCheck()) {
+        SSL_clear(ssl);
+        freeOpenSslErrorState();
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_write_BIO exception => exception pending (reneg)", ssl);
+        return -1;
+    }
+    int sslError = SSL_ERROR_NONE;
+    if (result <= 0) {
+        sslError = SSL_get_error(ssl, result);
+        freeOpenSslErrorState();
+    }
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_write_BIO SSL_write result=%d sslError=%d left=%d",
+              ssl, result, sslError, ssl->s3->wbuf.left);
+#ifdef WITH_JNI_TRACE_DATA
+    for (int i = 0; i < result; i+= WITH_JNI_TRACE_DATA_CHUNK_SIZE) {
+        int n = result - i;
+        if (n > WITH_JNI_TRACE_DATA_CHUNK_SIZE) {
+            n = WITH_JNI_TRACE_DATA_CHUNK_SIZE;
+        }
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_write_BIO data: %d:\n%.*s", ssl, n, n, buf+i);
+    }
+#endif
+
+    MUTEX_UNLOCK(appData->mutex);
+
+    switch (sslError) {
+        case SSL_ERROR_NONE:
+            return result;
+
+        // Wrote zero bytes. End of stream reached.
+        case SSL_ERROR_ZERO_RETURN:
+            return -1;
+
+        case SSL_ERROR_WANT_READ:
+        case SSL_ERROR_WANT_WRITE:
+            return 0;
+
+        case SSL_ERROR_SYSCALL: {
+            // Connection closed without proper shutdown. Tell caller we
+            // have reached end-of-stream.
+            if (result == 0) {
+                return -1;
+            }
+
+            // System call has been interrupted. Simply retry.
+            if (errno == EINTR) {
+                return 0;
+            }
+
+            // Note that for all other system call errors we fall through
+            // to the default case, which results in an Exception.
+        }
+
+        // Everything else is basically an error.
+        default: {
+            throwSSLExceptionWithSslErrors(env, ssl, sslError, "Write error");
+            break;
+        }
+    }
+    return -1;
+}
+
+/**
+ * OpenSSL write function (2): write into buffer at offset n chunks.
+ */
 static void NativeCrypto_SSL_write(JNIEnv* env, jclass, jlong ssl_address, jobject fdObject,
                                    jobject shc, jbyteArray b, jint offset, jint len, jint write_timeout_millis)
 {
@@ -7863,6 +9276,94 @@
 }
 
 /**
+ * OpenSSL close SSL socket function.
+ */
+static void NativeCrypto_SSL_shutdown_BIO(JNIEnv* env, jclass, jlong ssl_address, jlong rbioRef,
+        jlong wbioRef, jobject shc) {
+    SSL* ssl = to_SSL(env, ssl_address, false);
+    BIO* rbio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(rbioRef));
+    BIO* wbio = reinterpret_cast<BIO*>(static_cast<uintptr_t>(wbioRef));
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_shutdown rbio=%p wbio=%p shc=%p", ssl, rbio, wbio, shc);
+    if (ssl == NULL) {
+        return;
+    }
+    if (rbio == NULL || wbio == NULL) {
+        jniThrowNullPointerException(env, "rbio == null || wbio == null");
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_shutdown => rbio == null || wbio == null", ssl);
+        return;
+    }
+    if (shc == NULL) {
+        jniThrowNullPointerException(env, "sslHandshakeCallbacks == null");
+        JNI_TRACE("ssl=%p NativeCrypto_SSL_shutdown => sslHandshakeCallbacks == null", ssl);
+        return;
+    }
+
+    AppData* appData = toAppData(ssl);
+    if (appData != NULL) {
+        if (!appData->setCallbackState(env, shc, NULL, NULL, NULL)) {
+            // SocketException thrown by NetFd.isClosed
+            SSL_clear(ssl);
+            freeOpenSslErrorState();
+            return;
+        }
+
+        ScopedSslBio scopedBio(ssl, rbio, wbio);
+
+        int ret = SSL_shutdown(ssl);
+        appData->clearCallbackState();
+        // callbacks can happen if server requests renegotiation
+        if (env->ExceptionCheck()) {
+            SSL_clear(ssl);
+            JNI_TRACE("ssl=%p NativeCrypto_SSL_shutdown => exception", ssl);
+            return;
+        }
+        switch (ret) {
+            case 0:
+                /*
+                 * Shutdown was not successful (yet), but there also
+                 * is no error. Since we can't know whether the remote
+                 * server is actually still there, and we don't want to
+                 * get stuck forever in a second SSL_shutdown() call, we
+                 * simply return. This is not security a problem as long
+                 * as we close the underlying socket, which we actually
+                 * do, because that's where we are just coming from.
+                 */
+                break;
+            case 1:
+                /*
+                 * Shutdown was successful. We can safely return. Hooray!
+                 */
+                break;
+            default:
+                /*
+                 * Everything else is a real error condition. We should
+                 * let the Java layer know about this by throwing an
+                 * exception.
+                 */
+                int sslError = SSL_get_error(ssl, ret);
+                throwSSLExceptionWithSslErrors(env, ssl, sslError, "SSL shutdown failed");
+                break;
+        }
+    }
+
+    SSL_clear(ssl);
+    freeOpenSslErrorState();
+}
+
+static jint NativeCrypto_SSL_get_shutdown(JNIEnv* env, jclass, jlong ssl_address) {
+    const SSL* ssl = to_SSL(env, ssl_address, true);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_shutdown", ssl);
+    if (ssl == NULL) {
+        jniThrowNullPointerException(env, "ssl == null");
+        return 0;
+    }
+
+    int status = SSL_get_shutdown(ssl);
+    JNI_TRACE("ssl=%p NativeCrypto_SSL_get_shutdown => %d", ssl, status);
+    return static_cast<jint>(status);
+}
+
+/**
  * public static native void SSL_free(long ssl);
  */
 static void NativeCrypto_SSL_free(JNIEnv* env, jclass, jlong ssl_address)
@@ -8027,6 +9528,7 @@
     NATIVE_METHOD(NativeCrypto, ENGINE_load_private_key, "(JLjava/lang/String;)J"),
     NATIVE_METHOD(NativeCrypto, ENGINE_get_id, "(J)Ljava/lang/String;"),
     NATIVE_METHOD(NativeCrypto, ENGINE_ctrl_cmd_string, "(JLjava/lang/String;Ljava/lang/String;I)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_PKEY_new_DH, "([B[B[B[B)J"),
     NATIVE_METHOD(NativeCrypto, EVP_PKEY_new_DSA, "([B[B[B[B[B)J"),
     NATIVE_METHOD(NativeCrypto, EVP_PKEY_new_RSA, "([B[B[B[B[B[B[B[B)J"),
     NATIVE_METHOD(NativeCrypto, EVP_PKEY_new_EC_KEY, "(JJ[B)J"),
@@ -8041,6 +9543,9 @@
     NATIVE_METHOD(NativeCrypto, d2i_PKCS8_PRIV_KEY_INFO, "([B)J"),
     NATIVE_METHOD(NativeCrypto, i2d_PUBKEY, "(J)[B"),
     NATIVE_METHOD(NativeCrypto, d2i_PUBKEY, "([B)J"),
+    NATIVE_METHOD(NativeCrypto, getRSAPrivateKeyWrapper, "(Ljava/security/interfaces/RSAPrivateKey;[B)J"),
+    NATIVE_METHOD(NativeCrypto, getDSAPrivateKeyWrapper, "(Ljava/security/interfaces/DSAPrivateKey;)J"),
+    NATIVE_METHOD(NativeCrypto, getECPrivateKeyWrapper, "(Ljava/security/interfaces/ECPrivateKey;J)J"),
     NATIVE_METHOD(NativeCrypto, RSA_generate_key_ex, "(I[B)J"),
     NATIVE_METHOD(NativeCrypto, RSA_size, "(J)I"),
     NATIVE_METHOD(NativeCrypto, RSA_private_encrypt, "(I[B[BJI)I"),
@@ -8051,6 +9556,10 @@
     NATIVE_METHOD(NativeCrypto, get_RSA_public_params, "(J)[[B"),
     NATIVE_METHOD(NativeCrypto, DSA_generate_key, "(I[B[B[B[B)J"),
     NATIVE_METHOD(NativeCrypto, get_DSA_params, "(J)[[B"),
+    NATIVE_METHOD(NativeCrypto, set_DSA_flag_nonce_from_hash, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, DH_generate_parameters_ex, "(IJ)J"),
+    NATIVE_METHOD(NativeCrypto, DH_generate_key, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, get_DH_params, "(J)[[B"),
     NATIVE_METHOD(NativeCrypto, EC_GROUP_new_by_curve_name, "(Ljava/lang/String;)J"),
     NATIVE_METHOD(NativeCrypto, EC_GROUP_new_curve, "(I[B[B[B)J"),
     NATIVE_METHOD(NativeCrypto, EC_GROUP_dup, "(J)J"),
@@ -8075,26 +9584,27 @@
     NATIVE_METHOD(NativeCrypto, EC_KEY_get0_group, "(J)J"),
     NATIVE_METHOD(NativeCrypto, EC_KEY_get_private_key, "(J)[B"),
     NATIVE_METHOD(NativeCrypto, EC_KEY_get_public_key, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, EC_KEY_set_nonce_from_hash, "(JZ)V"),
     NATIVE_METHOD(NativeCrypto, ECDH_compute_key, "([BIJJ)I"),
     NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_create, "()J"),
-    NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_init, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_init, "(L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLDigestContext;)V"),
     NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_destroy, "(J)V"),
-    NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_copy, "(J)J"),
-    NATIVE_METHOD(NativeCrypto, EVP_DigestFinal, "(J[BI)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_DigestInit, "(J)J"),
+    NATIVE_METHOD(NativeCrypto, EVP_MD_CTX_copy, "(L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLDigestContext;L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLDigestContext;)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_DigestInit, "(L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLDigestContext;J)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_DigestUpdate, "(L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLDigestContext;[BII)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_DigestFinal, "(L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLDigestContext;[BI)I"),
     NATIVE_METHOD(NativeCrypto, EVP_get_digestbyname, "(Ljava/lang/String;)J"),
     NATIVE_METHOD(NativeCrypto, EVP_MD_block_size, "(J)I"),
     NATIVE_METHOD(NativeCrypto, EVP_MD_size, "(J)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_DigestUpdate, "(J[BII)V"),
-    NATIVE_METHOD(NativeCrypto, EVP_SignInit, "(Ljava/lang/String;)J"),
-    NATIVE_METHOD(NativeCrypto, EVP_SignUpdate, "(J[BII)V"),
-    NATIVE_METHOD(NativeCrypto, EVP_SignFinal, "(J[BIJ)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_VerifyInit, "(Ljava/lang/String;)J"),
-    NATIVE_METHOD(NativeCrypto, EVP_VerifyUpdate, "(J[BII)V"),
-    NATIVE_METHOD(NativeCrypto, EVP_VerifyFinal, "(J[BIIJ)I"),
-    NATIVE_METHOD(NativeCrypto, EVP_DigestSignInit, "(JJJ)V"),
-    NATIVE_METHOD(NativeCrypto, EVP_DigestSignUpdate, "(J[B)V"),
-    NATIVE_METHOD(NativeCrypto, EVP_DigestSignFinal, "(J)[B"),
+    NATIVE_METHOD(NativeCrypto, EVP_SignInit, "(L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLDigestContext;J)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_SignUpdate, "(L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLDigestContext;[BII)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_SignFinal, "(L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLDigestContext;[BIJ)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_VerifyInit, "(L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLDigestContext;J)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_VerifyUpdate, "(L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLDigestContext;[BII)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_VerifyFinal, "(L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLDigestContext;[BIIJ)I"),
+    NATIVE_METHOD(NativeCrypto, EVP_DigestSignInit, "(L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLDigestContext;JJ)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_DigestSignUpdate, "(L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLDigestContext;[B)V"),
+    NATIVE_METHOD(NativeCrypto, EVP_DigestSignFinal, "(L" TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLDigestContext;)[B"),
     NATIVE_METHOD(NativeCrypto, EVP_get_cipherbyname, "(Ljava/lang/String;)J"),
     NATIVE_METHOD(NativeCrypto, EVP_CipherInit_ex, "(JJ[B[BZ)V"),
     NATIVE_METHOD(NativeCrypto, EVP_CipherUpdate, "(J[BI[BII)I"),
@@ -8116,7 +9626,7 @@
     NATIVE_METHOD(NativeCrypto, create_BIO_OutputStream, "(Ljava/io/OutputStream;)J"),
     NATIVE_METHOD(NativeCrypto, BIO_read, "(J[B)I"),
     NATIVE_METHOD(NativeCrypto, BIO_write, "(J[BII)V"),
-    NATIVE_METHOD(NativeCrypto, BIO_free, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, BIO_free_all, "(J)V"),
     NATIVE_METHOD(NativeCrypto, X509_NAME_print_ex, "(JJ)Ljava/lang/String;"),
     NATIVE_METHOD(NativeCrypto, d2i_X509_bio, "(J)J"),
     NATIVE_METHOD(NativeCrypto, d2i_X509, "([B)J"),
@@ -8202,23 +9712,33 @@
     NATIVE_METHOD(NativeCrypto, SSL_get_options, "(J)J"),
     NATIVE_METHOD(NativeCrypto, SSL_set_options, "(JJ)J"),
     NATIVE_METHOD(NativeCrypto, SSL_clear_options, "(JJ)J"),
+    NATIVE_METHOD(NativeCrypto, SSL_use_psk_identity_hint, "(JLjava/lang/String;)V"),
+    NATIVE_METHOD(NativeCrypto, set_SSL_psk_client_callback_enabled, "(JZ)V"),
+    NATIVE_METHOD(NativeCrypto, set_SSL_psk_server_callback_enabled, "(JZ)V"),
     NATIVE_METHOD(NativeCrypto, SSL_set_cipher_lists, "(J[Ljava/lang/String;)V"),
     NATIVE_METHOD(NativeCrypto, SSL_get_ciphers, "(J)[J"),
     NATIVE_METHOD(NativeCrypto, get_SSL_CIPHER_algorithm_auth, "(J)I"),
     NATIVE_METHOD(NativeCrypto, get_SSL_CIPHER_algorithm_mkey, "(J)I"),
+    NATIVE_METHOD(NativeCrypto, SSL_set_accept_state, "(J)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_set_connect_state, "(J)V"),
     NATIVE_METHOD(NativeCrypto, SSL_set_verify, "(JI)V"),
     NATIVE_METHOD(NativeCrypto, SSL_set_session, "(JJ)V"),
     NATIVE_METHOD(NativeCrypto, SSL_set_session_creation_enabled, "(JZ)V"),
     NATIVE_METHOD(NativeCrypto, SSL_set_tlsext_host_name, "(JLjava/lang/String;)V"),
     NATIVE_METHOD(NativeCrypto, SSL_get_servername, "(J)Ljava/lang/String;"),
     NATIVE_METHOD(NativeCrypto, SSL_do_handshake, "(J" FILE_DESCRIPTOR SSL_CALLBACKS "IZ[B[B)J"),
+    NATIVE_METHOD(NativeCrypto, SSL_do_handshake_bio, "(JJJ" SSL_CALLBACKS "Z[B[B)J"),
     NATIVE_METHOD(NativeCrypto, SSL_renegotiate, "(J)V"),
     NATIVE_METHOD(NativeCrypto, SSL_get_certificate, "(J)[J"),
     NATIVE_METHOD(NativeCrypto, SSL_get_peer_cert_chain, "(J)[J"),
     NATIVE_METHOD(NativeCrypto, SSL_read, "(J" FILE_DESCRIPTOR SSL_CALLBACKS "[BIII)I"),
+    NATIVE_METHOD(NativeCrypto, SSL_read_BIO, "(J[BIIJJ" SSL_CALLBACKS ")I"),
     NATIVE_METHOD(NativeCrypto, SSL_write, "(J" FILE_DESCRIPTOR SSL_CALLBACKS "[BIII)V"),
+    NATIVE_METHOD(NativeCrypto, SSL_write_BIO, "(J[BIJ" SSL_CALLBACKS ")I"),
     NATIVE_METHOD(NativeCrypto, SSL_interrupt, "(J)V"),
     NATIVE_METHOD(NativeCrypto, SSL_shutdown, "(J" FILE_DESCRIPTOR SSL_CALLBACKS ")V"),
+    NATIVE_METHOD(NativeCrypto, SSL_shutdown_BIO, "(JJJ" SSL_CALLBACKS ")V"),
+    NATIVE_METHOD(NativeCrypto, SSL_get_shutdown, "(J)I"),
     NATIVE_METHOD(NativeCrypto, SSL_free, "(J)V"),
     NATIVE_METHOD(NativeCrypto, SSL_SESSION_session_id, "(J)[B"),
     NATIVE_METHOD(NativeCrypto, SSL_SESSION_get_time, "(J)J"),
@@ -8230,31 +9750,73 @@
     NATIVE_METHOD(NativeCrypto, SSL_CTX_enable_npn, "(J)V"),
     NATIVE_METHOD(NativeCrypto, SSL_CTX_disable_npn, "(J)V"),
     NATIVE_METHOD(NativeCrypto, SSL_get_npn_negotiated_protocol, "(J)[B"),
-    NATIVE_METHOD(NativeCrypto, SSL_CTX_set_alpn_protos, "(J[B)I"),
+    NATIVE_METHOD(NativeCrypto, SSL_set_alpn_protos, "(J[B)I"),
     NATIVE_METHOD(NativeCrypto, SSL_get0_alpn_selected, "(J)[B"),
     NATIVE_METHOD(NativeCrypto, ERR_peek_last_error, "()J"),
 };
 
+static jclass getGlobalRefToClass(JNIEnv* env, const char* className) {
+    ScopedLocalRef<jclass> localClass(env, env->FindClass(className));
+    jclass globalRef = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get()));
+    if (globalRef == NULL) {
+        ALOGE("failed to find class %s", className);
+        abort();
+    }
+    return globalRef;
+}
+
+static jmethodID getStaticMethodRef(JNIEnv* env, jclass clazz, const char* name, const char* sig) {
+    jmethodID localMethod = env->GetStaticMethodID(clazz, name, sig);
+    if (localMethod == NULL) {
+        ALOGE("could not find static method %s", name);
+        abort();
+    }
+    return localMethod;
+}
+
+static jmethodID getMethodRef(JNIEnv* env, jclass clazz, const char* name, const char* sig) {
+    jmethodID localMethod = env->GetMethodID(clazz, name, sig);
+    if (localMethod == NULL) {
+        ALOGE("could not find method %s", name);
+        abort();
+    }
+    return localMethod;
+}
+
+static jfieldID getFieldRef(JNIEnv* env, jclass clazz, const char* name, const char* sig) {
+    jfieldID localField = env->GetFieldID(clazz, name, sig);
+    if (localField == NULL) {
+        ALOGE("could not find field %s", name);
+        abort();
+    }
+    return localField;
+}
+
 static void initialize_conscrypt(JNIEnv* env) {
     jniRegisterNativeMethods(env, TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/NativeCrypto",
                              sNativeCryptoMethods, NELEM(sNativeCryptoMethods));
 
-    ScopedLocalRef<jclass> localClass(env,
-            env->FindClass(TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLBIOInputStream"));
-    openSslOutputStreamClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get()));
-    if (openSslOutputStreamClass == NULL) {
-        ALOGE("failed to find class OpenSSLBIOInputStream");
-        abort();
-    }
+    cryptoUpcallsClass = getGlobalRefToClass(env,
+            TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/CryptoUpcalls");
+    openSslNativeReferenceClass = getGlobalRefToClass(env,
+            TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLNativeReference");
+    openSslInputStreamClass = getGlobalRefToClass(env,
+            TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLBIOInputStream");
 
-    calendar_setMethod = env->GetMethodID(calendarClass, "set", "(IIIIII)V");
-    inputStream_readMethod = env->GetMethodID(inputStreamClass, "read", "([B)I");
+    openSslNativeReference_context = getFieldRef(env, openSslNativeReferenceClass, "context", "J");
+
+    calendar_setMethod = getMethodRef(env, calendarClass, "set", "(IIIIII)V");
+    inputStream_readMethod = getMethodRef(env, inputStreamClass, "read", "([B)I");
     integer_valueOfMethod = env->GetStaticMethodID(integerClass, "valueOf",
             "(I)Ljava/lang/Integer;");
-    openSslInputStream_readLineMethod = env->GetMethodID(openSslOutputStreamClass, "gets",
+    openSslInputStream_readLineMethod = getMethodRef(env, openSslInputStreamClass, "gets",
             "([B)I");
-    outputStream_writeMethod = env->GetMethodID(outputStreamClass, "write", "([B)V");
-    outputStream_flushMethod = env->GetMethodID(outputStreamClass, "flush", "()V");
+    outputStream_writeMethod = getMethodRef(env, outputStreamClass, "write", "([B)V");
+    outputStream_flushMethod = getMethodRef(env, outputStreamClass, "flush", "()V");
+
+#ifdef CONSCRYPT_UNBUNDLED
+    findAsynchronousCloseMonitorFuncs();
+#endif
 }
 
 static jclass findClass(JNIEnv* env, const char* name) {
@@ -8267,9 +9829,14 @@
     return result;
 }
 
+#ifdef STATIC_LIB
+// Give client libs everything they need to initialize our JNI
+int libconscrypt_JNI_OnLoad(JavaVM *vm, void*) {
+#else
 // Use JNI_OnLoad for when we're standalone
 int JNI_OnLoad(JavaVM *vm, void*) {
     JNI_TRACE("JNI_OnLoad NativeCrypto");
+#endif
     gJavaVM = vm;
 
     JNIEnv *env;
diff --git a/src/main/java/org/conscrypt/CertPinManager.java b/src/platform/java/org/conscrypt/CertPinManager.java
similarity index 100%
rename from src/main/java/org/conscrypt/CertPinManager.java
rename to src/platform/java/org/conscrypt/CertPinManager.java
diff --git a/src/main/java/org/conscrypt/JSSEProvider.java b/src/platform/java/org/conscrypt/JSSEProvider.java
similarity index 100%
rename from src/main/java/org/conscrypt/JSSEProvider.java
rename to src/platform/java/org/conscrypt/JSSEProvider.java
diff --git a/src/main/java/org/conscrypt/PinEntryException.java b/src/platform/java/org/conscrypt/PinEntryException.java
similarity index 100%
rename from src/main/java/org/conscrypt/PinEntryException.java
rename to src/platform/java/org/conscrypt/PinEntryException.java
diff --git a/src/main/java/org/conscrypt/PinFailureLogger.java b/src/platform/java/org/conscrypt/PinFailureLogger.java
similarity index 100%
rename from src/main/java/org/conscrypt/PinFailureLogger.java
rename to src/platform/java/org/conscrypt/PinFailureLogger.java
diff --git a/src/main/java/org/conscrypt/PinListEntry.java b/src/platform/java/org/conscrypt/PinListEntry.java
similarity index 85%
rename from src/main/java/org/conscrypt/PinListEntry.java
rename to src/platform/java/org/conscrypt/PinListEntry.java
index 0b24dc7..d908dcf 100644
--- a/src/main/java/org/conscrypt/PinListEntry.java
+++ b/src/platform/java/org/conscrypt/PinListEntry.java
@@ -89,18 +89,22 @@
      *
      * <p>If enforcing is on and the given {@code chain} does not include the
      * expected pinned certificate, this will return {@code false} indicating
-     * the chain is not valid. Otherwise this will return {@code true}
-     * indicating the {@code chain} is valid.
+     * the chain is not valid unless the {@code chain} chains up to an user-installed
+     * CA cert. Otherwise this will return {@code true} indicating the {@code chain}
+     * is valid.
      */
     public boolean isChainValid(List<X509Certificate> chain) {
-        for (X509Certificate cert : chain) {
-            String fingerprint = getFingerprint(cert);
-            if (pinnedFingerprints.contains(fingerprint)) {
-                return true;
+        boolean containsUserCert = chainContainsUserCert(chain);
+        if (!containsUserCert) {
+            for (X509Certificate cert : chain) {
+                String fingerprint = getFingerprint(cert);
+                if (pinnedFingerprints.contains(fingerprint)) {
+                    return true;
+                }
             }
         }
-        logPinFailure(chain);
-        return !enforcing;
+        logPinFailure(chain, containsUserCert);
+        return !enforcing || containsUserCert;
     }
 
     private static String getFingerprint(X509Certificate cert) {
@@ -146,8 +150,8 @@
         return false;
     }
 
-    private void logPinFailure(List<X509Certificate> chain) {
-        PinFailureLogger.log(cn, chainContainsUserCert(chain), enforcing, chain);
+    private void logPinFailure(List<X509Certificate> chain, boolean containsUserCert) {
+        PinFailureLogger.log(cn, containsUserCert, enforcing, chain);
     }
 }
 
diff --git a/src/main/java/org/conscrypt/PinManagerException.java b/src/platform/java/org/conscrypt/PinManagerException.java
similarity index 100%
rename from src/main/java/org/conscrypt/PinManagerException.java
rename to src/platform/java/org/conscrypt/PinManagerException.java
diff --git a/src/platform/java/org/conscrypt/Platform.java b/src/platform/java/org/conscrypt/Platform.java
new file mode 100644
index 0000000..668914b
--- /dev/null
+++ b/src/platform/java/org/conscrypt/Platform.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 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 static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_SNDTIMEO;
+
+import org.apache.harmony.security.utils.AlgNameMapper;
+import org.apache.harmony.security.utils.AlgNameMapperSource;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructTimeval;
+import java.io.FileDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.SocketImpl;
+import java.security.PrivateKey;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.security.spec.ECParameterSpec;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.X509TrustManager;
+
+class Platform {
+    private static class NoPreloadHolder {
+        public static final Platform MAPPER = new Platform();
+    }
+
+    /**
+     * Runs all the setup for the platform that only needs to run once.
+     */
+    public static void setup() {
+        NoPreloadHolder.MAPPER.ping();
+    }
+
+    /**
+     * Just a placeholder to make sure the class is initialized.
+     */
+    private void ping() {
+    }
+
+    private Platform() {
+        AlgNameMapper.setSource(new OpenSSLMapper());
+    }
+
+    private static class OpenSSLMapper implements AlgNameMapperSource {
+        @Override
+        public String mapNameToOid(String algName) {
+            return NativeCrypto.OBJ_txt2nid_oid(algName);
+        }
+
+        @Override
+        public String mapOidToName(String oid) {
+            return NativeCrypto.OBJ_txt2nid_longName(oid);
+        }
+    }
+
+    public static FileDescriptor getFileDescriptor(Socket s) {
+        return s.getFileDescriptor$();
+    }
+
+    public static FileDescriptor getFileDescriptorFromSSLSocket(OpenSSLSocketImpl openSSLSocketImpl) {
+        try {
+            Field f_impl = Socket.class.getDeclaredField("impl");
+            f_impl.setAccessible(true);
+            Object socketImpl = f_impl.get(openSSLSocketImpl);
+            Field f_fd = SocketImpl.class.getDeclaredField("fd");
+            f_fd.setAccessible(true);
+            return (FileDescriptor) f_fd.get(socketImpl);
+        } catch (Exception e) {
+            throw new RuntimeException("Can't get FileDescriptor from socket", e);
+        }
+    }
+
+    public static String getCurveName(ECParameterSpec spec) {
+        return spec.getCurveName();
+    }
+
+    public static void setCurveName(ECParameterSpec spec, String curveName) {
+        spec.setCurveName(curveName);
+    }
+
+    public static void setSocketTimeout(Socket s, long timeoutMillis) throws SocketException {
+        StructTimeval tv = StructTimeval.fromMillis(timeoutMillis);
+        try {
+            Os.setsockoptTimeval(s.getFileDescriptor$(), SOL_SOCKET, SO_SNDTIMEO, tv);
+        } catch (ErrnoException errnoException) {
+            throw errnoException.rethrowAsSocketException();
+        }
+    }
+
+    public static void checkServerTrusted(X509TrustManager x509tm, X509Certificate[] chain,
+            String authType, String host) throws CertificateException {
+        if (x509tm instanceof TrustManagerImpl) {
+            TrustManagerImpl tm = (TrustManagerImpl) x509tm;
+            tm.checkServerTrusted(chain, authType, host);
+        } else {
+            x509tm.checkServerTrusted(chain, authType);
+        }
+    }
+
+    /**
+     * Wraps an old AndroidOpenSSL key instance. This is not needed on platform
+     * builds since we didn't backport, so return null.
+     */
+    public static OpenSSLKey wrapRsaKey(PrivateKey key) {
+        return null;
+    }
+
+    /**
+     * Logs to the system EventLog system.
+     */
+    public static void logEvent(String message) {
+        try {
+            Class processClass = Class.forName("android.os.Process");
+            Object processInstance = processClass.newInstance();
+            Method myUidMethod = processClass.getMethod("myUid", (Class[]) null);
+            int uid = (Integer) myUidMethod.invoke(processInstance);
+
+            Class eventLogClass = Class.forName("android.util.EventLog");
+            Object eventLogInstance = eventLogClass.newInstance();
+            Method writeEventMethod = eventLogClass.getMethod("writeEvent",
+                    new Class[] { Integer.TYPE, Object[].class });
+            writeEventMethod.invoke(eventLogInstance, 0x534e4554 /* SNET */,
+                    new Object[] { "conscrypt", uid, message });
+        } catch (Exception e) {
+            // Do not log and fail silently
+        }
+    }
+}
diff --git a/src/main/java/org/conscrypt/TrustManagerFactoryImpl.java b/src/platform/java/org/conscrypt/TrustManagerFactoryImpl.java
similarity index 100%
rename from src/main/java/org/conscrypt/TrustManagerFactoryImpl.java
rename to src/platform/java/org/conscrypt/TrustManagerFactoryImpl.java
diff --git a/src/main/java/org/conscrypt/TrustManagerImpl.java b/src/platform/java/org/conscrypt/TrustManagerImpl.java
similarity index 92%
rename from src/main/java/org/conscrypt/TrustManagerImpl.java
rename to src/platform/java/org/conscrypt/TrustManagerImpl.java
index 2537d1a..2af84d6 100644
--- a/src/main/java/org/conscrypt/TrustManagerImpl.java
+++ b/src/platform/java/org/conscrypt/TrustManagerImpl.java
@@ -17,6 +17,7 @@
 
 package org.conscrypt;
 
+import java.net.Socket;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
@@ -39,6 +40,12 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
 import javax.net.ssl.X509TrustManager;
 
 /**
@@ -90,7 +97,7 @@
     /**
      * Creates X509TrustManager based on a keystore
      *
-     * @param ks
+     * @param keyStore
      */
     public TrustManagerImpl(KeyStore keyStore) {
         this(keyStore, null);
@@ -100,6 +107,14 @@
      * For testing only
      */
     public TrustManagerImpl(KeyStore keyStore, CertPinManager manager) {
+        this(keyStore, manager, null);
+    }
+
+    /**
+     * For testing only.
+     */
+    public TrustManagerImpl(KeyStore keyStore, CertPinManager manager,
+                            TrustedCertificateStore certStore) {
         CertPathValidator validatorLocal = null;
         CertificateFactory factoryLocal = null;
         KeyStore rootKeyStoreLocal = null;
@@ -114,12 +129,13 @@
             // if we have an AndroidCAStore, we will lazily load CAs
             if ("AndroidCAStore".equals(keyStore.getType())) {
                 rootKeyStoreLocal = keyStore;
-                trustedCertificateStoreLocal = new TrustedCertificateStore();
+                trustedCertificateStoreLocal =
+                    (certStore != null) ? certStore : new TrustedCertificateStore();
                 acceptedIssuersLocal = null;
                 trustedCertificateIndexLocal = new TrustedCertificateIndex();
             } else {
                 rootKeyStoreLocal = null;
-                trustedCertificateStoreLocal = null;
+                trustedCertificateStoreLocal = certStore;
                 acceptedIssuersLocal = acceptedIssuers(keyStore);
                 trustedCertificateIndexLocal
                         = new TrustedCertificateIndex(trustAnchors(acceptedIssuersLocal));
@@ -203,6 +219,25 @@
         return checkTrusted(chain, authType, host, false);
     }
 
+    public boolean isUserAddedCertificate(X509Certificate cert) {
+        if (trustedCertificateStore == null) {
+            return false;
+        } else {
+            return trustedCertificateStore.isUserAddedCertificate(cert);
+        }
+    }
+
+    /**
+     * Validates whether a server is trusted. If session is given and non-null
+     * it also checks if chain is pinned appropriately for that peer host. If
+     * null, it does not check for pinned certs. The return value is a list of
+     * the certificates used for making the trust decision.
+     */
+    public List<X509Certificate> checkServerTrusted(X509Certificate[] chain, String authType,
+            SSLSession session) throws CertificateException {
+        return checkTrusted(chain, authType, session.getPeerHost(), false);
+    }
+
     public void handleTrustStorageUpdate() {
         if (acceptedIssuers == null) {
             trustedCertificateIndex.reset();
@@ -527,10 +562,11 @@
         // probe KeyStore for a cert. AndroidCAStore stores its
         // contents hashed by cert subject on the filesystem to make
         // this faster than scanning all key store entries.
-        if (trustedCertificateStore.isTrustAnchor(cert)) {
+        X509Certificate systemCert = trustedCertificateStore.getTrustAnchor(cert);
+        if (systemCert != null) {
             // add new TrustAnchor to params index to avoid
             // checking filesystem next time around.
-            return trustedCertificateIndex.index(cert);
+            return trustedCertificateIndex.index(systemCert);
         }
         return null;
     }
diff --git a/src/main/java/org/conscrypt/TrustedCertificateIndex.java b/src/platform/java/org/conscrypt/TrustedCertificateIndex.java
similarity index 100%
rename from src/main/java/org/conscrypt/TrustedCertificateIndex.java
rename to src/platform/java/org/conscrypt/TrustedCertificateIndex.java
diff --git a/src/main/java/org/conscrypt/TrustedCertificateKeyStoreSpi.java b/src/platform/java/org/conscrypt/TrustedCertificateKeyStoreSpi.java
similarity index 100%
rename from src/main/java/org/conscrypt/TrustedCertificateKeyStoreSpi.java
rename to src/platform/java/org/conscrypt/TrustedCertificateKeyStoreSpi.java
diff --git a/src/main/java/org/conscrypt/TrustedCertificateStore.java b/src/platform/java/org/conscrypt/TrustedCertificateStore.java
similarity index 93%
rename from src/main/java/org/conscrypt/TrustedCertificateStore.java
rename to src/platform/java/org/conscrypt/TrustedCertificateStore.java
index c8ba5cd..7287b75 100644
--- a/src/main/java/org/conscrypt/TrustedCertificateStore.java
+++ b/src/platform/java/org/conscrypt/TrustedCertificateStore.java
@@ -90,16 +90,15 @@
         return alias.startsWith(PREFIX_USER);
     }
 
-    private static final File CA_CERTS_DIR_SYSTEM;
-    private static final File CA_CERTS_DIR_ADDED;
-    private static final File CA_CERTS_DIR_DELETED;
+    private static File defaultCaCertsSystemDir;
+    private static File defaultCaCertsAddedDir;
+    private static File defaultCaCertsDeletedDir;
     private static final CertificateFactory CERT_FACTORY;
     static {
         String ANDROID_ROOT = System.getenv("ANDROID_ROOT");
         String ANDROID_DATA = System.getenv("ANDROID_DATA");
-        CA_CERTS_DIR_SYSTEM = new File(ANDROID_ROOT + "/etc/security/cacerts");
-        CA_CERTS_DIR_ADDED = new File(ANDROID_DATA + "/misc/keychain/cacerts-added");
-        CA_CERTS_DIR_DELETED = new File(ANDROID_DATA + "/misc/keychain/cacerts-removed");
+        defaultCaCertsSystemDir = new File(ANDROID_ROOT + "/etc/security/cacerts");
+        setDefaultUserDirectory(new File(ANDROID_DATA + "/misc/keychain"));
 
         try {
             CERT_FACTORY = CertificateFactory.getInstance("X509");
@@ -108,12 +107,17 @@
         }
     }
 
+    public static void setDefaultUserDirectory(File root) {
+        defaultCaCertsAddedDir = new File(root, "cacerts-added");
+        defaultCaCertsDeletedDir = new File(root, "cacerts-removed");
+    }
+
     private final File systemDir;
     private final File addedDir;
     private final File deletedDir;
 
     public TrustedCertificateStore() {
-        this(CA_CERTS_DIR_SYSTEM, CA_CERTS_DIR_ADDED, CA_CERTS_DIR_DELETED);
+        this(defaultCaCertsSystemDir, defaultCaCertsAddedDir, defaultCaCertsDeletedDir);
     }
 
     public TrustedCertificateStore(File systemDir, File addedDir, File deletedDir) {
@@ -271,6 +275,10 @@
     }
 
     public String getCertificateAlias(Certificate c) {
+        return getCertificateAlias(c, false);
+    }
+
+    public String getCertificateAlias(Certificate c, boolean includeDeletedSystem) {
         if (c == null || !(c instanceof X509Certificate)) {
             return null;
         }
@@ -279,7 +287,7 @@
         if (user.exists()) {
             return PREFIX_USER + user.getName();
         }
-        if (isDeletedSystemCertificate(x)) {
+        if (!includeDeletedSystem && isDeletedSystemCertificate(x)) {
             return null;
         }
         File system = getCertificateFile(systemDir, x);
@@ -323,7 +331,7 @@
      * with other differences (for example when switching signature
      * from md2WithRSAEncryption to SHA1withRSA)
      */
-    public boolean isTrustAnchor(final X509Certificate c) {
+    public X509Certificate getTrustAnchor(final X509Certificate c) {
         // compare X509Certificate.getPublicKey values
         CertSelector selector = new CertSelector() {
             @Override
@@ -331,18 +339,21 @@
                 return ca.getPublicKey().equals(c.getPublicKey());
             }
         };
-        boolean user = findCert(addedDir,
-                                c.getSubjectX500Principal(),
-                                selector,
-                                Boolean.class);
-        if (user) {
-            return true;
+        X509Certificate user = findCert(addedDir,
+                                        c.getSubjectX500Principal(),
+                                        selector,
+                                        X509Certificate.class);
+        if (user != null) {
+            return user;
         }
         X509Certificate system = findCert(systemDir,
                                           c.getSubjectX500Principal(),
                                           selector,
                                           X509Certificate.class);
-        return system != null && !isDeletedSystemCertificate(system);
+        if (system != null && !isDeletedSystemCertificate(system)) {
+            return system;
+        }
+        return null;
     }
 
     /**
diff --git a/src/test/java/org/conscrypt/CipherSuiteTest.java b/src/test/java/org/conscrypt/CipherSuiteTest.java
deleted file mode 100644
index a6338f9..0000000
--- a/src/test/java/org/conscrypt/CipherSuiteTest.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2010 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.MessageDigest;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-import junit.framework.TestCase;
-import libcore.java.security.StandardNames;
-
-public class CipherSuiteTest extends TestCase {
-    public void test_getByName() throws Exception {
-        for (String name : StandardNames.CIPHER_SUITES) {
-            if (name.equals(StandardNames.CIPHER_SUITE_SECURE_RENEGOTIATION)) {
-                assertNull(CipherSuite.getByName(name));
-            } else if ((name.endsWith("_SHA256")) || (name.endsWith("_SHA384"))) {
-                // TLSv1.2 cipher suites not supported by the SSLEngine implementation which is the
-                // only user of the CipherSuite class.
-                assertNull(CipherSuite.getByName(name));
-            } else {
-                test_CipherSuite(name);
-            }
-        }
-
-        assertNull(CipherSuite.getByName("bogus"));
-        try {
-            CipherSuite.getByName(null);
-            fail();
-        } catch (NullPointerException expected) {
-        }
-    }
-
-    private void test_CipherSuite(String name) throws Exception {
-        CipherSuite cs = CipherSuite.getByName(name);
-        assertNotNull(name, cs);
-        assertEquals(name, cs.getName());
-        test_CipherSuite(cs);
-    }
-
-    private void test_CipherSuite(CipherSuite cs) throws Exception {
-        assertNotNull(cs);
-
-        String name = cs.getName();
-        assertNotNull(name);
-        assertSame(name, cs, CipherSuite.getByName(name));
-        assertTrue(name, StandardNames.CIPHER_SUITES.contains(name));
-        assertTrue(name, name.startsWith("SSL_") || name.startsWith("TLS_"));
-
-        assertEquals(cs.isAnonymous(), name.contains("_anon_"));
-
-        byte[] bytes = cs.toBytes();
-        assertNotNull(name, bytes);
-        assertEquals(name, 2, bytes.length);
-        assertTrue(name + bytes[0], bytes[0] == (byte) 0x00 || bytes[0] == (byte) 0xc0);
-        assertSame(name, cs, CipherSuite.getByCode(bytes[0], bytes[1]));
-        assertSame(name, cs, CipherSuite.getByCode((byte) 0, bytes[0], bytes[1]));
-
-        assertTrue(name, cs.toString().contains(name));
-
-        String bulkEncryptionAlgorithm = cs.getBulkEncryptionAlgorithm();
-        int blockSize = cs.getBlockSize();
-        if (bulkEncryptionAlgorithm == null) {
-            assertTrue(name, name.contains("_NULL_"));
-            assertEquals(name, 0, blockSize);
-        } else {
-            assertNotNull(name, Cipher.getInstance(cs.getBulkEncryptionAlgorithm()));
-            assertTrue(name, blockSize == 0 || blockSize == 8 || blockSize == 16);
-        }
-
-        String hmacName = cs.getHmacName();
-        assertNotNull(name, hmacName);
-        assertNotNull(name, Mac.getInstance(hmacName));
-
-        String hashName = cs.getHashName();
-        assertNotNull(name, hashName);
-        assertNotNull(name, MessageDigest.getInstance(hashName));
-
-        int macLength = cs.getMACLength();
-        assertTrue(name, macLength == 0 || macLength == 16 || macLength == 20);
-
-        assertTrue(name,
-                   cs.isExportable() == name.contains("_EXPORT_")
-                   || cs.isExportable() == name.contains("_NULL_"));
-
-        String keyType = cs.getServerKeyType();
-        assertEquals(name, cs.isAnonymous(), keyType == null);
-        assertTrue(name, keyType == null || StandardNames.KEY_TYPES.contains(keyType));
-    }
-
-    public void test_getByCode() {
-        // CipherSuite.getByCode is also covered by test_CipherSuite
-        assertUnknown(CipherSuite.getByCode((byte) 0x12, (byte) 0x34));
-        assertUnknown(CipherSuite.getByCode((byte) 0x12, (byte) 0x34, (byte) 0x56));
-        assertUnknown(CipherSuite.getByCode((byte) -1, (byte) -1));
-        assertUnknown(CipherSuite.getByCode((byte) -1, (byte) -1, (byte) -1));
-    }
-    private void assertUnknown(CipherSuite cs) {
-        assertNotNull(cs);
-        assertNotNull(cs.getName().contains("UNKNOWN"));
-    }
-
-    public void test_getSupported() throws Exception {
-        CipherSuite[] suites = CipherSuite.getSupported();
-        List<String> names = new ArrayList<String>(suites.length);
-        for (CipherSuite cs : suites) {
-            test_CipherSuite(cs);
-            names.add(cs.getName());
-        }
-        assertEquals(Arrays.asList(CipherSuite.getSupportedCipherSuiteNames()), names);
-    }
-
-    public void test_getSupportedCipherSuiteNames() throws Exception {
-        String[] names = CipherSuite.getSupportedCipherSuiteNames();
-        StandardNames.assertSSLEngineSupportedCipherSuites(names);
-        for (String name : names) {
-            test_CipherSuite(name);
-        }
-    }
-
-    public void test_getClientKeyType() throws Exception {
-        byte b = Byte.MIN_VALUE;
-        do {
-            String byteString = Byte.toString(b);
-            String keyType = CipherSuite.getClientKeyType(b);
-            switch (b) {
-                case 1:
-                    assertEquals(byteString, "RSA", keyType);
-                    break;
-                case 2:
-                    assertEquals(byteString, "DSA", keyType);
-                    break;
-                case 3:
-                    assertEquals(byteString, "DH_RSA", keyType);
-                    break;
-                case 4:
-                    assertEquals(byteString, "DH_DSA", keyType);
-                    break;
-                case 64:
-                    assertEquals(byteString, "EC", keyType);
-                    break;
-                case 65:
-                    assertEquals(byteString, "EC_RSA", keyType);
-                    break;
-                case 66:
-                    assertEquals(byteString, "EC_EC", keyType);
-                    break;
-                default:
-                    assertNull(byteString, keyType);
-            }
-            b++;
-        } while (b != Byte.MIN_VALUE);
-    }
-}
diff --git a/src/test/java/org/conscrypt/NativeCryptoTest.java b/src/test/java/org/conscrypt/NativeCryptoTest.java
index c561b06..b0fc7dd 100644
--- a/src/test/java/org/conscrypt/NativeCryptoTest.java
+++ b/src/test/java/org/conscrypt/NativeCryptoTest.java
@@ -21,9 +21,11 @@
 import java.io.ByteArrayOutputStream;
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.math.BigInteger;
 import java.net.ServerSocket;
 import java.net.Socket;
+import java.net.SocketException;
 import java.net.SocketTimeoutException;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
@@ -335,6 +337,8 @@
         assertTrue((NativeCrypto.SSL_get_options(s) & NativeCrypto.SSL_OP_NO_TLSv1_1) == 0);
         assertTrue((NativeCrypto.SSL_get_options(s) & NativeCrypto.SSL_OP_NO_TLSv1_2) == 0);
 
+        assertTrue((NativeCrypto.SSL_get_options(s) & NativeCrypto.SSL_OP_TLSEXT_PADDING) != 0);
+
         long s2 = NativeCrypto.SSL_new(c);
         assertTrue(s != s2);
         NativeCrypto.SSL_free(s2);
@@ -635,8 +639,15 @@
     private static final boolean DEBUG = false;
 
     public static class Hooks {
+        protected String negotiatedCipherSuite;
         private OpenSSLKey channelIdPrivateKey;
+        protected boolean pskEnabled;
+        protected byte[] pskKey;
+        protected List<String> enabledCipherSuites;
 
+        /**
+         * @throws SSLException
+         */
         public long getContext() throws SSLException {
             return NativeCrypto.SSL_CTX_new();
         }
@@ -645,19 +656,34 @@
             // without this SSL_set_cipher_lists call the tests were
             // negotiating DHE-RSA-AES256-SHA by default which had
             // very slow ephemeral RSA key generation
-            NativeCrypto.SSL_set_cipher_lists(s, new String[] { "RC4-MD5" });
+            List<String> cipherSuites = new ArrayList<String>();
+            if (enabledCipherSuites == null) {
+                cipherSuites.add("RC4-MD5");
+                if (pskEnabled) {
+                    // In TLS-PSK the client indicates that PSK key exchange is desired by offering
+                    // at least one PSK cipher suite.
+                    cipherSuites.add(0, "PSK-AES128-CBC-SHA");
+                }
+            } else {
+                cipherSuites.addAll(enabledCipherSuites);
+            }
+            NativeCrypto.SSL_set_cipher_lists(
+                    s, cipherSuites.toArray(new String[cipherSuites.size()]));
 
             if (channelIdPrivateKey != null) {
                 NativeCrypto.SSL_set1_tls_channel_id(s, channelIdPrivateKey.getPkeyContext());
             }
             return s;
         }
-        public void clientCertificateRequested(long s) {}
+        public void configureCallbacks(
+                @SuppressWarnings("unused") TestSSLHandshakeCallbacks callbacks) {}
+        public void clientCertificateRequested(@SuppressWarnings("unused") long s) {}
         public void afterHandshake(long session, long ssl, long context,
                                    Socket socket, FileDescriptor fd,
                                    SSLHandshakeCallbacks callback)
                 throws Exception {
             if (session != NULL) {
+                negotiatedCipherSuite = NativeCrypto.SSL_SESSION_cipher(session);
                 NativeCrypto.SSL_SESSION_free(session);
             }
             if (ssl != NULL) {
@@ -693,11 +719,13 @@
         public String authMethod;
         public boolean verifyCertificateChainCalled;
 
-        public void verifyCertificateChain(long[] certChainRefs, String authMethod)
-                throws CertificateException {
+        @Override
+        public void verifyCertificateChain(long sslSessionNativePtr, long[] certChainRefs,
+                String authMethod) throws CertificateException {
             if (DEBUG) {
                 System.out.println("ssl=0x" + Long.toString(sslNativePointer, 16)
                                    + " verifyCertificateChain"
+                                   + " sessionPtr=0x" + Long.toString(sslSessionNativePtr, 16)
                                    + " asn1DerEncodedCertificateChain="
                                    + Arrays.toString(certChainRefs)
                                    + " authMethod=" + authMethod);
@@ -710,6 +738,8 @@
         public byte[] keyTypes;
         public byte[][] asn1DerEncodedX500Principals;
         public boolean clientCertificateRequestedCalled;
+
+        @Override
         public void clientCertificateRequested(byte[] keyTypes,
                                                byte[][] asn1DerEncodedX500Principals) {
             if (DEBUG) {
@@ -728,10 +758,12 @@
         }
 
         public boolean handshakeCompletedCalled;
-        public void handshakeCompleted() {
+
+        @Override
+        public void onSSLStateChange(long sslSessionNativePtr, int type, int val) {
             if (DEBUG) {
                 System.out.println("ssl=0x" + Long.toString(sslNativePointer, 16)
-                                   + " handshakeCompleted");
+                                   + " onSSLStateChange");
             }
             this.handshakeCompletedCalled = true;
         }
@@ -739,6 +771,97 @@
         public Socket getSocket() {
             return socket;
         }
+
+        private boolean clientPSKKeyRequestedInvoked;
+        private String clientPSKKeyRequestedIdentityHint;
+        private int clientPSKKeyRequestedResult;
+        private byte[] clientPSKKeyRequestedResultKey;
+        private byte[] clientPSKKeyRequestedResultIdentity;
+
+        @Override
+        public int clientPSKKeyRequested(String identityHint, byte[] identity, byte[] key) {
+            if (DEBUG) {
+                System.out.println("ssl=0x" + Long.toString(sslNativePointer, 16)
+                                   + " clientPSKKeyRequested"
+                                   + " identityHint=" + identityHint
+                                   + " identity capacity=" + identity.length
+                                   + " key capacity=" + key.length);
+            }
+            clientPSKKeyRequestedInvoked = true;
+            clientPSKKeyRequestedIdentityHint = identityHint;
+            if (clientPSKKeyRequestedResultKey != null) {
+                System.arraycopy(
+                        clientPSKKeyRequestedResultKey, 0,
+                        key, 0,
+                        clientPSKKeyRequestedResultKey.length);
+            }
+            if (clientPSKKeyRequestedResultIdentity != null) {
+                System.arraycopy(
+                        clientPSKKeyRequestedResultIdentity, 0,
+                        identity, 0,
+                        Math.min(clientPSKKeyRequestedResultIdentity.length, identity.length));
+            }
+            return clientPSKKeyRequestedResult;
+        }
+
+        private boolean serverPSKKeyRequestedInvoked;
+        private int serverPSKKeyRequestedResult;
+        private byte[] serverPSKKeyRequestedResultKey;
+        private String serverPSKKeyRequestedIdentityHint;
+        private String serverPSKKeyRequestedIdentity;
+        @Override
+        public int serverPSKKeyRequested(String identityHint, String identity, byte[] key) {
+            if (DEBUG) {
+                System.out.println("ssl=0x" + Long.toString(sslNativePointer, 16)
+                                   + " serverPSKKeyRequested"
+                                   + " identityHint=" + identityHint
+                                   + " identity=" + identity
+                                   + " key capacity=" + key.length);
+            }
+            serverPSKKeyRequestedInvoked = true;
+            serverPSKKeyRequestedIdentityHint = identityHint;
+            serverPSKKeyRequestedIdentity = identity;
+            if (serverPSKKeyRequestedResultKey != null) {
+                System.arraycopy(
+                        serverPSKKeyRequestedResultKey, 0,
+                        key, 0,
+                        serverPSKKeyRequestedResultKey.length);
+            }
+            return serverPSKKeyRequestedResult;
+        }
+    }
+
+    public static class ClientHooks extends Hooks {
+        protected String pskIdentity;
+
+        @Override
+        public void configureCallbacks(TestSSLHandshakeCallbacks callbacks) {
+            super.configureCallbacks(callbacks);
+            if (pskEnabled) {
+                if (pskIdentity != null) {
+                    // Create a NULL-terminated modified UTF-8 representation of pskIdentity.
+                    byte[] b;
+                    try {
+                        b = pskIdentity.getBytes("UTF-8");
+                    } catch (UnsupportedEncodingException e) {
+                        throw new RuntimeException("UTF-8 encoding not supported", e);
+                    }
+                    callbacks.clientPSKKeyRequestedResultIdentity =
+                            Arrays.copyOf(b, b.length + 1);
+                }
+                callbacks.clientPSKKeyRequestedResultKey = pskKey;
+                callbacks.clientPSKKeyRequestedResult = (pskKey != null) ? pskKey.length : 0;
+            }
+        }
+
+        @Override
+        public long beforeHandshake(long c) throws SSLException {
+            long s = super.beforeHandshake(c);
+            if (pskEnabled) {
+                NativeCrypto.set_SSL_psk_client_callback_enabled(s, true);
+            }
+            return s;
+        }
     }
 
     public static class ServerHooks extends Hooks {
@@ -748,6 +871,12 @@
         private byte[] channelIdAfterHandshake;
         private Throwable channelIdAfterHandshakeException;
 
+        protected String pskIdentityHint;
+
+        public ServerHooks() {
+            this(null, null);
+        }
+
         public ServerHooks(OpenSSLKey privateKey, long[] certificates) {
             this.privateKey = privateKey;
             this.certificates = certificates;
@@ -765,10 +894,24 @@
             if (channelIdEnabled) {
                 NativeCrypto.SSL_enable_tls_channel_id(s);
             }
+            if (pskEnabled) {
+                NativeCrypto.set_SSL_psk_server_callback_enabled(s, true);
+                NativeCrypto.SSL_use_psk_identity_hint(s, pskIdentityHint);
+            }
+            NativeCrypto.SSL_set_verify(s, NativeCrypto.SSL_VERIFY_NONE);
             return s;
         }
 
         @Override
+        public void configureCallbacks(TestSSLHandshakeCallbacks callbacks) {
+            super.configureCallbacks(callbacks);
+            if (pskEnabled) {
+                callbacks.serverPSKKeyRequestedResultKey = pskKey;
+                callbacks.serverPSKKeyRequestedResult = (pskKey != null) ? pskKey.length : 0;
+            }
+        }
+
+        @Override
         public void afterHandshake(long session, long ssl, long context,
                                    Socket socket, FileDescriptor fd,
                                    SSLHandshakeCallbacks callback)
@@ -783,6 +926,7 @@
           super.afterHandshake(session, ssl, context, socket, fd, callback);
         }
 
+        @Override
         public void clientCertificateRequested(long s) {
             fail("Server asked for client certificates");
         }
@@ -795,6 +939,7 @@
         Future<TestSSLHandshakeCallbacks> future = executor.submit(
                 new Callable<TestSSLHandshakeCallbacks>() {
             @Override public TestSSLHandshakeCallbacks call() throws Exception {
+                @SuppressWarnings("resource") // Socket needs to remain open after the handshake
                 Socket socket = (client
                                  ? new Socket(listener.getInetAddress(),
                                               listener.getLocalPort())
@@ -807,6 +952,7 @@
                 long s = hooks.beforeHandshake(c);
                 TestSSLHandshakeCallbacks callback
                         = new TestSSLHandshakeCallbacks(socket, s, hooks);
+                hooks.configureCallbacks(callback);
                 if (DEBUG) {
                     System.out.println("ssl=0x" + Long.toString(s, 16)
                                        + " handshake"
@@ -880,6 +1026,10 @@
         assertFalse(serverCallback.verifyCertificateChainCalled);
         assertFalse(clientCallback.clientCertificateRequestedCalled);
         assertFalse(serverCallback.clientCertificateRequestedCalled);
+        assertFalse(clientCallback.clientPSKKeyRequestedInvoked);
+        assertFalse(serverCallback.clientPSKKeyRequestedInvoked);
+        assertFalse(clientCallback.serverPSKKeyRequestedInvoked);
+        assertFalse(serverCallback.serverPSKKeyRequestedInvoked);
         assertTrue(clientCallback.handshakeCompletedCalled);
         assertTrue(serverCallback.handshakeCompletedCalled);
     }
@@ -923,13 +1073,18 @@
         // this depends on the SSL_set_cipher_lists call in beforeHandshake
         // the three returned are the non-ephemeral cases.
         assertEquals(3, clientCallback.keyTypes.length);
-        assertEquals("RSA", OpenSSLSocketImpl.getClientKeyType(clientCallback.keyTypes[0]));
-        assertEquals("DSA", OpenSSLSocketImpl.getClientKeyType(clientCallback.keyTypes[1]));
-        assertEquals("EC", OpenSSLSocketImpl.getClientKeyType(clientCallback.keyTypes[2]));
+        assertEquals("RSA", SSLParametersImpl.getClientKeyType(clientCallback.keyTypes[0]));
+        assertEquals("DSA", SSLParametersImpl.getClientKeyType(clientCallback.keyTypes[1]));
+        assertEquals("EC", SSLParametersImpl.getClientKeyType(clientCallback.keyTypes[2]));
         assertEqualPrincipals(getCaPrincipals(),
                               clientCallback.asn1DerEncodedX500Principals);
         assertFalse(serverCallback.clientCertificateRequestedCalled);
 
+        assertFalse(clientCallback.clientPSKKeyRequestedInvoked);
+        assertFalse(serverCallback.clientPSKKeyRequestedInvoked);
+        assertFalse(clientCallback.serverPSKKeyRequestedInvoked);
+        assertFalse(serverCallback.serverPSKKeyRequestedInvoked);
+
         assertTrue(clientCallback.handshakeCompletedCalled);
         assertTrue(serverCallback.handshakeCompletedCalled);
     }
@@ -950,6 +1105,7 @@
                     return s;
                 }
             };
+            @SuppressWarnings("unused")
             Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null,
                     null);
             Future<TestSSLHandshakeCallbacks> server = handshake(listener, 0, false, sHooks, null,
@@ -1079,8 +1235,11 @@
         final ServerSocket listener = new ServerSocket(0);
         Hooks cHooks = new Hooks();
         cHooks.channelIdPrivateKey = CHANNEL_ID_PRIVATE_KEY;
+        // TLS Channel ID currently requires ECDHE-based key exchanges.
+        cHooks.enabledCipherSuites = Arrays.asList(new String[] {"ECDHE-RSA-AES128-SHA"});
         ServerHooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
         sHooks.channelIdEnabled = true;
+        sHooks.enabledCipherSuites = cHooks.enabledCipherSuites;
         Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null, null);
         Future<TestSSLHandshakeCallbacks> server = handshake(listener, 0, false, sHooks, null, null);
         TestSSLHandshakeCallbacks clientCallback = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
@@ -1088,10 +1247,14 @@
         assertTrue(clientCallback.verifyCertificateChainCalled);
         assertEqualCertificateChains(getServerCertificates(),
                                      clientCallback.certificateChainRefs);
-        assertEquals("RSA", clientCallback.authMethod);
+        assertEquals("ECDHE_RSA", clientCallback.authMethod);
         assertFalse(serverCallback.verifyCertificateChainCalled);
         assertFalse(clientCallback.clientCertificateRequestedCalled);
         assertFalse(serverCallback.clientCertificateRequestedCalled);
+        assertFalse(clientCallback.clientPSKKeyRequestedInvoked);
+        assertFalse(serverCallback.clientPSKKeyRequestedInvoked);
+        assertFalse(clientCallback.serverPSKKeyRequestedInvoked);
+        assertFalse(serverCallback.serverPSKKeyRequestedInvoked);
         assertTrue(clientCallback.handshakeCompletedCalled);
         assertTrue(serverCallback.handshakeCompletedCalled);
         assertNull(sHooks.channelIdAfterHandshakeException);
@@ -1105,8 +1268,11 @@
         final ServerSocket listener = new ServerSocket(0);
         Hooks cHooks = new Hooks();
         cHooks.channelIdPrivateKey = CHANNEL_ID_PRIVATE_KEY;
+        // TLS Channel ID currently requires ECDHE-based key exchanges.
+        cHooks.enabledCipherSuites = Arrays.asList(new String[] {"ECDHE-RSA-AES128-SHA"});
         ServerHooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
         sHooks.channelIdEnabled = false;
+        sHooks.enabledCipherSuites = cHooks.enabledCipherSuites;
         Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null, null);
         Future<TestSSLHandshakeCallbacks> server = handshake(listener, 0, false, sHooks, null, null);
         TestSSLHandshakeCallbacks clientCallback = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
@@ -1114,10 +1280,14 @@
         assertTrue(clientCallback.verifyCertificateChainCalled);
         assertEqualCertificateChains(getServerCertificates(),
                                      clientCallback.certificateChainRefs);
-        assertEquals("RSA", clientCallback.authMethod);
+        assertEquals("ECDHE_RSA", clientCallback.authMethod);
         assertFalse(serverCallback.verifyCertificateChainCalled);
         assertFalse(clientCallback.clientCertificateRequestedCalled);
         assertFalse(serverCallback.clientCertificateRequestedCalled);
+        assertFalse(clientCallback.clientPSKKeyRequestedInvoked);
+        assertFalse(serverCallback.clientPSKKeyRequestedInvoked);
+        assertFalse(clientCallback.serverPSKKeyRequestedInvoked);
+        assertFalse(serverCallback.serverPSKKeyRequestedInvoked);
         assertTrue(clientCallback.handshakeCompletedCalled);
         assertTrue(serverCallback.handshakeCompletedCalled);
         assertNull(sHooks.channelIdAfterHandshakeException);
@@ -1131,8 +1301,11 @@
         final ServerSocket listener = new ServerSocket(0);
         Hooks cHooks = new Hooks();
         cHooks.channelIdPrivateKey = null;
+        // TLS Channel ID currently requires ECDHE-based key exchanges.
+        cHooks.enabledCipherSuites = Arrays.asList(new String[] {"ECDHE-RSA-AES128-SHA"});
         ServerHooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
         sHooks.channelIdEnabled = true;
+        sHooks.enabledCipherSuites = cHooks.enabledCipherSuites;
         Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null, null);
         Future<TestSSLHandshakeCallbacks> server = handshake(listener, 0, false, sHooks, null, null);
         TestSSLHandshakeCallbacks clientCallback = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
@@ -1140,14 +1313,232 @@
         assertTrue(clientCallback.verifyCertificateChainCalled);
         assertEqualCertificateChains(getServerCertificates(),
                                      clientCallback.certificateChainRefs);
-        assertEquals("RSA", clientCallback.authMethod);
+        assertEquals("ECDHE_RSA", clientCallback.authMethod);
+        assertFalse(serverCallback.verifyCertificateChainCalled);
+        assertFalse(clientCallback.clientCertificateRequestedCalled);
+        assertFalse(serverCallback.clientCertificateRequestedCalled);
+        assertFalse(clientCallback.clientPSKKeyRequestedInvoked);
+        assertFalse(serverCallback.clientPSKKeyRequestedInvoked);
+        assertFalse(clientCallback.serverPSKKeyRequestedInvoked);
+        assertFalse(serverCallback.serverPSKKeyRequestedInvoked);
+        assertTrue(clientCallback.handshakeCompletedCalled);
+        assertTrue(serverCallback.handshakeCompletedCalled);
+        assertNull(sHooks.channelIdAfterHandshakeException);
+        assertNull(sHooks.channelIdAfterHandshake);
+    }
+
+    public void test_SSL_do_handshake_with_psk_normal() throws Exception {
+        // normal TLS-PSK client and server case
+        final ServerSocket listener = new ServerSocket(0);
+        Hooks cHooks = new ClientHooks();
+        ServerHooks sHooks = new ServerHooks();
+        cHooks.pskEnabled = true;
+        sHooks.pskEnabled = true;
+        cHooks.pskKey = "1, 2, 3, 4, Testing...".getBytes("UTF-8");
+        sHooks.pskKey = cHooks.pskKey;
+        Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null, null);
+        Future<TestSSLHandshakeCallbacks> server =
+                handshake(listener, 0, false, sHooks, null, null);
+        TestSSLHandshakeCallbacks clientCallback = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        TestSSLHandshakeCallbacks serverCallback = server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        assertFalse(clientCallback.verifyCertificateChainCalled);
         assertFalse(serverCallback.verifyCertificateChainCalled);
         assertFalse(clientCallback.clientCertificateRequestedCalled);
         assertFalse(serverCallback.clientCertificateRequestedCalled);
         assertTrue(clientCallback.handshakeCompletedCalled);
         assertTrue(serverCallback.handshakeCompletedCalled);
-        assertNull(sHooks.channelIdAfterHandshakeException);
-        assertNull(sHooks.channelIdAfterHandshake);
+        assertTrue(clientCallback.clientPSKKeyRequestedInvoked);
+        assertFalse(clientCallback.serverPSKKeyRequestedInvoked);
+        assertFalse(serverCallback.clientPSKKeyRequestedInvoked);
+        assertTrue(serverCallback.serverPSKKeyRequestedInvoked);
+        assertContains(cHooks.negotiatedCipherSuite, "PSK");
+        assertEquals(cHooks.negotiatedCipherSuite, sHooks.negotiatedCipherSuite);
+        assertNull(clientCallback.clientPSKKeyRequestedIdentityHint);
+        assertNull(serverCallback.serverPSKKeyRequestedIdentityHint);
+        assertEquals("", serverCallback.serverPSKKeyRequestedIdentity);
+    }
+
+    public void test_SSL_do_handshake_with_psk_with_identity_and_hint() throws Exception {
+        // normal TLS-PSK client and server case where the server provides the client with a PSK
+        // identity hint, and the client provides the server with a PSK identity.
+        final ServerSocket listener = new ServerSocket(0);
+        ClientHooks cHooks = new ClientHooks();
+        ServerHooks sHooks = new ServerHooks();
+        cHooks.pskEnabled = true;
+        sHooks.pskEnabled = true;
+        cHooks.pskKey = "1, 2, 3, 4, Testing...".getBytes("UTF-8");
+        sHooks.pskKey = cHooks.pskKey;
+        sHooks.pskIdentityHint = "Some non-ASCII characters: \u00c4\u0332";
+        cHooks.pskIdentity = "More non-ASCII characters: \u00f5\u044b";
+        Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null, null);
+        Future<TestSSLHandshakeCallbacks> server =
+                handshake(listener, 0, false, sHooks, null, null);
+        TestSSLHandshakeCallbacks clientCallback = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        TestSSLHandshakeCallbacks serverCallback = server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        assertFalse(clientCallback.verifyCertificateChainCalled);
+        assertFalse(serverCallback.verifyCertificateChainCalled);
+        assertFalse(clientCallback.clientCertificateRequestedCalled);
+        assertFalse(serverCallback.clientCertificateRequestedCalled);
+        assertTrue(clientCallback.handshakeCompletedCalled);
+        assertTrue(serverCallback.handshakeCompletedCalled);
+        assertTrue(clientCallback.clientPSKKeyRequestedInvoked);
+        assertFalse(clientCallback.serverPSKKeyRequestedInvoked);
+        assertFalse(serverCallback.clientPSKKeyRequestedInvoked);
+        assertTrue(serverCallback.serverPSKKeyRequestedInvoked);
+        assertContains(cHooks.negotiatedCipherSuite, "PSK");
+        assertEquals(cHooks.negotiatedCipherSuite, sHooks.negotiatedCipherSuite);
+        assertEquals(sHooks.pskIdentityHint, clientCallback.clientPSKKeyRequestedIdentityHint);
+        assertEquals(sHooks.pskIdentityHint, serverCallback.serverPSKKeyRequestedIdentityHint);
+        assertEquals(cHooks.pskIdentity, serverCallback.serverPSKKeyRequestedIdentity);
+    }
+
+    public void test_SSL_do_handshake_with_psk_with_identity_and_hint_of_max_length()
+            throws Exception {
+        // normal TLS-PSK client and server case where the server provides the client with a PSK
+        // identity hint, and the client provides the server with a PSK identity.
+        final ServerSocket listener = new ServerSocket(0);
+        ClientHooks cHooks = new ClientHooks();
+        ServerHooks sHooks = new ServerHooks();
+        cHooks.pskEnabled = true;
+        sHooks.pskEnabled = true;
+        cHooks.pskKey = "1, 2, 3, 4, Testing...".getBytes("UTF-8");
+        sHooks.pskKey = cHooks.pskKey;
+        sHooks.pskIdentityHint = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
+                + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx";
+        cHooks.pskIdentity = "123456789012345678901234567890123456789012345678901234567890"
+                + "12345678901234567890123456789012345678901234567890123456789012345678";
+        assertEquals(PSKKeyManager.MAX_IDENTITY_HINT_LENGTH_BYTES, sHooks.pskIdentityHint.length());
+        assertEquals(PSKKeyManager.MAX_IDENTITY_LENGTH_BYTES, cHooks.pskIdentity.length());
+        Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null, null);
+        Future<TestSSLHandshakeCallbacks> server =
+                handshake(listener, 0, false, sHooks, null, null);
+        TestSSLHandshakeCallbacks clientCallback = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        TestSSLHandshakeCallbacks serverCallback = server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        assertFalse(clientCallback.verifyCertificateChainCalled);
+        assertFalse(serverCallback.verifyCertificateChainCalled);
+        assertFalse(clientCallback.clientCertificateRequestedCalled);
+        assertFalse(serverCallback.clientCertificateRequestedCalled);
+        assertTrue(clientCallback.handshakeCompletedCalled);
+        assertTrue(serverCallback.handshakeCompletedCalled);
+        assertTrue(clientCallback.clientPSKKeyRequestedInvoked);
+        assertFalse(clientCallback.serverPSKKeyRequestedInvoked);
+        assertFalse(serverCallback.clientPSKKeyRequestedInvoked);
+        assertTrue(serverCallback.serverPSKKeyRequestedInvoked);
+        assertContains(cHooks.negotiatedCipherSuite, "PSK");
+        assertEquals(cHooks.negotiatedCipherSuite, sHooks.negotiatedCipherSuite);
+        assertEquals(sHooks.pskIdentityHint, clientCallback.clientPSKKeyRequestedIdentityHint);
+        assertEquals(sHooks.pskIdentityHint, serverCallback.serverPSKKeyRequestedIdentityHint);
+        assertEquals(cHooks.pskIdentity, serverCallback.serverPSKKeyRequestedIdentity);
+    }
+
+    public void test_SSL_do_handshake_with_psk_key_mismatch() throws Exception {
+        final ServerSocket listener = new ServerSocket(0);
+        ClientHooks cHooks = new ClientHooks();
+        ServerHooks sHooks = new ServerHooks();
+        cHooks.pskEnabled = true;
+        sHooks.pskEnabled = true;
+        cHooks.pskKey = "1, 2, 3, 4, Testing...".getBytes("UTF-8");
+        sHooks.pskKey = "1, 2, 3, 3, Testing...".getBytes("UTF-8");
+        Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null, null);
+        Future<TestSSLHandshakeCallbacks> server =
+                handshake(listener, 0, false, sHooks, null, null);
+        try {
+            client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            fail();
+        } catch (ExecutionException expected) {
+            assertEquals(SSLProtocolException.class, expected.getCause().getClass());
+        }
+    }
+
+    public void test_SSL_do_handshake_with_psk_with_no_client_key() throws Exception {
+        final ServerSocket listener = new ServerSocket(0);
+        ClientHooks cHooks = new ClientHooks();
+        ServerHooks sHooks = new ServerHooks();
+        cHooks.pskEnabled = true;
+        sHooks.pskEnabled = true;
+        cHooks.pskKey = null;
+        sHooks.pskKey = "1, 2, 3, 4, Testing...".getBytes("UTF-8");
+        Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null, null);
+        Future<TestSSLHandshakeCallbacks> server =
+                handshake(listener, 0, false, sHooks, null, null);
+        try {
+            client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            fail();
+        } catch (ExecutionException expected) {
+            assertEquals(SSLProtocolException.class, expected.getCause().getClass());
+        }
+    }
+
+    public void test_SSL_do_handshake_with_psk_with_no_server_key() throws Exception {
+        final ServerSocket listener = new ServerSocket(0);
+        ClientHooks cHooks = new ClientHooks();
+        ServerHooks sHooks = new ServerHooks();
+        cHooks.pskEnabled = true;
+        sHooks.pskEnabled = true;
+        cHooks.pskKey = "1, 2, 3, 4, Testing...".getBytes("UTF-8");
+        sHooks.pskKey = null;
+        Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null, null);
+        Future<TestSSLHandshakeCallbacks> server =
+                handshake(listener, 0, false, sHooks, null, null);
+        try {
+            client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            fail();
+        } catch (ExecutionException expected) {
+            assertEquals(SSLProtocolException.class, expected.getCause().getClass());
+        }
+    }
+
+    public void test_SSL_do_handshake_with_psk_key_too_long() throws Exception {
+        final ServerSocket listener = new ServerSocket(0);
+        ClientHooks cHooks = new ClientHooks() {
+            @Override
+            public void configureCallbacks(TestSSLHandshakeCallbacks callbacks) {
+                super.configureCallbacks(callbacks);
+                callbacks.clientPSKKeyRequestedResult = PSKKeyManager.MAX_KEY_LENGTH_BYTES + 1;
+            }
+        };
+        ServerHooks sHooks = new ServerHooks();
+        cHooks.pskEnabled = true;
+        sHooks.pskEnabled = true;
+        cHooks.pskKey = "1, 2, 3, 4, Testing...".getBytes("UTF-8");
+        sHooks.pskKey = cHooks.pskKey;
+        Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null, null);
+        Future<TestSSLHandshakeCallbacks> server =
+                handshake(listener, 0, false, sHooks, null, null);
+        try {
+            client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+            fail();
+        } catch (ExecutionException expected) {
+            assertEquals(SSLProtocolException.class, expected.getCause().getClass());
+        }
+    }
+
+    public void test_SSL_use_psk_identity_hint() throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        try {
+            NativeCrypto.SSL_use_psk_identity_hint(s, null);
+            NativeCrypto.SSL_use_psk_identity_hint(s, "test");
+
+            try {
+                // 800 characters is much longer than the permitted maximum.
+                StringBuilder pskIdentityHint = new StringBuilder();
+                for (int i = 0; i < 160; i++) {
+                    pskIdentityHint.append(" long");
+                }
+                assertTrue(pskIdentityHint.length() > PSKKeyManager.MAX_IDENTITY_HINT_LENGTH_BYTES);
+                NativeCrypto.SSL_use_psk_identity_hint(s, pskIdentityHint.toString());
+                fail();
+            } catch (SSLException expected) {
+            }
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
+        }
     }
 
     public void test_SSL_set_session() throws Exception {
@@ -1288,6 +1679,7 @@
             Hooks sHooks = new ServerHooks(getServerPrivateKey(), getServerCertificates());
             Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null,
                     null);
+            @SuppressWarnings("unused")
             Future<TestSSLHandshakeCallbacks> server = handshake(listener, 0, false, sHooks, null,
                     null);
             client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
@@ -1309,6 +1701,7 @@
             };
             Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null,
                     null);
+            @SuppressWarnings("unused")
             Future<TestSSLHandshakeCallbacks> server = handshake(listener, 0, false, sHooks, null,
                     null);
             client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
@@ -1406,6 +1799,8 @@
                 assertEquals("spdy/2", new String(negotiated));
                 assertTrue("NPN should enable cutthrough on the client",
                         0 != (NativeCrypto.SSL_get_mode(ssl) & SSL_MODE_HANDSHAKE_CUTTHROUGH));
+                NativeCrypto.SSL_write(ssl, fd, callback, new byte[] { 42 }, 0, 1,
+                        (int) ((TIMEOUT_SECONDS * 1000) / 2));
                 super.afterHandshake(session, ssl, context, socket, fd, callback);
             }
         };
@@ -1420,6 +1815,9 @@
                 assertEquals("spdy/2", new String(negotiated));
                 assertEquals("NPN should not enable cutthrough on the server",
                         0, NativeCrypto.SSL_get_mode(ssl) & SSL_MODE_HANDSHAKE_CUTTHROUGH);
+                byte[] buffer = new byte[1];
+                NativeCrypto.SSL_read(ssl, fd, callback, buffer, 0, 1, 0);
+                assertEquals(42, buffer[0]);
                 super.afterHandshake(session, ssl, c, sock, fd, callback);
             }
         };
@@ -1447,8 +1845,9 @@
 
         Hooks cHooks = new Hooks() {
             @Override public long beforeHandshake(long context) throws SSLException {
-                NativeCrypto.SSL_CTX_set_alpn_protos(context, clientAlpnProtocols);
-                return super.beforeHandshake(context);
+                long sslContext = super.beforeHandshake(context);
+                NativeCrypto.SSL_set_alpn_protos(sslContext, clientAlpnProtocols);
+                return sslContext;
             }
             @Override public void afterHandshake(long session, long ssl, long context, Socket socket,
                     FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception {
@@ -1733,6 +2132,7 @@
             };
             Future<TestSSLHandshakeCallbacks> client = handshake(listener, 0, true, cHooks, null,
                     null);
+            @SuppressWarnings("unused")
             Future<TestSSLHandshakeCallbacks> server = handshake(listener, 0, false, sHooks, null,
                     null);
             client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
@@ -1836,6 +2236,7 @@
                                        SSLHandshakeCallbacks callback)
                     throws Exception {
                 new Thread() {
+                    @Override
                     public void run() {
                         try {
                             Thread.sleep(1*1000);
@@ -1854,32 +2255,61 @@
         server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
     }
 
+    private static abstract class SSLSessionWrappedTask {
+        public abstract void run(long sslSession) throws Exception;
+    }
+
+    private void wrapWithSSLSession(SSLSessionWrappedTask task) throws Exception {
+        long c = NativeCrypto.SSL_CTX_new();
+        long s = NativeCrypto.SSL_new(c);
+        try {
+            task.run(s);
+        } finally {
+            NativeCrypto.SSL_free(s);
+            NativeCrypto.SSL_CTX_free(c);
+        }
+    }
+
     public void test_SSL_shutdown() throws Exception {
 
         // null FileDescriptor
-        try {
-            NativeCrypto.SSL_shutdown(NULL, null, DUMMY_CB);
-        } catch (NullPointerException expected) {
-        }
+        wrapWithSSLSession(new SSLSessionWrappedTask() {
+            @Override
+            public void run(long sslSession) throws Exception {
+                try {
+                    NativeCrypto.SSL_shutdown(sslSession, null, DUMMY_CB);
+                    fail();
+                } catch (NullPointerException expected) {
+                }
+            }
+        });
 
         // null SSLHandshakeCallbacks
-        try {
-            NativeCrypto.SSL_shutdown(NULL, INVALID_FD, null);
-        } catch (NullPointerException expected) {
-        }
+        wrapWithSSLSession(new SSLSessionWrappedTask() {
+            @Override
+            public void run(long sslSession) throws Exception {
+                try {
+                    NativeCrypto.SSL_shutdown(sslSession, INVALID_FD, null);
+                    fail();
+                } catch (NullPointerException expected) {
+                }
+            }
+        });
 
         // SSL_shutdown is a rare case that tolerates a null SSL argument
         NativeCrypto.SSL_shutdown(NULL, INVALID_FD, DUMMY_CB);
 
         // handshaking not yet performed
-        long c = NativeCrypto.SSL_CTX_new();
-        long s = NativeCrypto.SSL_new(c);
-        try {
-            NativeCrypto.SSL_shutdown(s, INVALID_FD, DUMMY_CB);
-        } catch (SSLProtocolException expected) {
-        }
-        NativeCrypto.SSL_free(s);
-        NativeCrypto.SSL_CTX_free(c);
+        wrapWithSSLSession(new SSLSessionWrappedTask() {
+            @Override
+            public void run(long sslSession) throws Exception {
+                try {
+                    NativeCrypto.SSL_shutdown(sslSession, INVALID_FD, DUMMY_CB);
+                    fail();
+                } catch (SocketException expected) {
+                }
+            }
+        });
 
         // positively tested elsewhere because handshake uses use
         // SSL_shutdown to ensure SSL_SESSIONs are reused.
@@ -2218,12 +2648,12 @@
     }
 
     public void test_EVP_SignInit() throws Exception {
-        final long ctx = NativeCrypto.EVP_SignInit("RSA-SHA256");
-        assertTrue(ctx != NULL);
-        NativeCrypto.EVP_MD_CTX_destroy(ctx);
+        final OpenSSLDigestContext ctx = new OpenSSLDigestContext(NativeCrypto.EVP_MD_CTX_create());
+        assertEquals(1,
+                NativeCrypto.EVP_SignInit(ctx, NativeCrypto.EVP_get_digestbyname("sha256")));
 
         try {
-            NativeCrypto.EVP_SignInit("foobar");
+            NativeCrypto.EVP_SignInit(ctx, 0);
             fail();
         } catch (RuntimeException expected) {
         }
@@ -2287,6 +2717,42 @@
         }
     }
 
+    public void test_RSA_size_null_key_Failure() throws Exception {
+        try {
+            NativeCrypto.RSA_size(0);
+            fail();
+        } catch (NullPointerException expected) {}
+    }
+
+    public void test_RSA_private_encrypt_null_key_Failure() throws Exception {
+        try {
+            NativeCrypto.RSA_private_encrypt(0, new byte[0], new byte[0], 0, 0);
+            fail();
+        } catch (NullPointerException expected) {}
+    }
+
+    public void test_RSA_private_decrypt_null_key_Failure() throws Exception {
+        try {
+            NativeCrypto.RSA_private_decrypt(0, new byte[0], new byte[0], 0, 0);
+            fail();
+        } catch (NullPointerException expected) {}
+    }
+
+    public void test_RSA_public_encrypt_null_key_Failure() throws Exception {
+        try {
+            NativeCrypto.RSA_public_encrypt(0, new byte[0], new byte[0], 0, 0);
+            fail();
+        } catch (NullPointerException expected) {}
+    }
+
+    public void test_RSA_public_decrypt_null_key_Failure() throws Exception {
+        try {
+            NativeCrypto.RSA_public_decrypt(0, new byte[0], new byte[0], 0, 0);
+            fail();
+        } catch (NullPointerException expected) {}
+    }
+
+
     final byte[] dsa2048_p = {
             (byte) 0x00, (byte) 0xC3, (byte) 0x16, (byte) 0xD4, (byte) 0xBA, (byte) 0xDC,
             (byte) 0x0E, (byte) 0xB8, (byte) 0xFC, (byte) 0x40, (byte) 0xDB, (byte) 0xB0,
@@ -2433,6 +2899,20 @@
         }
     }
 
+    public void test_get_DSA_params_null_key_Failure() throws Exception {
+        try {
+            NativeCrypto.get_DSA_params(0);
+            fail();
+        } catch (NullPointerException expected) {}
+    }
+
+    public void test_set_DSA_flag_nonce_from_hash_null_key_Failure() throws Exception {
+        try {
+            NativeCrypto.set_DSA_flag_nonce_from_hash(0);
+            fail();
+        } catch (NullPointerException expected) {}
+    }
+
     /*
      * Test vector generation:
      * openssl rand -hex 16
@@ -2443,12 +2923,6 @@
             (byte) 0x8a, (byte) 0x41, (byte) 0x55, (byte) 0x5f,
     };
 
-    private static final byte[] AES_IV_ZEROES = new byte[] {
-            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
-            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
-            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
-    };
-
     public void testEC_GROUP() throws Exception {
         /* Test using NIST's P-256 curve */
         check_EC_GROUP(NativeCrypto.EC_CURVE_GFP, "prime256v1",
@@ -2566,6 +3040,68 @@
         }
     }
 
+    public void test_EC_KEY_get_private_key_null_key_Failure() throws Exception {
+        try {
+            NativeCrypto.EC_KEY_get_private_key(0);
+            fail();
+        } catch (NullPointerException expected) {}
+    }
+
+    public void test_EC_KEY_get_public_key_null_key_Failure() throws Exception {
+        try {
+            NativeCrypto.EC_KEY_get_public_key(0);
+            fail();
+        } catch (NullPointerException expected) {}
+    }
+
+    public void test_EC_KEY_set_nonce_from_hash_null_key_Failure() throws Exception {
+        try {
+            NativeCrypto.EC_KEY_set_nonce_from_hash(0, true);
+            fail();
+        } catch (NullPointerException expected) {}
+    }
+
+    public void test_ECDH_compute_key_null_key_Failure() throws Exception {
+        long groupRef = NativeCrypto.EC_GROUP_new_by_curve_name("prime256v1");
+        if (groupRef == 0) {
+            fail();
+        }
+        try {
+            long pkey1Ref = NativeCrypto.EC_KEY_generate_key(groupRef);
+            long pkey2Ref = NativeCrypto.EC_KEY_generate_key(groupRef);
+            try {
+                if (pkey1Ref == 0) {
+                    fail();
+                }
+                if (pkey2Ref == 0) {
+                    fail();
+                }
+
+                byte[] out = new byte[128];
+                int outOffset = 0;
+                // Assert that the method under test works fine with the two non-null keys
+                NativeCrypto.ECDH_compute_key(out, outOffset, pkey1Ref, pkey2Ref);
+
+                // Assert that it fails when only the first key is null
+                try {
+                    NativeCrypto.ECDH_compute_key(out, outOffset, 0, pkey2Ref);
+                    fail();
+                } catch (NullPointerException expected) {}
+
+                // Assert that it fails when only the second key is null
+                try {
+                    NativeCrypto.ECDH_compute_key(out, outOffset, pkey1Ref, 0);
+                    fail();
+                } catch (NullPointerException expected) {}
+            } finally {
+                NativeCrypto.EVP_PKEY_free(pkey1Ref);
+                NativeCrypto.EVP_PKEY_free(pkey2Ref);
+            }
+        } finally {
+            NativeCrypto.EC_GROUP_clear_free(groupRef);
+        }
+    }
+
     public void test_EVP_CipherInit_ex_Null_Failure() throws Exception {
         final long ctx = NativeCrypto.EVP_CIPHER_CTX_new();
         try {
@@ -2634,15 +3170,16 @@
         byte[] actual = "Test".getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream(actual);
 
-        long ctx = NativeCrypto.create_BIO_InputStream(new OpenSSLBIOInputStream(is));
+        @SuppressWarnings("resource")
+        OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is);
         try {
             byte[] buffer = new byte[1024];
-            int numRead = NativeCrypto.BIO_read(ctx, buffer);
+            int numRead = NativeCrypto.BIO_read(bis.getBioContext(), buffer);
             assertEquals(actual.length, numRead);
             assertEquals(Arrays.toString(actual),
                     Arrays.toString(Arrays.copyOfRange(buffer, 0, numRead)));
         } finally {
-            NativeCrypto.BIO_free(ctx);
+            bis.release();
         }
 
     }
@@ -2657,7 +3194,17 @@
             assertEquals(actual.length, os.size());
             assertEquals(Arrays.toString(actual), Arrays.toString(os.toByteArray()));
         } finally {
-            NativeCrypto.BIO_free(ctx);
+            NativeCrypto.BIO_free_all(ctx);
         }
     }
+
+    private static void assertContains(String actualValue, String expectedSubstring) {
+        if (actualValue == null) {
+            return;
+        }
+        if (actualValue.contains(expectedSubstring)) {
+            return;
+        }
+        fail("\"" + actualValue + "\" does not contain \"" + expectedSubstring + "\"");
+    }
 }
diff --git a/src/test/java/org/conscrypt/OpenSSLSignatureTest.java b/src/test/java/org/conscrypt/OpenSSLSignatureTest.java
deleted file mode 100644
index 045c509..0000000
--- a/src/test/java/org/conscrypt/OpenSSLSignatureTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2010 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.NoSuchAlgorithmException;
-import junit.framework.TestCase;
-
-public class OpenSSLSignatureTest extends TestCase {
-
-    public void test_getInstance() throws Exception {
-        try {
-            OpenSSLSignature.getInstance("SHA1WITHDSA");
-            OpenSSLSignature.getInstance("MD5WITHRSAENCRYPTION");
-            OpenSSLSignature.getInstance("SHA1WITHRSAENCRYPTION");
-            OpenSSLSignature.getInstance("SHA256WITHRSAENCRYPTION");
-            OpenSSLSignature.getInstance("SHA384WITHRSAENCRYPTION");
-            OpenSSLSignature.getInstance("SHA512WITHRSAENCRYPTION");
-        } catch (NoSuchAlgorithmException e) {
-            fail("getInstance is not case insensitive");
-        }
-    }
-}
diff --git a/src/test/java/org/conscrypt/OpenSSLSocketImplTest.java b/src/test/java/org/conscrypt/SSLParametersImplTest.java
similarity index 93%
rename from src/test/java/org/conscrypt/OpenSSLSocketImplTest.java
rename to src/test/java/org/conscrypt/SSLParametersImplTest.java
index 1d03694..6371648 100644
--- a/src/test/java/org/conscrypt/OpenSSLSocketImplTest.java
+++ b/src/test/java/org/conscrypt/SSLParametersImplTest.java
@@ -18,14 +18,14 @@
 
 import junit.framework.TestCase;
 
-public class OpenSSLSocketImplTest extends TestCase {
+public class SSLParametersImplTest extends TestCase {
 
   public void testGetClientKeyType() throws Exception {
     // See http://www.ietf.org/assignments/tls-parameters/tls-parameters.xml
     byte b = Byte.MIN_VALUE;
     do {
       String byteString = Byte.toString(b);
-      String keyType = OpenSSLSocketImpl.getClientKeyType(b);
+      String keyType = SSLParametersImpl.getClientKeyType(b);
       switch (b) {
         case 1:
           assertEquals(byteString, "RSA", keyType);
diff --git a/src/test/java/org/conscrypt/TrustManagerImplTest.java b/src/test/java/org/conscrypt/TrustManagerImplTest.java
index 9f757cf..035d4f2 100644
--- a/src/test/java/org/conscrypt/TrustManagerImplTest.java
+++ b/src/test/java/org/conscrypt/TrustManagerImplTest.java
@@ -18,13 +18,18 @@
 
 import java.io.File;
 import java.io.FileWriter;
+import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.security.KeyStore;
 import java.security.MessageDigest;
+import java.security.Principal;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionContext;
 import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.X509TrustManager;
 import junit.framework.TestCase;
@@ -128,9 +133,10 @@
 
         assertTrue(tm instanceof TrustManagerImpl);
         TrustManagerImpl tmi = (TrustManagerImpl) tm;
-        List<X509Certificate> certs = tmi.checkServerTrusted(chain2, "RSA", "purple.com");
+        List<X509Certificate> certs = tmi.checkServerTrusted(chain2, "RSA", new MySSLSession(
+                "purple.com"));
         assertEquals(Arrays.asList(chain3), certs);
-        certs = tmi.checkServerTrusted(chain1, "RSA", "purple.com");
+        certs = tmi.checkServerTrusted(chain1, "RSA", new MySSLSession("purple.com"));
         assertEquals(Arrays.asList(chain3), certs);
     }
 
@@ -156,7 +162,12 @@
         assertInvalidPinned(chain1, trustManager(intermediate, "gugle.com", root), "gugle.com");
         // test a pinned hostname that should succeed
         assertValidPinned(chain2, trustManager(intermediate, "gugle.com", server), "gugle.com",
-                                                                                            chain2);
+                          chain2);
+        // test a pinned hostname that chains to user installed that should succeed
+        assertValidPinned(chain2, trustManagerUserInstalled(
+            (X509Certificate)TestKeyStore.getIntermediateCa2().getPrivateKey("RSA", "RSA")
+                .getCertificateChain()[1], intermediate, "gugle.com", server), "gugle.com",
+                chain2, true);
     }
 
     private X509TrustManager trustManager(X509Certificate ca) throws Exception {
@@ -179,6 +190,29 @@
         return new TrustManagerImpl(keyStore, cm);
     }
 
+    private TrustManagerImpl trustManagerUserInstalled(
+        X509Certificate caKeyStore, X509Certificate caUserStore, String hostname,
+        X509Certificate pin) throws Exception {
+        // build the cert pin manager
+        CertPinManager cm = certManager(hostname, pin);
+
+        // install at least one cert in the store (requirement)
+        KeyStore keyStore = TestKeyStore.createKeyStore();
+        keyStore.setCertificateEntry("alias", caKeyStore);
+
+        // install a cert into the user installed store
+        final File DIR_TEMP = new File(System.getProperty("java.io.tmpdir"));
+        final File DIR_TEST = new File(DIR_TEMP, "test");
+        final File system = new File(DIR_TEST, "system-test");
+        final File added = new File(DIR_TEST, "added-test");
+        final File deleted = new File(DIR_TEST, "deleted-test");
+
+        TrustedCertificateStore tcs = new TrustedCertificateStore(system, added, deleted);
+        added.mkdirs();
+        tcs.installCertificate(caUserStore);
+        return new TrustManagerImpl(keyStore, cm, tcs);
+    }
+
     private CertPinManager certManager(String hostname, X509Certificate pin) throws Exception {
         String pinString = "";
         if (pin != null) {
@@ -200,10 +234,24 @@
 
     private void assertValidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname,
                                    X509Certificate[] fullChain) throws Exception {
+        assertValidPinned(chain, tm, hostname, fullChain, false);
+    }
+
+    private void assertValidPinned(X509Certificate[] chain, X509TrustManager tm, String hostname,
+                                   X509Certificate[] fullChain, boolean expectUserInstalled)
+                                   throws Exception {
         if (tm instanceof TrustManagerImpl) {
             TrustManagerImpl tmi = (TrustManagerImpl) tm;
-            List<X509Certificate> checkedChain = tmi.checkServerTrusted(chain, "RSA", hostname);
+            List<X509Certificate> checkedChain = tmi.checkServerTrusted(chain, "RSA",
+                    new MySSLSession(hostname));
             assertEquals(checkedChain, Arrays.asList(fullChain));
+            boolean chainContainsUserInstalled = false;
+            for (X509Certificate cert : checkedChain) {
+                if (tmi.isUserAddedCertificate(cert)) {
+                    chainContainsUserInstalled = true;
+                }
+            }
+            assertEquals(expectUserInstalled, chainContainsUserInstalled);
         }
         tm.checkServerTrusted(chain, "RSA");
     }
@@ -226,9 +274,123 @@
         assertTrue(tm.getClass().getName(), tm instanceof TrustManagerImpl);
         try {
             TrustManagerImpl tmi = (TrustManagerImpl) tm;
-            tmi.checkServerTrusted(chain, "RSA", hostname);
+            tmi.checkServerTrusted(chain, "RSA", new MySSLSession(hostname));
             fail();
         } catch (CertificateException expected) {
         }
     }
+
+    private class MySSLSession implements SSLSession {
+        private final String hostname;
+
+        public MySSLSession(String hostname) {
+            this.hostname = hostname;
+        }
+
+        @Override
+        public int getApplicationBufferSize() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String getCipherSuite() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public long getCreationTime() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public byte[] getId() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public long getLastAccessedTime() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Certificate[] getLocalCertificates() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Principal getLocalPrincipal() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int getPacketBufferSize() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public javax.security.cert.X509Certificate[] getPeerCertificateChain()
+                throws SSLPeerUnverifiedException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String getPeerHost() {
+            return hostname;
+        }
+
+        @Override
+        public int getPeerPort() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String getProtocol() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public SSLSessionContext getSessionContext() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Object getValue(String name) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public String[] getValueNames() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void invalidate() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public boolean isValid() {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void putValue(String name, Object value) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void removeValue(String name) {
+            throw new UnsupportedOperationException();
+        }
+    }
 }
diff --git a/src/test/java/org/conscrypt/TrustedCertificateStoreTest.java b/src/test/java/org/conscrypt/TrustedCertificateStoreTest.java
index c75f540..2bb9efd 100644
--- a/src/test/java/org/conscrypt/TrustedCertificateStoreTest.java
+++ b/src/test/java/org/conscrypt/TrustedCertificateStoreTest.java
@@ -22,26 +22,24 @@
 import java.security.KeyStore;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.List;
-import java.util.NoSuchElementException;
+import java.util.Random;
 import java.util.Set;
 import javax.security.auth.x500.X500Principal;
 import junit.framework.TestCase;
 import libcore.java.security.TestKeyStore;
 
 public class TrustedCertificateStoreTest extends TestCase {
+    private static final Random tempFileRandom = new Random();
 
-    private static final File DIR_TEMP = new File(System.getProperty("java.io.tmpdir"));
-    private static final File DIR_TEST = new File(DIR_TEMP, "test");
-    private static final File DIR_SYSTEM = new File(DIR_TEST, "system");
-    private static final File DIR_ADDED = new File(DIR_TEST, "added");
-    private static final File DIR_DELETED = new File(DIR_TEST, "removed");
+    private final File dirTest = new File(System.getProperty("java.io.tmpdir", "."),
+            "cert-store-test" + tempFileRandom.nextInt());
+    private final File dirSystem = new File(dirTest, "system");
+    private final File dirAdded = new File(dirTest, "added");
+    private final File dirDeleted = new File(dirTest, "removed");
 
     private static X509Certificate CA1;
     private static X509Certificate CA2;
@@ -196,12 +194,13 @@
     }
 
     private void setupStore() {
-        DIR_SYSTEM.mkdirs();
+        dirSystem.mkdirs();
+        cleanStore();
         createStore();
     }
 
     private void createStore() {
-        store = new TrustedCertificateStore(DIR_SYSTEM, DIR_ADDED, DIR_DELETED);
+        store = new TrustedCertificateStore(dirSystem, dirAdded, dirDeleted);
     }
 
     @Override protected void tearDown() {
@@ -209,7 +208,7 @@
     }
 
     private void cleanStore() {
-        for (File dir : new File[] { DIR_SYSTEM, DIR_ADDED, DIR_DELETED, DIR_TEST }) {
+        for (File dir : new File[] { dirSystem, dirAdded, dirDeleted, dirTest }) {
             File[] files = dir.listFiles();
             if (files == null) {
                 continue;
@@ -249,6 +248,7 @@
 
     public void testPartialFileIsIgnored() throws Exception {
         File file = file(getAliasSystemCa1());
+        file.getParentFile().mkdirs();
         OutputStream os = new FileOutputStream(file);
         os.write(0);
         os.close();
@@ -292,11 +292,11 @@
         assertNull(store.getCertificateAlias(getCa1()));
 
         try {
-            store.isTrustAnchor(null);
+            store.getTrustAnchor(null);
             fail();
         } catch (NullPointerException expected) {
         }
-        assertFalse(store.isTrustAnchor(getCa1()));
+        assertNull(store.getTrustAnchor(getCa1()));
 
         try {
             store.findIssuer(null);
@@ -314,7 +314,7 @@
         store.deleteCertificateEntry(null);
         store.deleteCertificateEntry("");
 
-        String[] userFiles = DIR_ADDED.list();
+        String[] userFiles = dirAdded.list();
         assertTrue(userFiles == null || userFiles.length == 0);
     }
 
@@ -432,8 +432,8 @@
     }
 
     public void testWithExistingUserDirectories() throws Exception {
-        DIR_ADDED.mkdirs();
-        DIR_DELETED.mkdirs();
+        dirAdded.mkdirs();
+        dirDeleted.mkdirs();
         install(getCa1(), getAliasSystemCa1());
         assertRootCa(getCa1(), getAliasSystemCa1());
         assertAliases(getAliasSystemCa1());
@@ -451,14 +451,14 @@
         String systemAlias = alias(false, ca1, 0);
         install(ca1, systemAlias);
         assertRootCa(ca1, systemAlias);
-        assertTrue(store.isTrustAnchor(ca2));
+        assertEquals(ca1, store.getTrustAnchor(ca2));
         assertEquals(ca1, store.findIssuer(ca2));
         resetStore();
 
         String userAlias = alias(true, ca1, 0);
         store.installCertificate(ca1);
         assertRootCa(ca1, userAlias);
-        assertTrue(store.isTrustAnchor(ca2));
+        assertNotNull(store.getTrustAnchor(ca2));
         assertEquals(ca1, store.findIssuer(ca2));
         resetStore();
     }
@@ -559,7 +559,7 @@
         assertEquals(x, store.getCertificate(alias));
         assertEquals(file(alias).lastModified(), store.getCreationDate(alias).getTime());
         assertTrue(store.containsAlias(alias));
-        assertTrue(store.isTrustAnchor(x));
+        assertEquals(x, store.getTrustAnchor(x));
     }
 
     private void assertIntermediateCa(X509Certificate x, String alias) {
@@ -576,7 +576,7 @@
         assertNull(store.getCertificate(alias));
         assertFalse(store.containsAlias(alias));
         assertNull(store.getCertificateAlias(x));
-        assertFalse(store.isTrustAnchor(x));
+        assertNull(store.getTrustAnchor(x));
         assertEquals(store.allSystemAliases().contains(alias),
                      store.getCertificate(alias, true) != null);
     }
@@ -626,7 +626,7 @@
     /**
      * Install certificate under specified alias
      */
-    private static void install(X509Certificate x, String alias) {
+    private void install(X509Certificate x, String alias) {
         try {
             File file = file(alias);
             file.getParentFile().mkdirs();
@@ -641,12 +641,12 @@
     /**
      * Compute file for an alias
      */
-    private static File file(String alias) {
+    private File file(String alias) {
         File dir;
         if (TrustedCertificateStore.isSystem(alias)) {
-            dir = DIR_SYSTEM;
+            dir = dirSystem;
         } else if (TrustedCertificateStore.isUser(alias)) {
-            dir = DIR_ADDED;
+            dir = dirAdded;
         } else {
             throw new IllegalArgumentException(alias);
         }
diff --git a/update_prebuilts.sh b/update_prebuilts.sh
new file mode 100755
index 0000000..c765e5d
--- /dev/null
+++ b/update_prebuilts.sh
@@ -0,0 +1,118 @@
+#!/usr/bin/env bash
+
+if (( BASH_VERSINFO[0] < 3 )); then
+  echo "Must be running BASH version 3 or newer!"
+  exit 1
+fi
+
+if [[ -z $TOP ]]; then \
+  echo "You must do envsetup beforehand."
+  exit 1
+fi
+
+# We are currently in frameworks/rs, so compute our top-level directory.
+MY_ANDROID_DIR="$TOP"
+cd "$MY_ANDROID_DIR"
+
+if [[ $OSTYPE != *linux* ]]; then \
+  echo "Only works on Linux."
+  exit 1
+fi
+
+SHORT_OSNAME=linux
+SONAME=so
+# Target architectures and their system library names.
+declare -a TARGETS=(generic_armv5 aosp_arm aosp_mips aosp_x86)
+declare -a ABI_NAMES=(armeabi armeabi-v7a mips x86)
+declare -a SYS_NAMES=(generic_armv5 generic generic_mips generic_x86)
+declare -i NUM_CORES="$(awk '/^processor/ { i++ } END { print i }' /proc/cpuinfo)"
+
+echo "Using $NUM_CORES cores"
+
+# Turn off the build cache and make sure we build all of LLVM from scratch.
+#export ANDROID_USE_BUILDCACHE=false
+
+# PREBUILTS_DIR is where we want to copy our new files to.
+PREBUILTS_DIR="$MY_ANDROID_DIR/prebuilts/conscrypt/"
+
+print_usage() {
+  echo "USAGE: $0 [-h|--help] [-n|--no-build] [-x]"
+  echo "OPTIONS:"
+  echo "    -h, --help     : Display this help message."
+  echo "    -n, --no-build : Skip the build step and just copy files."
+  echo "    -x             : Display commands before they are executed."
+}
+
+build_libs() {
+  local t="$1"
+  echo Building for target $t
+  cd $MY_ANDROID_DIR
+  WITH_HOST_DALVIK=false make -j32 PRODUCT-$t-userdebug APP-conscrypt_unbundled-libconscrypt_jni || exit 1
+}
+
+# Build everything by default
+build_me=1
+
+while [[ $# -gt 0 ]]; do
+  case "$1" in
+    -h|--help)
+      print_usage
+      exit 0
+      ;;
+    -n|--no-build)
+      build_me=0
+      ;;
+    -x)
+      # set lets us enable bash -x mode.
+      set -x
+      ;;
+    *)
+      echo Unknown argument: "$1"
+      print_usage
+      exit 99
+      break
+      ;;
+  esac
+  shift
+done
+
+declare -i i
+
+if [ $build_me -eq 1 ]; then
+
+  echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  echo !!! BUILDING CONSCRYPT PREBUILTS !!!
+  echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+  source build/envsetup.sh
+
+  for (( i=0; i < ${#TARGETS[@]}; i++ )); do
+    build_libs "${TARGETS[$i]}"
+  done
+
+  echo DONE BUILDING CONSCRYPT PREBUILTS
+
+else
+
+  echo SKIPPING BUILD OF CONSCRYPT PREBUILTS
+
+fi
+
+DATE="$(date +"%Y%m%d")"
+
+cd "$PREBUILTS_DIR" || exit 3
+repo start "pb_$DATE" .
+
+# Don't copy device prebuilts on Darwin. We don't need/use them.
+for (( i=0; i < ${#TARGETS[@]}; i++ )); do
+  sys="${SYS_NAMES[$i]}"
+  abi="${ABI_NAMES[$i]}"
+  sys_lib_dir="$MY_ANDROID_DIR/out/target/product/$sys/system/lib"
+  if [[ ! -d "jni/$abi" ]]; then
+    mkdir -p "jni/$abi"
+  fi
+  cp "$sys_lib_dir/libconscrypt_jni.so" "jni/$abi/" || exit 4
+done
+
+# javalib.jar
+cp "$MY_ANDROID_DIR/out/target/common/obj/JAVA_LIBRARIES/conscrypt_unbundled_intermediates/classes.jar" .