突如、WebMoneyのAPIが、SSLPeerUnverifiedExceptionを投げ始めた。

TLS1.2に移行したようです。

すでに知られていることですが、java7は、デフォルトではTLS1.2をサポートしていません。 TLS1.2限定のサーバにリクエストを送信すると、以下のようなExceptionがスローされました。

javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
        at sun.security.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:421)
        at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128)
        at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:397)
        at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:148)
        at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:149)
        at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:121)
        at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:573)
        at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:425)
        at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820)
        at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:754)
        at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:732)

近年のTSL1.2必須化の動きは、有名な話だけど、なぜか、本件は、アナウンスが届かなかった。 何も情報がなかったので、サーバ証明書の問題かなと思ってしまいました。

TLS1.2に、対応するには、いくつか方法があります。

  1. デフォルトで1.2をサポートしているから、java8以上にする。理想を言えばそうしよう。
  2. JVMの引数に、-Djdk.tls.client.protocols=TLSv1.1,TLSv1.2,,TLSv1.2-Dhttps.protocols=TLSv1.1,TLSv1.2,TLSv1.3をつけて実行しよう。
  3. これで駄目なケースもあるようだ。プログラムの修正が必要。

私の場合、3のパターンで。 Apache CommonsのHttpClientを使用しているので、以下のような修正をしました。

            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
            sslContext.init(null, null, new SecureRandom());
            SSLSocketFactory sf = new SSLSocketFactory(sslContext);
            Scheme httpsScheme = new Scheme("https",  443, sf);
            SchemeRegistry schemeRegistry = new SchemeRegistry();
            schemeRegistry.register(httpsScheme);
            ClientConnectionManager cm = new SingleClientConnManager(schemeRegistry);
            DefaultHttpClient client = new DefaultHttpClient(cm);

HttpClientのバージョンよっては、こちらの修正方法になります。 というかググるとこればかり出てきます。

SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
  SSLContexts.createDefault(),
  new String[] { "TLSv1.2", "TLSv1.3" },
  null,
  SSLConnectionSocketFactory.getDefaultHostnameVerifier());

CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();

よく見かけるこのやり方は、おそらく全体的に適用されるので、気をつける必要があると思う。

            SSLContext ctx = SSLContext.getInstance("TLSv1.2");
            ctx.init(null, null, null);
            SSLContext.setDefault(ctx);