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

Merge branch 'cert_validation'

This commit is contained in:
cketti 2013-12-04 04:46:06 +01:00
commit 448a80e96a
20 changed files with 844 additions and 299 deletions

View File

@ -27,11 +27,13 @@ android {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
instrumentTest {
manifest.srcFile 'tests/AndroidManifest.xml'
java.srcDirs = ['tests/src']
assets.srcDirs = ['tests/assets']
}
}
}

View File

@ -1,6 +1,8 @@
package com.fsck.k9;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@ -22,6 +24,7 @@ import android.net.ConnectivityManager;
import android.net.Uri;
import android.util.Log;
import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection;
import com.fsck.k9.crypto.Apg;
import com.fsck.k9.crypto.CryptoProvider;
import com.fsck.k9.helper.Utility;
@ -40,6 +43,7 @@ import com.fsck.k9.search.SqlQueryBuilder;
import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.SearchCondition;
import com.fsck.k9.search.SearchSpecification.Searchfield;
import com.fsck.k9.security.LocalKeyStore;
import com.fsck.k9.view.ColorChip;
import com.larswerkman.colorpicker.ColorPicker;
@ -1865,4 +1869,57 @@ public class Account implements BaseAccount {
search.and(Searchfield.FOLDER, folderName, Attribute.NOT_EQUALS);
}
}
/**
* Add a new certificate for the incoming or outgoing server to the local key store.
*/
public void addCertificate(CheckDirection direction,
X509Certificate certificate) throws CertificateException {
Uri uri;
if (direction.equals(CheckDirection.INCOMING)) {
uri = Uri.parse(getStoreUri());
} else {
uri = Uri.parse(getTransportUri());
}
LocalKeyStore localKeyStore = LocalKeyStore.getInstance();
localKeyStore.addCertificate(uri.getHost(), uri.getPort(), certificate);
}
/**
* Examine the existing settings for an account. If the old host/port is different from the
* new host/port, then try and delete any (possibly non-existent) certificate stored for the
* old host/port.
*/
public void deleteCertificate(String newHost, int newPort,
CheckDirection direction) {
Uri uri;
if (direction.equals(CheckDirection.INCOMING)) {
uri = Uri.parse(getStoreUri());
} else {
uri = Uri.parse(getTransportUri());
}
String oldHost = uri.getHost();
int oldPort = uri.getPort();
if (oldPort == -1) {
// This occurs when a new account is created
return;
}
if (!newHost.equals(oldHost) || newPort != oldPort) {
LocalKeyStore localKeyStore = LocalKeyStore.getInstance();
localKeyStore.deleteCertificate(oldHost, oldPort);
}
}
/**
* Examine the settings for the account and attempt to delete (possibly non-existent)
* certificates for the incoming and outgoing servers.
*/
public void deleteCertificates() {
LocalKeyStore localKeyStore = LocalKeyStore.getInstance();
Uri uri = Uri.parse(getStoreUri());
localKeyStore.deleteCertificate(uri.getHost(), uri.getPort());
uri = Uri.parse(getTransportUri());
localKeyStore.deleteCertificate(uri.getHost(), uri.getPort());
}
}

View File

