CA証明書とandroidの危うい関係 4

java(SDK)で実装する

Apache HttpClientではなくjavax.net.ssl.HttpsUrlConnectionを使った例も載せておく。
同クラスを使う場合、TrustManagerFactoryとKeyManagerFactoryという二つのファクトリを用意しなくてはならず、若干煩雑になる。

  • HttpsUrlConnectionを使用する
URL url = new URL("接続するURL");
HttpURLConnection con = (HttpURLConnection)url.openConnection();
if ( uri.startsWith("https")) {
    HttpsURLConnection https = (HttpsURLConnection)con;
    https.setSSLSocketFactory(getSSLSocketFactory(context));
}

〜以降、HttpURLConnectionを使って通信を実行する

private static SSLSocketFactory getSSLSocketFactory(Context context)  {
    final Resources resource = context.getResources();
    
    TrustManagerFactory trustManagerFactory;
    KeyManagerFactory keyManagerFactory;
    
    char[] passwdchars = "<パスワード>".toCharArray();

    InputStream cacerts_stream = resource.openRawResource(R.raw.cacerts);
    try {
        KeyStore tm_keystore = KeyStore.getInstance("BKS");
        tm_keystore.load(cacerts_stream, passwdchars);
        trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(tm_keystore);
    } finally {
        cacerts_stream.close();
    }

    try {
        cacerts_stream = resource.openRawResource(R.raw.cacerts);
        KeyStore km_keystore = KeyStore.getInstance("BKS");
        km_keystore.load(cacerts_stream, passwdchars);
        keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(km_keystore, passwdchars);
    } finally {
        cacerts_stream.close();
    }
    
    SSLContext sslContext = SSLContext.getInstance("TLSv1");
    sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
    return sslContext.getSocketFactory();
}

例のごとく例外処理は省いている。

これでHttpClient同様通信ができると思うが、サーバの環境如何によっては、

    • 接続できたり出来なかったりと不定期又は定期に問題が発生する
    • HTTPのレスポンスコードに-1が戻る

等問題が発生する場合がある。
androidは何故か※のHTTP KeepAliveがデフォルトで有効になっており、これが問題になるケースがあるため、アプリケーションの初期化時や通信時に以下のコードでKeepAliveを無効にすることで問題が解決する場合がある。

System.setProperty("http.keepAlive", "false");

前回のエントリから自前の証明書とキーストアを使う例を二通り掲載したが、正直なところ、こんなコーディングなぞしなくても最新のルートCA証明書位はOTA等で端末にプッシュする仕組みを提供すべきだと思うが(それが結果としてセキュリティ強化にもつながる)、いかがですかGoogleさん。

※ソケットは比較的重いリソースなので、なるべく接続を節約したいという意図があるのかもしれない。