Make client certificate requests optional.

This prevents things from always failing if the server requires a client
SSL certificate.

Note that the solution used to determine if a certificate request was
made for a given request is approximate; it is timestamp based and can
theoretically give a false positive. In practice, this is very unlikely,
since another cert request had to have happened around the same time,
AND the response must be a 401/403.

Change-Id: Ieb77cf91db3bd52ba4adf1fb07357fef7e204ba5
This commit is contained in:
Ben Komalo 2011-06-30 20:25:03 -07:00
parent 490708b079
commit f4f10a3fdf
2 changed files with 46 additions and 18 deletions

View File

@ -17,6 +17,10 @@
package com.android.emailcommon.utility;
import android.content.Context;
import android.net.SSLCertificateSocketFactory;
import android.util.Log;
import com.android.emailcommon.Logging;
import com.android.emailcommon.utility.SSLUtils.KeyChainKeyManager;
import com.android.emailcommon.utility.SSLUtils.TrackingKeyManager;
@ -27,10 +31,6 @@ import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.HttpParams;
import android.content.Context;
import android.net.SSLCertificateSocketFactory;
import android.util.Log;
import java.security.cert.CertificateException;
import javax.net.ssl.KeyManager;
@ -43,11 +43,18 @@ public class EmailClientConnectionManager extends ThreadSafeClientConnManager {
private static final boolean LOG_ENABLED = false;
/**
* A {@link KeyManager} to track client certificate requests from servers.
*/
private final TrackingKeyManager mTrackingKeyManager;
/**
* Not publicly instantiable except via {@link #newInstance(HttpParams)}
*/
private EmailClientConnectionManager(HttpParams params, SchemeRegistry registry) {
private EmailClientConnectionManager(
HttpParams params, SchemeRegistry registry, TrackingKeyManager keyManager) {
super(params, registry);
mTrackingKeyManager = keyManager;
}
public static EmailClientConnectionManager newInstance(HttpParams params) {
@ -64,7 +71,7 @@ public class EmailClientConnectionManager extends ThreadSafeClientConnManager {
registry.register(new Scheme("httpts",
SSLUtils.getHttpSocketFactory(true /*insecure*/, keyManager), 443));
return new EmailClientConnectionManager(params, registry);
return new EmailClientConnectionManager(params, registry, keyManager);
}
/**
@ -129,4 +136,13 @@ public class EmailClientConnectionManager extends ThreadSafeClientConnManager {
String safeAlias = SSLUtils.escapeForSchemeName(clientCertAlias);
return (trustAllServerCerts ? "httpts" : "https") + "+clientCert+" + safeAlias;
}
/**
* @param since A timestamp in millis from epoch from which to check
* @return whether or not this connection manager has detected any unsatisfied requests for
* a client SSL certificate by any servers
*/
public synchronized boolean hasDetectedUnsatisfiedCertReq(long since) {
return mTrackingKeyManager.getLastCertReqTime() >= since;
}
}

View File

@ -16,14 +16,15 @@
package com.android.emailcommon.utility;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context;
import android.net.SSLCertificateSocketFactory;
import android.security.KeyChain;
import android.security.KeyChainException;
import android.util.Log;
import com.google.common.annotations.VisibleForTesting;
import java.net.InetAddress;
import java.net.Socket;
import java.security.Principal;
import java.security.PrivateKey;
@ -137,35 +138,46 @@ public class SSLUtils {
}
/**
* A dummy {@link KeyManager} which throws a {@link CertificateRequestedException} if the
* server requests a certificate.
* A dummy {@link KeyManager} which keeps track of the last time a server has requested
* a client certificate.
*/
public static class TrackingKeyManager extends StubKeyManager {
private volatile long mLastTimeCertRequested = 0L;
@Override
public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
if (LOG_ENABLED) {
InetAddress address = socket.getInetAddress();
Log.i(TAG, "TrackingKeyManager: requesting a client cert alias for "
+ Arrays.toString(keyTypes));
+ address.getCanonicalHostName());
}
throw new CertificateRequestedException();
mLastTimeCertRequested = System.currentTimeMillis();
return null;
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
if (LOG_ENABLED) {
Log.i(TAG, "TrackingKeyManager: returning a null cert chain");
}
return null;
}
@Override
public PrivateKey getPrivateKey(String alias) {
if (LOG_ENABLED) {
Log.i(TAG, "TrackingKeyManager: returning a null private key");
}
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 {
/**
* @return the last time that this {@link KeyManager} detected a request by a server
* for a client certificate (in millis since epoch).
*/
public long getLastCertReqTime() {
return mLastTimeCertRequested;
}
}
/**