null
.
* @throws Exception
*/
- void initializeComponent(K9 application);
+ void initializeComponent(Application application);
}
public static Application app = null;
@@ -91,6 +91,15 @@ public class K9 extends Application {
*/
private static Listnull
.
*/
public static void registerApplicationAware(final ApplicationAware component) {
- if (!observers.contains(component)) {
- observers.add(component);
+ synchronized (observers) {
+ if (sInitialized) {
+ component.initializeComponent(K9.app);
+ } else if (!observers.contains(component)) {
+ observers.add(component);
+ }
}
}
diff --git a/src/com/fsck/k9/mail/store/TrustManagerFactory.java b/src/com/fsck/k9/mail/store/TrustManagerFactory.java
index 37c9f32dc..ff572ebff 100644
--- a/src/com/fsck/k9/mail/store/TrustManagerFactory.java
+++ b/src/com/fsck/k9/mail/store/TrustManagerFactory.java
@@ -1,7 +1,6 @@
package com.fsck.k9.mail.store;
-import android.app.Application;
import android.content.Context;
import android.util.Log;
import com.fsck.k9.K9;
@@ -13,6 +12,7 @@ import org.apache.commons.io.IOUtils;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
@@ -114,26 +114,9 @@ public final class TrustManagerFactory {
}
static {
- java.io.InputStream fis = null;
try {
javax.net.ssl.TrustManagerFactory tmf = javax.net.ssl.TrustManagerFactory.getInstance("X509");
- Application app = K9.app;
- keyStoreFile = new File(app.getDir("KeyStore", Context.MODE_PRIVATE) + File.separator + "KeyStore.bks");
- keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
- try {
- fis = new java.io.FileInputStream(keyStoreFile);
- } catch (FileNotFoundException e1) {
- fis = null;
- }
- try {
- keyStore.load(fis, "".toCharArray());
- } catch (IOException e) {
- Log.e(LOG_TAG, "KeyStore IOException while initializing TrustManagerFactory ", e);
- keyStore = null;
- } catch (CertificateException e) {
- Log.e(LOG_TAG, "KeyStore CertificateException while initializing TrustManagerFactory ", e);
- keyStore = null;
- }
+ loadKeyStore();
tmf.init(keyStore);
TrustManager[] tms = tmf.getTrustManagers();
if (tms != null) {
@@ -160,10 +143,36 @@ public final class TrustManagerFactory {
Log.e(LOG_TAG, "Unable to get X509 Trust Manager ", e);
} catch (KeyStoreException e) {
Log.e(LOG_TAG, "Key Store exception while initializing TrustManagerFactory ", e);
+ }
+ unsecureTrustManager = new SimpleX509TrustManager();
+ }
+
+ static void loadKeyStore() throws KeyStoreException, NoSuchAlgorithmException {
+ Context context = K9.app;
+
+ keyStoreFile = new File(context.getDir("KeyStore", Context.MODE_PRIVATE) +
+ File.separator + "KeyStore.bks");
+ keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+
+ FileInputStream fis;
+ try {
+ fis = new FileInputStream(keyStoreFile);
+ } catch (FileNotFoundException e) {
+ // If the file doesn't exist, that's fine, too
+ fis = null;
+ }
+
+ try {
+ keyStore.load(fis, "".toCharArray());
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "KeyStore IOException while initializing TrustManagerFactory ", e);
+ keyStore = null;
+ } catch (CertificateException e) {
+ Log.e(LOG_TAG, "KeyStore CertificateException while initializing TrustManagerFactory ", e);
+ keyStore = null;
} finally {
IOUtils.closeQuietly(fis);
}
- unsecureTrustManager = new SimpleX509TrustManager();
}
private TrustManagerFactory() {
diff --git a/src/com/fsck/k9/provider/MessageProvider.java b/src/com/fsck/k9/provider/MessageProvider.java
index 80c5b0be7..989ee5e16 100644
--- a/src/com/fsck/k9/provider/MessageProvider.java
+++ b/src/com/fsck/k9/provider/MessageProvider.java
@@ -1,5 +1,6 @@
package com.fsck.k9.provider;
+import android.app.Application;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -981,7 +982,7 @@ public class MessageProvider extends ContentProvider {
K9.registerApplicationAware(new K9.ApplicationAware() {
@Override
- public void initializeComponent(final K9 application) {
+ public void initializeComponent(final Application application) {
Log.v(K9.LOG_TAG, "Registering content resolver notifier");
MessagingController.getInstance(application).addListener(new MessagingListener() {
diff --git a/tests/assets/cert1.der b/tests/assets/cert1.der
new file mode 100644
index 000000000..cdfe84df7
Binary files /dev/null and b/tests/assets/cert1.der differ
diff --git a/tests/assets/cert2.der b/tests/assets/cert2.der
new file mode 100644
index 000000000..aecc3459d
Binary files /dev/null and b/tests/assets/cert2.der differ
diff --git a/tests/src/com/fsck/k9/mail/store/TrustManagerFactoryTest.java b/tests/src/com/fsck/k9/mail/store/TrustManagerFactoryTest.java
new file mode 100644
index 000000000..b68c33328
--- /dev/null
+++ b/tests/src/com/fsck/k9/mail/store/TrustManagerFactoryTest.java
@@ -0,0 +1,129 @@
+package com.fsck.k9.mail.store;
+
+import javax.net.ssl.X509TrustManager;
+import com.fsck.k9.K9;
+import java.io.File;
+import java.lang.reflect.Method;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.concurrent.CountDownLatch;
+
+import android.app.Application;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.test.AndroidTestCase;
+
+/**
+ * Test the functionality of {@link TrustManagerFactory}.
+ */
+public class TrustManagerFactoryTest extends AndroidTestCase {
+ public static final String MATCHING_HOST = "k9.example.com";
+ public static final String NOT_MATCHING_HOST = "bla.example.com";
+ public static final int PORT1 = 993;
+ public static final int PORT2 = 465;
+
+
+ private Context mTestContext;
+ private X509Certificate mCert1;
+ private X509Certificate mCert2;
+
+
+ @Override
+ public void setUp() throws Exception {
+ waitForAppInitialization();
+
+ // Hack to make sure TrustManagerFactory.loadKeyStore() can create the key store file
+ K9.app = new DummyApplication(getContext());
+
+ // Source: https://kmansoft.wordpress.com/2011/04/18/accessing-resources-in-an-androidtestcase/
+ Method m = AndroidTestCase.class.getMethod("getTestContext", new Class[] {});
+ mTestContext = (Context) m.invoke(this, (Object[]) null);
+
+ // Delete the key store file to make sure we start without any stored certificates
+ File keyStoreDir = getContext().getDir("KeyStore", Context.MODE_PRIVATE);
+ new File(keyStoreDir + File.separator + "KeyStore.bks").delete();
+
+ // Load the empty key store file
+ TrustManagerFactory.loadKeyStore();
+
+ // Load certificates
+ AssetManager assets = mTestContext.getAssets();
+
+ CertificateFactory certFactory = CertificateFactory.getInstance("X509");
+ mCert1 = (X509Certificate) certFactory.generateCertificate(assets.open("cert1.der"));
+ mCert2 = (X509Certificate) certFactory.generateCertificate(assets.open("cert2.der"));
+ }
+
+ private void waitForAppInitialization() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ K9.registerApplicationAware(new K9.ApplicationAware() {
+ @Override
+ public void initializeComponent(Application application) {
+ latch.countDown();
+ }
+ });
+
+ latch.await();
+ }
+
+ /**
+ * Checks if TrustManagerFactory supports a host with different certificates for different
+ * services (e.g. SMTP and IMAP).
+ *
+ * + * This test is to make sure entries in the keystore file aren't overwritten. + * See Issue 1326. + *
+ * + * @throws Exception + * if anything goes wrong + */ + public void testDifferentCertificatesOnSameServer() throws Exception { + TrustManagerFactory.addCertificate(NOT_MATCHING_HOST, PORT1, mCert1); + TrustManagerFactory.addCertificate(NOT_MATCHING_HOST, PORT2, mCert2); + + X509TrustManager trustManager1 = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1, true); + X509TrustManager trustManager2 = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT2, true); + trustManager2.checkServerTrusted(new X509Certificate[] { mCert2 }, "authType"); + trustManager1.checkServerTrusted(new X509Certificate[] { mCert1 }, "authType"); + } + + public void testSelfSignedCertificateMatchingHost() throws Exception { + TrustManagerFactory.addCertificate(MATCHING_HOST, PORT1, mCert1); + X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1, true); + trustManager.checkServerTrusted(new X509Certificate[] { mCert1 }, "authType"); + } + + public void testSelfSignedCertificateNotMatchingHost() throws Exception { + TrustManagerFactory.addCertificate(NOT_MATCHING_HOST, PORT1, mCert1); + X509TrustManager trustManager = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1, true); + trustManager.checkServerTrusted(new X509Certificate[] { mCert1 }, "authType"); + } + + public void testWrongCertificate() throws Exception { + TrustManagerFactory.addCertificate(MATCHING_HOST, PORT1, mCert1); + X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1, true); + boolean certificateValid; + try { + trustManager.checkServerTrusted(new X509Certificate[] { mCert2 }, "authType"); + certificateValid = true; + } catch (CertificateException e) { + certificateValid = false; + } + assertFalse("The certificate should have been rejected but wasn't", certificateValid); + } + + private static class DummyApplication extends Application { + private final Context mContext; + + DummyApplication(Context context) { + mContext = context; + } + + public File getDir(String name, int mode) { + return mContext.getDir(name, mode); + } + } +}