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; 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.Logging;
import com.android.emailcommon.utility.SSLUtils.KeyChainKeyManager; import com.android.emailcommon.utility.SSLUtils.KeyChainKeyManager;
import com.android.emailcommon.utility.SSLUtils.TrackingKeyManager; 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.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.HttpParams; import org.apache.http.params.HttpParams;
import android.content.Context;
import android.net.SSLCertificateSocketFactory;
import android.util.Log;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManager;
@ -43,11 +43,18 @@ public class EmailClientConnectionManager extends ThreadSafeClientConnManager {
private static final boolean LOG_ENABLED = false; 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)} * 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); super(params, registry);
mTrackingKeyManager = keyManager;
} }
public static EmailClientConnectionManager newInstance(HttpParams params) { public static EmailClientConnectionManager newInstance(HttpParams params) {
@ -64,7 +71,7 @@ public class EmailClientConnectionManager extends ThreadSafeClientConnManager {
registry.register(new Scheme("httpts", registry.register(new Scheme("httpts",
SSLUtils.getHttpSocketFactory(true /*insecure*/, keyManager), 443)); 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); String safeAlias = SSLUtils.escapeForSchemeName(clientCertAlias);
return (trustAllServerCerts ? "httpts" : "https") + "+clientCert+" + safeAlias; 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; package com.android.emailcommon.utility;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context; import android.content.Context;
import android.net.SSLCertificateSocketFactory; import android.net.SSLCertificateSocketFactory;
import android.security.KeyChain; import android.security.KeyChain;
import android.security.KeyChainException; import android.security.KeyChainException;
import android.util.Log; import android.util.Log;
import com.google.common.annotations.VisibleForTesting;
import java.net.InetAddress;
import java.net.Socket; import java.net.Socket;
import java.security.Principal; import java.security.Principal;
import java.security.PrivateKey; import java.security.PrivateKey;
@ -137,35 +138,46 @@ public class SSLUtils {
} }
/** /**
* A dummy {@link KeyManager} which throws a {@link CertificateRequestedException} if the * A dummy {@link KeyManager} which keeps track of the last time a server has requested
* server requests a certificate. * a client certificate.
*/ */
public static class TrackingKeyManager extends StubKeyManager { public static class TrackingKeyManager extends StubKeyManager {
private volatile long mLastTimeCertRequested = 0L;
@Override @Override
public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
if (LOG_ENABLED) { if (LOG_ENABLED) {
InetAddress address = socket.getInetAddress();
Log.i(TAG, "TrackingKeyManager: requesting a client cert alias for " Log.i(TAG, "TrackingKeyManager: requesting a client cert alias for "
+ Arrays.toString(keyTypes)); + address.getCanonicalHostName());
} }
throw new CertificateRequestedException(); mLastTimeCertRequested = System.currentTimeMillis();
return null;
} }
@Override @Override
public X509Certificate[] getCertificateChain(String alias) { public X509Certificate[] getCertificateChain(String alias) {
if (LOG_ENABLED) {
Log.i(TAG, "TrackingKeyManager: returning a null cert chain");
}
return null; return null;
} }
@Override @Override
public PrivateKey getPrivateKey(String alias) { public PrivateKey getPrivateKey(String alias) {
if (LOG_ENABLED) {
Log.i(TAG, "TrackingKeyManager: returning a null private key");
}
return null; return null;
} }
}
/** /**
* An exception indicating that a server requested a client certificate but none was * @return the last time that this {@link KeyManager} detected a request by a server
* available to be presented. * for a client certificate (in millis since epoch).
*/ */
public static class CertificateRequestedException extends RuntimeException { public long getLastCertReqTime() {
return mLastTimeCertRequested;
}
} }
/** /**