javaでHTTPS通信の実装をするという記事を適当に書くのはやめるべき

Filed in ゴミコード, コンピュータ, プログラム

いろんなブログでjavaを使ったHTTPSクライアントの実装について書かれている。
だが適当な記事が多すぎて困る。RFCを読んでいるのか疑問に思うことがほとんどだ。というかRFCの存在を知らないのかもしれない。

このブログではリンクは基本的に張らないことにしているが特別に張っておこう。
こちらはIPAが提供してくれているHTTP オーバー TLSを規定したRFC2818の和訳である。
http://www.ipa.go.jp/security/rfc/RFC2818JA.html#31
こちらはRFC9110の日本語訳である。(RFC9110はRFC2818の修正版)
https://tex2e.github.io/rfc-translater/html/rfc9110.html

javaはセキュリティライブラリをJSSEとしてコアパッケージと分離することでセキュリティ機能をサードパーティによるカスタマイズが可能となるような思想で設計されている。そのためSSL/TLSについても柔軟なカスタマイズが可能である。

逆に言うとしっかりと理解していないとセキュリティホールを作りこんでしまうことになる。世のブログはこれに該当してしまう記事が異常に多い。さらに悪いのはそれが正解であるかのように広がってしまっていることだ。
エンジニアの中にもそういったブログの記事が正解だと思っている人が思いのほか多いという事実があっては笑ってもいられない。

よく紹介されているHTTPSクライアントの実装はこうである。

TrustManager[] tm = {
      new X509TrustManager() {
           public X509Certificate[] getAcceptedIssuers() {
               return null;
           }
           public void checkClientTrusted(X509Certificate[] xc,String type) {
           }
           public void checkServerTrusted(X509Certificate[] xc,String type) {
           }
     }
};
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tm, new SecureRandom());

URL url = new URL("https://hogehoge/foo/bar");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
conn.setDefaultHostnameVerifier(new HostnameVerifier() {
      public boolean verify(String hostname, SSLSession session) {return true;}
    }
);

  以下略

まあ通信できるでしょう。しかしこれはやるべきことを全くやっていません。
このコードには簡単に以下の2つの問題がある。

  • 証明書が信頼できるかをチェックしないトラストマネージャを無条件で使っている(1~13行目)
  • サーバ名の検証を無条件でOKにしている(17~20行目)

1点目の問題では、証明書が送られてきさえすればそれが偽造されたものであろうと何だろうと通信OKとなってしまう。
2点目の問題では、信頼できる認証局から発行された証明書でありさえすればそれが目的のサーバのものでなくても通信OKとなってしまう。

1点目の偽造証明書にOKを出すのがダメなことは直感的にも分かるでしょう。
では、2点目はなぜダメなのでしょう。

たとえば、こんなケースがある。

上記のような実装をされたウェブブラウザであなたが銀行のサイトに接続したつもりでいます。
しかし、あなたが使っているDNSがレコード書き換えの攻撃を受けており実際には別のサイト(攻撃者が管理するサイト)に繋がってしまいました。
ですが、そのサーバには第三者機関から発行された証明書が設定されていました。
ブラウザは通信相手は期待したサーバであると判断して通信を続行してしまいます。
あなたは銀行のサイトだと思ってIDとパスワードを入力してしまいます。
攻撃者はあなたのIDとパスワードをまんまと手に入れてしまうわけです。

本来は偽のサーバに設定されている証明書のサーバ名とあなたが接続しようとしたサーバ名は異なっているので、接続は拒否されるべきである。ですが上記のコードではこれがなされません。さらに悪いことに上記のコードでは無条件で処理がされてしまうためユーザがそんなことが起こっていることを知る術もない。

では正しく処理するにはどう実装したらよいのだろうか。こうです。

URL url = new URL("https://hogehoge/foo/bar");
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
以下略

非常に簡単です。なんのことはない。javaは特に設定を変えない限りデフォルトで正しく動作をするようになっている。
無理やりその動作を歪めてしまう必要はないのである。

自己署名証明書を設定したサーバに対してはこのままで通信をしようとしても通信はできない。
それは当然である。なぜなら自己署名証明書は信用できる証明書ではないからだ。
どうにもならないのかといえばそんなことはない。

自己署名証明書を信用できるという前提でクライアント側のトラストストアにインポートすればよいのだ。
これにより通信は可能になる。
ただ、これはあくまで普通の行為ではないということを認識したうえで行う必要がある。

セキュリティとはかくも面倒くさいことであるが、こういったことが守られてようやく実用レベルの安全性をもったネットワークというのが実現できるのである。守られていてもクラッカーとはイタチごっこであるのに守られていない場合に安全であるなど言えっこないのである。

Feedback

Comments

No Responses to “javaでHTTPS通信の実装をするという記事を適当に書くのはやめるべき”

Trackbacks

  1. httpsとかsslとかをおさらいしてみる - CTOの日記

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)