package com.seafile.seadroid2.ssl; import android.os.Build; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; /** * Custom SSLSocketFactory. * * Used to manually select TLS protocol versions. * * based on: * https://stackoverflow.com/questions/1037590/which-cipher-suites-to-enable-for-ssl-socket */ public class SSLSeafileSocketFactory extends SSLSocketFactory { private SSLContext context; private String[] allowedCiphers; private String[] allowedProtocols; public SSLSeafileSocketFactory(KeyManager[] km, TrustManager[] tm, SecureRandom random) throws NoSuchAlgorithmException, KeyManagementException { context = SSLContext.getInstance("TLS"); context.init(km, tm, random); allowedProtocols = getProtocolList(); allowedCiphers = getCipherList(); } public String[] getDefaultCipherSuites() { return allowedCiphers; } public String[] getSupportedCipherSuites() { return allowedCiphers; } public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { SSLSocketFactory factory = context.getSocketFactory(); SSLSocket ss = (SSLSocket)factory.createSocket(s, host, port, autoClose); ss.setEnabledProtocols(allowedProtocols); ss.setEnabledCipherSuites(allowedCiphers); return ss; } public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { SSLSocketFactory factory = context.getSocketFactory(); SSLSocket ss = (SSLSocket)factory.createSocket(address, port, localAddress, localPort); ss.setEnabledProtocols(allowedProtocols); ss.setEnabledCipherSuites(allowedCiphers); return ss; } public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { SSLSocketFactory factory = context.getSocketFactory(); SSLSocket ss = (SSLSocket)factory.createSocket(host, port, localHost, localPort); ss.setEnabledProtocols(allowedProtocols); ss.setEnabledCipherSuites(allowedCiphers); return ss; } public Socket createSocket(InetAddress host, int port) throws IOException { SSLSocketFactory factory = context.getSocketFactory(); SSLSocket ss = (SSLSocket)factory.createSocket(host, port); ss.setEnabledProtocols(allowedProtocols); ss.setEnabledCipherSuites(allowedCiphers); return ss; } public Socket createSocket(String host, int port) throws IOException { SSLSocketFactory factory = context.getSocketFactory(); SSLSocket ss = (SSLSocket)factory.createSocket(host, port); ss.setEnabledProtocols(allowedProtocols); ss.setEnabledCipherSuites(allowedCiphers); return ss; } protected String[] getProtocolList() { // don't offer SSLv2 or SSLv3 if (Build.VERSION.SDK_INT >= 16) { return new String[]{ "TLSv1", "TLSv1.1", "TLSv1.2" }; } else { return new String[]{ "TLSv1" }; } } protected String[] getCipherList() { // only allow ciphers which are still considered secure. // based on: // https://briansmith.org/browser-ciphersuites-01.html String[] preferredCiphers; // Android up to 2.2 use other names if (Build.VERSION.SDK_INT <= 8) { preferredCiphers = new String[] { "DHE-RSA-AES128-SHA", "DHE-RSA-AES256-SHA", "DHE-DSS-AES128-SHA", "AES128-SHA", "AES256-SHA" }; } else { preferredCiphers = new String[] { "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "TLS_ECDHE_ECDSA_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", // backward compatibility. offers no forward security. "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA", // RFC 5746 "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" }; } // now filter out any ciphers that aren't supported by this device SSLSocketFactory factory = context.getSocketFactory(); String[] availableCiphers = factory.getSupportedCipherSuites(); ArrayList<String> available = new ArrayList<String>(Arrays.asList(availableCiphers)); List<String> result = new ArrayList<String>(); for(int i = 0; i < preferredCiphers.length; i++) { if(available.contains(preferredCiphers[i])) result.add(preferredCiphers[i]); } return result.toArray(new String[0]); } }