1. 概要

HTTPSはHTTPの拡張であり、コンピュータネットワーク内の2つのエンティティ間の安全な通信を可能にします。 HTTPSは、 TLS (トランスポート層セキュリティ)プロトコルを使用して、安全な接続を実現します。

TLSは、一方向または双方向の証明書検証で実装できます。 一方向では、サーバーは公開証明書を共有するため、クライアントはサーバーが信頼できるサーバーであることを確認できます。 代替案は双方向の検証。 クライアントとサーバーの両方が公開証明書を共有して、互いのIDを確認します

この記事では、サーバーがクライアントの証明書もチェックする双方向の証明書検証に焦点を当てます。

2. JavaおよびTLSバージョン

TLS 1.3は、プロトコルの最新バージョンです。 このバージョンは、よりパフォーマンスが高く、安全です 。 より効率的なハンドシェイクプロトコルを備えており、最新の暗号化アルゴリズムを使用しています。

Javaは、Java11でこのバージョンのプロトコルのサポートを開始しました。 このバージョンを使用して証明書を生成し、TLSを使用して相互に認証する単純なクライアントサーバーペアを実装します。

3. Javaでの証明書の生成

双方向のTLS認証を実行しているため、クライアントとサーバーの証明書を生成する必要があります。

実稼働環境では、認証局から証明書を購入することをお勧めします。 ただし、テストまたはデモの目的では、自己署名証明書を使用するだけで十分です。 この記事では、Javaのkeytoolを使用して自己署名証明書を生成します。

3.1. サーバー証明書

まず、サーバーキーストアを生成します。

keytool -genkey -alias serverkey -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -keystore serverkeystore.p12 -storepass password -ext san=ip:127.0.0.1,dns:localhost

keytool -extオプションを使用してサブジェクト代替名(SAN)を設定し、サーバーを識別するローカルホスト名/IPアドレスを定義します。 通常、このオプションで複数のアドレスを指定できます。 ただし、クライアントはこれらのアドレスの1つを使用してサーバーに接続するように制限されます。

次に、証明書をファイルserver-certificate.pemにエクスポートします。

keytool -exportcert -keystore serverkeystore.p12 -alias serverkey -storepass password -rfc -file server-certificate.pem

最後に、サーバー証明書をクライアントのトラストストアに追加します。

keytool -import -trustcacerts -file server-certificate.pem -keypass password -storepass password -keystore clienttruststore.jks

3.2. クライアント証明書

同様に、クライアントキーストアを生成し、その証明書をエクスポートします。

keytool -genkey -alias clientkey -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -keystore clientkeystore.p12 -storepass password -ext san=ip:1`27.0.0.1,dns:localhost

keytool -exportcert -keystore clientkeystore.p12 -alias clientkey -storepass password -rfc -file client-certificate.pem

keytool -import -trustcacerts -file client-certificate.pem -keypass password -storepass password -keystore servertruststore.jks

最後のコマンドでは、クライアントの証明書をサーバートラストストアに追加しました。

4. サーバーJavaの実装

Javaソケットを使用すると、サーバーの実装は簡単です。 SSLSocketEchoServer クラスは、 SSLServerSocket を取得して、TLS認証を簡単にサポートします。 暗号とプロトコルを指定するだけで、残りはクライアントから送信されたものと同じメッセージに応答する標準のエコーサーバーです。

public class SSLSocketEchoServer {

    static void startServer(int port) throws IOException {

        ServerSocketFactory factory = SSLServerSocketFactory.getDefault();
        try (SSLServerSocket listener = (SSLServerSocket) factory.createServerSocket(port)) {
            listener.setNeedClientAuth(true);
            listener.setEnabledCipherSuites(new String[] { "TLS_AES_128_GCM_SHA256" });
            listener.setEnabledProtocols(new String[] { "TLSv1.3" });
            System.out.println("listening for messages...");
            try (Socket socket = listener.accept()) {
                
                InputStream is = new BufferedInputStream(socket.getInputStream());
                byte[] data = new byte[2048];
                int len = is.read(data);
                
                String message = new String(data, 0, len);
                OutputStream os = new BufferedOutputStream(socket.getOutputStream());
                System.out.printf("server received %d bytes: %s%n", len, message);
                String response = message + " processed by server";
                os.write(response.getBytes(), 0, response.getBytes().length);
                os.flush();
            }
        }
    }
}

サーバーはクライアント接続をリッスンします。 listener.setNeedClientAuth(true)を呼び出すには、クライアントがサーバーと証明書を共有する必要があります。 バックグラウンドでは、 SSLServerSocket 実装は、TLSプロトコルを使用してクライアントを認証します。

この場合、自己署名クライアント証明書はサーバーのトラストストアにあるため、ソケットは接続を受け入れます。 サーバーは、InputStreamを使用してメッセージの読み取りに進みます。 次に、 OuputStream を使用して、確認応答を追加する着信メッセージをエコーバックします。

5. クライアントJavaの実装

サーバーで行ったのと同じ方法で、クライアントの実装は単純なSSLScocketClientクラスです。

public class SSLScocketClient {

    static void startClient(String host, int port) throws IOException {

        SocketFactory factory = SSLSocketFactory.getDefault();
        try (SSLSocket socket = (SSLSocket) factory.createSocket(host, port)) {
            
            socket.setEnabledCipherSuites(new String[] { "TLS_AES_128_GCM_SHA256" });
            socket.setEnabledProtocols(new String[] { "TLSv1.3" });
            
            String message = "Hello World Message";
            System.out.println("sending message: " + message);
            OutputStream os = new BufferedOutputStream(socket.getOutputStream());
            os.write(message.getBytes());
            os.flush();
            
            InputStream is = new BufferedInputStream(socket.getInputStream());
            byte[] data = new byte[2048];
            int len = is.read(data);
            System.out.printf("client received %d bytes: %s%n", len, new String(data, 0, len));
        }
    }
}

まず、サーバーとの接続を確立するSSLSocketを作成します。 バックグラウンドで、ソケットはTLS接続確立ハンドシェイクをセットアップします。 このハンドシェイクの一部として、クライアントはサーバーの証明書を検証し、それがクライアントのトラストストアにあることを確認します。

接続が正常に確立されると、クライアントは出力ストリームを使用してサーバーにメッセージを送信します。 次に、入力ストリームを使用してサーバーの応答を読み取ります。

6. アプリケーションの実行

サーバーを実行するには、コマンドウィンドウを開いて、次のコマンドを実行します。

java -Djavax.net.ssl.keyStore=/path/to/serverkeystore.p12 \ 
  -Djavax.net.ssl.keyStorePassword=password \
  -Djavax.net.ssl.trustStore=/path/to/servertruststore.jks \ 
  -Djavax.net.ssl.trustStorePassword=password \
  com.baeldung.httpsclientauthentication.SSLSocketEchoServer

javax.net.ssl。keystoreおよびjavax.net.ssl。trustStoreのシステムプロパティを指定して[以前にkeytoolで作成したX141X]serverkeystore.p12およびservertruststore.jksファイル。

クライアントを実行するには、別のコマンドウィンドウを開いて、次のコマンドを実行します。

java -Djavax.net.ssl.keyStore=/path/to/clientkeystore.p12 \ 
  -Djavax.net.ssl.keyStorePassword=password \ 
  -Djavax.net.ssl.trustStore=/path/to/clienttruststore.jks \ 
  -Djavax.net.ssl.trustStorePassword=password \ 
  com.baeldung.httpsclientauthentication.SSLScocketClient	

同様に、javax.net.ssl.keyStoreおよびjavax.net.ssl。trustStoreシステムプロパティがclientkeystore.p12を指すように設定します。以前にkeytoolで生成したおよびclienttruststore.jksファイル。

7. 結論

サーバーとクライアントの証明書を使用して双方向TLS認証を行う単純なクライアントサーバーJava実装を作成しました

keytool を使用して、自己署名証明書を生成しました。

例のソースコードは、GitHubにあります。