Indicate to the user when a cert error happens.

This introduces an exception which needs to be thrown from a KeyManager
when it tries to establish a connection with a server requesting a
certificate.

Change-Id: I06dfad7789ed5d320b630e7e4380e15da42a48df
This commit is contained in:
Ben Komalo 2011-06-15 17:54:45 -07:00
parent f8d8dfac7a
commit f4dbbf1099
5 changed files with 70 additions and 16 deletions

View File

@ -50,7 +50,7 @@ public class MessagingException extends Exception {
public static final int SECURITY_POLICIES_UNSUPPORTED = 8;
/** The protocol (or protocol version) isn't supported */
public static final int PROTOCOL_VERSION_UNSUPPORTED = 9;
/** An SSL certificate couldn't be validated */
/** The server's SSL certificate couldn't be validated */
public static final int CERTIFICATE_VALIDATION_ERROR = 10;
/** Authentication failed during autodiscover */
public static final int AUTODISCOVER_AUTHENTICATION_FAILED = 11;
@ -62,6 +62,8 @@ public class MessagingException extends Exception {
public static final int ACCESS_DENIED = 14;
/** The server refused access */
public static final int ATTACHMENT_NOT_FOUND = 15;
/** A client SSL certificate is required for connections to the server */
public static final int CLIENT_CERTIFICATE_ERROR = 16;
protected int mExceptionType;
// Exception type-specific data

View File

@ -19,7 +19,10 @@ package com.android.emailcommon.utility;
import com.android.emailcommon.Logging;
import com.android.emailcommon.utility.SSLUtils.KeyChainKeyManager;
import com.android.emailcommon.utility.SSLUtils.TrackingKeyManager;
import org.apache.http.conn.ClientConnectionRequest;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
@ -48,15 +51,18 @@ public class EmailClientConnectionManager extends ThreadSafeClientConnManager {
}
public static EmailClientConnectionManager newInstance(HttpParams params) {
TrackingKeyManager keyManager = new TrackingKeyManager();
// Create a registry for our three schemes; http and https will use built-in factories
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http",
PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", SSLUtils.getHttpSocketFactory(false), 443));
registry.register(new Scheme("https",
SSLUtils.getHttpSocketFactory(false, keyManager), 443));
// Register the httpts scheme with our insecure factory
registry.register(new Scheme("httpts",
SSLUtils.getHttpSocketFactory(true /*insecure*/), 443));
SSLUtils.getHttpSocketFactory(true /*insecure*/, keyManager), 443));
return new EmailClientConnectionManager(params, registry);
}

View File

@ -65,10 +65,11 @@ public class SSLUtils {
* Returns a {@link org.apache.http.conn.ssl.SSLSocketFactory SSLSocketFactory} for use with the
* Apache HTTP stack.
*/
public static SSLSocketFactory getHttpSocketFactory(boolean insecure) {
public static SSLSocketFactory getHttpSocketFactory(boolean insecure, KeyManager keyManager) {
SSLCertificateSocketFactory underlying = getSSLSocketFactory(insecure);
// TODO: register a keymanager that will simply listen for requests for a client
// certificate so that higher levels know to ask the user for such credentials.
if (keyManager != null) {
underlying.setKeyManagers(new KeyManager[] { keyManager });
}
return new SSLSocketFactory(underlying);
}
@ -132,6 +133,38 @@ public class SSLUtils {
}
}
/**
* A dummy {@link KeyManager} which throws a {@link CertificateRequestedException} if the
* server requests a certificate.
*/
public static class TrackingKeyManager extends StubKeyManager {
@Override
public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
if (LOG_ENABLED) {
Log.i(TAG, "TrackingKeyManager: requesting a client cert alias for "
+ Arrays.toString(keyTypes));
}
throw new CertificateRequestedException();
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
return null;
}
@Override
public PrivateKey getPrivateKey(String alias) {
return null;
}
}
/**
* An exception indicating that a server requested a client certificate but none was
* available to be presented.
*/
public static class CertificateRequestedException extends RuntimeException {
}
/**
* A {@link KeyManager} that reads uses credentials stored in the system {@link KeyChain}.
*/
@ -150,12 +183,10 @@ public class SSLUtils {
try {
certificateChain = KeyChain.getCertificateChain(context, alias);
} catch (KeyChainException e) {
Log.e(TAG, "Unable to retrieve certificate chain for [" + alias + "] due to "
+ e);
logError(alias, "certificate chain", e);
return null;
} catch (InterruptedException e) {
Log.e(TAG, "Unable to retrieve certificate chain for [" + alias + "] due to "
+ e);
logError(alias, "certificate chain", e);
return null;
}
@ -163,16 +194,20 @@ public class SSLUtils {
try {
privateKey = KeyChain.getPrivateKey(context, alias);
} catch (KeyChainException e) {
Log.e(TAG, "Unable to retrieve private key for [" + alias + "] due to " + e);
logError(alias, "private key", e);
return null;
} catch (InterruptedException e) {
Log.e(TAG, "Unable to retrieve private key for [" + alias + "] due to " + e);
logError(alias, "private key", e);
return null;
}
return new KeyChainKeyManager(alias, certificateChain, privateKey);
}
private static void logError(String alias, String type, Exception ex) {
Log.e(TAG, "Unable to retrieve " + type + " for [" + alias + "] due to " + ex);
}
private KeyChainKeyManager(
String clientAlias, X509Certificate[] certificateChain, PrivateKey privateKey) {
mClientAlias = clientAlias;

View File

@ -760,10 +760,18 @@ save attachment.</string>
<!-- "Setup could not finish" dialog text; e.g., Username or password incorrect\n(ERR01 Account does not exist) -->
<string name="account_setup_failed_dlg_auth_message_fmt">Username or password incorrect.\n(<xliff:g id="error">%s</xliff:g>)</string>
<!-- "Setup could not finish" dialog text; e.g., Cannot safely connect to server -->
<string name="account_setup_failed_dlg_certificate_message">Cannot safely connect to server.</string>
<!-- "Setup could not finish" dialog text; e.g., Cannot safely connect to server\n(TLS Not Supported) -->
<string name="account_setup_failed_dlg_certificate_message_fmt">Cannot safely connect to server.\n(<xliff:g id="error">%s</xliff:g>)</string>
<!-- An error message presented to the user when the server's identity
cannot be established or trusted [CHAR LIMIT=NONE] -->
<string name="account_setup_failed_dlg_certificate_message"
>Cannot safely connect to server.</string>
<!-- An error message presented to the user when the server's identity
cannot be established or trusted [CHAR LIMIT=NONE] -->
<string name="account_setup_failed_dlg_certificate_message_fmt"
>Cannot safely connect to server.\n(<xliff:g id="error">%s</xliff:g>)</string>
<!-- An error message presented to the user when the server requires a
client certificate to connect [CHAR LIMIT=NONE] -->
<string name="account_setup_failed_certificate_required">User certificate
required to connect to server.</string>
<!-- Dialog text for ambiguous setup failure; server error/bad credentials [CHAR LIMIT=none] -->
<string name="account_setup_failed_check_credentials_message">

View File

@ -645,6 +645,9 @@ public class AccountCheckSettingsFragment extends Fragment {
case MessagingException.GENERAL_SECURITY:
id = R.string.account_setup_failed_security;
break;
case MessagingException.CLIENT_CERTIFICATE_ERROR:
id = R.string.account_setup_failed_certificate_required;
break;
default:
id = TextUtils.isEmpty(message)
? R.string.account_setup_failed_dlg_server_message