七色七音

「いつも」と「今日」は別の日だから……

RaspbianのOracleJDKでSSL通信が出来ない話

結論から書いておくけど、OpenJDKに入れ替えたら解決した。

なんかごちゃごちゃと触ってたらラズパイで動いてるJavaのプログラムからSSL通信(URLでhttpsにアクセスする)が出来ないという事に気付いた

具体的に言うと以下のような例外を吐いて動かない。

Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
        at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949)
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
        at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1509)
        at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
        at sun.security.ssl.Handshaker.processLoop(Handshaker.java:979)
        at sun.security.ssl.Handshaker.process_record(Handshaker.java:914)
        at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1062)
        at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
        at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
        at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1513)
        at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1441)
        at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
        at java.net.URL.openStream(URL.java:1038)
        at org.apache.commons.io.IOUtils.toString(IOUtils.java:1198)
        at org.apache.commons.io.IOUtils.toString(IOUtils.java:1216)
        at jp.jyn.jurltest.Main.main(Main.java:20)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:387)
        at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
        at sun.security.validator.Validator.validate(Validator.java:260)
        at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
        at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
        at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
        at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1491)
        ... 16 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:146)
        at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:131)
        at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
        ... 22 more

適当に調べてたら、どうもJavaで使うルート証明書か何かがおかしいんだろうなと言う所までは分かったのだが、なぜおかしいのか分からない(aptでインストールしているのだからそのままで完璧に動いてくれないとおかしい、他のx64マシンではOpen/Oracleのどちらでもキチンと動いた)

その過程でOpenJDKに入れ替えたら上手く動くようになったので、恐らくRaspbianのaptで提供されてるOracleJDKがなんかおかしいんだろうな。と
とりあえずそれで問題は解決したが、以前、RaspbianのOpenJDKがZeroVMというものでめちゃくちゃ性能が悪い(10倍くらい遅い)的な事があったので、その辺りの検証もした

Raspbian OpenJDK vs OracleJDK

結論から書くけど、知らん間にZeroVMじゃなくなってて性能問題も(ほとんど)ないみたいなのでこれからはOpenJDKを使おう

という訳で性能を測定する、性能測定にはマイクラ鯖(Spigot)を使う。理由としては起動にほどほどに時間が掛かり、なおかつ起動までにかかった時間を表示してくれるから

まずはOracleJDK、使ったのはaptでインストールした奴(sudo apt install oracle-java8-jdk)

java version "1.8.0_65"
Java(TM) SE Runtime Environment (build 1.8.0_65-b17)
Java HotSpot(TM) Client VM (build 25.65-b01, mixed mode)

ページキャッシュを解放しながら3回くらい起動してみたが、平均して30秒前後で起動する

次にOpenJDK、もちろんaptでインストールした奴(sudo apt install openjdk-8-jre)

openjdk version "1.8.0_171"
OpenJDK Runtime Environment (build 1.8.0_171-8u171-b11-1~deb9u1-b11)
OpenJDK Client VM (build 25.171-b11, mixed mode)

同じくページキャッシュを解放しながら3回起動してみた、34秒程でほんの少しだけ遅い。

JVMオプションなどは指定しなかった(デフォルトオプションの違いがあるかも知れない)ので、その辺りの条件を整えれば同じくらいになるかも知れない。

ただ、OpenJDK版はserverモードが利用できない(以前はOracleJDKでも使えなかったのだが、知らん間に使えるようになってた)
ちなみに、OracleJDK+serverモードだと23秒くらいまで早くなる

設定を弄って解決する気があるならOracleJDK+serverモードが最速。僕はそういうのはめんどくさくて嫌なのでOpenJDKを使う事にした(シビアに速度が求められる用途でもないし)

ca-certificates-javaとかをインストールして、-Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacertsを指定して起動すると上手く行くよ。めんどくさいけど

分かってる事

多分aptに入ってる証明書がおかしい

OracleJDK(/usr/lib/jvm/jdk-8-oracle-arm32-vfp-hflt/jre/lib/security/cacerts)にある証明書は単一のファイルで
OpenJDK(/usr/lib/jvm/java-8-openjdk-armhf/jre/lib/security/cacerts)にある証明書は/etc/ssl/certs/java/cacertsへのシンボリックリンクになっている

OracleJDK版はca-certificates-javaへの依存をなくそうとしてaptにルート証明書を同梱したが、それが古くてエラーになっているっぽい

ちなみにエラーが出たサイトで使ってるのはLet's Encryptで、今回利用したOracleJDKの1.8.0_65ではLet's EncryptのルートCAの証明書(IdenTrustのDST Root CA X3)が入ってないっぽい

その辺りをシンボリックリンクに貼り直してやれば(それかkeytoolでインポートすれば)恐らく上手く行くと思うが、設定を触りたくない(触った設定が自動アップデートで元に戻される可能性がある)ので、僕はそれはやらない事にした

OracleJDKがアップデートされたら上手く行くようになるかも知れない(その前にOpenJDK 11辺りで一本化されそうだが……)