diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index fd1876558..5c8d93981 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -2449,7 +2449,7 @@ public class ImapStore extends Store { sslContext.init(null, new TrustManager[] { TrustManagerFactory.get(mSettings.getHost(), secure) }, new SecureRandom()); - mSocket = sslContext.getSocketFactory().createSocket(); + mSocket = TrustedSocketFactory.createSocket(sslContext); } else { mSocket = new Socket(); } diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java index 7cac7b48e..1621c1527 100644 --- a/src/com/fsck/k9/mail/store/Pop3Store.java +++ b/src/com/fsck/k9/mail/store/Pop3Store.java @@ -330,7 +330,7 @@ public class Pop3Store extends Store { sslContext.init(null, new TrustManager[] { TrustManagerFactory.get(mHost, secure) }, new SecureRandom()); - mSocket = sslContext.getSocketFactory().createSocket(); + mSocket = TrustedSocketFactory.createSocket(sslContext); } else { mSocket = new Socket(); } diff --git a/src/com/fsck/k9/mail/store/TrustedSocketFactory.java b/src/com/fsck/k9/mail/store/TrustedSocketFactory.java new file mode 100644 index 000000000..5268c01ee --- /dev/null +++ b/src/com/fsck/k9/mail/store/TrustedSocketFactory.java @@ -0,0 +1,96 @@ +package com.fsck.k9.mail.store; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import java.io.IOException; +import java.net.Socket; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.*; + + +/** + * Filter and reorder list of cipher suites and TLS versions. + * + *

+ * See: http://op-co.de/blog/posts/android_ssl_downgrade/ + *

+ */ +public class TrustedSocketFactory { + protected static final String ENABLED_CIPHERS[]; + protected static final String ENABLED_PROTOCOLS[]; + + static { + String preferredCiphers[] = { + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "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", + "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "SSL_RSA_WITH_3DES_EDE_CBC_SHA", + "SSL_RSA_WITH_RC4_128_SHA", + "SSL_RSA_WITH_RC4_128_MD5", + }; + String preferredProtocols[] = { + "TLSv1.2", "TLSv1.1", "TLSv1" + }; + + String[] supportedCiphers = null; + String[] supportedProtocols = null; + + try { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, null, new SecureRandom()); + SSLSocketFactory sf = sslContext.getSocketFactory(); + supportedCiphers = sf.getSupportedCipherSuites(); + SSLSocket sock = (SSLSocket)sf.createSocket(); + supportedProtocols = sock.getSupportedProtocols(); + } catch (IOException ioe) { + ioe.printStackTrace(); + } catch (KeyManagementException kme) { + kme.printStackTrace(); + } catch (NoSuchAlgorithmException nsae) { + nsae.printStackTrace(); + } + + ENABLED_CIPHERS = supportedCiphers == null ? null : + filterBySupport(preferredCiphers, supportedCiphers); + ENABLED_PROTOCOLS = supportedProtocols == null ? null : + filterBySupport(preferredProtocols, supportedProtocols); + } + + protected static String[] filterBySupport(String[] preferred, String[] supported) { + List enabled = new ArrayList(); + Set available = new HashSet(); + Collections.addAll(available, supported); + + for (String item : preferred) { + if (available.contains(item)) enabled.add(item); + } + return enabled.toArray(new String[enabled.size()]); + } + + public static Socket createSocket(SSLContext sslContext) throws IOException { + SSLSocket socket = (SSLSocket) sslContext.getSocketFactory().createSocket(); + hardenSocket(socket); + + return socket; + } + + private static void hardenSocket(SSLSocket sock) { + if (ENABLED_CIPHERS != null) { + sock.setEnabledCipherSuites(ENABLED_CIPHERS); + } + if (ENABLED_PROTOCOLS != null) { + sock.setEnabledProtocols(ENABLED_PROTOCOLS); + } + } +} diff --git a/src/com/fsck/k9/mail/transport/TrustedSocketFactory.java b/src/com/fsck/k9/mail/store/WebDavSocketFactory.java similarity index 64% rename from src/com/fsck/k9/mail/transport/TrustedSocketFactory.java rename to src/com/fsck/k9/mail/store/WebDavSocketFactory.java index fa23f079b..2d4f959ed 100644 --- a/src/com/fsck/k9/mail/transport/TrustedSocketFactory.java +++ b/src/com/fsck/k9/mail/store/WebDavSocketFactory.java @@ -1,14 +1,9 @@ -package com.fsck.k9.mail.transport; +package com.fsck.k9.mail.store; -import com.fsck.k9.mail.store.TrustManagerFactory; import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.conn.scheme.LayeredSocketFactory; import org.apache.http.params.HttpParams; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; @@ -17,24 +12,34 @@ import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -public class TrustedSocketFactory implements LayeredSocketFactory { +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + + +/* + * TODO: find out what's going on here and document it. + * Using two socket factories looks suspicious. + */ +public class WebDavSocketFactory implements LayeredSocketFactory { private SSLSocketFactory mSocketFactory; private org.apache.http.conn.ssl.SSLSocketFactory mSchemeSocketFactory; - public TrustedSocketFactory(String host, boolean secure) throws NoSuchAlgorithmException, KeyManagementException { + public WebDavSocketFactory(String host, boolean secure) throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, new TrustManager[] { - TrustManagerFactory.get(host, secure) - }, new SecureRandom()); + TrustManagerFactory.get(host, secure) + }, new SecureRandom()); mSocketFactory = sslContext.getSocketFactory(); mSchemeSocketFactory = org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory(); mSchemeSocketFactory.setHostnameVerifier( - org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); } public Socket connectSocket(Socket sock, String host, int port, - InetAddress localAddress, int localPort, HttpParams params) - throws IOException, UnknownHostException, ConnectTimeoutException { + InetAddress localAddress, int localPort, HttpParams params) + throws IOException, UnknownHostException, ConnectTimeoutException { return mSchemeSocketFactory.connectSocket(sock, host, port, localAddress, localPort, params); } @@ -46,17 +51,17 @@ public class TrustedSocketFactory implements LayeredSocketFactory { return mSchemeSocketFactory.isSecure(sock); } public Socket createSocket( - final Socket socket, - final String host, - final int port, - final boolean autoClose + final Socket socket, + final String host, + final int port, + final boolean autoClose ) throws IOException, UnknownHostException { SSLSocket sslSocket = (SSLSocket) mSocketFactory.createSocket( - socket, - host, - port, - autoClose - ); + socket, + host, + port, + autoClose + ); //hostnameVerifier.verify(host, sslSocket); // verifyHostName() didn't blowup - good! return sslSocket; diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java index 6d4b69d3f..b3f8d5f24 100644 --- a/src/com/fsck/k9/mail/store/WebDavStore.java +++ b/src/com/fsck/k9/mail/store/WebDavStore.java @@ -10,7 +10,6 @@ import com.fsck.k9.mail.*; import com.fsck.k9.mail.filter.EOLConvertingOutputStream; import com.fsck.k9.mail.internet.MimeMessage; -import com.fsck.k9.mail.transport.TrustedSocketFactory; import org.apache.commons.io.IOUtils; import org.apache.http.*; import org.apache.http.client.CookieStore; @@ -1080,7 +1079,7 @@ public class WebDavStore extends Store { SchemeRegistry reg = mHttpClient.getConnectionManager().getSchemeRegistry(); try { - Scheme s = new Scheme("https", new TrustedSocketFactory(mHost, mSecure), 443); + Scheme s = new Scheme("https", new WebDavSocketFactory(mHost, mSecure), 443); reg.register(s); } catch (NoSuchAlgorithmException nsa) { Log.e(K9.LOG_TAG, "NoSuchAlgorithmException in getHttpClient: " + nsa); diff --git a/src/com/fsck/k9/mail/transport/SmtpTransport.java b/src/com/fsck/k9/mail/transport/SmtpTransport.java index 30ad91b4d..4e71d3ee8 100644 --- a/src/com/fsck/k9/mail/transport/SmtpTransport.java +++ b/src/com/fsck/k9/mail/transport/SmtpTransport.java @@ -14,6 +14,7 @@ import com.fsck.k9.mail.filter.SmtpDataStuffing; import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.store.TrustManagerFactory; import com.fsck.k9.mail.store.LocalStore.LocalMessage; +import com.fsck.k9.mail.store.TrustedSocketFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; @@ -244,7 +245,7 @@ public class SmtpTransport extends Transport { sslContext.init(null, new TrustManager[] { TrustManagerFactory.get(mHost, secure) }, new SecureRandom()); - mSocket = sslContext.getSocketFactory().createSocket(); + mSocket = TrustedSocketFactory.createSocket(sslContext); mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT); } else { mSocket = new Socket();