From 43c38a047feedda4720af5bfbc188a33f8dfaced Mon Sep 17 00:00:00 2001 From: Joe Steele Date: Sat, 6 Sep 2014 16:35:48 -0400 Subject: [PATCH] Implement SSL file-based session caching Caching is beneficial because it can eliminate redundant cryptographic computations and network traffic when re-establishing a connection to the same server, thus saving time and conserving power. --- .../k9/net/ssl/SslSessionCacheHelper.java | 89 +++++++++++++++++++ .../fsck/k9/net/ssl/TrustedSocketFactory.java | 1 + 2 files changed, 90 insertions(+) create mode 100644 src/com/fsck/k9/net/ssl/SslSessionCacheHelper.java 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/TrustedSocketFactory.java b/src/com/fsck/k9/net/ssl/TrustedSocketFactory.java index 6b7bad9a0..7d2438cda 100644 --- a/src/com/fsck/k9/net/ssl/TrustedSocketFactory.java +++ b/src/com/fsck/k9/net/ssl/TrustedSocketFactory.java @@ -138,6 +138,7 @@ public class TrustedSocketFactory { SSLContext context = SSLContext.getInstance("TLS"); context.init(keyManagers, trustManagers, null); + SslSessionCacheHelper.setPersistentCache(context); SSLSocketFactory socketFactory = context.getSocketFactory(); Socket trustedSocket; if (socket == null) {