我正在编写一个连接到服务器以调用一些 Web 服务的 Android 应用程序。此服务器使用 SSL 的自签名证书,并且需要客户端证书进行身份验证。
当我使用 Android Chrome 浏览器或 iPhone 上的 safari 浏览器连接到服务器时,它可以完美运行。SSL 连接正在建立,客户端证书身份验证成功并重定向到我的 Web 服务。但是当我与其他浏览器连接时,如股票 android 浏览器、dolphin 等,它会导致网络超时。
此外,当我连接我的 Android 应用程序时,我收到以下错误:
javax.net.ssl.SSLException: Read error: ssl=0x12746b8: I/O error during system call, Connection reset by peer
at org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_read(Native Method)
at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLInputStream.read(OpenSSLSocketImpl.java:671)
at libcore.io.Streams.readSingleByte(Streams.java:41)
at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLInputStream.read(OpenSSLSocketImpl.java:655)
at libcore.io.Streams.readAsciiLine(Streams.java:201)
at libcore.net.http.HttpEngine.readResponseHeaders(HttpEngine.java:544)
我关注了 chariotsolutions 的博客: http: //chariotsolutions.com/blog/post/https-with-client-certificates-on/
我还使用 portecle GUI 将客户端证书转换为 BKS 格式。
这是我的代码:
private static final String USER_AGENT = "Mozilla/5.0 (Linux; U; Android 4.0.3; et-ee; GT-P5100 Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30";
....
@Override
protected Object doInBackground(String... urlString) {
....
URL url = new URL(urlString[0]);
HttpsURLConnection urlConnection =(HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(getSSLContext().getSocketFactory());
urlConnection.addRequestProperty("User-Agent", USER_AGENT);
urlConnection.setDoInput(true);
urlConnection.connect();
....
}
private SSLContext getSSLContext() throws CertificateException,IOException, KeyStoreException, NoSuchAlgorithmException,KeyManagementException, java.security.cert.CertificateException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caRootInput = context.getAssets().open("MySSLCertificate_here.crt");
InputStream caClientAuth = context.getAssets().open("MyCientCertificate_here");
....
Certificate caRoot;
caRoot = cf.generateCertificate(caRootInput);
// KeyStore and KeyManager for Client Certificate
KeyStore keystore_client = KeyStore.getInstance("BKS");
keystore_client.load(caClientAuth, "My_passWord_here".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keystore_client, "My_passWord_here".toCharArray());
KeyManager[] keyManagers = kmf.getKeyManagers();
// KeyStore and Trustmanager for SSL Certificate
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("caRoot", caRoot);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext context = SSLContext.getInstance("TLSv1");
context.init(keyManagers, tmf.getTrustManagers(), null);
return context;
....
}
注意:出于测试目的,我还可以选择在没有客户端证书身份验证的情况下连接到服务器(导致不同的 URL)。在这种情况下,上面的代码是相同的,但只有context.init(null, tmf.getTrustManagers(), null)。这是完美的工作,建立一个 SSL 连接并将我重定向到我的 Web 服务。
真正困扰我的是,Iphone 上的 Chrome 和 Safari 都能正常工作。所以我想问题可能只出在我的 Android 代码中?
有没有人有类似的问题并且有一个想法或者可能是他的解决方案?
非常感谢!
编辑:
我忘了提一下,我还设置了一个 Cookie 管理器,这是 SSL 连接首先需要的。(在 onCreate() 中的 MainActivity 中设置)
// Required for SSL connection
CookieHandler.setDefault(new CookieManager());
没有它,我在尝试建立 SSL 连接(没有客户端证书)时遇到了同样的错误,但遗憾的是它对客户端证书也没有帮助。
更新#2
从 nelenkov.blogspot.de/2011/12/using-custom-certificate-trust-store-on.html(感谢@Nikolay 的这篇精彩文章)和他在 Github 上的 Android Connectiontest Application 获得了很多背景信息,我发现了一些有趣的东西。
服务器设置为在连接时给我一个 Cookie。如果我在前面的步骤中没有客户端身份验证进行连接,那么连接“有效”,因此在第二次连接(使用客户端身份验证)时,他只验证该 Cookie 并让我通过。
我终于注意到的第二件事,服务器给了我一个Nonce。我猜连接应该通过返回该 Nonce 自动遵循该重定向。一些浏览器,如 Firefox 和普通浏览器正在停止连接,(在使用 Responsecode 302 重定向之后)并最终以 404 停止,从而产生类似 ..policy?nonce=fno3i9Hsfn3uz 的 URL。然后我必须手动重新加载此 URL 才能访问 Web 服务。遗憾的是,这不适用于我的 Android 应用程序。
我想,这个问题可能是服务器端的错误配置?