summaryrefslogtreecommitdiffstats
path: root/emailcommon
diff options
context:
space:
mode:
authorMartin Hibdon <mhibdon@google.com>2014-10-21 21:19:42 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2014-10-21 21:19:43 +0000
commita908d45ae3519ecdc82e187d1ab72e8991ea7ed8 (patch)
treeb80bd1dc818c7e748147aff06be1280cee0e7657 /emailcommon
parent9d73facfa8c1c16e2443bf26778ad70d17efc350 (diff)
parent4ecd51a794c5a2af1a5a838f22997b4e361acb8b (diff)
downloadandroid_packages_apps_Email-a908d45ae3519ecdc82e187d1ab72e8991ea7ed8.tar.gz
android_packages_apps_Email-a908d45ae3519ecdc82e187d1ab72e8991ea7ed8.tar.bz2
android_packages_apps_Email-a908d45ae3519ecdc82e187d1ab72e8991ea7ed8.zip
Merge "Add deprecated cipher suites to our SSLSocket" into ub-gmail-ur14-dev
Diffstat (limited to 'emailcommon')
-rw-r--r--emailcommon/src/com/android/emailcommon/utility/SSLSocketFactoryWrapper.java258
-rw-r--r--emailcommon/src/com/android/emailcommon/utility/SSLUtils.java62
2 files changed, 284 insertions, 36 deletions
diff --git a/emailcommon/src/com/android/emailcommon/utility/SSLSocketFactoryWrapper.java b/emailcommon/src/com/android/emailcommon/utility/SSLSocketFactoryWrapper.java
new file mode 100644
index 000000000..516084fe4
--- /dev/null
+++ b/emailcommon/src/com/android/emailcommon/utility/SSLSocketFactoryWrapper.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.emailcommon.utility;
+
+import com.android.mail.utils.LogUtils;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.net.ssl.*;
+import javax.net.ssl.SSLSocketFactory;
+
+public class SSLSocketFactoryWrapper extends javax.net.ssl.SSLSocketFactory {
+ private final SSLSocketFactory mFactory;
+ private final boolean mSecure;
+ private final int mHandshakeTimeout;
+ private final String[] mDefaultCipherSuites;
+
+ private final String[] DEPRECATED_CIPHER_SUITES_TO_ENABLE = new String[] {
+ "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
+ "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
+ "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
+ "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
+ "SSL_RSA_WITH_3DES_EDE_CBC_SHA",
+ "SSL_RSA_WITH_RC4_128_MD5",
+ "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
+ "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
+ "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
+ "TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
+ "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA",
+ "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",
+ "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
+ "TLS_ECDH_RSA_WITH_RC4_128_SHA",
+ "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
+ "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
+ "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
+ "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
+ "SSL_DHE_DSS_WITH_DES_CBC_SHA",
+ "SSL_DHE_RSA_WITH_DES_CBC_SHA",
+ "SSL_RSA_WITH_DES_CBC_SHA"
+ };
+
+ SSLSocketFactoryWrapper(final SSLSocketFactory factory, final boolean secure,
+ int handshakeTimeout) {
+ mFactory = factory;
+ mSecure = secure;
+ mHandshakeTimeout = handshakeTimeout;
+
+ // Find the base factory's list of defaultCipherSuites, and merge our extras with it.
+ // Remember that the order is important. We'll add our extras at the end, and only
+ // if they weren't already in the base factory's list.
+ final String[] baseDefaultCipherSuites = mFactory.getDefaultCipherSuites();
+ final List<String> fullCipherSuiteList = new ArrayList<String>(Arrays.asList(
+ mFactory.getDefaultCipherSuites()));
+ final Set<String> baseDefaultCipherSuiteSet = new HashSet<String>(fullCipherSuiteList);
+
+ final String[] baseSupportedCipherSuites = mFactory.getSupportedCipherSuites();
+ final Set<String> baseSupportedCipherSuiteSet = new HashSet<String>(Arrays.asList(
+ mFactory.getSupportedCipherSuites()));
+
+ for (String cipherSuite : DEPRECATED_CIPHER_SUITES_TO_ENABLE) {
+ if (baseSupportedCipherSuiteSet.contains(cipherSuite) &&
+ !baseDefaultCipherSuiteSet.contains(cipherSuite)) {
+ fullCipherSuiteList.add(cipherSuite);
+ }
+ }
+ mDefaultCipherSuites = new String[fullCipherSuiteList.size()];
+ fullCipherSuiteList.toArray(mDefaultCipherSuites);
+ }
+
+ public static SSLSocketFactory getDefault(final KeyManager[] keyManagers, int handshakeTimeout)
+ throws NoSuchAlgorithmException, KeyManagementException{
+ final SSLContext context = SSLContext.getInstance("TLS");
+ context.init(keyManagers, null, null);
+ return new SSLSocketFactoryWrapper(context.getSocketFactory(), true, handshakeTimeout);
+ }
+
+ public static SSLSocketFactory getInsecure(final KeyManager[] keyManagers,
+ final TrustManager[] trustManagers,
+ int handshakeTimeout)
+ throws NoSuchAlgorithmException, KeyManagementException {
+ final SSLContext context = SSLContext.getInstance("TLS");
+ context.init(keyManagers, trustManagers, null);
+ return new SSLSocketFactoryWrapper(context.getSocketFactory(), false, handshakeTimeout);
+ }
+
+ public Socket createSocket()throws IOException {
+ return mFactory.createSocket();
+ }
+
+ public Socket createSocket(final Socket socket, final String host, final int port,
+ final boolean autoClose) throws IOException {
+ final SSLSocket sslSocket = (SSLSocket)mFactory.createSocket(socket, host, port, autoClose);
+ setHandshakeTimeout(sslSocket, mHandshakeTimeout);
+ sslSocket.setEnabledCipherSuites(mDefaultCipherSuites);
+ if (mSecure) {
+ verifyHostname(sslSocket, host);
+ }
+ return sslSocket;
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
+ final SSLSocket sslSocket = (SSLSocket)mFactory.createSocket(host, port);
+ setHandshakeTimeout(sslSocket, mHandshakeTimeout);
+ sslSocket.setEnabledCipherSuites(mDefaultCipherSuites);
+ if (mSecure) {
+ verifyHostname(sslSocket, host);
+ }
+ return sslSocket;
+ }
+
+ @Override
+ public Socket createSocket(String host, int i, InetAddress inetAddress, int i2) throws
+ IOException, UnknownHostException {
+ final SSLSocket sslSocket = (SSLSocket)mFactory.createSocket(host, i, inetAddress, i2);
+ setHandshakeTimeout(sslSocket, mHandshakeTimeout);
+ sslSocket.setEnabledCipherSuites(mDefaultCipherSuites);
+ if (mSecure) {
+ verifyHostname(sslSocket, host);
+ }
+ return sslSocket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress inetAddress, int i) throws IOException {
+ final SSLSocket sslSocket = (SSLSocket)mFactory.createSocket(inetAddress, i);
+ setHandshakeTimeout(sslSocket, mHandshakeTimeout);
+ sslSocket.setEnabledCipherSuites(mDefaultCipherSuites);
+ return sslSocket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress2, int i2)
+ throws IOException {
+ final SSLSocket sslSocket = (SSLSocket)mFactory.createSocket(inetAddress, i, inetAddress2,
+ i2);
+ setHandshakeTimeout(sslSocket, mHandshakeTimeout);
+ sslSocket.setEnabledCipherSuites(mDefaultCipherSuites);
+ return sslSocket;
+ }
+
+ public String[] getDefaultCipherSuites() {
+ return mDefaultCipherSuites.clone();
+ }
+
+ public String[] getSupportedCipherSuites() {
+ return mFactory.getSupportedCipherSuites();
+ }
+
+ /**
+ * Attempt to set the hostname of the socket.
+ * @param sslSocket The SSLSocket
+ * @param hostname the hostname
+ * @return true if able to set the hostname, false if not.
+ */
+ public static boolean potentiallyEnableSni(SSLSocket sslSocket, String hostname) {
+ try {
+ // Many implementations of SSLSocket support setHostname, although it is not part of
+ // the class definition. We will attempt to setHostname using reflection. If the
+ // particular SSLSocket implementation we are using does not support this meethod,
+ // we'll fail and return false.
+ sslSocket.getClass().getMethod("setHostname", String.class).invoke(sslSocket, hostname);
+ return true;
+ } catch (Exception ignored) {
+ return false;
+ }
+ }
+
+ /**
+ * Attempt to enable session tickets.
+ * @param sslSocket the SSLSocket.
+ * @return true if able to enable session tickets, false otherwise.
+ */
+ public static boolean potentiallyEnableSessionTickets(SSLSocket sslSocket) {
+ try {
+ // Many implementations of SSLSocket support setUseSessionTickets, although it is not
+ // part of the class definition. We will attempt to setHostname using reflection. If the
+ // particular SSLSocket implementation we are using does not support this meethod,
+ // we'll fail and return false.
+ sslSocket.getClass().getMethod("setUseSessionTickets", boolean.class)
+ .invoke(sslSocket, true);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * Verify the hostname of the certificate used by the other end of a
+ * connected socket. You MUST call this if you did not supply a hostname
+ * to {@link #createSocket()}. It is harmless to call this method
+ * redundantly if the hostname has already been verified.
+ *
+ * @param socket An SSL socket which has been connected to a server
+ * @param hostname The expected hostname of the remote server
+ * @throws IOException if something goes wrong handshaking with the server
+ * @throws SSLPeerUnverifiedException if the server cannot prove its identity
+ *
+ * @hide
+ */
+ public static void verifyHostname(Socket socket, String hostname) throws IOException {
+ if (!(socket instanceof SSLSocket)) {
+ throw new IllegalArgumentException("Attempt to verify non-SSL socket");
+ }
+
+ // The code at the start of OpenSSLSocketImpl.startHandshake()
+ // ensures that the call is idempotent, so we can safely call it.
+ SSLSocket ssl = (SSLSocket) socket;
+ ssl.startHandshake();
+
+ SSLSession session = ssl.getSession();
+ LogUtils.d(LogUtils.TAG, "using cipherSuite %s", session.getCipherSuite());
+ if (session == null) {
+ throw new SSLException("Cannot verify SSL socket without session");
+ }
+ if (!HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session)) {
+ throw new SSLPeerUnverifiedException("Cannot verify hostname: " + hostname);
+ }
+ }
+
+ private void setHandshakeTimeout(SSLSocket sslSocket, int timeout) {
+ try {
+ // Most implementations of SSLSocket support setHandshakeTimeout(), but it is not
+ // actually part of the class definition. We will attempt to set it using reflection.
+ // If the particular implementation of SSLSocket we are using does not support this
+ // function, then we will just have to use the default handshake timeout.
+ sslSocket.getClass().getMethod("setHandshakeTimeout", int.class).invoke(sslSocket,
+ timeout);
+ } catch (Exception e) {
+ LogUtils.w(LogUtils.TAG, e, "unable to set handshake timeout");
+ }
+ }
+}
diff --git a/emailcommon/src/com/android/emailcommon/utility/SSLUtils.java b/emailcommon/src/com/android/emailcommon/utility/SSLUtils.java
index 779bc5296..f42e83c6e 100644
--- a/emailcommon/src/com/android/emailcommon/utility/SSLUtils.java
+++ b/emailcommon/src/com/android/emailcommon/utility/SSLUtils.java
@@ -20,8 +20,6 @@ import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
-import android.net.SSLCertificateSocketFactory;
-import android.net.SSLSessionCache;
import android.security.KeyChain;
import android.security.KeyChainException;
@@ -34,6 +32,8 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.PublicKey;
@@ -159,43 +159,33 @@ public class SSLUtils {
public synchronized static javax.net.ssl.SSLSocketFactory getSSLSocketFactory(
final Context context, final HostAuth hostAuth, final KeyManager keyManager,
final boolean insecure) {
- if (insecure) {
- final SSLCertificateSocketFactory insecureFactory = (SSLCertificateSocketFactory)
- SSLCertificateSocketFactory.getInsecure(SSL_HANDSHAKE_TIMEOUT, null);
- insecureFactory.setTrustManagers(
- new TrustManager[] {
- new SameCertificateCheckingTrustManager(context, hostAuth)});
- if (keyManager != null) {
- insecureFactory.setKeyManagers(new KeyManager[] { keyManager });
- }
- return insecureFactory;
- } else {
- if (sSecureFactory == null) {
- // First try to get use an externally supplied, more secure SSLSocketBuilder.
- // If so we should use that.
- javax.net.ssl.SSLSocketFactory socketFactory = null;
- if (sExternalSocketFactoryBuilder != null) {
- socketFactory = sExternalSocketFactoryBuilder.createSecureSocketFactory(
- context, SSL_HANDSHAKE_TIMEOUT);
- }
- if (socketFactory != null) {
- sSecureFactory = socketFactory;
- LogUtils.d(TAG, "Using externally created CertificateSocketFactory");
- return sSecureFactory;
- }
- // Only fall back to the platform one if that fails.
- LogUtils.d(TAG, "Falling back to platform CertificateSocketFactory");
- final SSLCertificateSocketFactory certificateSocketFactory =
- (SSLCertificateSocketFactory)
- SSLCertificateSocketFactory.getDefault(SSL_HANDSHAKE_TIMEOUT,
- new SSLSessionCache(context));
- if (keyManager != null) {
- certificateSocketFactory.setKeyManagers(new KeyManager[] { keyManager });
+ try {
+ final KeyManager[] keyManagers = (keyManager == null ? null :
+ new KeyManager[]{keyManager});
+ if (insecure) {
+ final TrustManager[] trustManagers = new TrustManager[]{
+ new SameCertificateCheckingTrustManager(context, hostAuth)};
+ SSLSocketFactoryWrapper insecureFactory =
+ (SSLSocketFactoryWrapper) SSLSocketFactoryWrapper.getInsecure(
+ keyManagers, trustManagers, SSL_HANDSHAKE_TIMEOUT);
+ return insecureFactory;
+ } else {
+ if (sSecureFactory == null) {
+ SSLSocketFactoryWrapper secureFactory =
+ (SSLSocketFactoryWrapper) SSLSocketFactoryWrapper.getDefault(
+ keyManagers, SSL_HANDSHAKE_TIMEOUT);
+ sSecureFactory = secureFactory;
}
- sSecureFactory = certificateSocketFactory;
+ return sSecureFactory;
}
- return sSecureFactory;
+ } catch (NoSuchAlgorithmException e) {
+ LogUtils.wtf(TAG, e, "Unable to acquire SSLSocketFactory");
+ // TODO: what can we do about this?
+ } catch (KeyManagementException e) {
+ LogUtils.wtf(TAG, e, "Unable to acquire SSLSocketFactory");
+ // TODO: what can we do about this?
}
+ return null;
}
/**