1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-27 11:42:16 -05:00

Remove trusted socket factory statics

This commit is contained in:
Jan Berkel 2014-12-17 16:12:55 +01:00
parent 27e0c75021
commit 16f8a3ef14
7 changed files with 213 additions and 173 deletions

View File

@ -30,6 +30,8 @@ import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Folder.FolderClass;
import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.mail.ssl.DefaultTrustedSocketFactory;
import com.fsck.k9.mail.ssl.TrustedSocketFactory;
import com.fsck.k9.mail.store.RemoteStore;
import com.fsck.k9.mail.store.StoreConfig;
import com.fsck.k9.mailstore.StorageManager;
@ -890,6 +892,11 @@ public class Account implements BaseAccount, StoreConfig {
return Uri.parse("content://accounts/" + getUuid());
}
@Override
public TrustedSocketFactory trustedSocketFactory() {
return new DefaultTrustedSocketFactory(K9.app);
}
public synchronized String getStoreUri() {
return mStoreUri;
}

View File

@ -0,0 +1,169 @@
package com.fsck.k9.mail.ssl;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import com.fsck.k9.mail.MessagingException;
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;
import java.io.IOException;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
/**
* Filter and reorder list of cipher suites and TLS versions.
*/
public class DefaultTrustedSocketFactory implements TrustedSocketFactory {
protected static final String ENABLED_CIPHERS[];
protected static final String ENABLED_PROTOCOLS[];
// Order taken from OpenSSL 1.0.1c
protected static final String ORDERED_KNOWN_CIPHERS[] = {
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
"SSL_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDH_RSA_WITH_RC4_128_SHA",
"TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
"SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
"SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
"SSL_RSA_WITH_RC4_128_SHA",
"SSL_RSA_WITH_RC4_128_MD5",
};
protected static final String[] BLACKLISTED_CIPHERS = {
"SSL_RSA_WITH_DES_CBC_SHA",
"SSL_DHE_RSA_WITH_DES_CBC_SHA",
"SSL_DHE_DSS_WITH_DES_CBC_SHA",
"SSL_RSA_EXPORT_WITH_RC4_40_MD5",
"SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
};
protected static final String ORDERED_KNOWN_PROTOCOLS[] = {
"TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3"
};
static {
String[] enabledCiphers = null;
String[] supportedProtocols = null;
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, null, null);
SSLSocketFactory sf = sslContext.getSocketFactory();
SSLSocket sock = (SSLSocket) sf.createSocket();
enabledCiphers = sock.getEnabledCipherSuites();
/*
* Retrieve all supported protocols, not just the (default) enabled
* ones. TLSv1.1 & TLSv1.2 are supported on API levels 16+, but are
* only enabled by default on API levels 20+.
*/
supportedProtocols = sock.getSupportedProtocols();
} catch (Exception e) {
Log.e(LOG_TAG, "Error getting information about available SSL/TLS ciphers and " +
"protocols", e);
}
ENABLED_CIPHERS = (enabledCiphers == null) ? null :
reorder(enabledCiphers, ORDERED_KNOWN_CIPHERS, BLACKLISTED_CIPHERS);
ENABLED_PROTOCOLS = (supportedProtocols == null) ? null :
reorder(supportedProtocols, ORDERED_KNOWN_PROTOCOLS, null);
}
public DefaultTrustedSocketFactory(Context context) {
this.context = context;
}
protected static String[] reorder(String[] enabled, String[] known, String[] blacklisted) {
List<String> unknown = new ArrayList<String>();
Collections.addAll(unknown, enabled);
// Remove blacklisted items
if (blacklisted != null) {
for (String item : blacklisted) {
unknown.remove(item);
}
}
// Order known items
List<String> result = new ArrayList<String>();
for (String item : known) {
if (unknown.remove(item)) {
result.add(item);
}
}
// Add unknown items at the end. This way security won't get worse when unknown ciphers
// start showing up in the future.
result.addAll(unknown);
return result.toArray(new String[result.size()]);
}
private Context context;
public Socket createSocket(Socket socket, String host, int port, String clientCertificateAlias)
throws NoSuchAlgorithmException, KeyManagementException, MessagingException, IOException {
TrustManager[] trustManagers = new TrustManager[] { TrustManagerFactory.get(host, port) };
KeyManager[] keyManagers = null;
if (!TextUtils.isEmpty(clientCertificateAlias)) {
keyManagers = new KeyManager[] { new KeyChainKeyManager(context, clientCertificateAlias) };
}
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
Socket trustedSocket;
if (socket == null) {
trustedSocket = socketFactory.createSocket();
} else {
trustedSocket = socketFactory.createSocket(socket, host, port, true);
}
hardenSocket((SSLSocket) trustedSocket);
return trustedSocket;
}
private static void hardenSocket(SSLSocket sock) {
if (ENABLED_CIPHERS != null) {
sock.setEnabledCipherSuites(ENABLED_CIPHERS);
}
if (ENABLED_PROTOCOLS != null) {
sock.setEnabledProtocols(ENABLED_PROTOCOLS);
}
}
}