@ -39,6 +39,7 @@ import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.provider.UnreadWidgetProvider;
import com.fsck.k9.security.LocalKeyStore;
import com.fsck.k9.service.BootReceiver;
import com.fsck.k9.service.MailService;
import com.fsck.k9.service.ShutdownReceiver;
@ -59,7 +60,7 @@ public class K9 extends Application {
* The application instance. Never <code>null</code>.
* @throws Exception
*/
void initializeComponent(K9 application);
void initializeComponent(Application application);
}
public static Application app = null;
@ -91,6 +92,15 @@ public class K9 extends Application {
*/
private static List<ApplicationAware> observers = new ArrayList<ApplicationAware>();
/**
* This will be {@code true} once the initialization is complete and {@link #notifyObservers()}
* was called.
* Afterwards calls to {@link #registerApplicationAware(com.fsck.k9.K9.ApplicationAware)} will
* immediately call {@link com.fsck.k9.K9.ApplicationAware#initializeComponent(K9)} for the
* supplied argument.
*/
private static boolean sInitialized = false;
public enum BACKGROUND_OPS {
WHEN_CHECKED, ALWAYS, NEVER, WHEN_CHECKED_AUTO_SYNC
}
@ -581,6 +591,8 @@ public class K9 extends Application {
*/
BinaryTempFileBody.setTempDirectory(getCacheDir());
LocalKeyStore.setKeyStoreLocation(getDir("KeyStore", MODE_PRIVATE).toString());
/*
* Enable background sync of messages
*/
@ -829,6 +841,7 @@ public class K9 extends Application {
* component that the application is available and ready
*/
protected void notifyObservers() {
synchronized (observers) {
for (final ApplicationAware aware : observers) {
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "Initializing observer: " + aware);
@ -839,6 +852,10 @@ public class K9 extends Application {
Log.w(K9.LOG_TAG, "Failure when notifying " + aware, e);
}
}
sInitialized = true;
observers.clear();
}
}
/**
@ -848,10 +865,14 @@ public class K9 extends Application {
* Never <code>null</code>.
*/
public static void registerApplicationAware(final ApplicationAware component) {
if (!observers.contains(component)) {
synchronized (observers) {
if (sInitialized) {
component.initializeComponent(K9.app);
} else if (!observers.contains(component)) {
observers.add(component);
}
}
}
public static String getK9Language() {
return language;

View File

@ -127,6 +127,7 @@ public class Preferences {
Store.removeAccount(account);
account.deleteCertificates();
account.delete(this);
if (newAccount == account) {

View File

@ -17,6 +17,7 @@ import android.widget.Button;
import android.widget.EditText;
import com.fsck.k9.*;
import com.fsck.k9.activity.K9Activity;
import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection;
import com.fsck.k9.helper.Utility;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
@ -37,6 +38,8 @@ public class AccountSetupBasics extends K9Activity
private final static int DIALOG_NOTE = 1;
private final static String STATE_KEY_PROVIDER =
"com.fsck.k9.AccountSetupBasics.provider";
private final static String STATE_KEY_CHECKED_INCOMING =
"com.fsck.k9.AccountSetupBasics.checkedIncoming";
private EditText mEmailView;
private EditText mPasswordView;
@ -46,6 +49,7 @@ public class AccountSetupBasics extends K9Activity
private Provider mProvider;
private EmailAddressValidator mEmailValidator = new EmailAddressValidator();
private boolean mCheckedIncoming = false;
public static void actionNewAccount(Context context) {
Intent i = new Intent(context, AccountSetupBasics.class);
@ -66,15 +70,6 @@ public class AccountSetupBasics extends K9Activity
mEmailView.addTextChangedListener(this);
mPasswordView.addTextChangedListener(this);
if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
String accountUuid = savedInstanceState.getString(EXTRA_ACCOUNT);
mAccount = Preferences.getPreferences(this).getAccount(accountUuid);
}
if (savedInstanceState != null && savedInstanceState.containsKey(STATE_KEY_PROVIDER)) {
mProvider = (Provider)savedInstanceState.getSerializable(STATE_KEY_PROVIDER);
}
}
@Override
@ -92,6 +87,23 @@ public class AccountSetupBasics extends K9Activity
if (mProvider != null) {
outState.putSerializable(STATE_KEY_PROVIDER, mProvider);
}
outState.putBoolean(STATE_KEY_CHECKED_INCOMING, mCheckedIncoming);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
String accountUuid = savedInstanceState.getString(EXTRA_ACCOUNT);
mAccount = Preferences.getPreferences(this).getAccount(accountUuid);
}
if (savedInstanceState.containsKey(STATE_KEY_PROVIDER)) {
mProvider = (Provider) savedInstanceState.getSerializable(STATE_KEY_PROVIDER);
}
mCheckedIncoming = savedInstanceState.getBoolean(STATE_KEY_CHECKED_INCOMING);
}
public void afterTextChanged(Editable s) {
@ -229,7 +241,8 @@ public class AccountSetupBasics extends K9Activity
} else if (incomingUri.toString().startsWith("pop3")) {
mAccount.setDeletePolicy(Account.DELETE_POLICY_NEVER);
}
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, true);
// Check incoming here. Then check outgoing in onActivityResult()
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.INCOMING);
} catch (UnsupportedEncodingException enc) {
// This really shouldn't happen since the encoding is hardcoded to UTF-8
Log.e(K9.LOG_TAG, "Couldn't urlencode username or password.", enc);
@ -266,6 +279,12 @@ public class AccountSetupBasics extends K9Activity
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
if (!mCheckedIncoming) {
//We've successfully checked incoming. Now check outgoing.
mCheckedIncoming = true;
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.OUTGOING);
} else {
//We've successfully checked outgoing as well.
mAccount.setDescription(mAccount.getEmail());
mAccount.save(Preferences.getPreferences(this));
K9.setServicesEnabled(this);
@ -273,6 +292,7 @@ public class AccountSetupBasics extends K9Activity
finish();
}
}
}
private void onManualSetup() {
String email = mEmailView.getText().toString();

View File

@ -22,7 +22,6 @@ import com.fsck.k9.mail.AuthenticationFailedException;
import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.store.TrustManagerFactory;
import com.fsck.k9.mail.store.WebDavStore;
import com.fsck.k9.mail.filter.Hex;
@ -47,9 +46,12 @@ public class AccountSetupCheckSettings extends K9Activity implements OnClickList
private static final String EXTRA_ACCOUNT = "account";
private static final String EXTRA_CHECK_INCOMING = "checkIncoming";
private static final String EXTRA_CHECK_DIRECTION ="checkDirection";
private static final String EXTRA_CHECK_OUTGOING = "checkOutgoing";
public enum CheckDirection {
INCOMING,
OUTGOING
}
private Handler mHandler = new Handler();
@ -59,20 +61,16 @@ public class AccountSetupCheckSettings extends K9Activity implements OnClickList
private Account mAccount;
private boolean mCheckIncoming;
private boolean mCheckOutgoing;
private CheckDirection mDirection;
private boolean mCanceled;
private boolean mDestroyed;
public static void actionCheckSettings(Activity context, Account account,
boolean checkIncoming, boolean checkOutgoing) {
public static void actionCheckSettings(Activity context, Account account, CheckDirection direction) {
Intent i = new Intent(context, AccountSetupCheckSettings.class);
i.putExtra(EXTRA_ACCOUNT, account.getUuid());
i.putExtra(EXTRA_CHECK_INCOMING, checkIncoming);
i.putExtra(EXTRA_CHECK_OUTGOING, checkOutgoing);
i.putExtra(EXTRA_CHECK_DIRECTION, direction);
context.startActivityForResult(i, ACTIVITY_REQUEST_CODE);
}
@ -89,8 +87,7 @@ public class AccountSetupCheckSettings extends K9Activity implements OnClickList
String accountUuid = getIntent().getStringExtra(EXTRA_ACCOUNT);
mAccount = Preferences.getPreferences(this).getAccount(accountUuid);
mCheckIncoming = getIntent().getBooleanExtra(EXTRA_CHECK_INCOMING, false);
mCheckOutgoing = getIntent().getBooleanExtra(EXTRA_CHECK_OUTGOING, false);
mDirection = (CheckDirection) getIntent().getSerializableExtra(EXTRA_CHECK_DIRECTION);
new Thread() {
@Override
@ -108,9 +105,9 @@ public class AccountSetupCheckSettings extends K9Activity implements OnClickList
final MessagingController ctrl = MessagingController.getInstance(getApplication());
ctrl.clearCertificateErrorNotifications(AccountSetupCheckSettings.this,
mAccount, mCheckIncoming, mCheckOutgoing);
mAccount, mDirection);
if (mCheckIncoming) {
if (mDirection.equals(CheckDirection.INCOMING)) {
store = mAccount.getRemoteStore();
if (store instanceof WebDavStore) {
@ -133,7 +130,7 @@ public class AccountSetupCheckSettings extends K9Activity implements OnClickList
finish();
return;
}
if (mCheckOutgoing) {
if (mDirection.equals(CheckDirection.OUTGOING)) {
if (!(mAccount.getRemoteStore() instanceof WebDavStore)) {
setMessage(R.string.account_setup_check_settings_check_outgoing_msg);
}
@ -366,21 +363,14 @@ public class AccountSetupCheckSettings extends K9Activity implements OnClickList
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);
mAccount.addCertificate(mDirection, chain[0]);
} 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);
mDirection);
}
})
.setNegativeButton(

View File

@ -16,6 +16,7 @@ import android.widget.CompoundButton.OnCheckedChangeListener;
import com.fsck.k9.*;
import com.fsck.k9.activity.K9Activity;
import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.ServerSettings;
@ -427,6 +428,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
mWebdavMailboxPathView.getText().toString());
}
mAccount.deleteCertificate(host, port, CheckDirection.INCOMING);
ServerSettings settings = new ServerSettings(mStoreType, host, port,
connectionSecurity, authType, username, password, extra);
@ -437,7 +439,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
mAccount.setCompression(Account.TYPE_OTHER, mCompressionOther.isChecked());
mAccount.setSubscribedFoldersOnly(mSubscribedFoldersOnly.isChecked());
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, false);
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.INCOMING);
} catch (Exception e) {
failure(e);
}

View File

