8

サーバーに接続していくつかの 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 コードだけにあると思いますか?

誰かが同様の問題を抱えていて、アイデアや解決策を持っていますか?

どうもありがとうございました!

編集

そもそも SSL 接続に必要な Cookie Manager もセットアップしたことを忘れていました。(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 を受け取るように設定されています。前のステップでクライアント認証なしで接続すると、接続が「機能する」ため、2回目の接続(クライアント認証を使用)で、彼はそのCookieのみを検証し、通過させます。

最後に気付いた 2 番目のことは、サーバーがNonceを提供してくれることです。そのノンスを返すことにより、接続は自動的にそのリダイレクトに従うべきだと思います。Firefox やストック ブラウザなどの一部のブラウザは、(Responsecode 302 でリダイレクトされた後) 接続を停止し、最終的に 404 で停止し、..policy?nonce=fno3i9Hsfn3uz のような URL になります。次に、この URL を手動でリロードして、Web サービスにアクセスする必要があります。残念ながら、これは私の Android アプリケーションでは機能しません。

おそらく、この問題はサーバー側の設定ミスではないでしょうか?

4

0 に答える 0