Last active
August 29, 2015 14:18
-
-
Save patrickhammond/ecc3fcb82f4e6f2b675d to your computer and use it in GitHub Desktop.
Workaround to https://code.google.com/p/android/issues/detail?id=78187 where a connection from the upgraded Play Services SSL provider will be downgraded to use SSLv3 on older versions of Android and result in a connection failure if the server does not accept that protocol (and it shouldn't due to POODLE).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.os.Build; | |
import android.util.Log; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import java.net.InetAddress; | |
import java.net.Socket; | |
import java.net.SocketAddress; | |
import java.net.SocketException; | |
import java.nio.channels.SocketChannel; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.List; | |
import javax.net.ssl.HandshakeCompletedListener; | |
import javax.net.ssl.HttpsURLConnection; | |
import javax.net.ssl.SSLParameters; | |
import javax.net.ssl.SSLSession; | |
import javax.net.ssl.SSLSocket; | |
import javax.net.ssl.SSLSocketFactory; | |
public class NoSSLv3Util { | |
private static final String TAG = NoSSLv3Util.class.getSimpleName(); | |
public static void updateSSLFactory() { | |
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { | |
SSLSocketFactory sslSocketFactory = new NoSSLv3Factory(); | |
HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory); | |
} | |
} | |
private static class NoSSLv3SSLSocket extends DelegateSSLSocket { | |
private NoSSLv3SSLSocket(SSLSocket delegate) { | |
super(delegate); | |
} | |
@Override | |
public void setEnabledProtocols(String[] protocols) { | |
if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) { | |
List<String> enabledProtocols = new ArrayList<>(Arrays.asList(delegate.getEnabledProtocols())); | |
if (enabledProtocols.size() > 1) { | |
enabledProtocols.remove("SSLv3"); | |
Log.d(TAG, "Removed SSLv3 from enabled protocols"); | |
} else { | |
Log.d(TAG, "SSL stuck with protocol available for " + String.valueOf(enabledProtocols)); | |
} | |
protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]); | |
} | |
super.setEnabledProtocols(protocols); | |
} | |
} | |
public static class NoSSLv3Factory extends SSLSocketFactory { | |
private final SSLSocketFactory delegate; | |
public NoSSLv3Factory() { | |
this.delegate = HttpsURLConnection.getDefaultSSLSocketFactory(); | |
} | |
@Override | |
public String[] getDefaultCipherSuites() { | |
return delegate.getDefaultCipherSuites(); | |
} | |
@Override | |
public String[] getSupportedCipherSuites() { | |
return delegate.getSupportedCipherSuites(); | |
} | |
private static Socket makeSocketSafe(Socket socket) { | |
if (socket instanceof SSLSocket) { | |
socket = new NoSSLv3SSLSocket((SSLSocket) socket); | |
} | |
return socket; | |
} | |
@Override | |
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { | |
return makeSocketSafe(delegate.createSocket(s, host, port, autoClose)); | |
} | |
@Override | |
public Socket createSocket(String host, int port) throws IOException { | |
return makeSocketSafe(delegate.createSocket(host, port)); | |
} | |
@Override | |
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { | |
return makeSocketSafe(delegate.createSocket(host, port, localHost, localPort)); | |
} | |
@Override | |
public Socket createSocket(InetAddress host, int port) throws IOException { | |
return makeSocketSafe(delegate.createSocket(host, port)); | |
} | |
@Override | |
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { | |
return makeSocketSafe(delegate.createSocket(address, port, localAddress, localPort)); | |
} | |
} | |
private static class DelegateSSLSocket extends SSLSocket { | |
protected final SSLSocket delegate; | |
DelegateSSLSocket(SSLSocket delegate) { | |
this.delegate = delegate; | |
} | |
@Override | |
public String[] getSupportedCipherSuites() { | |
return delegate.getSupportedCipherSuites(); | |
} | |
@Override | |
public String[] getEnabledCipherSuites() { | |
return delegate.getEnabledCipherSuites(); | |
} | |
@Override | |
public void setEnabledCipherSuites(String[] suites) { | |
delegate.setEnabledCipherSuites(suites); | |
} | |
@Override | |
public String[] getSupportedProtocols() { | |
return delegate.getSupportedProtocols(); | |
} | |
@Override | |
public String[] getEnabledProtocols() { | |
return delegate.getEnabledProtocols(); | |
} | |
@Override | |
public void setEnabledProtocols(String[] protocols) { | |
delegate.setEnabledProtocols(protocols); | |
} | |
@Override | |
public SSLSession getSession() { | |
return delegate.getSession(); | |
} | |
@Override | |
public void addHandshakeCompletedListener(HandshakeCompletedListener listener) { | |
delegate.addHandshakeCompletedListener(listener); | |
} | |
@Override | |
public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) { | |
delegate.removeHandshakeCompletedListener(listener); | |
} | |
@Override | |
public void startHandshake() throws IOException { | |
delegate.startHandshake(); | |
} | |
@Override | |
public void setUseClientMode(boolean mode) { | |
delegate.setUseClientMode(mode); | |
} | |
@Override | |
public boolean getUseClientMode() { | |
return delegate.getUseClientMode(); | |
} | |
@Override | |
public void setNeedClientAuth(boolean need) { | |
delegate.setNeedClientAuth(need); | |
} | |
@Override | |
public void setWantClientAuth(boolean want) { | |
delegate.setWantClientAuth(want); | |
} | |
@Override | |
public boolean getNeedClientAuth() { | |
return delegate.getNeedClientAuth(); | |
} | |
@Override | |
public boolean getWantClientAuth() { | |
return delegate.getWantClientAuth(); | |
} | |
@Override | |
public void setEnableSessionCreation(boolean flag) { | |
delegate.setEnableSessionCreation(flag); | |
} | |
@Override | |
public boolean getEnableSessionCreation() { | |
return delegate.getEnableSessionCreation(); | |
} | |
@Override | |
public void bind(SocketAddress localAddr) throws IOException { | |
delegate.bind(localAddr); | |
} | |
@Override | |
public synchronized void close() throws IOException { | |
delegate.close(); | |
} | |
@Override | |
public void connect(SocketAddress remoteAddr) throws IOException { | |
delegate.connect(remoteAddr); | |
} | |
@Override | |
public void connect(SocketAddress remoteAddr, int timeout) throws IOException { | |
delegate.connect(remoteAddr, timeout); | |
} | |
@Override | |
public SocketChannel getChannel() { | |
return delegate.getChannel(); | |
} | |
@Override | |
public InetAddress getInetAddress() { | |
return delegate.getInetAddress(); | |
} | |
@Override | |
public InputStream getInputStream() throws IOException { | |
return delegate.getInputStream(); | |
} | |
@Override | |
public boolean getKeepAlive() throws SocketException { | |
return delegate.getKeepAlive(); | |
} | |
@Override | |
public InetAddress getLocalAddress() { | |
return delegate.getLocalAddress(); | |
} | |
@Override | |
public int getLocalPort() { | |
return delegate.getLocalPort(); | |
} | |
@Override | |
public SocketAddress getLocalSocketAddress() { | |
return delegate.getLocalSocketAddress(); | |
} | |
@Override | |
public boolean getOOBInline() throws SocketException { | |
return delegate.getOOBInline(); | |
} | |
@Override | |
public OutputStream getOutputStream() throws IOException { | |
return delegate.getOutputStream(); | |
} | |
@Override | |
public int getPort() { | |
return delegate.getPort(); | |
} | |
@Override | |
public synchronized int getReceiveBufferSize() throws SocketException { | |
return delegate.getReceiveBufferSize(); | |
} | |
@Override | |
public SocketAddress getRemoteSocketAddress() { | |
return delegate.getRemoteSocketAddress(); | |
} | |
@Override | |
public boolean getReuseAddress() throws SocketException { | |
return delegate.getReuseAddress(); | |
} | |
@Override | |
public synchronized int getSendBufferSize() throws SocketException { | |
return delegate.getSendBufferSize(); | |
} | |
@Override | |
public int getSoLinger() throws SocketException { | |
return delegate.getSoLinger(); | |
} | |
@Override | |
public synchronized int getSoTimeout() throws SocketException { | |
return delegate.getSoTimeout(); | |
} | |
@Override | |
public boolean getTcpNoDelay() throws SocketException { | |
return delegate.getTcpNoDelay(); | |
} | |
@Override | |
public int getTrafficClass() throws SocketException { | |
return delegate.getTrafficClass(); | |
} | |
@Override | |
public boolean isBound() { | |
return delegate.isBound(); | |
} | |
@Override | |
public boolean isClosed() { | |
return delegate.isClosed(); | |
} | |
@Override | |
public boolean isConnected() { | |
return delegate.isConnected(); | |
} | |
@Override | |
public boolean isInputShutdown() { | |
return delegate.isInputShutdown(); | |
} | |
@Override | |
public boolean isOutputShutdown() { | |
return delegate.isOutputShutdown(); | |
} | |
@Override | |
public void sendUrgentData(int value) throws IOException { | |
delegate.sendUrgentData(value); | |
} | |
@Override | |
public void setKeepAlive(boolean keepAlive) throws SocketException { | |
delegate.setKeepAlive(keepAlive); | |
} | |
@Override | |
public void setOOBInline(boolean oobinline) throws SocketException { | |
delegate.setOOBInline(oobinline); | |
} | |
@Override | |
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { | |
delegate.setPerformancePreferences(connectionTime, latency, bandwidth); | |
} | |
@Override | |
public synchronized void setReceiveBufferSize(int size) throws SocketException { | |
delegate.setReceiveBufferSize(size); | |
} | |
@Override | |
public void setReuseAddress(boolean reuse) throws SocketException { | |
delegate.setReuseAddress(reuse); | |
} | |
@Override | |
public synchronized void setSendBufferSize(int size) throws SocketException { | |
delegate.setSendBufferSize(size); | |
} | |
@Override | |
public void setSoLinger(boolean on, int timeout) throws SocketException { | |
delegate.setSoLinger(on, timeout); | |
} | |
@Override | |
public synchronized void setSoTimeout(int timeout) throws SocketException { | |
delegate.setSoTimeout(timeout); | |
} | |
@Override | |
public void setSSLParameters(SSLParameters p) { | |
delegate.setSSLParameters(p); | |
} | |
@Override | |
public void setTcpNoDelay(boolean on) throws SocketException { | |
delegate.setTcpNoDelay(on); | |
} | |
@Override | |
public void setTrafficClass(int value) throws SocketException { | |
delegate.setTrafficClass(value); | |
} | |
@Override | |
public void shutdownInput() throws IOException { | |
delegate.shutdownInput(); | |
} | |
@Override | |
public void shutdownOutput() throws IOException { | |
delegate.shutdownOutput(); | |
} | |
@Override | |
public String toString() { | |
return delegate.toString(); | |
} | |
@Override | |
public boolean equals(Object o) { | |
return delegate.equals(o); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// In case the provider upgrade fails or something else tries to use the network stack before upgrade completes | |
NoSSLv3Util.updateSSLFactory(); | |
ProviderInstaller.installIfNeededAsync(this, new ProviderInstallListener() { | |
@Override | |
public void onProviderInstalled() { | |
NoSSLv3Util.updateSSLFactory(); // And again in case it got replaced during upgrade | |
} | |
@Override | |
public void onProviderInstallFailed(int errorCode, Intent recoveryIntent) { | |
GooglePlayServicesUtil.showErrorNotification(errorCode, MyIndexApplication.this); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment