请求自动或用户选择适当的客户端证书
Request with automatic or user selection of appropriate client certificate
我正在开发一个可能连接到不同服务器的混合 cordova 应用程序。其中一些确实需要客户端证书。
在 Android 手机上安装了相应的根证书 + 客户端证书。
在 Chrome 浏览器上,我得到以下对话框,为 Web 连接选择相应的客户端证书。
使用 cordova 插件 cordova-client-cert-authentication 会为 WebView 中的 Http(s) 请求弹出相同的对话框。
我的问题是如何在 本机 Android 平台的 Http(s) 请求上实现 自动证书选择没有明确声明相应的客户端证书。或者是否有 类似于用户选择证书 的东西,就像在 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
}
如果您的 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
}
}
注意:如果您的 HTTPS URL 有效,您将不需要使用服务器生成的证书。您应该仅将此方法用于 testing/development。对于 release/production,您不必使用此方法。
您可以使用先前安装在 Android KeyChain(系统密钥库)中的证书扩展 X509ExtendedKeyManager
来配置 URLConnection
[=21= 使用的 SSLContext
]
证书由您需要的别名引用。要使用类似于 chrome 的对话框提示用户进行选择,请使用:
KeyChain.choosePrivateKeyAlias(this, this, // Callback
new String[] {"RSA", "DSA"}, // Any key types.
null, // Any issuers.
null, // Any host
-1, // Any port
DEFAULT_ALIAS);
这是使用自定义 KeyManager
配置 SSL 连接的代码。它使用默认的 TrustManager
和 HostnameVerifier
。如果服务器使用的是 Android 默认信任库中不存在的自签名证书(不建议信任所有证书)
,则需要配置它们
//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();
您终于得到了从 here and here 中提取的自定义 X509ExtendedKeyManager
的完整实现,该自定义 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();
}
}
}
我没有测试。报告任何错误!
我正在开发一个可能连接到不同服务器的混合 cordova 应用程序。其中一些确实需要客户端证书。
在 Android 手机上安装了相应的根证书 + 客户端证书。
在 Chrome 浏览器上,我得到以下对话框,为 Web 连接选择相应的客户端证书。
使用 cordova 插件 cordova-client-cert-authentication 会为 WebView 中的 Http(s) 请求弹出相同的对话框。
我的问题是如何在 本机 Android 平台的 Http(s) 请求上实现 自动证书选择没有明确声明相应的客户端证书。或者是否有 类似于用户选择证书 的东西,就像在 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
}
如果您的 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
}
}
注意:如果您的 HTTPS URL 有效,您将不需要使用服务器生成的证书。您应该仅将此方法用于 testing/development。对于 release/production,您不必使用此方法。
您可以使用先前安装在 Android KeyChain(系统密钥库)中的证书扩展 X509ExtendedKeyManager
来配置 URLConnection
[=21= 使用的 SSLContext
]
证书由您需要的别名引用。要使用类似于 chrome 的对话框提示用户进行选择,请使用:
KeyChain.choosePrivateKeyAlias(this, this, // Callback
new String[] {"RSA", "DSA"}, // Any key types.
null, // Any issuers.
null, // Any host
-1, // Any port
DEFAULT_ALIAS);
这是使用自定义 KeyManager
配置 SSL 连接的代码。它使用默认的 TrustManager
和 HostnameVerifier
。如果服务器使用的是 Android 默认信任库中不存在的自签名证书(不建议信任所有证书)
//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();
您终于得到了从 here and here 中提取的自定义 X509ExtendedKeyManager
的完整实现,该自定义 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();
}
}
}
我没有测试。报告任何错误!