Created
August 24, 2017 16:58
-
-
Save tanaykumarbera/b84281961f5cfe74035ad94fe251fc0d to your computer and use it in GitHub Desktop.
This was initially mirrored from bitfireAT. Since they have refactored most of their codebase, I am keeping a copy here. More about this at https://blog.tanay.co/sslexception-or-sslhandshakeexception-due-to-sslv3-on-android
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
/* | |
* Copyright © 2013 – 2015 Ricki Hirner (bitfire web engineering). | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the GNU Public License v3.0 | |
* which accompanies this distribution, and is available at | |
* http://www.gnu.org/licenses/gpl.html | |
*/ | |
package at.bitfire.davdroid; | |
import android.os.Build; | |
import android.support.annotation.NonNull; | |
import android.text.TextUtils; | |
import java.io.IOException; | |
import java.net.InetAddress; | |
import java.net.Socket; | |
import java.security.GeneralSecurityException; | |
import java.util.Arrays; | |
import java.util.HashSet; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.Locale; | |
import javax.net.ssl.SSLContext; | |
import javax.net.ssl.SSLSocket; | |
import javax.net.ssl.SSLSocketFactory; | |
import javax.net.ssl.X509TrustManager; | |
import lombok.Cleanup; | |
public class SSLSocketFactoryCompat extends SSLSocketFactory { | |
private SSLSocketFactory delegate; | |
// Android 5.0+ (API level21) provides reasonable default settings | |
// but it still allows SSLv3 | |
// https://developer.android.com/reference/javax/net/ssl/SSLSocket.html | |
static String protocols[] = null, cipherSuites[] = null; | |
static { | |
try { | |
@Cleanup SSLSocket socket = (SSLSocket)SSLSocketFactory.getDefault().createSocket(); | |
if (socket != null) { | |
/* set reasonable protocol versions */ | |
// - enable all supported protocols (enables TLSv1.1 and TLSv1.2 on Android <5.0) | |
// - remove all SSL versions (especially SSLv3) because they're insecure now | |
List<String> protocols = new LinkedList<>(); | |
for (String protocol : socket.getSupportedProtocols()) | |
if (!protocol.toUpperCase(Locale.US).contains("SSL")) | |
protocols.add(protocol); | |
App.log.info("Setting allowed TLS protocols: " + TextUtils.join(", ", protocols)); | |
SSLSocketFactoryCompat.protocols = protocols.toArray(new String[protocols.size()]); | |
/* set up reasonable cipher suites */ | |
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { | |
// choose known secure cipher suites | |
List<String> allowedCiphers = Arrays.asList( | |
// TLS 1.2 | |
"TLS_RSA_WITH_AES_256_GCM_SHA384", | |
"TLS_RSA_WITH_AES_128_GCM_SHA256", | |
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", | |
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", | |
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", | |
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", | |
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", | |
// maximum interoperability | |
"TLS_RSA_WITH_3DES_EDE_CBC_SHA", | |
"TLS_RSA_WITH_AES_128_CBC_SHA", | |
// additionally | |
"TLS_RSA_WITH_AES_256_CBC_SHA", | |
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", | |
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", | |
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", | |
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"); | |
List<String> availableCiphers = Arrays.asList(socket.getSupportedCipherSuites()); | |
App.log.info("Available cipher suites: " + TextUtils.join(", ", availableCiphers)); | |
App.log.info("Cipher suites enabled by default: " + TextUtils.join(", ", socket.getEnabledCipherSuites())); | |
// take all allowed ciphers that are available and put them into preferredCiphers | |
HashSet<String> preferredCiphers = new HashSet<>(allowedCiphers); | |
preferredCiphers.retainAll(availableCiphers); | |
/* For maximum security, preferredCiphers should *replace* enabled ciphers (thus disabling | |
* ciphers which are enabled by default, but have become unsecure), but I guess for | |
* the security level of DAVdroid and maximum compatibility, disabling of insecure | |
* ciphers should be a server-side task */ | |
// add preferred ciphers to enabled ciphers | |
HashSet<String> enabledCiphers = preferredCiphers; | |
enabledCiphers.addAll(new HashSet<>(Arrays.asList(socket.getEnabledCipherSuites()))); | |
App.log.info("Enabling (only) those TLS ciphers: " + TextUtils.join(", ", enabledCiphers)); | |
SSLSocketFactoryCompat.cipherSuites = enabledCiphers.toArray(new String[enabledCiphers.size()]); | |
} | |
} | |
} catch (IOException e) { | |
App.log.severe("Couldn't determine default TLS settings"); | |
} | |
} | |
public SSLSocketFactoryCompat(@NonNull X509TrustManager trustManager) { | |
try { | |
SSLContext sslContext = SSLContext.getInstance("TLS"); | |
sslContext.init(null, new X509TrustManager[] { trustManager }, null); | |
delegate = sslContext.getSocketFactory(); | |
} catch (GeneralSecurityException e) { | |
throw new AssertionError(); // The system has no TLS. Just give up. | |
} | |
} | |
private void upgradeTLS(SSLSocket ssl) { | |
if (protocols != null) | |
ssl.setEnabledProtocols(protocols); | |
if (cipherSuites != null) | |
ssl.setEnabledCipherSuites(cipherSuites); | |
} | |
@Override | |
public String[] getDefaultCipherSuites() { | |
return cipherSuites; | |
} | |
@Override | |
public String[] getSupportedCipherSuites() { | |
return cipherSuites; | |
} | |
@Override | |
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { | |
Socket ssl = delegate.createSocket(s, host, port, autoClose); | |
if (ssl instanceof SSLSocket) | |
upgradeTLS((SSLSocket)ssl); | |
return ssl; | |
} | |
@Override | |
public Socket createSocket(String host, int port) throws IOException { | |
Socket ssl = delegate.createSocket(host, port); | |
if (ssl instanceof SSLSocket) | |
upgradeTLS((SSLSocket)ssl); | |
return ssl; | |
} | |
@Override | |
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { | |
Socket ssl = delegate.createSocket(host, port, localHost, localPort); | |
if (ssl instanceof SSLSocket) | |
upgradeTLS((SSLSocket)ssl); | |
return ssl; | |
} | |
@Override | |
public Socket createSocket(InetAddress host, int port) throws IOException { | |
Socket ssl = delegate.createSocket(host, port); | |
if (ssl instanceof SSLSocket) | |
upgradeTLS((SSLSocket)ssl); | |
return ssl; | |
} | |
@Override | |
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { | |
Socket ssl = delegate.createSocket(address, port, localAddress, localPort); | |
if (ssl instanceof SSLSocket) | |
upgradeTLS((SSLSocket)ssl); | |
return ssl; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment