SSLContextのTLSv1インスタンスからSSLEngineを作成した場合にClientHelloで送信されるCipherSuitesが8u192以降で変わる

久々にJDKのバージョン違いで挙動が違ってハマったのでメモとして残しておく。
SSL/TLSについて付け焼き刃の知識で書いているので勘違いしたままの箇所や理解が足りない部分があるかもしれない。

# 何が起こるのか
下記のコードの出力が、8u191以前と8u192以降で違う。

```java
public static void main(String[] args) throws Exception {
    SSLContext context = SSLContext.getInstance("TLSv1");
    context.init(null, null, null);

    SSLEngine engine = context.createSSLEngine();
    engine.setUseClientMode(true);

    System.out.println(String.join("\n", engine.getEnabledProtocols()));
    System.out.println(String.join("\n", engine.getEnabledCipherSuites()));
}
```

## 8u191の場合

```console
% java -version
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)

% java SSLEngineTest
TLSv1
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
TLS_RSA_WITH_AES_256_CBC_SHA256
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384
TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
TLS_RSA_WITH_AES_256_CBC_SHA
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
TLS_DHE_RSA_WITH_AES_256_CBC_SHA
TLS_DHE_DSS_WITH_AES_256_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
TLS_RSA_WITH_AES_128_CBC_SHA
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
TLS_DHE_RSA_WITH_AES_128_CBC_SHA
TLS_DHE_DSS_WITH_AES_128_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
TLS_RSA_WITH_AES_256_GCM_SHA384
TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384
TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS_RSA_WITH_AES_128_GCM_SHA256
TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256
TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
TLS_EMPTY_RENEGOTIATION_INFO_SCSV
```

8u181でも同じ結果を得たので、おそらく8u191以前なら上記の結果となるはず。

## 8u192の場合
```console
% java -version
java version "1.8.0_192"
Java(TM) SE Runtime Environment (build 1.8.0_192-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.192-b12, mixed mode)

% java SSLEngineTest
TLSv1
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
TLS_RSA_WITH_AES_256_CBC_SHA
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
TLS_DHE_RSA_WITH_AES_256_CBC_SHA
TLS_DHE_DSS_WITH_AES_256_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
TLS_RSA_WITH_AES_128_CBC_SHA
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
TLS_DHE_RSA_WITH_AES_128_CBC_SHA
TLS_DHE_DSS_WITH_AES_128_CBC_SHA
TLS_EMPTY_RENEGOTIATION_INFO_SCSV
```

8u201, 8u202, 8u212で同様の結果を得た。

# なぜ？
わからない……。

[8u192のリリースノート](https://www.oracle.com/technetwork/java/javase/8u192-relnotes-4479409.html)を見ると、確かにCipherSuites関連で変更は入っているが、単にデフォルトで有効なものを設定できるようになった、という変更なのでここに影響するべき話ではないような気がする。

CipherSuitesの差分を見ると8u192以降で無効になっているのは末尾に`SHA256`や`SHA384`が付いているもの。これらは[HMACを表す](https://developer.mozilla.org/ja/docs/Glossary/Cipher_suite)ものらしく、ついでに[HMACにSHA256が追加されたのはTLS 1.2](https://www.ipa.go.jp/security/rfc/RFC5246-01JA.html#012)らしい。

ということは、8u191で`TLSv1`でSSLContextを生成しているにも関わらずTLS 1.2以降で追加されたCipherSuiteが有効化されてしまっていたのが、8u192以降でむしろ正常化された？ように見える。

# これが原因で実際に直面した問題

ESB Mule 3.9.0は、内部で[TLSv1](https://github.com/mulesoft/mule/blob/mule-3.9.0/core/src/main/java/org/mule/api/security/tls/TlsConfiguration.java#L133)の[SSLContextをデフォルトで使用](https://github.com/mulesoft/mule/blob/mule-3.9.0/core/src/main/java/org/mule/api/security/tls/TlsConfiguration.java#L461)する。
ただし、有効なTLSプロトコルのデフォルトは、[TLSv1.1およびTLSv1.2](https://github.com/mulesoft/mule/blob/mule-3.9.0/distributions/standalone/src/main/resources/conf/tls-default.conf#L41)に設定されている。

ESB Muleは内部でGrizzly Async Http Clientを利用している。
[GrizzlyによるSSLEngineの初期化処理](https://github.com/javaee/grizzly-ahc/blob/1_14/src/main/java/com/ning/http/client/SSLEngineFactory.java#L48)で、SSLEngineがTLSv1のSSLContextによって生成され、Clientモード有効化のあと、有効なTLSプロトコルがセットされる。

これによって、「プロトコルとしてTLS 1.2は利用可能だが、ClientHelloで送信するCipherSuitesの候補値にTLS 1.2以降で利用可能になったものが含まれないSSLEngine」が生成されてしまう。

このSSLEngineで「TLS 1.2以降で利用可能になったCipherSuitesのみをサポートするHTTPSサーバー」にアクセスすると、ClientHello送信後、handshake_failure(40)でAlertが返ってきてエラーとなる。

ちなみに[この問題は ESB Mule 3.9.1 で修正](https://github.com/mulesoft/mule/commit/96b57493049f3e94e4ffed44fc5608760ab75e72)されている。

# SSLContextはどのprotocolで使うべきか

このご時世で`TLSv1`など特定のTLSバージョンに限定したSSLContextインスタンスを使う必要がある場面は限られる。おそらく使うべきなのは下記の3種類のうちの1つなはず。

- `SSLContext.getDefault()`
- `SSLContext.getInstance("SSL")`
- `SSLContext.getInstance("TLS")`

`SSLContext.getInstance`の引数に指定可能な値については[標準アルゴリズム名のドキュメント](https://docs.oracle.com/javase/jp/8/docs/technotes/guides/security/StandardNames.html#SSLContext)を参照。

もし諸事情あって`TLSv1`のSSLContextを使い続ける必要がある場合、setEnableCipherSuitesを呼べば任意のCipherSuitesを有効化できる。ただし、JDKのデフォルト値を使わないということはセキュリティのリスクを伴う（アップデートしても古くなったCipherSuitesが無効にならない）ので注意。

# その他

JavaのSSL関連のデバッグ情報はシステムプロパティで`-Djava.ssl.debug=ssl`とすると有効化できる。
読み方は[SSL/TLS 接続のデバッグ](https://docs.oracle.com/javase/jp/6/technotes/guides/security/jsse/ReadDebug.html)のドキュメントにある。

サーバー側のSSL設定状況を知りたい場合は[SSLLabsのSSL Server Test](https://www.ssllabs.com/ssltest/analyze.html)が便利っぽい。