我正在开发一个可能连接到不同服务器的混合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 }
解决方法
您可以使用先前安装在Android KeyChain(系统密钥库)中的证书,扩展X509ExtendedKeyManager以配置URLConnection使用的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和here中提取的自定义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,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(); } } }
我没有测试它.报告任何错误!