View File

@ -1,168 +1,13 @@
package com.fsck.k9.mail.ssl;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.mail.MessagingException;
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;
import java.io.IOException;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
/**
* Filter and reorder list of cipher suites and TLS versions.
*/
public class TrustedSocketFactory {
protected static final String ENABLED_CIPHERS[];
protected static final String ENABLED_PROTOCOLS[];
// Order taken from OpenSSL 1.0.1c
protected static final String ORDERED_KNOWN_CIPHERS[] = {
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
"TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA",
"SSL_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
"TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDH_RSA_WITH_RC4_128_SHA",
"TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
"SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
"SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA",
"SSL_RSA_WITH_RC4_128_SHA",
"SSL_RSA_WITH_RC4_128_MD5",
};
protected static final String[] BLACKLISTED_CIPHERS = {
"SSL_RSA_WITH_DES_CBC_SHA",
"SSL_DHE_RSA_WITH_DES_CBC_SHA",
"SSL_DHE_DSS_WITH_DES_CBC_SHA",
"SSL_RSA_EXPORT_WITH_RC4_40_MD5",
"SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA",
};
protected static final String ORDERED_KNOWN_PROTOCOLS[] = {
"TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3"
};
static {
String[] enabledCiphers = null;
String[] supportedProtocols = null;
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, null, null);
SSLSocketFactory sf = sslContext.getSocketFactory();
SSLSocket sock = (SSLSocket) sf.createSocket();
enabledCiphers = sock.getEnabledCipherSuites();
/*
* Retrieve all supported protocols, not just the (default) enabled
* ones. TLSv1.1 & TLSv1.2 are supported on API levels 16+, but are
* only enabled by default on API levels 20+.
*/
supportedProtocols = sock.getSupportedProtocols();
} catch (Exception e) {
Log.e(LOG_TAG, "Error getting information about available SSL/TLS ciphers and " +
"protocols", e);
}
ENABLED_CIPHERS = (enabledCiphers == null) ? null :
reorder(enabledCiphers, ORDERED_KNOWN_CIPHERS, BLACKLISTED_CIPHERS);
ENABLED_PROTOCOLS = (supportedProtocols == null) ? null :
reorder(supportedProtocols, ORDERED_KNOWN_PROTOCOLS, null);
}
protected static String[] reorder(String[] enabled, String[] known, String[] blacklisted) {
List<String> unknown = new ArrayList<String>();
Collections.addAll(unknown, enabled);
// Remove blacklisted items
if (blacklisted != null) {
for (String item : blacklisted) {
unknown.remove(item);
}
}
// Order known items
List<String> result = new ArrayList<String>();
for (String item : known) {
if (unknown.remove(item)) {
result.add(item);
}
}
// Add unknown items at the end. This way security won't get worse when unknown ciphers
// start showing up in the future.
result.addAll(unknown);
return result.toArray(new String[result.size()]);
}
public static Socket createSocket(String host, int port, String clientCertificateAlias)
throws IOException, MessagingException, KeyManagementException, NoSuchAlgorithmException {
return createSocket(null, host, port, clientCertificateAlias);
}
public static Socket createSocket(Socket socket, String host, int port, String clientCertificateAlias)
throws NoSuchAlgorithmException, KeyManagementException, MessagingException, IOException {
TrustManager[] trustManagers = new TrustManager[] { TrustManagerFactory.get(host, port) };
KeyManager[] keyManagers = null;
if (clientCertificateAlias != null && !clientCertificateAlias.isEmpty()) {
keyManagers = new KeyManager[] { new KeyChainKeyManager(K9.app, clientCertificateAlias) };
}
SSLContext context = SSLContext.getInstance("TLS");
context.init(keyManagers, trustManagers, null);
SSLSocketFactory socketFactory = context.getSocketFactory();
Socket trustedSocket;
if (socket == null) {
trustedSocket = socketFactory.createSocket();
} else {
trustedSocket = socketFactory.createSocket(socket, host, port, true);
}
hardenSocket((SSLSocket) trustedSocket);
return trustedSocket;
}
private static void hardenSocket(SSLSocket sock) {
if (ENABLED_CIPHERS != null) {
sock.setEnabledCipherSuites(ENABLED_CIPHERS);
}
if (ENABLED_PROTOCOLS != null) {
sock.setEnabledProtocols(ENABLED_PROTOCOLS);
}
}
public interface TrustedSocketFactory {
Socket createSocket(Socket socket, String host, int port, String clientCertificateAlias)
throws NoSuchAlgorithmException, KeyManagementException, MessagingException, IOException;
}

