From 313586c8eb4e23ceec068b82f3dc0be1c8a7045f Mon Sep 17 00:00:00 2001 From: Ben Komalo Date: Tue, 7 Jun 2011 11:39:16 -0700 Subject: [PATCH] Introduce client cert alias for HostAuth. Some email servers require client certificates to be presented to establish an SSL connection. While this certificate will be maintained by the system key store, we need to store the "alias" of the certificate stored in that system store. Wiring up to use the actual alias will be done in future CL's. It is currently unused. Change-Id: I8d1290151342daea9ceb0df8a4088405b44faa81 --- .../emailcommon/provider/EmailContent.java | 2 + .../emailcommon/provider/HostAuth.java | 98 +++++++++- .../android/emailcommon/utility/Utility.java | 21 --- .../activity/setup/AccountSetupBasics.java | 8 +- .../email/mail/transport/MailTransport.java | 11 +- .../android/email/provider/EmailProvider.java | 21 ++- .../activity/setup/AccountSettingsTests.java | 6 +- .../setup/AccountSetupAccountTypeTests.java | 3 +- .../setup/AccountSetupExchangeTests.java | 3 +- .../setup/AccountSetupIncomingTests.java | 3 +- .../setup/AccountSetupOptionsTests.java | 3 +- .../setup/AccountSetupOutgoingTests.java | 3 +- .../emailcommon/provider/HostAuthTests.java | 172 ++++++++++++------ 13 files changed, 252 insertions(+), 102 deletions(-) diff --git a/emailcommon/src/com/android/emailcommon/provider/EmailContent.java b/emailcommon/src/com/android/emailcommon/provider/EmailContent.java index b16a67a0a..1f5c237f8 100644 --- a/emailcommon/src/com/android/emailcommon/provider/EmailContent.java +++ b/emailcommon/src/com/android/emailcommon/provider/EmailContent.java @@ -2313,6 +2313,8 @@ public abstract class EmailContent { static final String PASSWORD = "password"; // A domain or path, if required (used in IMAP and EAS) static final String DOMAIN = "domain"; + // An alias to a local client certificate for SSL + static final String CLIENT_CERT_ALIAS = "certAlias"; // DEPRECATED - Will not be set or stored static final String ACCOUNT_KEY = "accountKey"; } diff --git a/emailcommon/src/com/android/emailcommon/provider/HostAuth.java b/emailcommon/src/com/android/emailcommon/provider/HostAuth.java index 83d46b276..61d688b35 100644 --- a/emailcommon/src/com/android/emailcommon/provider/HostAuth.java +++ b/emailcommon/src/com/android/emailcommon/provider/HostAuth.java @@ -41,6 +41,7 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par public static final String SCHEME_POP3 = "pop3"; public static final String SCHEME_EAS = "eas"; public static final String SCHEME_SMTP = "smtp"; + public static final String SCHEME_TRUST_ALL_CERTS = "trustallcerts"; public static final int PORT_UNKNOWN = -1; @@ -59,6 +60,7 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par public String mLogin; public String mPassword; public String mDomain; + public String mClientCertAlias = null; public static final int CONTENT_ID_COLUMN = 0; public static final int CONTENT_PROTOCOL_COLUMN = 1; @@ -68,11 +70,12 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par public static final int CONTENT_LOGIN_COLUMN = 5; public static final int CONTENT_PASSWORD_COLUMN = 6; public static final int CONTENT_DOMAIN_COLUMN = 7; + public static final int CONTENT_CLIENT_CERT_ALIAS_COLUMN = 8; public static final String[] CONTENT_PROJECTION = new String[] { RECORD_ID, HostAuthColumns.PROTOCOL, HostAuthColumns.ADDRESS, HostAuthColumns.PORT, HostAuthColumns.FLAGS, HostAuthColumns.LOGIN, - HostAuthColumns.PASSWORD, HostAuthColumns.DOMAIN + HostAuthColumns.PASSWORD, HostAuthColumns.DOMAIN, HostAuthColumns.CLIENT_CERT_ALIAS }; /** @@ -101,6 +104,14 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par * Returns the scheme for the specified flags. */ public static String getSchemeString(String protocol, int flags) { + return getSchemeString(protocol, flags, null); + } + + /** + * Builds a URI scheme name given the parameters for a {@code HostAuth}. + * If a {@code clientAlias} is provided, this indicates that a secure connection must be used. + */ + public static String getSchemeString(String protocol, int flags, String clientAlias) { String security = ""; switch (flags & USER_CONFIG_MASK) { case FLAG_SSL: @@ -116,6 +127,21 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par security = "+tls+trustallcerts"; break; } + + if (!TextUtils.isEmpty(clientAlias)) { + if (TextUtils.isEmpty(security)) { + throw new IllegalArgumentException( + "Can't specify a certificate alias for a non-secure connection"); + } + // TODO: investigate what the certificate aliases look like from the framework + // and ensure they're safe scheme names. + String safeScheme = clientAlias; + if (!security.endsWith("+")) { + security += "+"; + } + security += safeScheme; + } + return protocol + security; } @@ -134,7 +160,7 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par } if (schemeParts.length >= 3) { String part2 = schemeParts[2]; - if ("trustallcerts".equals(part2)) { + if (SCHEME_TRUST_ALL_CERTS.equals(part2)) { flags |= HostAuth.FLAG_TRUST_ALL; } } @@ -153,6 +179,7 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par mLogin = cursor.getString(CONTENT_LOGIN_COLUMN); mPassword = cursor.getString(CONTENT_PASSWORD_COLUMN); mDomain = cursor.getString(CONTENT_DOMAIN_COLUMN); + mClientCertAlias = cursor.getString(CONTENT_CLIENT_CERT_ALIAS_COLUMN); } @Override @@ -165,6 +192,7 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par values.put(HostAuthColumns.LOGIN, mLogin); values.put(HostAuthColumns.PASSWORD, mPassword); values.put(HostAuthColumns.DOMAIN, mDomain); + values.put(HostAuthColumns.CLIENT_CERT_ALIAS, mClientCertAlias); values.put(HostAuthColumns.ACCOUNT_KEY, 0); // Need something to satisfy the DB return values; } @@ -202,6 +230,33 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par } } + /** + * Legacy URI parser. Used in one of three different scenarios: + * 1. Backup / Restore of account + * 2. Parsing template from provider.xml + * 3. Forcefully creating URI for test + * Example string: + * "eas+ssl+trustallcerts://user:password@server/domain:123" + * + * Note that the use of client certificate is specified in the URI, a secure connection type + * must be used. + */ + public static void setHostAuthFromString(HostAuth auth, String uriString) + throws URISyntaxException { + URI uri = new URI(uriString); + String path = uri.getPath(); + String domain = null; + if (!TextUtils.isEmpty(path)) { + // Strip off the leading slash that begins the path. + domain = path.substring(1); + } + auth.mDomain = domain; + auth.setLogin(uri.getUserInfo()); + + String scheme = uri.getScheme(); + auth.setConnection(scheme, uri.getHost(), uri.getPort()); + } + /** * Sets the user name and password from URI user info string */ @@ -251,18 +306,48 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par public void setConnection(String scheme, String host, int port) { String[] schemeParts = scheme.split("\\+"); String protocol = schemeParts[0]; + String clientCertAlias = null; int flags = getSchemeFlags(scheme); - setConnection(protocol, host, port, flags); + // Example scheme: "eas+ssl+trustallcerts" or "eas+tls+trustallcerts+client-cert-alias" + if (schemeParts.length > 3) { + clientCertAlias = schemeParts[3]; + } else if (schemeParts.length > 2) { + if (!SCHEME_TRUST_ALL_CERTS.equals(schemeParts[2])) { + mClientCertAlias = schemeParts[2]; + } + } + + setConnection(protocol, host, port, flags, clientCertAlias); } public void setConnection(String protocol, String address, int port, int flags) { + setConnection(protocol, address, port, flags, null); + } + + /** + * Sets the internal connection parameters based on the specified parameter values. + * @param protocol the mail protocol to use (e.g. "eas", "imap"). + * @param address the address of the server + * @param port the port for the connection + * @param flags flags indicating the security and type of the connection + * @param clientCertAlias an optional alias to use if a client user certificate is to be + * presented during connection establishment. If this is non-empty, it must be the case + * that flags indicates use of a secure connection + */ + public void setConnection(String protocol, String address, + int port, int flags, String clientCertAlias) { // Set protocol, security, and additional flags based on uri scheme mProtocol = protocol; mFlags &= ~(FLAG_SSL | FLAG_TLS | FLAG_TRUST_ALL); mFlags |= (flags & USER_CONFIG_MASK); + boolean useSecureConnection = (flags & (FLAG_SSL | FLAG_TLS)) != 0; + if (!useSecureConnection && !TextUtils.isEmpty(clientCertAlias)) { + throw new IllegalArgumentException("Can't use client alias on non-secure connections"); + } + mAddress = address; mPort = port; if (mPort == PORT_UNKNOWN) { @@ -283,6 +368,8 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par mPort = useSSL ? 465 : 587; } } + + mClientCertAlias = clientCertAlias; } /** Returns {@code true} if this is an EAS connection; otherwise, {@code false}. */ @@ -328,6 +415,7 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par dest.writeString(mLogin); dest.writeString(mPassword); dest.writeString(mDomain); + dest.writeString(mClientCertAlias); } /** @@ -343,6 +431,7 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par mLogin = in.readString(); mPassword = in.readString(); mDomain = in.readString(); + mClientCertAlias = in.readString(); } /** @@ -365,6 +454,7 @@ public final class HostAuth extends EmailContent implements HostAuthColumns, Par && Utility.areStringsEqual(mAddress, that.mAddress) && Utility.areStringsEqual(mLogin, that.mLogin) && Utility.areStringsEqual(mPassword, that.mPassword) - && Utility.areStringsEqual(mDomain, that.mDomain); + && Utility.areStringsEqual(mDomain, that.mDomain) + && Utility.areStringsEqual(mClientCertAlias, that.mClientCertAlias); } } diff --git a/emailcommon/src/com/android/emailcommon/utility/Utility.java b/emailcommon/src/com/android/emailcommon/utility/Utility.java index 34f641e76..f941cf04d 100644 --- a/emailcommon/src/com/android/emailcommon/utility/Utility.java +++ b/emailcommon/src/com/android/emailcommon/utility/Utility.java @@ -63,8 +63,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; @@ -1059,25 +1057,6 @@ public class Utility { } } - /** - * Legacy URI parser. Used in one of three different scenarios: - * 1. Backup / Restore of account - * 2. Parsing template from provider.xml - * 3. Forcefully creating URI for test - */ - public static void setHostAuthFromString(HostAuth auth, String uriString) - throws URISyntaxException { - URI uri = new URI(uriString); - String path = uri.getPath(); - String domain = null; - if (path != null && path.length() > 0) { - domain = path.substring(1); - } - auth.mDomain = domain; - auth.setLogin(uri.getUserInfo()); - auth.setConnection(uri.getScheme(), uri.getHost(), uri.getPort()); - } - /** * Test that the given strings are equal in a null-pointer safe fashion. */ diff --git a/src/com/android/email/activity/setup/AccountSetupBasics.java b/src/com/android/email/activity/setup/AccountSetupBasics.java index 02cb9e4b6..73f4a8e7b 100644 --- a/src/com/android/email/activity/setup/AccountSetupBasics.java +++ b/src/com/android/email/activity/setup/AccountSetupBasics.java @@ -425,11 +425,11 @@ public class AccountSetupBasics extends AccountSetupActivity Account account = SetupData.getAccount(); HostAuth recvAuth = account.getOrCreateHostAuthRecv(this); - Utility.setHostAuthFromString(recvAuth, mProvider.incomingUri); + HostAuth.setHostAuthFromString(recvAuth, mProvider.incomingUri); recvAuth.setLogin(mProvider.incomingUsername, password); HostAuth sendAuth = account.getOrCreateHostAuthSend(this); - Utility.setHostAuthFromString(sendAuth, mProvider.outgoingUri); + HostAuth.setHostAuthFromString(sendAuth, mProvider.outgoingUri); sendAuth.setLogin(mProvider.outgoingUsername, password); // Populate the setup data, assuming that the duplicate account check will succeed @@ -576,10 +576,10 @@ public class AccountSetupBasics extends AccountSetupActivity Account account = SetupData.getAccount(); try { HostAuth recvAuth = account.getOrCreateHostAuthRecv(this); - Utility.setHostAuthFromString(recvAuth, incoming); + HostAuth.setHostAuthFromString(recvAuth, incoming); HostAuth sendAuth = account.getOrCreateHostAuthSend(this); - Utility.setHostAuthFromString(sendAuth, outgoing); + HostAuth.setHostAuthFromString(sendAuth, outgoing); populateSetupData(user, email, false); } catch (URISyntaxException e) { diff --git a/src/com/android/email/mail/transport/MailTransport.java b/src/com/android/email/mail/transport/MailTransport.java index acc80d592..751be50d6 100644 --- a/src/com/android/email/mail/transport/MailTransport.java +++ b/src/com/android/email/mail/transport/MailTransport.java @@ -61,7 +61,16 @@ public class MailTransport implements Transport { private String mHost; private int mPort; private String[] mUserInfoParts; + + /** + * One of the {@code Transport.CONNECTION_SECURITY_*} values. + */ private int mConnectionSecurity; + + /** + * Whether or not to trust all server certificates (i.e. skip host verification) in SSL + * handshakes + */ private boolean mTrustCertificates; private Socket mSocket; @@ -131,7 +140,7 @@ public class MailTransport implements Transport { @Override public boolean canTrySslSecurity() { - return mConnectionSecurity == CONNECTION_SECURITY_SSL; + return mConnectionSecurity == Transport.CONNECTION_SECURITY_SSL; } @Override diff --git a/src/com/android/email/provider/EmailProvider.java b/src/com/android/email/provider/EmailProvider.java index e40d69781..a73ea7eb0 100644 --- a/src/com/android/email/provider/EmailProvider.java +++ b/src/com/android/email/provider/EmailProvider.java @@ -141,8 +141,9 @@ public class EmailProvider extends ContentProvider { // Version 21: Add lastSeenMessageKey column to Mailbox table // Version 22: Upgrade path for IMAP/POP accounts to integrate with AccountManager // Version 23: Add column to mailbox table for time of last access + // Version 24: Add column to hostauth table for client cert alias - public static final int DATABASE_VERSION = 23; + public static final int DATABASE_VERSION = 24; // Any changes to the database format *must* include update-in-place code. // Original version: 2 @@ -607,7 +608,8 @@ public class EmailProvider extends ContentProvider { + HostAuthColumns.LOGIN + " text, " + HostAuthColumns.PASSWORD + " text, " + HostAuthColumns.DOMAIN + " text, " - + HostAuthColumns.ACCOUNT_KEY + " integer" + + HostAuthColumns.ACCOUNT_KEY + " integer," + + HostAuthColumns.CLIENT_CERT_ALIAS + " text" + ");"; db.execSQL("create table " + HostAuth.TABLE_NAME + s); } @@ -1084,6 +1086,10 @@ public class EmailProvider extends ContentProvider { upgradeFromVersion22ToVersion23(db); oldVersion = 23; } + if (oldVersion == 23) { + upgradeFromVersion23ToVersion24(db); + oldVersion = 24; + } } @Override @@ -2087,4 +2093,15 @@ public class EmailProvider extends ContentProvider { Log.w(TAG, "Exception upgrading EmailProvider.db from 22 to 23 " + e); } } + + /** Adds in a column for information about a client certificate to use. */ + private static void upgradeFromVersion23ToVersion24(SQLiteDatabase db) { + try { + db.execSQL("alter table " + HostAuth.TABLE_NAME + + " add column " + HostAuth.CLIENT_CERT_ALIAS + " text;"); + } catch (SQLException e) { + // Shouldn't be needed unless we're debugging and interrupt the process + Log.w(TAG, "Exception upgrading EmailProvider.db from 23 to 24 " + e); + } + } } diff --git a/tests/src/com/android/email/activity/setup/AccountSettingsTests.java b/tests/src/com/android/email/activity/setup/AccountSettingsTests.java index 1567160c1..1c0a03bb0 100644 --- a/tests/src/com/android/email/activity/setup/AccountSettingsTests.java +++ b/tests/src/com/android/email/activity/setup/AccountSettingsTests.java @@ -18,7 +18,7 @@ package com.android.email.activity.setup; import com.android.email.mail.Store; import com.android.emailcommon.provider.EmailContent.Account; -import com.android.emailcommon.utility.Utility; +import com.android.emailcommon.provider.HostAuth; import android.content.ContentUris; import android.content.Context; @@ -164,8 +164,8 @@ public class AccountSettingsTests extends ActivityInstrumentationTestCase2