@ -15,6 +15,7 @@ import android.widget.*;
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.fsck.k9.*;
import com.fsck.k9.activity.K9Activity;
import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.transport.SmtpTransport;
@ -95,7 +96,7 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener,
try {
if (new URI(mAccount.getStoreUri()).getScheme().startsWith("webdav")) {
mAccount.setTransportUri(mAccount.getStoreUri());
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, false, true);
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.OUTGOING);
}
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
@ -308,10 +309,12 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener,
if (mRequireLoginView.isChecked()) {
userInfo = usernameEnc + ":" + passwordEnc + ":" + authType;
}
uri = new URI(smtpSchemes[securityType], userInfo, mServerView.getText().toString(),
Integer.parseInt(mPortView.getText().toString()), null, null, null);
String newHost = mServerView.getText().toString();
int newPort = Integer.parseInt(mPortView.getText().toString());
uri = new URI(smtpSchemes[securityType], userInfo, newHost, newPort, null, null, null);
mAccount.deleteCertificate(newHost, newPort, CheckDirection.OUTGOING);
mAccount.setTransportUri(uri.toString());
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, false, true);
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.OUTGOING);
} catch (UnsupportedEncodingException enc) {
// This really shouldn't happen since the encoding is hardcoded to UTF-8
Log.e(K9.LOG_TAG, "Couldn't urlencode username or password.", enc);

View File

@ -53,6 +53,7 @@ import com.fsck.k9.activity.FolderList;
import com.fsck.k9.activity.MessageList;
import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.activity.NotificationDeleteConfirmation;
import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection;
import com.fsck.k9.activity.setup.AccountSetupIncoming;
import com.fsck.k9.activity.setup.AccountSetupOutgoing;
import com.fsck.k9.cache.EmailProviderCache;
@ -2671,14 +2672,13 @@ public class MessagingController implements Runnable {
}
public void clearCertificateErrorNotifications(Context context,
final Account account, boolean incoming, boolean outgoing) {
final Account account, CheckDirection direction) {
final NotificationManager nm = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
if (incoming) {
if (direction.equals(CheckDirection.INCOMING)) {
nm.cancel(null, K9.CERTIFICATE_EXCEPTION_NOTIFICATION_INCOMING + account.getAccountNumber());
}
if (outgoing) {
} else {
nm.cancel(null, K9.CERTIFICATE_EXCEPTION_NOTIFICATION_OUTGOING + account.getAccountNumber());
}
}

View File

@ -96,6 +96,8 @@ 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.TrustManagerFactory;
import com.fsck.k9.net.ssl.TrustedSocketFactory;
import com.jcraft.jzlib.JZlib;
import com.jcraft.jzlib.ZOutputStream;
@ -2446,9 +2448,12 @@ public class ImapStore extends Store {
connectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) {
SSLContext sslContext = SSLContext.getInstance("TLS");
boolean secure = connectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
sslContext.init(null, new TrustManager[] {
TrustManagerFactory.get(mSettings.getHost(), secure)
}, new SecureRandom());
sslContext
.init(null,
new TrustManager[] { TrustManagerFactory.get(
mSettings.getHost(),
mSettings.getPort(), secure) },
new SecureRandom());
mSocket = TrustedSocketFactory.createSocket(sslContext);
} else {
mSocket = new Socket();
@ -2501,9 +2506,11 @@ public class ImapStore extends Store {
SSLContext sslContext = SSLContext.getInstance("TLS");
boolean secure = mSettings.getConnectionSecurity() == CONNECTION_SECURITY_TLS_REQUIRED;
sslContext.init(null, new TrustManager[] {
TrustManagerFactory.get(mSettings.getHost(), secure)
}, new SecureRandom());
sslContext.init(null,
new TrustManager[] { TrustManagerFactory.get(
mSettings.getHost(),
mSettings.getPort(), secure) },
new SecureRandom());
mSocket = TrustedSocketFactory.createSocket(sslContext, mSocket,
mSettings.getHost(), mSettings.getPort(), true);
mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT);

View File

@ -10,6 +10,8 @@ import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.net.ssl.TrustManagerFactory;
import com.fsck.k9.net.ssl.TrustedSocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
@ -327,9 +329,9 @@ public class Pop3Store extends Store {
mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) {
SSLContext sslContext = SSLContext.getInstance("TLS");
final boolean secure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
sslContext.init(null, new TrustManager[] {
TrustManagerFactory.get(mHost, secure)
}, new SecureRandom());
sslContext.init(null,
new TrustManager[] { TrustManagerFactory.get(mHost,
mPort, secure) }, new SecureRandom());
mSocket = TrustedSocketFactory.createSocket(sslContext);
} else {
mSocket = new Socket();
@ -356,9 +358,10 @@ public class Pop3Store extends Store {
SSLContext sslContext = SSLContext.getInstance("TLS");
boolean secure = mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED;
sslContext.init(null, new TrustManager[] {
TrustManagerFactory.get(mHost, secure)
}, new SecureRandom());
sslContext.init(null,
new TrustManager[] { TrustManagerFactory.get(
mHost, mPort, secure) },
new SecureRandom());
mSocket = TrustedSocketFactory.createSocket(sslContext, mSocket, mHost,
mPort, true);
mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT);

View File

@ -1,214 +0,0 @@
package com.fsck.k9.mail.store;
import android.app.Application;
import android.content.Context;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.helper.DomainNameChecker;
import com.fsck.k9.mail.CertificateChainException;
import org.apache.commons.io.IOUtils;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
public final class TrustManagerFactory {
private static final String LOG_TAG = "TrustManagerFactory";
private static X509TrustManager defaultTrustManager;
private static X509TrustManager unsecureTrustManager;
private static X509TrustManager localTrustManager;
private static File keyStoreFile;
private static KeyStore keyStore;
private static class SimpleX509TrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
private static class SecureX509TrustManager implements X509TrustManager {
private static final Map<String, SecureX509TrustManager> mTrustManager =
new HashMap<String, SecureX509TrustManager>();
private final String mHost;
private SecureX509TrustManager(String host) {
mHost = host;
}
public synchronized static X509TrustManager getInstance(String host) {
SecureX509TrustManager trustManager;
if (mTrustManager.containsKey(host)) {
trustManager = mTrustManager.get(host);
} else {
trustManager = new SecureX509TrustManager(host);
mTrustManager.put(host, trustManager);
}
return trustManager;
}
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
defaultTrustManager.checkClientTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
try {
defaultTrustManager.checkServerTrusted(chain, authType);
} catch (CertificateException e) {
try {
localTrustManager.checkServerTrusted(
new X509Certificate[] { chain[0] }, authType);
} catch (CertificateException ce) {
throw new CertificateChainException(ce, chain);
}
}
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 CertificateChainException(
"Certificate domain name does not match " + mHost,
chain);
}
}
public X509Certificate[] getAcceptedIssuers() {
return defaultTrustManager.getAcceptedIssuers();
}
}
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;
}
tmf.init(keyStore);
TrustManager[] tms = tmf.getTrustManagers();
if (tms != null) {
for (TrustManager tm : tms) {
if (tm instanceof X509TrustManager) {
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);
} finally {
IOUtils.closeQuietly(fis);
}
unsecureTrustManager = new SimpleX509TrustManager();
}
private TrustManagerFactory() {
}
public static X509TrustManager get(String host, boolean secure) {
return secure ? SecureX509TrustManager.getInstance(host) :
unsecureTrustManager;
}
public static KeyStore getKeyStore() {
return keyStore;
}
public static void addCertificateChain(String alias, X509Certificate[] chain) throws CertificateException {
try {
javax.net.ssl.TrustManagerFactory tmf = javax.net.ssl.TrustManagerFactory.getInstance("X509");
for (X509Certificate element : chain) {
keyStore.setCertificateEntry
(element.getSubjectDN().toString(), element);
}
tmf.init(keyStore);
TrustManager[] tms = tmf.getTrustManagers();
if (tms != null) {
for (TrustManager tm : tms) {
if (tm instanceof X509TrustManager) {
localTrustManager = (X509TrustManager) tm;
break;
}
}
}
java.io.OutputStream keyStoreStream = null;
try {
keyStoreStream = new java.io.FileOutputStream(keyStoreFile);
keyStore.store(keyStoreStream, "".toCharArray());
} 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());
} finally {
IOUtils.closeQuietly(keyStoreStream);
}
} 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);
}
}
}