View File

@ -84,10 +84,10 @@ import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.ssl.TrustedSocketFactory;
import com.fsck.k9.mail.store.ImapResponseParser.ImapList;
import com.fsck.k9.mail.store.ImapResponseParser.ImapResponse;
import com.fsck.k9.mail.transport.imap.ImapSettings;
import com.fsck.k9.mail.ssl.TrustedSocketFactory;
import com.beetstra.jutf7.CharsetProvider;
import com.jcraft.jzlib.JZlib;
@ -682,7 +682,7 @@ public class ImapStore extends RemoteStore {
@Override
public void checkSettings() throws MessagingException {
try {
ImapConnection connection = new ImapConnection(new StoreImapSettings());
ImapConnection connection = new ImapConnection(new StoreImapSettings(), mStoreConfig.trustedSocketFactory());
connection.open();
autoconfigureFolders(connection);
connection.close();
@ -697,7 +697,7 @@ public class ImapStore extends RemoteStore {
*/
private ImapConnection getConnection() throws MessagingException {
synchronized (mConnections) {
ImapConnection connection = null;
ImapConnection connection;
while ((connection = mConnections.poll()) != null) {
try {
connection.executeSimpleCommand("NOOP");
@ -707,7 +707,7 @@ public class ImapStore extends RemoteStore {
}
}
if (connection == null) {
connection = new ImapConnection(new StoreImapSettings());
connection = new ImapConnection(new StoreImapSettings(), mStoreConfig.trustedSocketFactory());
}
return connection;
}
@ -2315,6 +2315,7 @@ public class ImapStore extends RemoteStore {
* A cacheable class that stores the details for a single IMAP connection.
*/
public static class ImapConnection {
private final TrustedSocketFactory socketFactory;
private Socket mSocket;
private PeekableInputStream mIn;
private OutputStream mOut;
@ -2324,8 +2325,9 @@ public class ImapStore extends RemoteStore {
private ImapSettings mSettings;
public ImapConnection(final ImapSettings settings) {
public ImapConnection(final ImapSettings settings, TrustedSocketFactory socketFactory) {
this.mSettings = settings;
this.socketFactory = socketFactory;
}
protected String getLogId() {
@ -2405,8 +2407,11 @@ public class ImapStore extends RemoteStore {
mSettings.getPort());
if (connectionSecurity == ConnectionSecurity.SSL_TLS_REQUIRED) {
mSocket = TrustedSocketFactory.createSocket(mSettings.getHost(),
mSettings.getPort(), mSettings.getClientCertificateAlias());
mSocket = socketFactory.createSocket(
null,
mSettings.getHost(),
mSettings.getPort(),
mSettings.getClientCertificateAlias());
} else {
mSocket = new Socket();
}
@ -2455,8 +2460,10 @@ public class ImapStore extends RemoteStore {
// STARTTLS
executeSimpleCommand("STARTTLS");
mSocket = TrustedSocketFactory.createSocket(mSocket,
mSettings.getHost(), mSettings.getPort(),
mSocket = socketFactory.createSocket(
mSocket,
mSettings.getHost(),
mSettings.getPort(),
mSettings.getClientCertificateAlias());
mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
mIn = new PeekableInputStream(new BufferedInputStream(mSocket

View File

@ -8,7 +8,6 @@ import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.mail.filter.Hex;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.ssl.TrustedSocketFactory;
import com.fsck.k9.mail.MessageRetrievalListener;
import javax.net.ssl.SSLException;
@ -303,7 +302,7 @@ public class Pop3Store extends RemoteStore {
try {
SocketAddress socketAddress = new InetSocketAddress(mHost, mPort);
if (mConnectionSecurity == ConnectionSecurity.SSL_TLS_REQUIRED) {
mSocket = TrustedSocketFactory.createSocket(mHost, mPort, mClientCertificateAlias);
mSocket = mStoreConfig.trustedSocketFactory().createSocket(null, mHost, mPort, mClientCertificateAlias);
} else {
mSocket = new Socket();
}
@ -325,7 +324,10 @@ public class Pop3Store extends RemoteStore {
if (mCapabilities.stls) {
executeSimpleCommand(STLS_COMMAND);
mSocket = TrustedSocketFactory.createSocket(mSocket, mHost, mPort,
mSocket = mStoreConfig.trustedSocketFactory().createSocket(
mSocket,
mHost,
mPort,
mClientCertificateAlias);
mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);

View File

@ -1,5 +1,7 @@
package com.fsck.k9.mail.store;
import com.fsck.k9.mail.ssl.TrustedSocketFactory;
public interface StoreConfig {
String getStoreUri();
String getTransportUri();
@ -28,4 +30,6 @@ public interface StoreConfig {
int getDisplayCount();
int getIdleRefreshMinutes();
TrustedSocketFactory trustedSocketFactory();
}

View File

@ -12,8 +12,8 @@ import com.fsck.k9.mail.filter.PeekableInputStream;
import com.fsck.k9.mail.filter.SmtpDataStuffing;
import com.fsck.k9.mail.internet.CharsetSupport;
import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.store.StoreConfig;
import com.fsck.k9.mail.ssl.TrustedSocketFactory;
import com.fsck.k9.mail.store.StoreConfig;
import javax.net.ssl.SSLException;
@ -30,6 +30,8 @@ import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
import static com.fsck.k9.mail.CertificateValidationException.Reason.MissingCapability;
public class SmtpTransport extends Transport {
private TrustedSocketFactory mTrustedSocketFactory;
public static final String TRANSPORT_TYPE = "SMTP";
/**
@ -201,6 +203,7 @@ public class SmtpTransport extends Transport {
mUsername = settings.username;
mPassword = settings.password;
mClientCertificateAlias = settings.clientCertificateAlias;
mTrustedSocketFactory = storeConfig.trustedSocketFactory();
}
@Override
@ -212,7 +215,7 @@ public class SmtpTransport extends Transport {
try {
SocketAddress socketAddress = new InetSocketAddress(addresses[i], mPort);
if (mConnectionSecurity == ConnectionSecurity.SSL_TLS_REQUIRED) {
mSocket = TrustedSocketFactory.createSocket(mHost, mPort, mClientCertificateAlias);
mSocket = mTrustedSocketFactory.createSocket(null, mHost, mPort, mClientCertificateAlias);
mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
secureConnection = true;
} else {
@ -266,7 +269,10 @@ public class SmtpTransport extends Transport {
if (extensions.containsKey("STARTTLS")) {
executeSimpleCommand("STARTTLS");
mSocket = TrustedSocketFactory.createSocket(mSocket, mHost, mPort,
mSocket = mTrustedSocketFactory.createSocket(
mSocket,
mHost,
mPort,
mClientCertificateAlias);
mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(),