diff --git a/src/com/fsck/k9/helper/DomainNameChecker.java b/src/com/fsck/k9/helper/DomainNameChecker.java deleted file mode 100644 index 6731de648..000000000 --- a/src/com/fsck/k9/helper/DomainNameChecker.java +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.fsck.k9.helper; - -import android.net.http.SslCertificate; -import android.util.Log; -import com.fsck.k9.K9; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.security.cert.X509Certificate; -import java.security.cert.CertificateParsingException; -import java.util.Collection; -import java.util.List; -import java.util.Locale; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -/** - * Implements basic domain-name validation as specified by RFC2818. - */ -public class DomainNameChecker { - private static Pattern QUICK_IP_PATTERN; - static { - try { - QUICK_IP_PATTERN = Pattern.compile("^[a-f0-9\\.:]+$"); - } catch (PatternSyntaxException e) { - } - } - - private static final int ALT_DNS_NAME = 2; - private static final int ALT_IPA_NAME = 7; - - /** - * Checks the site certificate against the domain name of the site being - * visited - * - * @param certificate - * The certificate to check - * @param thisDomain - * The domain name of the site being visited - * @return True iff if there is a domain match as specified by RFC2818 - */ - public static boolean match(X509Certificate certificate, String thisDomain) { - if ((certificate == null) || (thisDomain == null) - || thisDomain.isEmpty()) { - return false; - } - - thisDomain = thisDomain.toLowerCase(Locale.US); - if (!isIpAddress(thisDomain)) { - return matchDns(certificate, thisDomain); - } else { - return matchIpAddress(certificate, thisDomain); - } - } - - /** - * @return True iff the domain name is specified as an IP address - */ - private static boolean isIpAddress(String domain) { - if ((domain == null) || domain.isEmpty()) { - return false; - } - - boolean rval; - try { - // do a quick-dirty IP match first to avoid DNS lookup - rval = QUICK_IP_PATTERN.matcher(domain).matches(); - if (rval) { - rval = domain.equals(InetAddress.getByName(domain) - .getHostAddress()); - } - } catch (UnknownHostException e) { - String errorMessage = e.getMessage(); - if (errorMessage == null) { - errorMessage = "unknown host exception"; - } - - if (K9.DEBUG) { - Log.v(K9.LOG_TAG, "DomainNameChecker.isIpAddress(): " - + errorMessage); - } - - rval = false; - } - - return rval; - } - - /** - * Checks the site certificate against the IP domain name of the site being - * visited - * - * @param certificate - * The certificate to check - * @param thisDomain - * The DNS domain name of the site being visited - * @return True iff if there is a domain match as specified by RFC2818 - */ - private static boolean matchIpAddress(X509Certificate certificate, String thisDomain) { - if (K9.DEBUG) { - Log.v(K9.LOG_TAG, "DomainNameChecker.matchIpAddress(): this domain: " + thisDomain); - } - - try { - Collection> subjectAltNames = certificate.getSubjectAlternativeNames(); - if (subjectAltNames != null) { - for (List altNameEntry : subjectAltNames) { - if ((altNameEntry != null) && (2 <= altNameEntry.size())) { - Integer altNameType = (Integer)(altNameEntry.get(0)); - if (altNameType != null && altNameType.intValue() == ALT_IPA_NAME) { - String altName = (String)(altNameEntry.get(1)); - if (altName != null) { - if (K9.DEBUG) { - Log.v(K9.LOG_TAG, "alternative IP: " + altName); - } - if (thisDomain.equalsIgnoreCase(altName)) { - return true; - } - } - } - } - } - } - } catch (CertificateParsingException e) { - } - - return false; - } - - /** - * Checks the site certificate against the DNS domain name of the site being - * visited - * - * @param certificate - * The certificate to check - * @param thisDomain - * The DNS domain name of the site being visited - * @return True iff if there is a domain match as specified by RFC2818 - */ - private static boolean matchDns(X509Certificate certificate, String thisDomain) { - boolean hasDns = false; - try { - Collection> subjectAltNames = certificate.getSubjectAlternativeNames(); - if (subjectAltNames != null) { - for (List altNameEntry : subjectAltNames) { - if ((altNameEntry != null) && (2 <= altNameEntry.size())) { - Integer altNameType = (Integer)(altNameEntry.get(0)); - if (altNameType != null && altNameType.intValue() == ALT_DNS_NAME) { - hasDns = true; - String altName = (String)(altNameEntry.get(1)); - if (altName != null && matchDns(thisDomain, altName)) { - return true; - } - } - } - } - } - } catch (CertificateParsingException e) { - // one way we can get here is if an alternative name starts with - // '*' character, which is contrary to one interpretation of the - // spec (a valid DNS name must start with a letter); there is no - // good way around this, and in order to be compatible we proceed - // to check the common name (ie, ignore alternative names) - if (K9.DEBUG) { - String errorMessage = e.getMessage(); - if (errorMessage == null) { - errorMessage = "failed to parse certificate"; - } - - Log.v(K9.LOG_TAG, "DomainNameChecker.matchDns(): " - + errorMessage); - } - } - - if (!hasDns) { - SslCertificate sslCertificate = new SslCertificate(certificate); - return matchDns(thisDomain, sslCertificate.getIssuedTo().getCName()); - } - - return false; - } - - /** - * @param thisDomain - * The domain name of the site being visited - * @param thatDomain - * The domain name from the certificate - * @return True iff thisDomain matches thatDomain as specified by RFC2818 - */ - private static boolean matchDns(String thisDomain, String thatDomain) { - if (K9.DEBUG) { - Log.v(K9.LOG_TAG, "DomainNameChecker.matchDns():" - + " this domain: " + thisDomain + " that domain: " - + thatDomain); - } - - if ((thisDomain == null) || thisDomain.isEmpty() - || (thatDomain == null) || thatDomain.isEmpty()) { - return false; - } - - thatDomain = thatDomain.toLowerCase(Locale.US); - - // (a) domain name strings are equal, ignoring case: X matches X - boolean rval = thisDomain.equals(thatDomain); - if (!rval) { - String[] thisDomainTokens = thisDomain.split("\\."); - String[] thatDomainTokens = thatDomain.split("\\."); - - int thisDomainTokensNum = thisDomainTokens.length; - int thatDomainTokensNum = thatDomainTokens.length; - - // (b) OR thatHost is a '.'-suffix of thisHost: Z.Y.X matches X - if (thisDomainTokensNum >= thatDomainTokensNum) { - for (int i = thatDomainTokensNum - 1; i >= 0; --i) { - rval = thisDomainTokens[i].equals(thatDomainTokens[i]); - if (!rval) { - // (c) OR we have a special *-match: - // Z.Y.X matches *.Y.X but does not match *.X - rval = ((i == 0) && (thisDomainTokensNum == thatDomainTokensNum)); - if (rval) { - rval = thatDomainTokens[0].equals("*"); - if (!rval) { - // (d) OR we have a *-component match: - // f*.com matches foo.com but not bar.com - rval = domainTokenMatch(thisDomainTokens[0], - thatDomainTokens[0]); - } - } - - break; - } - } - } - } - - return rval; - } - - /** - * @param thisDomainToken - * The domain token from the current domain name - * @param thatDomainToken - * The domain token from the certificate - * @return True iff thisDomainToken matches thatDomainToken, using the - * wildcard match as specified by RFC2818-3.1. For example, f*.com - * must match foo.com but not bar.com - */ - private static boolean domainTokenMatch(String thisDomainToken, String thatDomainToken) { - if ((thisDomainToken != null) && (thatDomainToken != null)) { - int starIndex = thatDomainToken.indexOf('*'); - if (starIndex >= 0) { - if (thatDomainToken.length() - 1 <= thisDomainToken.length()) { - String prefix = thatDomainToken.substring(0, starIndex); - String suffix = thatDomainToken.substring(starIndex + 1); - - return thisDomainToken.startsWith(prefix) - && thisDomainToken.endsWith(suffix); - } - } - } - - return false; - } -} diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 5f9dd1079..f2c002e05 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -96,7 +96,7 @@ import com.fsck.k9.mail.store.ImapResponseParser.ImapList; import com.fsck.k9.mail.store.ImapResponseParser.ImapResponse; import com.fsck.k9.mail.store.imap.ImapUtility; import com.fsck.k9.mail.transport.imap.ImapSettings; -import com.fsck.k9.net.ssl.SslHelper; +import com.fsck.k9.net.ssl.TrustedSocketFactory; import com.jcraft.jzlib.JZlib; import com.jcraft.jzlib.ZOutputStream; @@ -2435,7 +2435,7 @@ public class ImapStore extends Store { mSettings.getPort()); if (connectionSecurity == ConnectionSecurity.SSL_TLS_REQUIRED) { - mSocket = SslHelper.createSslSocket(mSettings.getHost(), + mSocket = TrustedSocketFactory.createSocket(mSettings.getHost(), mSettings.getPort(), mSettings.getClientCertificateAlias()); } else { mSocket = new Socket(); @@ -2485,8 +2485,8 @@ public class ImapStore extends Store { // STARTTLS executeSimpleCommand("STARTTLS"); - mSocket = SslHelper.createStartTlsSocket(mSocket, - mSettings.getHost(), mSettings.getPort(), true, + mSocket = TrustedSocketFactory.createSocket(mSocket, + mSettings.getHost(), mSettings.getPort(), mSettings.getClientCertificateAlias()); mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT); mIn = new PeekableInputStream(new BufferedInputStream(mSocket diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java index a66bca2ac..37c9b8efe 100644 --- a/src/com/fsck/k9/mail/store/Pop3Store.java +++ b/src/com/fsck/k9/mail/store/Pop3Store.java @@ -12,7 +12,7 @@ import com.fsck.k9.mail.*; 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.net.ssl.SslHelper; +import com.fsck.k9.net.ssl.TrustedSocketFactory; import javax.net.ssl.SSLException; @@ -314,7 +314,7 @@ public class Pop3Store extends Store { try { SocketAddress socketAddress = new InetSocketAddress(mHost, mPort); if (mConnectionSecurity == ConnectionSecurity.SSL_TLS_REQUIRED) { - mSocket = SslHelper.createSslSocket(mHost, mPort, mClientCertificateAlias); + mSocket = TrustedSocketFactory.createSocket(mHost, mPort, mClientCertificateAlias); } else { mSocket = new Socket(); } @@ -336,7 +336,7 @@ public class Pop3Store extends Store { if (mCapabilities.stls) { executeSimpleCommand(STLS_COMMAND); - mSocket = SslHelper.createStartTlsSocket(mSocket, mHost, mPort, true, + mSocket = TrustedSocketFactory.createSocket(mSocket, mHost, mPort, mClientCertificateAlias); mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT); mIn = new BufferedInputStream(mSocket.getInputStream(), 1024); diff --git a/src/com/fsck/k9/mail/transport/SmtpTransport.java b/src/com/fsck/k9/mail/transport/SmtpTransport.java index cedfaeaf5..cd8423c07 100644 --- a/src/com/fsck/k9/mail/transport/SmtpTransport.java +++ b/src/com/fsck/k9/mail/transport/SmtpTransport.java @@ -15,7 +15,7 @@ import com.fsck.k9.mail.filter.PeekableInputStream; import com.fsck.k9.mail.filter.SmtpDataStuffing; import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.store.LocalStore.LocalMessage; -import com.fsck.k9.net.ssl.SslHelper; +import com.fsck.k9.net.ssl.TrustedSocketFactory; import javax.net.ssl.SSLException; @@ -224,7 +224,7 @@ public class SmtpTransport extends Transport { try { SocketAddress socketAddress = new InetSocketAddress(addresses[i], mPort); if (mConnectionSecurity == ConnectionSecurity.SSL_TLS_REQUIRED) { - mSocket = SslHelper.createSslSocket(mHost, mPort, mClientCertificateAlias); + mSocket = TrustedSocketFactory.createSocket(mHost, mPort, mClientCertificateAlias); mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT); secureConnection = true; } else { @@ -278,7 +278,7 @@ public class SmtpTransport extends Transport { if (extensions.containsKey("STARTTLS")) { executeSimpleCommand("STARTTLS"); - mSocket = SslHelper.createStartTlsSocket(mSocket, mHost, mPort, true, + mSocket = TrustedSocketFactory.createSocket(mSocket, mHost, mPort, mClientCertificateAlias); mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(), diff --git a/src/com/fsck/k9/net/ssl/SslHelper.java b/src/com/fsck/k9/net/ssl/SslHelper.java deleted file mode 100644 index 38e281bf0..000000000 --- a/src/com/fsck/k9/net/ssl/SslHelper.java +++ /dev/null @@ -1,81 +0,0 @@ - -package com.fsck.k9.net.ssl; - -import java.io.IOException; -import java.net.Socket; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; - -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; - -import android.util.Log; - -import com.fsck.k9.K9; -import com.fsck.k9.mail.MessagingException; - -/** - * Helper class to create SSL sockets with support for client certificate - * authentication - */ -public class SslHelper { - - private static SSLContext createSslContext(String host, int port, String clientCertificateAlias) - throws NoSuchAlgorithmException, KeyManagementException, MessagingException { - if (K9.DEBUG) - Log.d(K9.LOG_TAG, "createSslContext: Client certificate alias: " - + clientCertificateAlias); - - KeyManager[] keyManagers; - if (clientCertificateAlias == null || clientCertificateAlias.isEmpty()) { - keyManagers = null; - } else { - keyManagers = new KeyManager[] { new KeyChainKeyManager(K9.app, clientCertificateAlias) }; - } - - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(keyManagers, - new TrustManager[] { - TrustManagerFactory.get( - host, port) - }, - new SecureRandom()); - - return sslContext; - } - - /** - * Create SSL socket - * - * @param host - * @param port - * @param clientCertificateAlias if not null, uses client certificate - * retrieved by this alias for authentication - */ - public static Socket createSslSocket(String host, int port, String clientCertificateAlias) - throws NoSuchAlgorithmException, KeyManagementException, IOException, - MessagingException { - SSLContext sslContext = createSslContext(host, port, clientCertificateAlias); - return TrustedSocketFactory.createSocket(sslContext); - } - - /** - * Create socket for START_TLS. autoClose = true - * - * @param socket - * @param host - * @param port - * @param secure - * @param clientCertificateAlias if not null, uses client certificate - * retrieved by this alias for authentication - */ - public static Socket createStartTlsSocket(Socket socket, String host, int port, boolean secure, - String clientCertificateAlias) throws NoSuchAlgorithmException, - KeyManagementException, IOException, MessagingException { - SSLContext sslContext = createSslContext(host, port, clientCertificateAlias); - boolean autoClose = true; - return TrustedSocketFactory.createSocket(sslContext, socket, host, port, autoClose); - } -} diff --git a/src/com/fsck/k9/net/ssl/SslSessionCacheHelper.java b/src/com/fsck/k9/net/ssl/SslSessionCacheHelper.java new file mode 100644 index 000000000..20439c4ff --- /dev/null +++ b/src/com/fsck/k9/net/ssl/SslSessionCacheHelper.java @@ -0,0 +1,89 @@ +package com.fsck.k9.net.ssl; + +import java.io.File; +import java.lang.reflect.Method; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSessionContext; + +import com.fsck.k9.K9; + +import android.content.Context; +import android.os.Build; +import android.util.Log; + +/** + * A class to help with associating an {@code SSLContext} with a persistent + * file-based cache of SSL sessions. + *

+ * This uses reflection to achieve its task. + *

+ * The alternative to this would be to use {@link SSLCertificateSocketFactory} + * which also provides session caching. The problem with using that occurs when + * using STARTTLS in combination with + * {@code TrustedSocketFactory.hardenSocket(SSLSocket)}. The result is that + * {@code hardenSocket()} fails to change anything because by the time it is + * applied to the socket, the SSL handshake has already been completed. (This is + * because of another feature of {@link SSLCertificateSocketFactory} whereby it + * performs host name verification which necessitates initiating the SSL + * handshake immediately on socket creation.) + *

+ * If eventually the use of hardenSocket() should become unnecessary, then + * switching to using {@link SSLCertificateSocketFactory} would be a better + * solution. + */ +public class SslSessionCacheHelper { + private static Object sSessionCache; + private static Method sSetPersistentCacheMethod; + private static boolean sIsDisabled = false; + + static { + final String packageName; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + packageName = "org.apache.harmony.xnet.provider.jsse"; + } else { + packageName = "com.android.org.conscrypt"; + } + final File cacheDirectory = K9.app.getDir("sslcache", Context.MODE_PRIVATE); + try { + Class fileClientSessionCacheClass = Class.forName(packageName + + ".FileClientSessionCache"); + Method usingDirectoryMethod = fileClientSessionCacheClass + .getMethod("usingDirectory", File.class); + sSessionCache = usingDirectoryMethod.invoke(null, cacheDirectory); + + Class sslClientSessionCacheClass = Class.forName(packageName + + ".SSLClientSessionCache"); + Class clientSessionContextClass = Class.forName(packageName + + ".ClientSessionContext"); + sSetPersistentCacheMethod = clientSessionContextClass.getMethod( + "setPersistentCache", sslClientSessionCacheClass); + } catch (Exception e) { + // Something went wrong. Proceed without a session cache. + Log.e(K9.LOG_TAG, "Failed to initialize SslSessionCacheHelper: " + e); + sIsDisabled = true; + } + } + + /** + * Associate an {@code SSLContext} with a persistent file-based cache of SSL + * sessions which can be used when re-establishing a connection to the same + * server. + *

+ * This is beneficial because it can eliminate redundant cryptographic + * computations and network traffic, thus saving time and conserving power. + */ + public static void setPersistentCache(SSLContext sslContext) { + if (sIsDisabled) { + return; + } + try { + SSLSessionContext sessionContext = sslContext.getClientSessionContext(); + sSetPersistentCacheMethod.invoke(sessionContext, sSessionCache); + } catch (Exception e) { + // Something went wrong. Proceed without a session cache. + Log.e(K9.LOG_TAG, "Failed to initialize persistent SSL cache: " + e); + sIsDisabled = true; + } + } +} diff --git a/src/com/fsck/k9/net/ssl/TrustManagerFactory.java b/src/com/fsck/k9/net/ssl/TrustManagerFactory.java index 27b2c70bb..4a2c7206a 100644 --- a/src/com/fsck/k9/net/ssl/TrustManagerFactory.java +++ b/src/com/fsck/k9/net/ssl/TrustManagerFactory.java @@ -3,12 +3,15 @@ package com.fsck.k9.net.ssl; import android.util.Log; -import com.fsck.k9.helper.DomainNameChecker; import com.fsck.k9.mail.CertificateChainException; import com.fsck.k9.security.LocalKeyStore; +import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; + +import org.apache.http.conn.ssl.StrictHostnameVerifier; + import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -58,31 +61,25 @@ public final class TrustManagerFactory { public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { String message = null; - boolean foundInGlobalKeyStore = false; + X509Certificate certificate = chain[0]; + try { defaultTrustManager.checkServerTrusted(chain, authType); - foundInGlobalKeyStore = true; + new StrictHostnameVerifier().verify(mHost, certificate); + return; } catch (CertificateException e) { + // cert. chain can't be validated + message = e.getMessage(); + } catch (SSLException e) { + // host name doesn't match certificate message = e.getMessage(); } - X509Certificate certificate = chain[0]; - // Check the local key store if we couldn't verify the certificate using the global // key store or if the host name doesn't match the certificate name - if (foundInGlobalKeyStore - && DomainNameChecker.match(certificate, mHost) - || keyStore.isValidCertificate(certificate, mHost, mPort)) { - return; + if (!keyStore.isValidCertificate(certificate, mHost, mPort)) { + throw new CertificateChainException(message, chain); } - - if (message == null) { - message = (foundInGlobalKeyStore) ? - "Certificate domain name does not match " + mHost : - "Couldn't find certificate in local key store"; - } - - throw new CertificateChainException(message, chain); } public X509Certificate[] getAcceptedIssuers() { diff --git a/src/com/fsck/k9/net/ssl/TrustedSocketFactory.java b/src/com/fsck/k9/net/ssl/TrustedSocketFactory.java index 030bf4910..7d2438cda 100644 --- a/src/com/fsck/k9/net/ssl/TrustedSocketFactory.java +++ b/src/com/fsck/k9/net/ssl/TrustedSocketFactory.java @@ -3,14 +3,21 @@ package com.fsck.k9.net.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.SecureRandom; -import java.util.*; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** @@ -71,7 +78,7 @@ public class TrustedSocketFactory { try { SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, null, new SecureRandom()); + sslContext.init(null, null, null); SSLSocketFactory sf = sslContext.getSocketFactory(); SSLSocket sock = (SSLSocket) sf.createSocket(); enabledCiphers = sock.getEnabledCipherSuites(); @@ -114,19 +121,33 @@ public class TrustedSocketFactory { return result.toArray(new String[result.size()]); } - public static Socket createSocket(SSLContext sslContext) throws IOException { - SSLSocket socket = (SSLSocket) sslContext.getSocketFactory().createSocket(); - hardenSocket(socket); + public static Socket createSocket(String host, int port, String clientCertificateAlias) + throws IOException, MessagingException, KeyManagementException, NoSuchAlgorithmException { - return socket; + return createSocket(null, host, port, clientCertificateAlias); } - public static Socket createSocket(SSLContext sslContext, Socket s, String host, int port, - boolean autoClose) throws IOException { - SSLSocket socket = (SSLSocket) sslContext.getSocketFactory().createSocket(s, host, port, autoClose); - hardenSocket(socket); + public static Socket createSocket(Socket socket, String host, int port, String clientCertificateAlias) + throws NoSuchAlgorithmException, KeyManagementException, MessagingException, IOException { - return socket; + 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); + SslSessionCacheHelper.setPersistentCache(context); + 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) {