am 78f4dcb5
: am 04b77626
: am a67e2212
: am be216a96
: am a908d45a
: Merge "Add deprecated cipher suites to our SSLSocket" into ub-gmail-ur14-dev
* commit '78f4dcb52be6818dd14fd148758895a3f5d3efad': Add deprecated cipher suites to our SSLSocket
This commit is contained in:
commit
eed5e7d102
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,8 +20,6 @@ import android.content.ContentUris;
|
|||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.SSLCertificateSocketFactory;
|
|
||||||
import android.net.SSLSessionCache;
|
|
||||||
import android.security.KeyChain;
|
import android.security.KeyChain;
|
||||||
import android.security.KeyChainException;
|
import android.security.KeyChainException;
|
||||||
|
|
||||||
@ -34,6 +32,8 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
@ -159,43 +159,33 @@ public class SSLUtils {
|
|||||||
public synchronized static javax.net.ssl.SSLSocketFactory getSSLSocketFactory(
|
public synchronized static javax.net.ssl.SSLSocketFactory getSSLSocketFactory(
|
||||||
final Context context, final HostAuth hostAuth, final KeyManager keyManager,
|
final Context context, final HostAuth hostAuth, final KeyManager keyManager,
|
||||||
final boolean insecure) {
|
final boolean insecure) {
|
||||||
if (insecure) {
|
try {
|
||||||
final SSLCertificateSocketFactory insecureFactory = (SSLCertificateSocketFactory)
|
final KeyManager[] keyManagers = (keyManager == null ? null :
|
||||||
SSLCertificateSocketFactory.getInsecure(SSL_HANDSHAKE_TIMEOUT, null);
|
new KeyManager[]{keyManager});
|
||||||
insecureFactory.setTrustManagers(
|
if (insecure) {
|
||||||
new TrustManager[] {
|
final TrustManager[] trustManagers = new TrustManager[]{
|
||||||
new SameCertificateCheckingTrustManager(context, hostAuth)});
|
new SameCertificateCheckingTrustManager(context, hostAuth)};
|
||||||
if (keyManager != null) {
|
SSLSocketFactoryWrapper insecureFactory =
|
||||||
insecureFactory.setKeyManagers(new KeyManager[] { keyManager });
|
(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;
|
||||||
|
}
|
||||||
|
return sSecureFactory;
|
||||||
}
|
}
|
||||||
return insecureFactory;
|
} catch (NoSuchAlgorithmException e) {
|
||||||
} else {
|
LogUtils.wtf(TAG, e, "Unable to acquire SSLSocketFactory");
|
||||||
if (sSecureFactory == null) {
|
// TODO: what can we do about this?
|
||||||
// First try to get use an externally supplied, more secure SSLSocketBuilder.
|
} catch (KeyManagementException e) {
|
||||||
// If so we should use that.
|
LogUtils.wtf(TAG, e, "Unable to acquire SSLSocketFactory");
|
||||||
javax.net.ssl.SSLSocketFactory socketFactory = null;
|
// TODO: what can we do about this?
|
||||||
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 });
|
|
||||||
}
|
|
||||||
sSecureFactory = certificateSocketFactory;
|
|
||||||
}
|
|
||||||
return sSecureFactory;
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user