【トラブル】【Java】 WebサービスにアクセスするJavaクライアントで、Exception: Broken pipeが発生する

■ 現象内容

 以下「発生環境」で、Javaで作成したアプリを、WebサービスSSL通信でアクセスしたところ、
例外「Broken pipe」が発生してしまった。

■ 発生環境

 * Webサービス(Java) - クライアント(Java)

サーバ側

 * OS : CentOS7
 * Web Server : Apache/2.4.6
 * Java : JDK1.8.0_66
 * サーブレットコンテナ : Tomcat8.5
 * Webサービス : Based on Metro-2.3.1
 * SSL証明書 : オレオレ証明書(作成方法は以下の関連記事を参照のこと)
http://blogs.yahoo.co.jp/dk521123/36499526.html

クライアント側

 * OS : Raspbian(Jessie8) ※HD : Raspberry PI 3
 * Java : JDK1.8.0_66 (以下の関連記事のソースをベースにしている)
http://blogs.yahoo.co.jp/dk521123/36528991.html

■ 原因

完全に断定はできないが、恐らく...
 暗号用乱数を生成する際に、SecureRandom.getInstanceStrong()を使用して
アルゴリズム「NativePRNGBlocking (nextBytes()およびgenerateSeed()は/dev/randomを使用)」が選択されたことにより
「/dev/random」の性質で、ブロッキングしてしまうことにより乱数発生にクライアント側で時間がかかり、
リクエスタイムアウトが発生したことによるもの。
# なぜ、そのような結論に至ったかは、以下の「■ 詳細:原因追求までの過程」を参照のこと。

似たような事例は以下のサイトを参照。
http://www.wdic.org/w/TECH//dev/random

■ 解決案

SecureRandom.getInstanceStrong()の代わりに、「new SecureRandom()」や「null」など、
ブロックされないようなアルゴリズムを指定し、乱数を生成する
※ 暗号用乱数については、以下の関連記事を参照のこと。
http://blogs.yahoo.co.jp/dk521123/36638762.html
ちなみに、「new SecureRandom()」の場合は、「NativePRNG」が指定され、
現象が発生しなくなり、問題なくSSL通信できた!!

■ 詳細:原因追求までの過程

 * まずは、「サーバ側」か「クライアント側」のどちら側の原因かを切り分ける必要があると考え
   以下「切り分け方法」の順序で、「クライアント側」が原因と絞り込んだ

切り分け方法

[1] 「-Djavax.net.debug=all」を使って、SSL通信ログを含めて、ログ出力をする
    (詳細は以下の関連記事を参照のこと)
http://blogs.yahoo.co.jp/dk521123/35261371.html
  => 「found key forXXXX」など、ちゃんと証明書を把握し、やり取りしていそう
  => 「ValidatorException: No trusted certificate found」も出ていないのでKeyStoreや証明書には問題はなさそう
  => クライアント側のアプリ・同じKeyStoreを、Windowsで実行したところ、問題なくSSL通信できた
https://docs.oracle.com/javase/jp/6/technotes/guides/security/jsse/ReadDebug.html
[2] Apache側のログファイル「accesslog」に対して、「tail -f accesslog」を行い、ログ監視する。
~~~~
[15/Jan/2017:10:48:54 +0000] "-" 408 0 "-" "-" - 
~~~~

 => HTTPステータスコード「408」は、「クライアント側のエラー」で「Request Timeout」。
    ★ここで、クライアント側が問題と切り分けられる★
http://blogs.yahoo.co.jp/dk521123/34553850.html
[3] 試しに、クライアント側の実装を、以下の関連記事のように、 証明書のチェックを行わないに実装をして
    上記の「発生環境/クライアント側」で確認。
 => 問題なく通信できた
http://blogs.yahoo.co.jp/dk521123/36516080.html
 => 後は、地道に、「証明書のチェックを行わないに実装」を「チェックする実装」に徐々に寄せて、
    動作確認してみたところ、乱数の指定で「SecureRandom.getInstanceStrong()」を使用すると
    現象が発生することが分かった

関連記事

Javaアプリにおける SSL通信時のデバッグ方法

https://www.youtube.com/watch?v=htYHnr7agqs

暗号用乱数 ~ SecureRandom ~

http://blogs.yahoo.co.jp/dk521123/36638762.html

Webサービス / Metro [6] ~ 証明書未チェックでSSL通信を行う (1) / クライアントサイド ~

http://blogs.yahoo.co.jp/dk521123/36516080.html

Webサービス / Metro [8] ~ SSL通信を行う (2) / クライアントサイド ~

http://blogs.yahoo.co.jp/dk521123/36528991.html