使用Android的证书进行SSL客户端身份验证

时间:2014-02-07 10:02:43

标签: android https httpsurlconnection authentication

我正在编写一个连接到服务器以调用某些Web服务的Android应用程序。此服务器使用自签名SSL证书,并需要客户端证书进行身份验证。

当我使用Android Chrome浏览器或使用Iphone的Safari浏览器连接到服务器时,它可以正常工作。 SSL连接正在建立,客户端证书身份验证正在成功并重定向到我的Web服务。 但是当我与其他浏览器连接时,比如股票Android浏览器,海豚等,它会导致网络超时。

当我连接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的大量背景信息(感谢这篇gread Article @ Nikolay)和他在Github上的Android Connectiontest应用程序我发现了一些有趣的的东西。

服务器设置为在连接时为我提供Cookie。如果我在之前的步骤中没有客户端身份验证连接,那么对于Conncetion来说“有效”,所以在第二次连接(使用Client Auth)时,他只验证Cookie并让我通过。

我终于注意到的第二件事,服务器给了我一个 Nonce 。我猜连接应该通过返回Nonce自动跟随Redirect。一些浏览器,如Firefox和股票浏览器正在停止连接(在使用Responsecode 302重定向后),最后停止使用404,产生一个像..policy?nonce = fno3i9Hsfn3uz这样的URL。然后我必须手动重新加载此URL以进入Webservices。 遗憾的是,这不适用于我的Android应用程序。

我想,这个问题可能是服务器端的配置错误呢?

0 个答案:

没有答案