View File

@ -4,6 +4,8 @@ import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.scheme.LayeredSocketFactory;
import org.apache.http.params.HttpParams;
import com.fsck.k9.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
@ -26,10 +28,10 @@ public class WebDavSocketFactory implements LayeredSocketFactory {
private SSLSocketFactory mSocketFactory;
private org.apache.http.conn.ssl.SSLSocketFactory mSchemeSocketFactory;
public WebDavSocketFactory(String host, boolean secure) throws NoSuchAlgorithmException, KeyManagementException {
public WebDavSocketFactory(String host, int port, boolean secure) throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] {
TrustManagerFactory.get(host, secure)
TrustManagerFactory.get(host, port, secure)
}, new SecureRandom());
mSocketFactory = sslContext.getSocketFactory();
mSchemeSocketFactory = org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory();

View File

@ -1079,7 +1079,7 @@ public class WebDavStore extends Store {
SchemeRegistry reg = mHttpClient.getConnectionManager().getSchemeRegistry();
try {
Scheme s = new Scheme("https", new WebDavSocketFactory(mHost, mSecure), 443);
Scheme s = new Scheme("https", new WebDavSocketFactory(mHost, 443, mSecure), 443);
reg.register(s);
} catch (NoSuchAlgorithmException nsa) {
Log.e(K9.LOG_TAG, "NoSuchAlgorithmException in getHttpClient: " + nsa);

View File

@ -12,9 +12,9 @@ import com.fsck.k9.mail.filter.LineWrapOutputStream;
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.TrustManagerFactory;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.TrustedSocketFactory;
import com.fsck.k9.net.ssl.TrustManagerFactory;
import com.fsck.k9.net.ssl.TrustedSocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
@ -242,9 +242,10 @@ public class SmtpTransport extends Transport {
mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) {
SSLContext sslContext = SSLContext.getInstance("TLS");
boolean secure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
sslContext.init(null, new TrustManager[] {
TrustManagerFactory.get(mHost, secure)
}, new SecureRandom());
sslContext.init(null,
new TrustManager[] { TrustManagerFactory.get(
mHost, mPort, secure) },
new SecureRandom());
mSocket = TrustedSocketFactory.createSocket(sslContext);
mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
} else {
@ -301,9 +302,9 @@ public class SmtpTransport extends Transport {
SSLContext sslContext = SSLContext.getInstance("TLS");
boolean secure = mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED;
sslContext.init(null, new TrustManager[] {
TrustManagerFactory.get(mHost, secure)
}, new SecureRandom());
sslContext.init(null,
new TrustManager[] { TrustManagerFactory.get(mHost,
mPort, secure) }, new SecureRandom());
mSocket = TrustedSocketFactory.createSocket(sslContext, mSocket, mHost,
mPort, true);
mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(),

View File

@ -0,0 +1,133 @@
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.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
public final class TrustManagerFactory {
private static final String LOG_TAG = "TrustManagerFactory";
private static X509TrustManager defaultTrustManager;
private static X509TrustManager unsecureTrustManager;
private static LocalKeyStore keyStore;
private static class SimpleX509TrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
private static class SecureX509TrustManager implements X509TrustManager {
private static final Map<String, SecureX509TrustManager> mTrustManager =
new HashMap<String, SecureX509TrustManager>();
private final String mHost;
private final int mPort;
private SecureX509TrustManager(String host, int port) {
mHost = host;
mPort = port;
}
public synchronized static X509TrustManager getInstance(String host, int port) {
String key = host + ":" + port;
SecureX509TrustManager trustManager;
if (mTrustManager.containsKey(key)) {
trustManager = mTrustManager.get(key);
} else {
trustManager = new SecureX509TrustManager(host, port);
mTrustManager.put(key, trustManager);
}
return trustManager;
}
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
defaultTrustManager.checkClientTrusted(chain, authType);
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
boolean foundInGlobalKeyStore = false;
try {
defaultTrustManager.checkServerTrusted(chain, authType);
foundInGlobalKeyStore = true;
} catch (CertificateException e) { /* ignore */ }
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;
}
String 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() {
return defaultTrustManager.getAcceptedIssuers();
}
}
static {
try {
keyStore = LocalKeyStore.getInstance();
javax.net.ssl.TrustManagerFactory tmf = javax.net.ssl.TrustManagerFactory.getInstance("X509");
tmf.init((KeyStore) null);
TrustManager[] 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);
}
unsecureTrustManager = new SimpleX509TrustManager();
}
private TrustManagerFactory() {
}
public static X509TrustManager get(String host, int port, boolean secure) {
return secure ? SecureX509TrustManager.getInstance(host, port) :
unsecureTrustManager;
}
}

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store;
package com.fsck.k9.net.ssl;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;

View File

@ -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() {

View File

@ -0,0 +1,181 @@
package com.fsck.k9.security;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import org.apache.commons.io.IOUtils;
import android.util.Log;
import com.fsck.k9.K9;
public class LocalKeyStore {
private static final int KEY_STORE_FILE_VERSION = 1;
private static String sKeyStoreLocation;
public static void setKeyStoreLocation(String directory) {
sKeyStoreLocation = directory;
}
private static class LocalKeyStoreHolder {
static final LocalKeyStore INSTANCE = new LocalKeyStore();
}
public static LocalKeyStore getInstance() {
return LocalKeyStoreHolder.INSTANCE;
}
private File mKeyStoreFile;
private KeyStore mKeyStore;
private LocalKeyStore() {
if (sKeyStoreLocation == null) {
Log.e(K9.LOG_TAG, "Local key store location has not been initialized");
} else {
upgradeKeyStoreFile();
setKeyStoreFile(null);
}
}
/**
* Reinitialize the local key store with certificates contained in
* {@code file}
*
* @param file
* {@link File} containing locally saved certificates. May be 0
* length, in which case it is deleted and recreated. May be
* {@code null}, in which case a default file location is used.
*/
public synchronized void setKeyStoreFile(File file) {
if (file == null) {
file = new File(getKeyStoreFilePath(KEY_STORE_FILE_VERSION));
}
if (file.length() == 0) {
/*
* The file may be empty (e.g., if it was created with
* File.createTempFile). We can't pass an empty file to
* Keystore.load. Instead, we let it be created anew.
*/
file.delete();
}
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
} catch (FileNotFoundException e) {
// If the file doesn't exist, that's fine, too
}
try {
KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());
store.load(fis, "".toCharArray());
mKeyStore = store;
mKeyStoreFile = file;
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Failed to initialize local key store", e);
// Use of the local key store is effectively disabled.
mKeyStore = null;
mKeyStoreFile = null;
} finally {
IOUtils.closeQuietly(fis);
}
}
public synchronized void addCertificate(String host, int port,
X509Certificate certificate) throws CertificateException {
if (mKeyStore == null) {
throw new CertificateException(
"Certificate not added because key store not initialized");
}
try {
mKeyStore.setCertificateEntry(getCertKey(host, port), certificate);
} catch (KeyStoreException e) {
throw new CertificateException(
"Failed to add certificate to local key store", e);
}
writeCertificateFile();
}
private void writeCertificateFile() throws CertificateException {
java.io.OutputStream keyStoreStream = null;
try {
keyStoreStream = new java.io.FileOutputStream(mKeyStoreFile);
mKeyStore.store(keyStoreStream, "".toCharArray());
} 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) {
throw new CertificateException("Unable to write KeyStore: "
+ e.getMessage());
} catch (KeyStoreException e) {
throw new CertificateException("Unable to write KeyStore: "
+ e.getMessage());
} finally {
IOUtils.closeQuietly(keyStoreStream);
}
}
public synchronized boolean isValidCertificate(Certificate certificate,
String host, int port) {
if (mKeyStore == null) {
return false;
}
Certificate storedCert = null;
try {
storedCert = mKeyStore.getCertificate(getCertKey(host, port));
return (storedCert != null && storedCert.equals(certificate));
} catch (KeyStoreException e) {
return false;
}
}
private static String getCertKey(String host, int port) {
return host + ":" + port;
}
public synchronized void deleteCertificate(String oldHost, int oldPort) {
if (mKeyStore == null) {
return;
}
try {
mKeyStore.deleteEntry(getCertKey(oldHost, oldPort));
writeCertificateFile();
} catch (KeyStoreException e) {
// Ignore: most likely there was no cert. found
} catch (CertificateException e) {
Log.e(K9.LOG_TAG, "Error updating the local key store file", e);
}
}
private void upgradeKeyStoreFile() {
if (KEY_STORE_FILE_VERSION > 0) {
// Blow away version "0" because certificate aliases have changed.
new File(getKeyStoreFilePath(0)).delete();
}
}
private String getKeyStoreFilePath(int version) {
if (version < 1) {
return sKeyStoreLocation + File.separator + "KeyStore.bks";
} else {
return sKeyStoreLocation + File.separator + "KeyStore_v" + version + ".bks";
}
}
}

View File

@ -0,0 +1,335 @@
package com.fsck.k9.net.ssl;
import javax.net.ssl.X509TrustManager;
import com.fsck.k9.security.LocalKeyStore;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
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 static final String K9_EXAMPLE_COM_CERT1 =
"-----BEGIN CERTIFICATE-----\n"
+ "MIICCTCCAXICCQD/R0TV7d0C5TANBgkqhkiG9w0BAQUFADBJMQswCQYDVQQGEwJD\n"
+ "SDETMBEGA1UECBMKU29tZS1TdGF0ZTEMMAoGA1UEChMDSy05MRcwFQYDVQQDEw5r\n"
+ "OS5leGFtcGxlLmNvbTAeFw0xMTA5MDYxOTU3MzVaFw0yMTA5MDMxOTU3MzVaMEkx\n"
+ "CzAJBgNVBAYTAkNIMRMwEQYDVQQIEwpTb21lLVN0YXRlMQwwCgYDVQQKEwNLLTkx\n"
+ "FzAVBgNVBAMTDms5LmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB\n"
+ "iQKBgQCp7FvHRaQaOIu3iyB5GB0PtPCxy/bLlBxBb8p9QsMimX2Yz3SNjWVUzU5N\n"
+ "ggpXmmeGopLAnvZlhWYSx0yIGWwPB44kGK5eaYDRWav+K+XXgdNCJij1UWPSmFwZ\n"
+ "hUoNbrahco5AFw0jC1qi+3Dht6Y64nfNzTOYTcm1Pz4tqXiADQIDAQABMA0GCSqG\n"
+ "SIb3DQEBBQUAA4GBAIPsgd6fuFRojSOAcUyhaoKaY5hXJf8d7R3AYWxcAPYmn6g7\n"
+ "3Zms+f7/CH0y/tM81oBTlq9ZLbrJyLzC7vG1pqWHMNaK7miAho22IRuk+HwvL6OA\n"
+ "uH3x3W1/mH4ci268cIFVmofID0nYLTqOxBTczfYhI7q0VBUXqv/bZ+3bVMSh\n"
+ "-----END CERTIFICATE-----\n";
private static final String K9_EXAMPLE_COM_CERT2 =
"-----BEGIN CERTIFICATE-----\n"
+ "MIICCTCCAXICCQDMryqq0gZ80jANBgkqhkiG9w0BAQUFADBJMQswCQYDVQQGEwJD\n"
+ "SDETMBEGA1UECBMKU29tZS1TdGF0ZTEMMAoGA1UEChMDSy05MRcwFQYDVQQDEw5r\n"
+ "OS5leGFtcGxlLmNvbTAeFw0xMTA5MDYyMDAwNTVaFw0yMTA5MDMyMDAwNTVaMEkx\n"
+ "CzAJBgNVBAYTAkNIMRMwEQYDVQQIEwpTb21lLVN0YXRlMQwwCgYDVQQKEwNLLTkx\n"
+ "FzAVBgNVBAMTDms5LmV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB\n"
+ "iQKBgQDOLzRucC3tuXL/NthnGkgTnVn03balrvYPkABvvrG83Dpp5ipIC/iPsQvw\n"
+ "pvqypSNHqrloEB7o3obQ8tiRDtbOsNQ7gKJ+YoD1drDNClV0pBvr7mvRgA2AcDpw\n"
+ "CTLKwVIyKmE+rm3vl8CWFd9CqHcYQ3Mc1KXXasN4DEAzZ/sHRwIDAQABMA0GCSqG\n"
+ "SIb3DQEBBQUAA4GBAFDcHFpmZ9SUrc0WayrKNUpSaHLRG94uzIx0VUMLROcXEEWU\n"
+ "soRw1RfoSBkcy2SEjB4CAvex6qAiOT3ubXuL+BYFav/uU8JPWZ9ovSAYqBZ9aUJo\n"
+ "G6A2hvA1lpvP97qQ/NFaGQ38XqSykZamZwSx3PlZUM/i9S9n/3MfuuXWqtLC\n"
+ "-----END CERTIFICATE-----\n";
private static final String CA_CERT =
"-----BEGIN CERTIFICATE-----\n"
+ "MIIDbTCCAlWgAwIBAgIJANCdQ+Cwnyg+MA0GCSqGSIb3DQEBBQUAME0xCzAJBgNV\n"
+ "BAYTAkNIMRMwEQYDVQQIDApTb21lLVN0YXRlMQwwCgYDVQQKDANLLTkxGzAZBgNV\n"
+ "BAMMEnRlc3QtY2EuazltYWlsLm9yZzAeFw0xMzEyMDIxMjUwNThaFw0yMzExMzAx\n"
+ "MjUwNThaME0xCzAJBgNVBAYTAkNIMRMwEQYDVQQIDApTb21lLVN0YXRlMQwwCgYD\n"
+ "VQQKDANLLTkxGzAZBgNVBAMMEnRlc3QtY2EuazltYWlsLm9yZzCCASIwDQYJKoZI\n"
+ "hvcNAQEBBQADggEPADCCAQoCggEBAJ+YLg9enfFk5eba6B3LtQzUE7GiR2tIpQSi\n"
+ "zHMtHzn8KUnRDiGwC8VnSuWCOX7hXyQ0P6i2+DVRVBYOAeDCNMZHOq1hRqI66B33\n"
+ "QqLfkBnJAIDeLqfqlgigHs1+//7eagVA6Z38ZFre3PFuKnK9NCwS+gz7PKw/poIG\n"
+ "/FZP+ltMlkwvPww4S8SMlY6RXXH09+S/uM8aG6DUBT298eoAXTbSEIeaNhwBHZPe\n"
+ "rXqqzd8QDAIE9BFXSkh/BQiVEFDPSBMSdmUzUAsT2aM8osntnKWY5/G7B60wutvA\n"
+ "jYCULgtR6lR6jIDbG3ECHVDsTWR+Pgl+h1zeyERhN5iG1ffOtLUCAwEAAaNQME4w\n"
+ "HQYDVR0OBBYEFBlUYiTGlOu9zIPx8Q13xcnDL5QpMB8GA1UdIwQYMBaAFBlUYiTG\n"
+ "lOu9zIPx8Q13xcnDL5QpMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB\n"
+ "AJ6oC6O6I6p0vgA4+7dfyxKX745zl/fK6IVHV/GO75mLjVdyw00USbHGHAmZM5C6\n"
+ "eCKVV83m/Re5lHf8ZBjc+3rWdGCEjwyUwvDeUvzpcKF3wPxYDUOOqSI+np1cxj6q\n"
+ "6+XI5QXwyUObWtWyw1GOpLuFPbxny/TlRWvk8AfOaLANg3UhvITNZMdMHoQ2sJ3u\n"
+ "MrQ+CHe/Tal2MkwiCrYT91f3YWVaswiEAxpqxnwuSXnYyaJpqMCcA1txBDgX84FP\n"
+ "dSIM4ut+QltV2Tlx0lpH43dvttAwkPB+iL7ZF6zUki/Nq5aKyNoHOL88TACe18Lq\n"
+ "zOztD2HZfxhIz3uH2gXmqUo=\n"
+ "-----END CERTIFICATE-----\n";
private static final String CERT3 =
"-----BEGIN CERTIFICATE-----\n"
+ "MIIDjDCCAnSgAwIBAgIBATANBgkqhkiG9w0BAQUFADBNMQswCQYDVQQGEwJDSDET\n"
+ "MBEGA1UECAwKU29tZS1TdGF0ZTEMMAoGA1UECgwDSy05MRswGQYDVQQDDBJ0ZXN0\n"
+ "LWNhLms5bWFpbC5vcmcwHhcNMTMxMjAyMTMxNzEyWhcNMjMxMTMwMTMxNzEyWjBJ\n"
+ "MQswCQYDVQQGEwJDSDETMBEGA1UECAwKU29tZS1TdGF0ZTEMMAoGA1UECgwDSy05\n"
+ "MRcwFQYDVQQDDA5rOS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP\n"
+ "ADCCAQoCggEBAL9OvWtLcp6bd40Hai6A6cCmJRwn3mwcTB8E41iEQgQexqx/f9RR\n"
+ "BuQi2s80k/vXq8QU2GbwGiPkBBXMUHuiT27Lsoj8kMOnH5BXeKLaWDiMpvNqfent\n"
+ "UzBXSIOK6Yu9UtlU0MzAuYxXaunrXoS5Dejrbz743P9yW8hx7pANNU0Qfck+ekR7\n"
+ "Q4PWNgfbFHrnvcobzuFzJeWg8x9iTTsVGIaX9AVMjMUlIKvhhOWTlcTJHKzU67sp\n"
+ "OLzwH9IJ3hqwdmsgZu5D/2AZlYlpFk6AlnoxNhfy9m+T41P8+iWDYCJoxvf3d6gl\n"
+ "TlZ1FL0PzPReXeAgugyJ1qx5gJ9Vhf/rBaUCAwEAAaN7MHkwCQYDVR0TBAIwADAs\n"
+ "BglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYD\n"
+ "VR0OBBYEFPm9hbTbfmcnjjfOzrec/TrvsS5ZMB8GA1UdIwQYMBaAFBlUYiTGlOu9\n"
+ "zIPx8Q13xcnDL5QpMA0GCSqGSIb3DQEBBQUAA4IBAQAgvYQoCEklJNXBwLuWpSMx\n"
+ "CQrVxLI1XsYRzqMs0kUgM59OhwAPwdSR+UEuyXQ8QGKwSt1d//DkdhzQDATXSBYc\n"
+ "VHr16ocYPGNd/VNo7BoUCvykp3cCH3WxYYpAugXbLU8RBJzQwCM75SLQtFe20qfI\n"
+ "LErbrmKONtMk3Rfg6XtLLcaOVh1A3q13CKqDvwtZT4oo56EJOvkBkzlCvTuxJb6s\n"
+ "FD9pwROFpIN8O54C333tZzj4TDP4g9zb3sofAJ4U0osfQAXekZJdZETFGJsU6TIM\n"
+ "Dcf5/G8bZe2DnavBQfML1wI5d7NUWE8CWb95SsIvFXI0qZE0oIR+axBVl9u97uaO\n"
+ "-----END CERTIFICATE-----\n";
private static final String DIGI_CERT =
"-----BEGIN CERTIFICATE-----\n"
+ "MIIG5jCCBc6gAwIBAgIQAze5KDR8YKauxa2xIX84YDANBgkqhkiG9w0BAQUFADBs\n"
+ "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
+ "d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j\n"
+ "ZSBFViBSb290IENBMB4XDTA3MTEwOTEyMDAwMFoXDTIxMTExMDAwMDAwMFowaTEL\n"
+ "MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3\n"
+ "LmRpZ2ljZXJ0LmNvbTEoMCYGA1UEAxMfRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug\n"
+ "RVYgQ0EtMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPOWYth1bhn/\n"
+ "PzR8SU8xfg0ETpmB4rOFVZEwscCvcLssqOcYqj9495BoUoYBiJfiOwZlkKq9ZXbC\n"
+ "7L4QWzd4g2B1Rca9dKq2n6Q6AVAXxDlpufFP74LByvNK28yeUE9NQKM6kOeGZrzw\n"
+ "PnYoTNF1gJ5qNRQ1A57bDIzCKK1Qss72kaPDpQpYSfZ1RGy6+c7pqzoC4E3zrOJ6\n"
+ "4GAiBTyC01Li85xH+DvYskuTVkq/cKs+6WjIHY9YHSpNXic9rQpZL1oRIEDZaARo\n"
+ "LfTAhAsKG3jf7RpY3PtBWm1r8u0c7lwytlzs16YDMqbo3rcoJ1mIgP97rYlY1R4U\n"
+ "pPKwcNSgPqcCAwEAAaOCA4UwggOBMA4GA1UdDwEB/wQEAwIBhjA7BgNVHSUENDAy\n"
+ "BggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUH\n"
+ "AwgwggHEBgNVHSAEggG7MIIBtzCCAbMGCWCGSAGG/WwCATCCAaQwOgYIKwYBBQUH\n"
+ "AgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5o\n"
+ "dG0wggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0\n"
+ "AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1\n"
+ "AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABp\n"
+ "AGcAaQBDAGUAcgB0ACAARQBWACAAQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBl\n"
+ "AGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBo\n"
+ "AGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAg\n"
+ "AGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAg\n"
+ "AGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wEgYDVR0TAQH/BAgwBgEB/wIBADCB\n"
+ "gwYIKwYBBQUHAQEEdzB1MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy\n"
+ "dC5jb20wTQYIKwYBBQUHMAKGQWh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NBQ2Vy\n"
+ "dHMvRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3J0MIGPBgNVHR8EgYcw\n"
+ "gYQwQKA+oDyGOmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hB\n"
+ "c3N1cmFuY2VFVlJvb3RDQS5jcmwwQKA+oDyGOmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0\n"
+ "LmNvbS9EaWdpQ2VydEhpZ2hBc3N1cmFuY2VFVlJvb3RDQS5jcmwwHQYDVR0OBBYE\n"
+ "FExYyyXwQU9S9CjIgUObpqig5pLlMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSYJhoI\n"
+ "Au9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQBMeheHKF0XvLIyc7/NLvVYMR3wsXFU\n"
+ "nNabZ5PbLwM+Fm8eA8lThKNWYB54lBuiqG+jpItSkdfdXJW777UWSemlQk808kf/\n"
+ "roF/E1S3IMRwFcuBCoHLdFfcnN8kpCkMGPAc5K4HM+zxST5Vz25PDVR708noFUjU\n"
+ "xbvcNRx3RQdIRYW9135TuMAW2ZXNi419yWBP0aKb49Aw1rRzNubS+QOy46T15bg+\n"
+ "BEkAui6mSnKDcp33C4ypieez12Qf1uNgywPE3IjpnSUBAHHLA7QpYCWP+UbRe3Gu\n"
+ "zVMSW4SOwg/H7ZMZ2cn6j1g0djIvruFQFGHUqFijyDATI+/GJYw2jxyA\n"
+ "-----END CERTIFICATE-----\n";
private static final String GITHUB_CERT =
"-----BEGIN CERTIFICATE-----\n"
+ "MIIHOjCCBiKgAwIBAgIQBH++LkveAITSyvjj7P5wWDANBgkqhkiG9w0BAQUFADBp\n"
+ "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
+ "d3cuZGlnaWNlcnQuY29tMSgwJgYDVQQDEx9EaWdpQ2VydCBIaWdoIEFzc3VyYW5j\n"
+ "ZSBFViBDQS0xMB4XDTEzMDYxMDAwMDAwMFoXDTE1MDkwMjEyMDAwMFowgfAxHTAb\n"
+ "BgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYBBAGCNzwCAQMTAlVT\n"
+ "MRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMRAwDgYDVQQFEwc1MTU3NTUwMRcw\n"
+ "FQYDVQQJEw41NDggNHRoIFN0cmVldDEOMAwGA1UEERMFOTQxMDcxCzAJBgNVBAYT\n"
+ "AlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2Nv\n"
+ "MRUwEwYDVQQKEwxHaXRIdWIsIEluYy4xEzARBgNVBAMTCmdpdGh1Yi5jb20wggEi\n"
+ "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDt04nDXXByCfMzTxpydNm2WpVQ\n"
+ "u2hhn/f7Hxnh2gQxrxV8Gn/5c68d5UMrVgkARWlK6MRb38J3UlEZW9Er2TllNqAy\n"
+ "GRxBc/sysj2fmOyCWws3ZDkstxCDcs3w6iRL+tmULsOFFTmpOvaI2vQniaaVT4Si\n"
+ "N058JXg6yYNtAheVeH1HqFWD7hPIGRqzPPFf/jsC4YX7EWarCV2fTEPwxyReKXIo\n"
+ "ztR1aE8kcimuOSj8341PTYNzdAxvEZun3WLe/+LrF+b/DL/ALTE71lmi8t2HSkh7\n"
+ "bTMRFE00nzI49sgZnfG2PcVG71ELisYz7UhhxB0XG718tmfpOc+lUoAK9OrNAgMB\n"
+ "AAGjggNUMIIDUDAfBgNVHSMEGDAWgBRMWMsl8EFPUvQoyIFDm6aooOaS5TAdBgNV\n"
+ "HQ4EFgQUh9GPGW7kh29TjHeRB1Dfo79VRyAwJQYDVR0RBB4wHIIKZ2l0aHViLmNv\n"
+ "bYIOd3d3LmdpdGh1Yi5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG\n"
+ "AQUFBwMBBggrBgEFBQcDAjBjBgNVHR8EXDBaMCugKaAnhiVodHRwOi8vY3JsMy5k\n"
+ "aWdpY2VydC5jb20vZXZjYTEtZzIuY3JsMCugKaAnhiVodHRwOi8vY3JsNC5kaWdp\n"
+ "Y2VydC5jb20vZXZjYTEtZzIuY3JsMIIBxAYDVR0gBIIBuzCCAbcwggGzBglghkgB\n"
+ "hv1sAgEwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3LmRpZ2ljZXJ0LmNvbS9z\n"
+ "c2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBBAG4A\n"
+ "eQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBhAHQA\n"
+ "ZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBjAGUA\n"
+ "IABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMAUABTACAA\n"
+ "YQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkAIABBAGcA\n"
+ "cgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwAaQBhAGIA\n"
+ "aQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQA\n"
+ "ZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAuMH0G\n"
+ "CCsGAQUFBwEBBHEwbzAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu\n"
+ "Y29tMEcGCCsGAQUFBzAChjtodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln\n"
+ "aUNlcnRIaWdoQXNzdXJhbmNlRVZDQS0xLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqG\n"
+ "SIb3DQEBBQUAA4IBAQBfFW1nwzrVo94WnEUzJtU9yRZ0NMqHSBsUkG31q0eGufW4\n"
+ "4wFFZWjuqRJ1n3Ym7xF8fTjP3fdKGQnxIHKSsE0nuuh/XbQX5DpBJknHdGFoLwY8\n"
+ "xZ9JPI57vgvzLo8+fwHyZp3Vm/o5IYLEQViSo+nlOSUQ8YAVqu6KcsP/e612UiqS\n"
+ "+UMBmgdx9KPDDzZy4MJZC2hbfUoXj9A54mJN8cuEOPyw3c3yKOcq/h48KzVguQXi\n"
+ "SdJbwfqNIbQ9oJM+YzDjzS62+TCtNSNWzWbwABZCmuQxK0oEOSbTmbhxUF7rND3/\n"
+ "+mx9u8cY//7uAxLWYS5gIZlCbxcf0lkiKSHJB319\n"
+ "-----END CERTIFICATE-----\n";
private File mKeyStoreFile;
private LocalKeyStore mKeyStore;
private X509Certificate mCert1;
private X509Certificate mCert2;
private X509Certificate mCaCert;
private X509Certificate mCert3;
private X509Certificate mDigiCert;
private X509Certificate mGithubCert;
public TrustManagerFactoryTest() throws CertificateException {
mCert1 = loadCert(K9_EXAMPLE_COM_CERT1);
mCert2 = loadCert(K9_EXAMPLE_COM_CERT2);
mCaCert = loadCert(CA_CERT);
mCert3 = loadCert(CERT3);
mDigiCert = loadCert(DIGI_CERT);
mGithubCert = loadCert(GITHUB_CERT);
}
private X509Certificate loadCert(String encodedCert) throws CertificateException {
CertificateFactory certFactory = CertificateFactory.getInstance("X509");
return (X509Certificate) certFactory.generateCertificate(
new ByteArrayInputStream(encodedCert.getBytes()));
}
@Override
public void setUp() throws Exception {
mKeyStoreFile = File.createTempFile("localKeyStore", null, getContext().getCacheDir());
mKeyStore = LocalKeyStore.getInstance();
mKeyStore.setKeyStoreFile(mKeyStoreFile);
}
@Override
protected void tearDown() {
mKeyStoreFile.delete();
}
/**
* Checks if TrustManagerFactory supports a host with different certificates for different
* services (e.g. SMTP and IMAP).
*
* <p>
* This test is to make sure entries in the keystore file aren't overwritten.
* See <a href="https://code.google.com/p/k9mail/issues/detail?id=1326">Issue 1326</a>.
* </p>
*
* @throws Exception
* if anything goes wrong
*/
public void testDifferentCertificatesOnSameServer() throws Exception {
mKeyStore.addCertificate(NOT_MATCHING_HOST, PORT1, mCert1);
mKeyStore.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 {
mKeyStore.addCertificate(MATCHING_HOST, PORT1, mCert1);
X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1, true);
trustManager.checkServerTrusted(new X509Certificate[] { mCert1 }, "authType");
}
public void testSelfSignedCertificateNotMatchingHost() throws Exception {
mKeyStore.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 {
mKeyStore.addCertificate(MATCHING_HOST, PORT1, mCert1);
X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1, true);
assertCertificateRejection(trustManager, new X509Certificate[] { mCert2 });
}
public void testCertificateOfOtherHost() throws Exception {
mKeyStore.addCertificate(MATCHING_HOST, PORT1, mCert1);
mKeyStore.addCertificate(MATCHING_HOST, PORT2, mCert2);
X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1, true);
assertCertificateRejection(trustManager, new X509Certificate[] { mCert2 });
}
public void testUntrustedCertificateChain() throws Exception {
X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1, true);
assertCertificateRejection(trustManager, new X509Certificate[] { mCert3, mCaCert });
}
public void testLocallyTrustedCertificateChain() throws Exception {
mKeyStore.addCertificate(MATCHING_HOST, PORT1, mCert3);
X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1, true);
trustManager.checkServerTrusted(new X509Certificate[] { mCert3, mCaCert }, "authType");
}
public void testLocallyTrustedCertificateChainNotMatchingHost() throws Exception {
mKeyStore.addCertificate(NOT_MATCHING_HOST, PORT1, mCert3);
X509TrustManager trustManager = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1, true);
trustManager.checkServerTrusted(new X509Certificate[] { mCert3, mCaCert }, "authType");
}
public void testGloballyTrustedCertificateChain() throws Exception {
X509TrustManager trustManager = TrustManagerFactory.get("github.com", PORT1, true);
X509Certificate[] certificates = new X509Certificate[] { mGithubCert, mDigiCert };
trustManager.checkServerTrusted(certificates, "authType");
}
public void testGloballyTrustedCertificateNotMatchingHost() throws Exception {
X509TrustManager trustManager = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1, true);
assertCertificateRejection(trustManager, new X509Certificate[] { mGithubCert, mDigiCert});
}
public void testGloballyTrustedCertificateNotMatchingHostOverride() throws Exception {
mKeyStore.addCertificate(MATCHING_HOST, PORT1, mGithubCert);
X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1, true);
X509Certificate[] certificates = new X509Certificate[] { mGithubCert, mDigiCert };
trustManager.checkServerTrusted(certificates, "authType");
}
private void assertCertificateRejection(X509TrustManager trustManager,
X509Certificate[] certificates) {
boolean certificateValid;
try {
trustManager.checkServerTrusted(certificates, "authType");
certificateValid = true;
} catch (CertificateException e) {
certificateValid = false;
}
assertFalse("The certificate should have been rejected but wasn't", certificateValid);
}
public void testKeyStoreLoading() throws Exception {
mKeyStore.addCertificate(MATCHING_HOST, PORT1, mCert1);
mKeyStore.addCertificate(NOT_MATCHING_HOST, PORT2, mCert2);
assertTrue(mKeyStore.isValidCertificate(mCert1, MATCHING_HOST, PORT1));
assertTrue(mKeyStore.isValidCertificate(mCert2, NOT_MATCHING_HOST, PORT2));
// reload store from same file
mKeyStore.setKeyStoreFile(mKeyStoreFile);
assertTrue(mKeyStore.isValidCertificate(mCert1, MATCHING_HOST, PORT1));
assertTrue(mKeyStore.isValidCertificate(mCert2, NOT_MATCHING_HOST, PORT2));
// reload store from empty file
mKeyStoreFile.delete();
mKeyStore.setKeyStoreFile(mKeyStoreFile);
assertFalse(mKeyStore.isValidCertificate(mCert1, MATCHING_HOST, PORT1));
assertFalse(mKeyStore.isValidCertificate(mCert2, NOT_MATCHING_HOST, PORT2));
}
}