3秒で要約: Java 16ではTLSv1.0が無効化されている。TLSv1.2で接続しましょう
現象と対策 – MySQL
Java 16にアップデートしたところ、ステージング環境で以下のような例外が発生しました。
Caused by: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate) at java.base/sun.security.ssl.HandshakeContext.(HandshakeContext.java:172) at java.base/sun.security.ssl.ClientHandshakeContext.(ClientHandshakeContext.java:98) at java.base/sun.security.ssl.TransportContext.kickstart(TransportContext.java:238) at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:434) at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:412) at com.mysql.cj.protocol.ExportControlled.performTlsHandshake(ExportControlled.java:336) at com.mysql.cj.protocol.StandardSocketFactory.performTlsHandshake(StandardSocketFactory.java:188) at com.mysql.cj.protocol.a.NativeSocketConnection.performTlsHandshake(NativeSocketConnection.java:99) at com.mysql.cj.protocol.a.NativeProtocol.negotiateSSLConnection(NativeProtocol.java:325)
使っているドライバは mysql:mysql-connector-java:8.0.23 です。
調べたところこれは接続先ホストからJavaが対応していないTLS(いわゆるSSL)のプロトコルバージョンを要求されたときに出るものみたいです。
接続先のMySQL on Amazon RDSではMySQL 5.6.44なのですが、TLS1.0までしかサポートしていないようです。
MySQL on Amazon RDS > Using SSL with a MySQL DB instance
MySQL 5.6はすでに2021年2月5日にEOLで、AWSでも2021年8月3日にサポート終了予定です。
Amazon RDS for MySQL バージョン 5.6 のサポート終了のお知らせ
最新のバージョン8への移行が推奨されていますが、5.6から8.0へは一気にアップグレードできないので5.7を経て8.0にアップグレードしたところ症状が治まりました。(深掘りしたSQLは発行していないので、コードの変更はなしにテストケースは全て通りました。)
現象と対策 – JavaMail
JavaMailでメール送信する箇所でも似た例外が出ていました。
Caused by: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate) at java.base/sun.security.ssl.HandshakeContext.(HandshakeContext.java:172) at java.base/sun.security.ssl.ClientHandshakeContext. (ClientHandshakeContext.java:98) at java.base/sun.security.ssl.TransportContext.kickstart(TransportContext.java:238) at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:434) at java.base/sun.security.ssl.SSLSocketImpl.ensureNegotiated(SSLSocketImpl.java:903) at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:994) at com.sun.mail.util.TraceInputStream.read(TraceInputStream.java:110)
最新のcom.sun.mail:javax.mail:1.6.2では問題ないのですが、org.eclipse.jetty.aggregate:jetty-all:9.4.38.v20210224が依存しているorg.eclipse.jetty.orbit:javax.mail.glassfish:1.4.1.v201005082020が優先されてしまっていたようです。これをexcludeすることで回避できました。
根本原因
Java 15で接続できていたのにJava 16で接続できなくなったのはセキュリティの設定が変更になったためです。
$JAVA_HOME/conf/security/java.security を見ると以下のようにTLSv1、TLSv1.1が無効化されている記述が見つかります。
# Note: This property is currently used by the JDK Reference implementation. # It is not guaranteed to be examined and used by other implementations. # # Example: # jdk.tls.disabledAlgorithms=MD5, SSLv3, DSA, RSA keySize < 2048, \ # rsa_pkcs1_sha1, secp224r1 jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA, \ DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL
Java 15.0.2ではこうなっています。
# Note: This property is currently used by the JDK Reference implementation. # It is not guaranteed to be examined and used by other implementations. # # Example: # jdk.tls.disabledAlgorithms=MD5, SSLv3, DSA, RSA keySize < 2048, \ # rsa_pkcs1_sha1, secp224r1 jdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, DH keySize < 1024, \ EC keySize < 224, 3DES_EDE_CBC, anon, NULL, \ include jdk.disabled.namedCurves
ちなみにTLSv1.0,1.1はIETFで非推奨とされ、Java 8,11,15でも無効化されるようです。
昨日 @yusuke さんもツイートしてたけど、Java 16ではTLS 1.0/1.1がデフォルトで無効化されています。
次回4月リリース予定のOpenJDK 15.0.3(Azulのみかな?)/11.0.11/8u292でも同様となるのでご注意ください。https://t.co/TLlNuAWQDS https://t.co/Su5Au1mysV— Takahiro YAMADA (@yamadamn) 2021年3月17日
Quick & Dirty消極的回避策
jdk.tls.disabledAlgorithmsから TLSv1 を削除することでも回避出来ます。
が、そもそも非推奨になっているプロトコルなのでTLSv1.2で接続したり、グローバルの回線を通らないようにしてプレイン通信するようにすべきです。
まとめ
CIではMySQLへの接続はTLSを使っておらず、メール送信も実際には行わない仕組みにしているので検出できませんでした。
本番DBと同じ構成のステージングサーバで発生したので障害には至りませんでした。日頃からCIや限りなく本番に近い構成のステージングサーバでの検証をしておきましょう。