編集: 元の投稿の表現が不十分だった場合はお詫びします。元の投稿へのコメントに代表される混乱を招きました。それでは、もう一度試してみましょう:
私は質問から始めました。Android の問題を解決したかったのですが、方法がわかりませんでした。私は解決策を求めてネットを見回すのに多くの時間を費やしましたが、この問題に関する議論はどこにもありませんでした。それでも、StackOverflow スレッドを含む多くの議論の結果、有望に見える手法にたどり着きました。問題を解決しました。しかし、解決策は少し複雑でした。だから私はここに質問を投稿することに決めました。または b) おそらくこれは良い解決策であり、ネット上のどこにもこの問題に関する議論が見つからなかったので、この問題に対する私の解決策は、同じことをしようとしている他の人にとって役立つかもしれません。いずれにせよ、結果は StackOverflow への新たな貢献となるでしょう: 他の場所では答えられていない質問で、最終的には、いずれにしても正解です。実際、StackOverflow は、最初に投稿したときに知識を共有することで、自分の質問に答えるように招待してくれました。実際、それは私の動機の一部でした。この件の事実でさえ、私が見つけたどこにも集められていません。
そう:
Q. AndroidHttpClient を使用して HTTPS 経由で REST リクエストを行う場合、使用する SSL プロトコルと暗号を指定するにはどうすればよいですか?
これは重要。サーバー上でできることはたくさんありますが、制限があることはよく理解されています。同じサーバーが、古いブラウザーを含むブラウザーと他のクライアントにサービスを提供する必要があります。つまり、サーバーは幅広いプロトコルと暗号をサポートする必要があります。Android 内でも、多数の異なるバージョンをサポートする必要がある場合は、多数の異なるプロトコルと暗号をサポートする必要があります。
さらに重要なことに、OpenSSL はデフォルトで、SSL ハンドシェーク中にサーバーではなくクライアントの暗号設定を尊重します。たとえば、この投稿を参照してください。SSL_OP_CIPHER_SERVER_PREFERENCE を設定することで、クライアントの動作をオーバーライドできると書かれています。このオプションを Java の SSLSocket で設定できるかどうかは完全には明らかではありません。可能な場合でも、暗号リストを自分で設定するか、サーバーのリストを尊重するようにクライアントに指示できます。それ以外の場合は、実行しているバージョン (リンク先のバージョンではない) に関係なく、Android のデフォルトを取得しています。
デフォルトを使用する場合、Jellybean 4.2+ クライアントによってクライアントからサーバーに送信される設定リストは、 504 行目あたりから始まるこちらで確認できます。プロトコルのデフォルト リストは 620 行目あたりから始まります。Jellybean 4.2+ には OpenSSL のサポートが含まれていますが、 1.0.1、特に TLSv1.1 と TLSv1.2 では、これらのプロトコルはデフォルトでは有効になっていません。最近のバージョンの Android では TLSv1.2 のサポートが宣伝されているにもかかわらず、私が行ったようなことをしないと、TLSv1.2 のサポートを利用することはできません。また、以前の Android バージョンに戻ると、詳細はかなり異なります。少なくとも、サポートされているすべてのバージョンのデフォルトをよく見て、クライアントが実際に何をしているかを確認することをお勧めします。あなたは驚くかもしれません。
さまざまなプロトコルと暗号のサポートについては、他にも多くのことが言えます。ポイントは、クライアントでこれらの設定を変更する必要がある場合があるということです。
A. カスタム SSLSocketFactory を使用する
これは私にとってはうまくいきましたが、最終的にはそれほど多くのコードではありませんでした。しかし、それはいくつかの理由で少し厄介でした:
- 2 つの異なる SSLSocketFactory クラスがあります。クライアントには org.apache.http.conn.ssl.SSLSocketFactory が必要ですが、OpenSSL は javax.net.ssl.SSLSocketFactory を返します。これは間違いなく混乱します。委任を使用して、一方が他方を呼び出すようにしましたが、それほど問題はありませんでした。
- OpenSSLContextImpl と SSLContextImpl の違いに注意してください。一方は他方をラップするだけですが、交換可能ではありません。SSLContextImpl.engineGetSocketFactory メソッドを使用したとき、正確に何が起こったかは忘れましたが、何かが静かに失敗しました。SSLContextImpl ではなく、必ず OpenSSLContextImpl を使用してソケット ファクトリを取得してください。javax.net.ssl.SSLSocketFactory.getDefault() を使用することもできるかもしれませんが、よくわかりません。
- AndroidHttpClient のコンストラクターはプライベートであるため、簡単にサブクラス化することはできません。リソースをリークするのではなく、適切にシャットダウンすることを確認するなど、他のいくつかの優れた機能を提供するため、これは残念です. DefaultHttpClient は問題なく動作します。AndroidHttpClient から newInstance メソッドを借用しました (105 行目あたり)。
要点:
public class SecureSocketFactory extends SSLSocketFactory {
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
// order should not matter here; the server should select the highest
// one from this list that it supports
s.setEnabledProtocols(new String[] { "TLSv1.2", "TLSv1" });
// order matters here; specify in preference order
s.setEnabledCipherSuites(new String[] { "ECDHE-RSA-RC4-SHA", "RC4-SHA" });
それで:
// when creating client
HttpParams params;
SchemeRegistry schemeRegistry = new SchemeRegistry();
// use custom socket factory for https
SSLSocketFactory sf = new SecureSocketFactory();
schemeRegistry.register(new Scheme("https", sf, 443));
// use the default for http
schemeRegistry.register(new Scheme("http",
PlainSocketFactory.getSocketFactory(), 80));
ClientConnectionManager manager =
new ThreadSafeClientConnManager(params, schemeRegistry);
HttpClient client = new DefaultHttpClient(manager, params);
Android 3.0 (Honeycomb/SDK 11) より下では、サポートされる暗号の選択肢がより制限され、デフォルトをオーバーライドする動機が少なくなります。FROYO/SDK 8 では、私の SecureSocketFactory が何らかの理由で爆発し、陪審員は 10 に出ています。しかし、11 以上では問題なく動作するようです。
完全なソリューションは、パブリック githubリポジトリにあります。
別の解決策として、カスタム ソケット ファクトリを簡単に使用できるようにする HttpsUrlConnection を使用することも考えられますが、高レベルの HTTP クライアントの利便性をさらに失うことになると思います。私は HttpsUrlConnection の経験がありません。