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:
parent
490708b079
commit
f4f10a3fdf
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue