I'm using client certificate to auth user in my app. Get certificate by KeyChain API and provide it to SSLContext by custom KeyManager, based on KeyManager implementation from SSLUtils in standart email app https://github.com/android/platform_packages_apps_email/blob/master/emailcommon/src/com/android/emailcommon/utility/SSLUtils.java
URL url = new URL("https://mydomain");
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
httpsURLConnection.setSSLSocketFactory(getMySSLSocketFactory());
....
private SSLSocketFactory getMySSLSocketFactory() {
SSLContext curSSLContext = null;
String protocol;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
protocol = "TLSv1.2";
} else {
protocol = "TLSv1"; //http://developer.android.com/reference/javax/net/ssl/SSLSocket.html
}
try {
curSSLContext = SSLContext.getInstance(protocol);
curSSLContext.init(getMyKeyManagers(), null, new SecureRandom());
} catch (Exception e) {...}
return curSSLContext != null ? curSSLContext.getSocketFactory() : null;
}
private KeyManager[] getMyKeyManagers() {
managers = new KeyManager[1];
try {
managers[0] = KeyChainKeyManager.fromAlias(context, "certAlias");
} catch (Exception e) {...}
return managers;
}
....
public class KeyChainKeyManager extends StubKeyManager {
private final String mClientAlias;
private final X509Certificate[] mCertificateChain;
private final PrivateKey mPrivateKey;
public static KeyChainKeyManager fromAlias(Context context, String alias)
throws CertificateException {
X509Certificate[] certificateChain;
try {
certificateChain = KeyChain.getCertificateChain(context, alias);
} catch (KeyChainException | InterruptedException e ) {...}
PrivateKey privateKey;
try {
privateKey = KeyChain.getPrivateKey(context, alias);
} catch (KeyChainException | InterruptedException e) {...}
if (certificateChain == null || privateKey == null) {
throw new CertificateException("Can't access certificate from keystore");
}
return new KeyChainKeyManager(alias, certificateChain, privateKey);
}
private KeyChainKeyManager(String clientAlias, X509Certificate[] certificateChain, PrivateKey privateKey) {
mClientAlias = clientAlias;
mCertificateChain = certificateChain;
mPrivateKey = privateKey;
}
@Override
public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
if (LOG_ENABLED) {
myLogger.info(...);
}
return mClientAlias;
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
if (LOG_ENABLED) {
myLogger.info(...);
}
return mCertificateChain;
}
@Override
public PrivateKey getPrivateKey(String alias) {
if (LOG_ENABLED) {
myLogger.info(...);
}
return mPrivateKey;
}
}
This code works on API 16+, but on API 15 KeyManager's methods never called and server does not receive client certificate. I put breakpoints in chooseClientAlias, getCertificateChain, and getPrivateKey methods and they triggered on API 16+ and not on API 15. There is no exception or errors occur in creating KeyManager and SocketFactory.
Default browser on device also doesn't work with client cert: it opens dialog to choose client cert and freeze atfer choosing.
I found possible reason here (post 112): https://code.google.com/p/android/issues/detail?id=11231#c112 but rebooting does not help.
May be the same problem described here: Client certificate not sent from android to ssl server
Is this a bug in ICS or i'm doing something wrong?