1. 概要

この記事では、自己署名証明書を信頼するようにOkHttpClientを初期化および構成する方法を説明します。 この目的のために、自己署名証明書で保護された最小限のHTTPS対応Spring Bootアプリケーションをセットアップします。

ライブラリの詳細については、OkHttpに関する記事のコレクションを参照してください。

2. 基礎

この作業を行うためのコードに飛び込む前に、結論を出しましょう。 SSLの本質は、任意の2つのパーティ、通常はクライアントとサーバーの間に安全な接続を確立することです。 また、ネットワークを介して転送されるデータのプライバシーと整合性を保護するのに役立ちます

JavaSEのセキュリティコンポーネントであるJSSEAPIは、SSL/TLSプロトコルの完全なAPIサポートを提供します。

SSL証明書(別名デジタル証明書)は、TLSハンドシェイクを確立する上で重要な役割を果たし、通信する当事者間の暗号化と信頼を促進します。 自己署名SSL証明書は、有名で信頼できる認証局(CA)によって発行されたものではありません。 それらは、開発者がソフトウェアでHTTPSを有効にするために、簡単に生成および署名することができます。

自己署名証明書は信頼できないため、ブラウザーも、OkHttpやApacheHTTPクライアントなどの標準のHTTPSクライアントもデフォルトで信頼しません

最後に、WebブラウザまたはOpenSSLコマンドラインユーティリティを使用して、サーバー証明書を簡単に取得できます。

3. テスト環境のセットアップ

OkHttpを使用して自己署名証明書を受け入れて信頼するアプリケーションを示すために、HTTPSが有効になっている(自己署名証明書で保護されている)単純なSpring Bootアプリケーションをすばやく構成して起動します。

デフォルト設定では、ポート8443でリッスンしているTomcatサーバーが起動し、「https:// localhost:8443/welcome」でアクセス可能なセキュリティで保護されたRESTAPIが公開されます。

次に、OkHttpクライアントを使用してこのサーバーにHTTPSリクエストを送信し、「/welcome」APIを使用してみましょう。

4. OkHttpClientおよびSSL

このセクションでは、 OkHttpClient を初期化し、それを使用して、セットアップしたばかりのテスト環境に接続します。 さらに、パスで発生したエラーを調べ、OkHttpを使用して自己署名証明書を信頼するという最終目標に段階的に到達します。

まず、OkHttpClientのビルダーを作成しましょう。

OkHttpClient.Builder builder = new OkHttpClient.Builder();

また、このチュートリアル全体で使用するHTTPSURLを宣言しましょう。

int SSL_APPLICATION_PORT = 8443;
String HTTPS_WELCOME_URL = "https://localhost:" + SSL_APPLICATION_PORT + "/welcome";

4.1. SSLHandshakeException

SSL用にOkHttpClientを構成せずに、HTTPS URLを使用しようとすると、セキュリティ例外が発生します。

@Test(expected = SSLHandshakeException.class)
public void whenHTTPSSelfSignedCertGET_thenException() {
    builder.build()
    .newCall(new Request.Builder()
    .url(HTTPS_WELCOME_URL).build())
    .execute();
}

スタックトレースは次のとおりです。

javax.net.ssl.SSLHandshakeException: PKIX path building failed: 
    sun.security.provider.certpath.SunCertPathBuilderException:
    unable to find valid certification path to requested target
    ...

上記のエラーは、サーバーが認証局(CA)によって署名されていない自己署名証明書を使用していることを正確に意味します。

したがって、クライアントはルート証明書までの信頼の鎖を検証できなかったため、SSLHandshakeExceptionをスローしました。

4.2. SSLPeerUnverifiedException

次に、CA署名または自己署名の性質に関係なく証明書を信頼するOkHttpClientを構成しましょう。

まず、デフォルトの証明書検証を無効にし、カスタム実装でそれらをオーバーライドする独自のTrustManagerを作成する必要があります。

TrustManager TRUST_ALL_CERTS = new X509TrustManager() {
    @Override
    public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
    }

    @Override 
    public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
    }

    @Override
    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
        return new java.security.cert.X509Certificate[] {};
    }
};

次に、上記の TrustManager を使用して、 SSLContext を初期化し、OkHttpClientビルダーのSSLSocketFactoryを設定します。

SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[] { TRUST_ALL_CERTS }, new java.security.SecureRandom());
builder.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) TRUST_ALL_CERTS);

もう一度、テストを実行してみましょう。 上記の調整を行った後でも、HTTPS URLを使用するとエラーが発生することは、信じがたいことではありません。

@Test(expected = SSLPeerUnverifiedException.class)
public void givenTrustAllCerts_whenHTTPSSelfSignedCertGET_thenException() {
    // initializing the SSLContext and set the sslSocketFactory
    builder.build()
        .newCall(new Request.Builder()
        .url(HTTPS_WELCOME_URL).build())
        .execute();
}

正確なエラーは次のとおりです。

javax.net.ssl.SSLPeerUnverifiedException: Hostname localhost not verified:
    certificate: sha256/bzdWeeiDwIVjErFX98l+ogWy9OFfBJsTRWZLB/bBxbw=
    DN: CN=localhost, OU=localhost, O=localhost, L=localhost, ST=localhost, C=IN
    subjectAltNames: []

これは、よく知られている問題、ホスト名検証の失敗が原因です。 上記の詳細なスタックトレースに示されているように、ほとんどの HTTPライブラリは、証明書のSubjectAlternativeNameのDNS名フィールドに対してホスト名検証を実行します。これは、サーバーの自己署名証明書では使用できません。

4.3. HostnameVerifierをオーバーライドする

OkHttpClient を正しく構成するための最後のステップは、デフォルトの HostnameVerifier を無効にして、ホスト名の検証をバイパスする別のホスト名に置き換えることです。

カスタマイズのこの最後のチャンクを入れましょう:

builder.hostnameVerifier(new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
});

それでは、最後にもう一度テストを実行してみましょう。

@Test
public void givenTrustAllCertsSkipHostnameVerification_whenHTTPSSelfSignedCertGET_then200OK() {
    // initializing the SSLContext and set the sslSocketFactory
    // set the custom hostnameVerifier
    Response response = builder.build()
        .newCall(new Request.Builder()
        .url(HTTPS_WELCOME_URL).build())
        .execute();
    assertEquals(200, response.code());
    assertNotNull(response.body());
    assertEquals("<h1>Welcome to Secured Site</h1>", response.body()
        .string());
}

最後に、OkHttpClientは、自己署名証明書によって保護されたHTTPSURLを正常に使用できます。

5. 結論

このチュートリアルでは、 OkHttpClient のSSLを構成して、自己署名証明書を信頼し、任意のHTTPSURLを使用できるようにする方法について学習しました。

ただし、考慮すべき重要な点は、この設計では証明書の検証とホスト名の検証が完全に省略されているにもかかわらず、クライアントとサーバー間のすべての通信が暗号化されていることです。 両者間の信頼は失われますが、SSLハンドシェイクと暗号化は危険にさらされません。

いつものように、完全なソースコードはGitHubで見つけることができます。