From 194d673f91bc1b4ea18a6892f4f06eac0dee7dad Mon Sep 17 00:00:00 2001 From: Jesse Vincent Date: Wed, 3 Dec 2008 00:04:24 +0000 Subject: [PATCH] Merge into 'trunk' r124@hotel-dan (orig r123): jessev | 2008-11-07 03:35:09 -0500 Branch for Bradley Young r126@hotel-dan (orig r125): young.bradley | 2008-11-08 17:27:30 -0500 Initial checkin of self signed certificates capability. Missing ability to save updated KeyStore. r127@hotel-dan (orig r126): young.bradley | 2008-11-10 13:04:49 -0500 Update to allow saving updated keys to keystore r17200@hotel-dan (orig r131): young.bradley | 2008-11-17 14:09:24 -0500 Updates to handle chains properly, and handle default behavior. r17206@hotel-dan (orig r137): young.bradley | 2008-11-29 14:14:25 -0500 Checkin for beta 2: this should be the release candidate. --- res/values-de-rDE/strings.xml | 4 + res/values-es-rUS/strings.xml | 6 +- res/values-fr-rFR/strings.xml | 5 +- res/values-it-rIT/strings.xml | 3 + res/values-zh-rTW/strings.xml | 3 + res/values/strings.xml | 12 +- .../setup/AccountSetupCheckSettings.java | 98 +++++++++- src/com/fsck/k9/k9.java | 15 +- .../k9/mail/store/TrustManagerFactory.java | 171 +++++++++++++++--- 9 files changed, 274 insertions(+), 43 deletions(-) diff --git a/res/values-de-rDE/strings.xml b/res/values-de-rDE/strings.xml index 16c44d392..b2155986c 100644 --- a/res/values-de-rDE/strings.xml +++ b/res/values-de-rDE/strings.xml @@ -192,4 +192,8 @@ Verbindungsfehler Senden\u2026 Details anzeigen/ausblenden + Unrecognized Certificate + Accept Key + Reject Key + diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index 162697bb5..56768baa5 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -191,5 +191,9 @@ Reintentar la carga de más mensajes Error de conexión Enviando\u2026 - Ver/ocultar detalles + Ver/ocultar detalles + Unrecognized Certificate + Accept Key + Reject Key + diff --git a/res/values-fr-rFR/strings.xml b/res/values-fr-rFR/strings.xml index 2c67c3d57..9330d9fef 100644 --- a/res/values-fr-rFR/strings.xml +++ b/res/values-fr-rFR/strings.xml @@ -191,5 +191,8 @@ Ressayer le chargement de plus de messages Erreur de connexion Envoi\u2026 - Afficher/masquer les détails + Afficher/masquer les détails + Unrecognized Certificate + Accept Key + Reject Key diff --git a/res/values-it-rIT/strings.xml b/res/values-it-rIT/strings.xml index e81bc1428..edc0c235c 100644 --- a/res/values-it-rIT/strings.xml +++ b/res/values-it-rIT/strings.xml @@ -192,4 +192,7 @@ Errore di connessione Invio in corso\u2026 Visualizza/Nascondi dettagli + Unrecognized Certificate + Accept Key + Reject Key diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index ec9538589..caeea0838 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -192,4 +192,7 @@ 連線錯誤 正在傳送\u2026 檢視/隱藏詳細資料 + Unrecognized Certificate + Accept Key + Reject Key diff --git a/res/values/strings.xml b/res/values/strings.xml index 69cb17f96..353fb0a12 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -248,9 +248,12 @@ Welcome to K-9 Mail setup. K-9 is an open source email client for Android based Send this address a copy of every outgoing message - Signature - Append a signature to every message you send - + Signature + Append a signature to every message you send + + Sent Items Folder + Save all sent messages to this folder + Remove The account \"%s\" will be removed from Email. @@ -260,4 +263,7 @@ Welcome to K-9 Mail setup. K-9 is an open source email client for Android based your correct email address and password, you may not have a paid \"Plus\" account. Please launch the Web browser to gain access to these mail accounts. + Unrecognized Certificate + Accept Key + Reject Key diff --git a/src/com/fsck/k9/activity/setup/AccountSetupCheckSettings.java b/src/com/fsck/k9/activity/setup/AccountSetupCheckSettings.java index ebc8a4b0a..8162355ba 100644 --- a/src/com/fsck/k9/activity/setup/AccountSetupCheckSettings.java +++ b/src/com/fsck/k9/activity/setup/AccountSetupCheckSettings.java @@ -1,6 +1,9 @@ package com.fsck.k9.activity.setup; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; @@ -21,6 +24,7 @@ import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.Store; import com.fsck.k9.mail.Transport; import com.fsck.k9.mail.CertificateValidationException; +import com.fsck.k9.mail.store.TrustManagerFactory; /** * Checks the given settings to make sure that they can be used to send and @@ -78,6 +82,7 @@ public class AccountSetupCheckSettings extends Activity implements OnClickListen new Thread() { public void run() { + Store store = null; Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); try { if (mDestroyed) { @@ -88,9 +93,9 @@ public class AccountSetupCheckSettings extends Activity implements OnClickListen return; } if (mCheckIncoming) { - setMessage(R.string.account_setup_check_settings_check_incoming_msg); - Store store = Store.getInstance(mAccount.getStoreUri(), getApplication()); - store.checkSettings(); + setMessage(R.string.account_setup_check_settings_check_incoming_msg); + store = Store.getInstance(mAccount.getStoreUri(), getApplication()); + store.checkSettings(); } if (mDestroyed) { return; @@ -118,17 +123,19 @@ public class AccountSetupCheckSettings extends Activity implements OnClickListen } catch (final AuthenticationFailedException afe) { showErrorDialog( R.string.account_setup_failed_dlg_auth_message_fmt, - afe.getMessage() == null ? "" : afe.getMessage()); + afe.getMessage() == null ? "" : afe.getMessage()); } catch (final CertificateValidationException cve) { - showErrorDialog( + acceptKeyDialog( R.string.account_setup_failed_dlg_certificate_message_fmt, - cve.getMessage() == null ? "" : cve.getMessage()); + cve); + //cve.getMessage() == null ? "" : cve.getMessage()); } catch (final MessagingException me) { showErrorDialog( R.string.account_setup_failed_dlg_server_message_fmt, me.getMessage() == null ? "" : me.getMessage()); } } + }.start(); } @@ -172,7 +179,86 @@ public class AccountSetupCheckSettings extends Activity implements OnClickListen } }); } + private void acceptKeyDialog(final int msgResId, final Object... args) { + mHandler.post(new Runnable() { + public void run() { + if (mDestroyed) { + return; + } + final X509Certificate[] chain = TrustManagerFactory.getLastCertChain(); + String exMessage = "Unknown Error"; + + Exception ex = ((Exception)args[0]); + if (ex != null) { + if (ex.getCause() != null) { + if (ex.getCause().getCause() != null) { + exMessage = ex.getCause().getCause().getMessage(); + + } else { + exMessage = ex.getCause().getMessage(); + } + } else { + exMessage = ex.getMessage(); + } + } + + mProgressBar.setIndeterminate(false); + StringBuffer chainInfo = new StringBuffer(100); + for (int i = 0; i < chain.length; i++) + { + // display certificate chain information + chainInfo.append("Certificate chain[" + i + "]:\n"); + chainInfo.append("Subject: " + chain[i].getSubjectDN().toString() + "\n"); + chainInfo.append("Issuer: " + chain[i].getIssuerDN().toString() + "\n"); + } + new AlertDialog.Builder(AccountSetupCheckSettings.this) + .setTitle(getString(R.string.account_setup_failed_dlg_invalid_certificate_title)) + //.setMessage(getString(R.string.account_setup_failed_dlg_invalid_certificate) + .setMessage(getString(msgResId,exMessage) + + " " + chainInfo.toString() + ) + .setCancelable(true) + .setPositiveButton( + getString(R.string.account_setup_failed_dlg_invalid_certificate_accept), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + try { + String alias = mAccount.getUuid(); + if (mCheckIncoming) { + alias = alias + ".incoming"; + } + if (mCheckOutgoing) { + alias = alias + ".outgoing"; + } + TrustManagerFactory.addCertificateChain(alias, chain); + } catch (CertificateException e) { + showErrorDialog( + R.string.account_setup_failed_dlg_certificate_message_fmt, + e.getMessage() == null ? "" : e.getMessage()); + } + AccountSetupCheckSettings.actionCheckSettings(AccountSetupCheckSettings.this, mAccount, + mCheckIncoming, mCheckOutgoing); + } + }) + .setNegativeButton( + getString(R.string.account_setup_failed_dlg_invalid_certificate_reject), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }) + .show(); + } + }); + } + + public void onActivityResult(int reqCode, int resCode, Intent data) { + setResult(resCode); + finish(); + } + + private void onCancel() { mCanceled = true; setMessage(R.string.account_setup_check_settings_canceling_msg); diff --git a/src/com/fsck/k9/k9.java b/src/com/fsck/k9/k9.java index 12f6e02b4..ea0084a68 100644 --- a/src/com/fsck/k9/k9.java +++ b/src/com/fsck/k9/k9.java @@ -17,6 +17,8 @@ import com.fsck.k9.service.BootReceiver; import com.fsck.k9.service.MailService; public class k9 extends Application { + public static Application app = null; + public static final String LOG_TAG = "k9"; public static File tempDirectory; @@ -64,13 +66,14 @@ public class k9 extends Application { * The MIME type(s) of attachments we're willing to download to SD. */ public static final String[] ACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES = new String[] { - "*/*", + "image/*", }; /** * The MIME type(s) of attachments we're not willing to download to SD. */ public static final String[] UNACCEPTABLE_ATTACHMENT_DOWNLOAD_TYPES = new String[] { + "image/gif", }; /** @@ -87,7 +90,7 @@ public class k9 extends Application { public static final int DEFAULT_VISIBLE_LIMIT = 25; /** - * Number of additional messages to load when a user selectes "Load more messages..." + * Number of additioanl messages to load when a user selectes "Load more messages..." */ public static final int VISIBLE_LIMIT_INCREMENT = 25; @@ -146,6 +149,7 @@ public class k9 extends Application { @Override public void onCreate() { super.onCreate(); + app = this; Preferences prefs = Preferences.getPreferences(this); DEBUG = prefs.geteEnableDebugLogging(); DEBUG_SENSITIVE = prefs.getEnableSensitiveLogging(); @@ -156,13 +160,6 @@ public class k9 extends Application { * doesn't work in Android and MimeMessage does not have access to a Context. */ BinaryTempFileBody.setTempDirectory(getCacheDir()); - - /* - * Enable background sync of messages - */ - - setServicesEnabled(this); - } } diff --git a/src/com/fsck/k9/mail/store/TrustManagerFactory.java b/src/com/fsck/k9/mail/store/TrustManagerFactory.java index dcfa2070c..374e8a33f 100644 --- a/src/com/fsck/k9/mail/store/TrustManagerFactory.java +++ b/src/com/fsck/k9/mail/store/TrustManagerFactory.java @@ -2,8 +2,13 @@ package com.fsck.k9.mail.store; import android.util.Log; +import android.app.Application; +import android.content.Context; import android.net.http.DomainNameChecker; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.KeyStoreException; @@ -13,11 +18,22 @@ import java.security.cert.CertificateException; import javax.net.ssl.X509TrustManager; import javax.net.ssl.TrustManager; +import com.fsck.k9.k9; + public final class TrustManagerFactory { private static final String LOG_TAG = "TrustManagerFactory"; - private static X509TrustManager sSecureTrustManager; - private static X509TrustManager sUnsecureTrustManager; + private static X509TrustManager defaultTrustManager; + private static X509TrustManager unsecureTrustManager; + private static X509TrustManager localTrustManager; + + private static SecureX509TrustManager secureTrustManager; + + private static X509Certificate[] lastCertChain = null; + + private static File keyStoreFile; + private static KeyStore keyStore; + private static class SimpleX509TrustManager implements X509TrustManager { public void checkClientTrusted(X509Certificate[] chain, String authType) @@ -34,62 +50,171 @@ public final class TrustManagerFactory { } private static class SecureX509TrustManager implements X509TrustManager { - private X509TrustManager mTrustManager; - private String mHost; + //private static X509TrustManager mTrustManager; + //private static X509TrustManager mLocalTrustManager; + private static String mHost; + private static SecureX509TrustManager me; - SecureX509TrustManager(X509TrustManager trustManager, String host) { - mTrustManager = trustManager; - mHost = host; + private SecureX509TrustManager() { } + public static X509TrustManager getInstance(String host) { + mHost = host; + if (me == null) { + me = new SecureX509TrustManager(); + } + return me; + } + + public static void setHost(String host){ + mHost = host; + } + + // +// public static void updateTrustManager(X509TrustManager trustManager) { +// mTrustManager = trustManager; +// } + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - mTrustManager.checkClientTrusted(chain, authType); + defaultTrustManager.checkClientTrusted(chain, authType); } public void checkServerTrusted(X509Certificate[] chain, String authType) - throws CertificateException { - - mTrustManager.checkServerTrusted(chain, authType); - - if (!DomainNameChecker.match(chain[0], mHost)) { - throw new CertificateException("Certificate domain name does not match " - + mHost); - } + throws CertificateException { + TrustManagerFactory.setLastCertChain(chain); + try { + defaultTrustManager.checkServerTrusted(chain, authType); + } catch (CertificateException e) { + localTrustManager.checkServerTrusted(chain, authType); + } + if (!DomainNameChecker.match(chain[0], mHost)) { + try { + String dn = chain[0].getSubjectDN().toString(); + if ((dn != null) && (dn.equalsIgnoreCase(keyStore.getCertificateAlias(chain[0])))) { + return; + } + } catch (KeyStoreException e) { + throw new CertificateException("Certificate cannot be verified; KeyStore Exception: " + e); + } + throw new CertificateException("Certificate domain name does not match " + + mHost); + } } public X509Certificate[] getAcceptedIssuers() { - return mTrustManager.getAcceptedIssuers(); + return defaultTrustManager.getAcceptedIssuers(); } + } static { try { javax.net.ssl.TrustManagerFactory tmf = javax.net.ssl.TrustManagerFactory.getInstance("X509"); - tmf.init((KeyStore) null); + Application app = k9.app; + keyStoreFile = new File(app.getDir("KeyStore", Context.MODE_PRIVATE) + File.separator + "KeyStore.bks"); + keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + //TODO: read store from disk. + java.io.FileInputStream fis; + 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; + } + tmf.init(keyStore); TrustManager[] tms = tmf.getTrustManagers(); if (tms != null) { for (TrustManager tm : tms) { if (tm instanceof X509TrustManager) { - sSecureTrustManager = (X509TrustManager) tm; + localTrustManager = (X509TrustManager)tm; break; } } } + tmf = javax.net.ssl.TrustManagerFactory.getInstance("X509"); + tmf.init((KeyStore)null); + tms = tmf.getTrustManagers(); + if (tms != null) { + for (TrustManager tm : tms) { + if (tm instanceof X509TrustManager) { + defaultTrustManager = (X509TrustManager) tm; + break; + } + } + } + } catch (NoSuchAlgorithmException e) { 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); } - - sUnsecureTrustManager = new SimpleX509TrustManager(); + unsecureTrustManager = new SimpleX509TrustManager(); } private TrustManagerFactory() { } public static X509TrustManager get(String host, boolean secure) { - return secure ? new SecureX509TrustManager(sSecureTrustManager, host) : - sUnsecureTrustManager; + return secure ? SecureX509TrustManager.getInstance(host) : + unsecureTrustManager; } + + public static KeyStore getKeyStore() { + return keyStore; + } + + public static void setLastCertChain(X509Certificate[] chain) { + lastCertChain = chain; + } + public static X509Certificate[] getLastCertChain() { + return lastCertChain; + } + + public static void addCertificateChain(String alias, X509Certificate[] chain) throws CertificateException { + try { + javax.net.ssl.TrustManagerFactory tmf = javax.net.ssl.TrustManagerFactory.getInstance("X509"); + for (int i = 0; i < chain.length; i++) + { + keyStore.setCertificateEntry + (chain[i].getSubjectDN().toString(), chain[i]); + } + + tmf.init(keyStore); + TrustManager[] tms = tmf.getTrustManagers(); + if (tms != null) { + for (TrustManager tm : tms) { + if (tm instanceof X509TrustManager) { + localTrustManager = (X509TrustManager) tm; + break; + } + } + } + java.io.FileOutputStream keyStoreStream; + try { + keyStoreStream = new java.io.FileOutputStream(keyStoreFile); + keyStore.store(keyStoreStream, "".toCharArray()); + keyStoreStream.close(); + } catch (FileNotFoundException e) { + throw new CertificateException("Unable to write KeyStore: " + e.getMessage()); + } catch (CertificateException e) { + throw new CertificateException("Unable to write KeyStore: " + e.getMessage()); + } catch (IOException e) { + throw new CertificateException("Unable to write KeyStore: " + e.getMessage()); + } + + } catch (NoSuchAlgorithmException e) { + 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); + } + } }