Запрос с автоматическим или пользовательским выбором соответствующего сертификата клиента
Я разрабатываю гибридное приложение cordova, которое может подключаться к разным серверам. Некоторые из них требуют сертификат клиента.
на Android mobile установлен соответствующий корневой сертификат + сертификат клиента.
в браузере Chrome я получаю следующее диалоговое окно для выбора соответствующего сертификата клиента для веб-соединения.
с плагином cordova cordova-клиент-сертификат-аутентификация в тот же диалог появляется для запросов Http (S) в WebView.
мой вопрос в том, как достичь автоматический выбор сертификата на Http(S) запросы на родном Android платформа без явного объявления соответствующего сертификата клиента. Или есть что-то похожее на выбор Пользователем сертификата как реализовано в Chrome?
Это текущая реализация, которая выдает рукопожатие исключение:
try {
URL url = new URL( versionUrl );
HttpsURLConnection urlConnection = ( HttpsURLConnection ) url.openConnection();
urlConnection.setConnectTimeout( 10000 );
InputStream in = urlConnection.getInputStream();
}
catch(Exception e)
{
//javax.net.ssl.SSLHandshakeException: Handshake failed
}
2 ответов
вы можете использовать сертификат, ранее установленный в Android KeyChain (хранилище системных ключей), расширяя X509ExtendedKeyManager
настройка SSLContext
используется URLConnection
сертификат, на который ссылается псевдоним, который вам нужен. Подсказать пользователю диалог похож на Chrome использовать:
KeyChain.choosePrivateKeyAlias(this, this, // Callback
new String[] {"RSA", "DSA"}, // Any key types.
null, // Any issuers.
null, // Any host
-1, // Any port
DEFAULT_ALIAS);
это код для настройки SSL-соединения с помощью пользовательского KeyManager
. Он использует значение по умолчанию TrustManager
и HostnameVerifier
. Вам нужно будет настроить их, если сервер использует самозаверяющий сертификат, отсутствующий в Android truststore по умолчанию (доверять всем сертификатам не рекомендуется)
//Configure trustManager if needed
TrustManager[] trustManagers = null;
//Configure keyManager to select the private key and the certificate chain from KeyChain
KeyManager keyManager = KeyChainKeyManager.fromAlias(
context, mClientCertAlias);
//Configure SSLContext
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(new KeyManager[] {keyManager}, trustManagers, null);
//Perform the connection
URL url = new URL( versionUrl );
HttpsURLConnection urlConnection = ( HttpsURLConnection ) url.openConnection();
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
//urlConnection.setHostnameVerifier(hostnameVerifier); //Configure hostnameVerifier if needed
urlConnection.setConnectTimeout( 10000 );
InputStream in = urlConnection.getInputStream();
наконец-то у вас есть и полная реализация пользовательского X509ExtendedKeyManager
извлеченные из здесь и здесь, что отвечает за выбор сертификата клиента. Я извлек необходимый код.
public static class KeyChainKeyManager extends X509ExtendedKeyManager {
private final String mClientAlias;
private final X509Certificate[] mCertificateChain;
private final PrivateKey mPrivateKey;
/**
* Builds an instance of a KeyChainKeyManager using the given certificate alias.
* If for any reason retrieval of the credentials from the system {@link android.security.KeyChain} fails,
* a {@code null} value will be returned.
*/
public static KeyChainKeyManager fromAlias(Context context, String alias)
throws CertificateException {
X509Certificate[] certificateChain;
try {
certificateChain = KeyChain.getCertificateChain(context, alias);
} catch (KeyChainException e) {
throw new CertificateException(e);
} catch (InterruptedException e) {
throw new CertificateException(e);
}
PrivateKey privateKey;
try {
privateKey = KeyChain.getPrivateKey(context, alias);
} catch (KeyChainException e) {
throw new CertificateException(e);
} catch (InterruptedException e) {
throw new CertificateException(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) {
return mClientAlias;
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
return mCertificateChain;
}
@Override
public PrivateKey getPrivateKey(String alias) {
return mPrivateKey;
}
@Override
public final String chooseServerAlias( String keyType, Principal[] issuers, Socket socket) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
@Override
public final String[] getClientAliases(String keyType, Principal[] issuers) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
@Override
public final String[] getServerAliases(String keyType, Principal[] issuers) {
// not a client SSLSocket callback
throw new UnsupportedOperationException();
}
}
}
Я не проверял его. Сообщите о любой ошибке!
Если ваши URL-адреса все еще находятся на стадии разработки (не производственная версия), вы можете пропустить установку сертификатов SSL/NON-SSL для доступа к URL-адресам.
вот как пропустить проверку SSL : Вызовите, когда activity onCreate () или где вам нужно перед доступом к URL.
public static void skipSSLValidation() {
try {
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
/* Create a new array with room for an additional trusted certificate. */
return new X509Certificate[0];
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
} catch (Exception e) {
// pass
}
}
Примечание: Если ваши URL-адреса HTTPS действительны, вам не потребуется использовать сертификаты, созданные сервером. Вы должны использовать этот метод только для тестирования / разработки. Для выпуска/производства у вас нет использовать этот метод.