CLOVER🍀

That was when it all began.

JavaでSSL証明書の検証無効化、ホスト名検証の無効化…とデバッグ

時々使う割には、かなりの頻度で忘れるのでメモ。

テストなどでSSL自己署名証明書を使った場合に、よく遭遇する状況

  • SSL証明書をマジメに検証しないようにしたい
  • 証明書のホスト名と実際のホスト名が異なる時にエラーにならないようにしたい

というのをゴマかそう?というお話。

以下のような例外がスローされるケースですね。

アクセス先が、未知の証明書を返却する場合。

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

※単に自己署名証明書を通したいだけなら、後述のキーストアに証明書を追加すれば済む話です

証明書に書かれているホスト名と、アクセス先のホスト名が合わない場合。

java.security.cert.CertificateException: No name matching [ホスト名] found

import文は、以下を前提とします。

import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;

また、javax.net.ssl.HttpsURLConnectionを使用した通信としてコード例は記載します。ま、SSLSocketFactoryをどうにかできればいいはずですが。

SSL証明書の検証を行わない

まずは、証明書の検証を行わないケースについて。X509TrustManagerインターフェースを実装したクラスを作成します。

public class LooseTrustManager implements X509TrustManager {
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }
 
    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }
 
    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }
}

ここでは、各種メソッドが何もしない、もしくはnullを返却しないように実装。

これを、SSLContextへの設定後、HttpsURLConnectionが使用するSSLSocketFactoryとして設定します。

        HttpsURLConnection secureConn = ...;
 
        // 証明書の検証をしない
        // sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null,
                        new X509TrustManager[] { new LooseTrustManager() },
                        new SecureRandom());

        secureConn.setSSLSocketFactory(sslContext.getSocketFactory());

証明書と実際にアクセスするホスト名の検証を行わない

ホスト名の検証をしない場合は、HostnameVerifierインターフェースを実装したクラスを作成します。ここで、verifyメソッドが常にtrueを返却するように実装します。

public class LooseHostnameVerifier implements HostnameVerifier {
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
}

あとは、HttpsURLConnection#setDefaultHostnameVerifier、またはHttpsURLConnection#setHostnameVerifierで作成したHostnameVerifierの実装のインスタンスを設定します。

        // ホスト名の検証をしない
        // java.security.cert.CertificateException: No name matching [ホスト名] found
        // HttpsURLConnection.setDefaultHostnameVerifier(new LooseHostnameVerifier());
        secureConn.setHostnameVerifier(new LooseHostnameVerifier());

繋げると、こんな感じ。

        HttpsURLConnection secureConn = ...;
 
        // 証明書の検証をしない
        // sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null,
                        new X509TrustManager[] { new LooseTrustManager() },
                        new SecureRandom());

        secureConn.setSSLSocketFactory(sslContext.getSocketFactory());
 
        // ホスト名の検証をしない
        // java.security.cert.CertificateException: No name matching [ホスト名] found
        // HttpsURLConnection.setDefaultHostnameVerifier(new LooseHostnameVerifier());
        secureConn.setHostnameVerifier(new LooseHostnameVerifier());

合わせて覚えたい

JavaでのSSL通信のデバッグ

システムプロパティに、「-Djavax.net.debug=all」を指定します。

$ java -Djavax.net.debug=all ....

「all」以外にも「ssl:handshake」なども指定可能です。詳しくは、こちらへ。

JSSEリファレンス・ガイド

SSL/TLS接続のデバッグ

信頼済みのキーストアを指定する。

システムプロパティに、「-Djavax.net.ssl.trustStore=[信頼済みキーストアのパス]」を指定します。

$ java -Djavax.net.ssl.trustStore=trustStore ...

キーストアに証明書を追加するには、

$ keytool -import -alias [エイリアス] -keystore [キーストアへのパス] -file [追加する証明書へのパス]

ですね。

参考)
SSL/TLS 接続のデバッグ
http://docs.oracle.com/javase/jp/7/technotes/guides/security/jsse/ReadDebug.html

Debugging SSL/TLS Connections
http://docs.oracle.com/javase/jp/8/technotes/guides/security/jsse/ReadDebug.html