8 Java Secure Socket Extension (JSSE)リファレンス・ガイド

Java Secure Socket Extension (JSSE)により、セキュアなインターネット通信が可能になります。これは、JavaバージョンのTLSおよびDTLSプロトコルのフレームワークおよび実装を提供し、データ暗号化、サーバー認証、メッセージの整合性の他、オプションでクライアント認証の機能を含んでいます。

JSSEの概要

ネットワークを通じてやり取りされるデータには、意図された受信者以外の人も、簡単にアクセスできます。データにパスワードやクレジット・カード番号などの個人情報が含まれる場合、権限のない者がデータを理解できないよう、手段を講じる必要があります。また、意図的であるかどうかにかかわらず、通信中にデータが変更されていないことを確認することも重要です。Transport Layer Security (TLS)は、ネットワークを通じたデータの送信時に、データの機密性および整合性を保護するために設計されたプロトコルです。

Java Secure Socket Extension (JSSE)により、セキュアなインターネット通信が可能になります。これは、JavaバージョンのTLSプロトコルのフレームワークおよび実装を提供し、データ暗号化、サーバー認証、メッセージの整合性の他、オプションでクライアント認証の機能を含んでいます。JSSEを使用すると、開発者はHTTP、Telnet、FTPなど、TCP/IP上のアプリケーション・プロトコルを実行するクライアントとサーバーの間で、セキュアなデータのやり取りを実現できます。

JSSEは、基盤となる複雑なセキュリティ・アルゴリズムやハンドシェーク・メカニズムを抽象化することにより、識別するのが難しく、しかし危険なセキュリティ上の弱点が生まれるリスクを最小限に抑えます。また、開発者がそれをアプリケーションに直接統合できる構築ブロックとして使用すると、アプリケーション開発が簡単になります。

JSSEは、アプリケーション・プログラミング・インタフェース(API)フレームワークと、そのAPIの実装を提供します。JSSE APIは、java.securityおよびjava.netパッケージによって定義されたコア・ネットワークおよび暗号化サービスを補い、拡張されたネットワーク・ソケット・クラス、トラスト・マネージャ、キー・マネージャ、SSLコンテキストおよびソケット作成動作をカプセル化するソケット・ファクトリのフレームワークを提供します。SSLSocketクラスはブロック入出力モデルに基づいているため、Java Development Kit (JDK)には、実装で独自の入出力メソッドを選択できるようにするために非ブロックSSLEngine クラスが含まれます。

JSSE APIでは、次のセキュリティ・プロトコルがサポートされています。

  • DTLS: バージョン1.0および1.2

  • TLS: バージョン1.0、1.1、1.2および1.3

  • SSL (Secure Socket Layer): バージョン3.0

これらのセキュリティ・プロトコルは、通常の双方向のストリーム・ソケットをカプセル化し、JSSE APIは認証、暗号化および整合性保護の透過的なサポートを追加します。

JSSEはJava SEプラットフォームのセキュリティ・コンポーネントであり、Java暗号化アーキテクチャ(JCA)リファレンス・ガイドのフレームワーク内の至るところで見られる同じ設計方針に基づいています。暗号化に関するセキュリティ・コンポーネントのこのフレームワークにより、実装の独立性と、可能な場合にはアルゴリズムの独立性を実現できます。JSSEはJCAフレームワークによって定義された暗号化サービス・プロバイダを使用します。

Java SEプラットフォーム内の他のセキュリティ・コンポーネントには、Java Authentication and Authorization Service (JAAS)リファレンス・ガイドおよびJavaセキュリティ・ツールがあります。JSSEはJCAと同じ概念およびアルゴリズムを多く含んでいますが、単純なストリーム・ソケットAPIの下でこれらを自動的に適用します。

JSSE APIは、その他のSSL/TLS/DTLSプロトコルと公開キー・インフラストラクチャ(PKI)実装をシームレスにプラグインできる設計になっています。開発者が、リモート・ホストの信頼性やリモート・ホストに送信する認証キー・データを決定するロジックを提供することもできます。

JSSEの特長と利点

JSSEには、次のような重要な利点と特長があります。

  • JDKの標準のコンポーネントとして含まれている
  • 拡張可能なプロバイダ・ベースのアーキテクチャ
  • 100% Pure Javaに実装される
  • TLS/DTLSのAPIサポートを提供する
  • SSL3.0、TLS (バージョン1.0、1.1、1.2および1.3)およびDTLS (バージョン1.0および1.2)の実装を提供する
  • セキュアなチャネルを作成するためにインスタンス化可能なクラスを含む(SSLSocketSSLServerSocket、およびSSLEngine)
  • セキュアな通信を開始または検証するのに使用されるTLS/DTLSハンドシェーク機能の一部として、暗号群ネゴシエーションをサポートする
  • 通常のTLS/DTLSハンドシェーク機能の一部として、クライアントとサーバーの認証をサポートする
  • TLSプロトコルでカプセル化されたHTTPをサポートし、これにより、HTTPSを使用するWebページなどのデータにアクセスできる
  • メモリー常駐型のSSLセッションを管理するためのサーバー・セッション管理APIを提供する
  • 証明書ステータス要求拡張機能(OCSPステープリング)をサポートする。これにより、クライアント証明書の検証のラウンドトリップおよびリソースが節約されます
  • Server Name Indication (SNI)拡張機能をサポートする。これにより、ハンドシェーク時にクライアントが接続を試みているサーバーの名前を示すようTLS/DTLSプロトコルが拡張されます
  • ハンドシェーク時のエンドポイント識別をサポートする。これにより、介入者攻撃を防ぐことができます
  • 暗号化アルゴリズム制約をサポートする。これにより、JSSEによってネゴシエーションされたアルゴリズムを、詳細に管理できるようになります

JSSE標準API

JSSE標準APIは、javax.netおよびjavax.net.sslパッケージで利用でき、次を提供します。

  • クライアント側アプリケーションとサーバー側アプリケーションに合わせたセキュア・ソケット。
  • TLS/DTLSデータのストリームを生成および消費する非ブロック・エンジン(SSLEngine)。
  • ソケット、サーバー・ソケット、SSLソケット、およびSSLサーバー・ソケットを作成するファクトリ。ソケット・ファクトリを使用すると、ソケットの作成および構成動作をカプセル化できます。
  • セキュアなソケット・ファクトリとエンジンのファクトリとして動作するセキュアなソケット・コンテキストを表すクラス。
  • X.509固有のキー・マネージャやトラスト・マネージャなどのキーおよびトラスト・マネージャ・インタフェース、およびそれらのインタフェースを作成するために使用可能なファクトリ。
  • セキュアなHTTP URL接続用のクラス(HTTPS)。

SunJSSEプロバイダ

OracleのJava SEの実装には、SunJSSEという名前のJSSEプロバイダが含まれており、これはあらかじめインストールされ、JCAに登録されています。このプロバイダが提供する暗号化サービスは次のとおりです。

  • SSL3.0、TLS (バージョン1.0、1.1、1.2および1.3)およびDTLS (バージョン1.0および1.2)セキュリティ・プロトコルの実装。
  • 最も一般的なTLSおよびDTLS暗号群の実装。この実装は、認証、キー合意、暗号化および整合性保護の組合せを含みます。
  • 標準JCAキーストアから適切な認証キーを選択するX.509ベースのキー・マネージャの実装。
  • 証明書チェーンを検証する規則を実装する、X.509ベースのトラスト・マネージャの実装。

SunJSSEプロバイダを参照してください。

JSSE関連ドキュメント

次のリストにはオンライン・ドキュメントのリンクと、関連サブジェクトの文書名を示しています。

JSSE APIドキュメント

JSSEクラスとインタフェース

セキュアな通信を行うには、接続の両側がSSL対応であることが必要です。JSSE APIの接続のエンドポイント・クラスは、SSLSocketおよびSSLEngineです。図8-1では、SSLSocketSSLEngineの作成に使用される主なクラスを論理的な順序で並べています。

図8-1 SSLSocketとSSLEngineの作成に使用されるJSSEクラス

図8-1の説明が続きます
「図8-1 SSLSocketとSSLEngineの作成に使用されるJSSEクラス」の説明

SSLSocketSSLSocketFactoryまたはイン・バウンド接続を受け取るSSLServerSocketによって作成されます。SSLServerSocketSSLServerSocketFactoryで作成されます。SSLSocketFactoryおよびSSLServerSocketFactoryオブジェクトはどちらもSSLContextで作成されます。SSLEngineは、SSLContextによって直接作成され、アプリケーションに依存してすべての入出力を処理します。

ノート:

raw SSLSocketクラスまたはSSLEngineクラスを使用する場合は、データの送信前に必ずピアのクレデンシャルをチェックしてください。JDK 7以降は、エンドポイント識別/検証の手順は、SSL/TLSハンドシェーク中に処理できます。メソッドSSLParameters.setEndpointIdentificationAlgorithmを参照してください。

たとえば、URL内のホスト名は、ピアのクレデンシャル内のホスト名と一致する必要があります。ホスト名が検証されない場合、URL不正行為によってアプリケーションが悪用される可能性があります。

JSSEのコア・クラスとインタフェース

コアJSSEクラスは、javax.netパッケージとjavax.net.sslパッケージに含まれています。

SocketFactoryおよびServerSocketFactoryクラス

抽象クラスjavax.net.SocketFactoryは、ソケットの作成に使われます。このクラスのサブクラスは、ソケットの特定のサブクラスを作成し、パブリック・ソケット・レベルの機能を追加するための汎用フレームワークを提供するファクトリです。たとえば、SSLSocketFactory and SSLServerSocketFactoryクラスを参照してください。

javax.net.ServerSocketFactory抽象クラスはSocketFactoryクラスに似ていますが、サーバー・ソケットの作成に特化して使われます。

ソケット・ファクトリは、構築されるソケットに関する様々なポリシーを取得するための簡単な方法であり、ソケットを要求する特別なコード構成を必要としない方法でそれらのソケットを生成します。

  • ファクトリとソケットに多相性があるため、種類が異なるファクトリを渡すだけで、種類が異なるソケットに同じアプリケーション・コードを使用できます。
  • ソケット構築時に使用するパラメータを使って、ファクトリ自身をカスタマイズできます。たとえば、ファクトリをカスタマイズして、異なるネットワーク・タイムアウトのソケットや、構成済のセキュリティ・パラメータを返すことができます。
  • アプリケーションに返されるソケットはjava.net.Socket (またはjavax.net.ssl.SSLSocket)のサブクラスにすることができます。そうすれば、圧縮、セキュリティ、レコード宣言、統計情報収集、ファイアウォール・トンネリングなどの機能の新しいAPIを直接公開できます。

SSLSocketFactoryおよびSSLServerSocketFactoryクラス

javax.net.ssl.SSLSocketFactoryクラスは、セキュアなソケットを作成するファクトリとして動作します。このクラスは、javax.net.SocketFactoryの抽象サブクラスです。

セキュアなソケット・ファクトリは、セキュアなソケットの作成と初期設定の詳細情報をカプセル化します。これには、認証キー、ピア証明書の検証、使用できる暗号化方式群などが含まれます。

javax.net.ssl.SSLServerSocketFactoryクラスはSSLSocketFactoryクラスに似ていますが、サーバー・ソケットの作成に特化して使われます。

SSLSocketFactoryの取得

次の方法でSSLSocketFactoryを取得できます。

  • SSLSocketFactory.getDefault() staticメソッドを呼び出してデフォルトのファクトリを取得します。
  • APIパラメータとしてファクトリを受信する。つまり、ソケットを作成する必要があるが、そのソケットの構成方法の詳細に関与しないコードには、クライアントによって呼び出され、ソケットの作成時に使用するSSLSocketFactoryを指定できるSSLSocketFactoryパラメータを持つメソッドを含めることができます(javax.net.ssl.HttpsURLConnectionなど)。
  • 動作を指定した新規ファクトリを構築する。

通常、デフォルトのファクトリはサーバー認証だけをサポートするように構成されています。このため、デフォルトのファクトリで作成されたソケットは、一般的なTCPソケット以上にクライアントの情報を漏らすことはありません。

ソケットを作成して使用するクラスの多くは、ソケットの作成方法を詳しく知る必要はありません。パラメータとして渡されたソケット・ファクトリを介してソケットを作成するのは、ソケット構成の詳細を分離し、ソケットを作成して使用するクラスの再利用性を高めるよい方法です。

新しいソケット・ファクトリ・インスタンスを作成するには、独自のソケット・ファクトリ・サブクラスを実装するか、ソケット・ファクトリのファクトリとして動作するクラスをべつに使用します。このようなクラスの1つの例がSSLContextで、これはプロバイダ・ベースの構成クラスとしてJSSE実装に提供されます。

SSLSocketおよびSSLServerSocketクラス

javax.net.ssl.SSLSocketクラスは標準のJava java.net.Socketクラスのサブクラスです。標準的なソケット・メソッドをすべてサポートし、セキュアなソケットに固有のメソッドを追加します。このクラスのインスタンスは、その作成に使用されたSSLContextをカプセル化します。「SSLContextクラス」を参照してください。ソケット・インスタンスのセキュアなソケット・セッションの作成を管理するAPIもありますが、トラストおよびキー管理は直接公開されません。

javax.net.ssl.SSLServerSocketクラスはSSLSocketクラスに似ていますが、サーバー・ソケットの作成に特化して使われます。

ピアの不正行為を防止するには、常にSSLSocketに提示されるクレデンシャルを検証してください。符号化方式の選択とリモート・エンティティの検証を参照してください。

ノート:

SSLプロトコルとTLSプロトコルは複雑なので、接続時の受信バイトがハンドシェークのデータとアプリケーション・データのどちらなのかを予測し、現在の接続状態にどのような影響を与えるか(処理を中断させることもある)を予測するのは困難です。Oracle JSSEの実装では、SSLSocket.getInputStream()によって取得されたオブジェクトのavailable()メソッドは、SSL接続で正常に復号化されても、アプリケーションではまだ読み込まれていないデータのバイト数を返します。
SSLSocketの取得

SSLSocketのインスタンスは、次のいずれかの方法で取得できます。

  • SSLSocketは、そのクラスの複数のcreateSocketメソッドのうちの1つを使用して、SSLSocketFactoryのインスタンスによって作成できます。
  • SSLSocketSSLServerSocketクラスのacceptメソッドを使用して作成できます。
符号化方式の選択とリモート・エンティティの検証

SSL/TLSプロトコルは、保護された接続を確保するための一連の特定のステップを定義します。ただし、暗号化方式群の選択が、接続で確保するセキュリティのタイプに直接影響します。たとえば、匿名暗号化方式群を選択した場合、アプリケーションにはリモート・ピアの識別情報を検証する方法がありません。暗号化しない方式群が選択された場合は、データの機密性を保護できません。またSSL/TLSプロトコルでは、受信した資格と、ピアから送信されることが予期される資格が一致するようにとは規定していません。接続がなんらかの理由で悪意のあるピアにリダイレクトされたときに、悪意のあるピアのクレデンシャルが現在のトラスト・データに基づいて受け付けられた場合、その接続は有効とみなされてしまいます。

raw SSLSocketおよびSSLEngineクラスを使用する場合は、データの送信前に必ずピアのクレデンシャルをチェックしてください。SSLSocketおよびSSLEngineクラスは、URL内のホスト名がピアのクレデンシャル内のホスト名と一致することを自動的に検証しません。ホスト名が検証されない場合、URL不正行為によってアプリケーションが悪用される可能性があります。JDK 7以降は、エンドポイント識別/検証の手順は、SSL/TLSハンドシェーク中に処理できます。SSLParameters.getEndpointIdentificationAlgorithmメソッドを参照してください。

HTTPS (HTTP Over TLS)などのプロトコルでは、ホスト名検証が必要です。JDK 7以降、HTTPSエンドポイント識別は、HttpsURLConnectionのためのハンドシェーク中にはデフォルトで強制されます。SSLParameters.getEndpointIdentificationAlgorithmメソッドを参照してください。また、アプリケーションは、HostnameVerifierインタフェースを使用してデフォルトのHTTPSホスト名規則をオーバーライドできます。「HostnameVerifierインタフェース」および「HttpsURLConnectionクラス」を参照してください。

SSLEngineクラス

前述のように、TLS/DTLSはセキュアなネットワーク通信のための標準プロトコルであり、広範なコンピューティング・プラットフォームおよびデバイスの様々なアプリケーションで使用されています。この普及に伴い、アプリケーションのパフォーマンス、拡張性、サイズおよびその他の要件を満たすため、様々な入出力モデルやスレッド・モデルでTLS/DTLSを使用することが求められています。ブロックおよび非ブロック入出力チャネル、非同期入出力、任意の入力ストリームと出力ストリームおよびバイト・バッファでTLS/DTLSの使用が求められています。また、数千のネットワーク接続を管理することが必要な、非常に拡張性の高いパフォーマンス重視の環境で使用することも求められています。

Java SEのSSLEngineクラスを使用する入出力トランスポート・メカニズムの抽象化により、トランスポートに依存せずにTLS/DTLSプロトコルをアプリケーションで使用できるようになったため、アプリケーション開発者は最もニーズを満たすトランスポート・モデルや計算モデルを自由に選択できます。この新しい抽象化により、非ブロック入出力チャネルや他の入出力モデルをアプリケーションで使用できるだけでなく、異なるスレッド・モデルにも対応できます。事実上これは、入出力とスレッドの決定がアプリケーション開発者に委ねられることになります。こうした柔軟性のため、アプリケーション開発者は、それ自体が複雑な問題でもある入出力とスレッドを管理するとともに、TLS/DTLSプロトコルをある程度理解する必要があります。SSLEngineクラスの場合、SSL/TLS、I/Oおよびスレッド・モデルを理解している必要があるため、高度なAPIとみなされます。初心者はSSLSocketを使用することをお薦めします。

Java Generic Security Services (Java GSS-API)やJava Simple Authentication Security Layer (Java SASL)などの他のJavaプログラミング言語APIのユーザーは、アプリケーションがデータをトランスポートする役割を担うことの類似点に気付くでしょう。

コア・クラスは、javax.net.ssl.SSLEngineです。TLS/DTLS状態マシンをカプセル化し、SSLEngineクラスのユーザーによってそれぞれ格納および排出されるインバウンド・バイト・バッファおよびアウトバウンド・バイト・バッファを操作します。図8-2は、アプリケーションから、SSLEngineを経由して、トランスポート・メカニズムまで進み、戻ってくるデータのフローを示しています。

図8-2 SSLEngineを通るデータのフロー

次の文は、この図について説明しています。

SSLEngineを呼び出すと、TLS/DTLSパケットが生成および消費されます。このパケットをピアと交換する必要があります。SSLEngineデータの命名規則は、常にローカル側から見たものです。ピアにバインドされたデータはアウトバウンド・データと呼ばれ、ローカル側のピアのデータはインバウンド・データと呼ばれます。アプリケーション・バッファのアプリケーション・データを生成/使用する前に、セキュリティ・パラメータをネゴシエートするハンドシェーク手順を完了する必要があります。生成/使用されるハンドシェーク・データはSSLEngineの内部にあり、アプリケーション・データが生成/使用される前にピアと交換する必要があります。すべてのデータ転送はアプリケーションで実行されます。

左側に示されるアプリケーションは、アプリケーションのプレーンテキスト・データをアプリケーション・バッファに供給し、それをSSLEngineに渡します。ハンドシェークが完了し、暗号化パラメータがネゴシエーションされると、SSLEngineオブジェクトは、アウトバウンド・アプリケーション・データ・バッファに格納されているデータを使用してTLS/DTLSエンコード・データを生成し、アプリケーションが提供するネットワーク・バッファに格納します。アプリケーションは、トランスポート・メカニズムを使用してアウトバウンド・ネットワーク・バッファの内容をピアに送信する必要があります。トランスポートを介してピアからTLS/DTLSエンコード・データを受け取ると、アプリケーションはインバウンド・データをネットワーク・バッファに格納し、SSLEngineに渡します。SSLEngineオブジェクトはネットワーク・バッファの内容を処理して、アプリケーション・データ(または内部で使用されるハンドシェーク・データ)を生成します。

SSLEngineクラスのインスタンスは次のいずれかの状態になります。

作成
SSLEngineの作成と初期化は完了しましたが、まだ使用されてはいません。この段階では、アプリケーションにより、SSLEngine固有のあらゆる設定(暗号化方式群の有効化、SSLEngineがクライアント・モードとサーバー・モードのどちらでハンドシェークを行うかなど)を行うことができます。ハンドシェークが始まると、次のハンドシェークから新しい設定(クライアント/サーバー・モードの設定を除く)が使用されます。
初期ハンドシェーク
初期ハンドシェークとは、SSLSessionが確立されるまでの間、2つのピアが通信パラメータを交換する手続きです。この段階では、アプリケーション・データは送信できません。
アプリケーション・データ
通信パラメータが確立され、ハンドシェークが完了すると、SSLEngineからアプリケーション・データが送信されます。アウトバウンド・アプリケーション・メッセージは暗号化され、データの整合性が確保されます。インバウンド・メッセージでは、この逆の手続きが行われます。
再ハンドシェーク
アプリケーション・データ段階の間はいつでも、どちら側のピアからでも、必要に応じてセッションの再ネゴシエーションを要求できます。アプリケーション・データに新しいハンドシェーク・データを混合できます。再ハンドシェークの段階を開始する前に、アプリケーションは、TLS/DTLS通信パラメータ(例: 有効な暗号化方式群のリスト)や、クライアント認証を使用するかどうかの設定をリセットできます。しかし、クライアント・モードとサーバー・モードを切り替えることはできません。前回と同様に、ハンドシェークが始まってから次のハンドシェークまで、新しいSSLEngine構成設定は使用されません。
終了
接続が不要になったとき、アプリケーションは、SSLEngineを終了し、ピアと送受信するメッセージが残っている場合は送受信を完了してから、配下の転送メカニズムを終了する必要があります。いったん閉じられたエンジンを再利用することはできません。新しいSSLEngineを作成する必要があります。
SSLEngineメソッド

SSLEngineメソッドには、SSLEngineを初期化してハンドシェークを開始するメソッド、ネットワークへの書込みまたはネットワークからの読取りのためにデータ・パケットを処理するメソッド、およびSSLEngineと接続を適切に閉じるメソッドの3つのタイプがあります。

次のステップでは、SSLEngineのメソッドに関するハンドシェーク・プロセスについて説明します:

  1. SSLEngineを作成した後、様々なset*メソッドを呼び出して、これから発生する接続のすべての側面(setEnabledProtocols()setEnabledCipherSuites()setUseClientMode()setWantClientAuth()など)を構成します。SSLParametersクラスを使用して接続を構成することもできます。これにより、単一のメソッド呼出しで複数の設定を行うことができます。
  2. SSLEngineに対して現在は空のSSLSessionを取得し、getApplicationBufferSize()およびgetPacketBufferSize()メソッドで生成できるアプリケーションおよびネットワーク・バイトの最大バッファ・サイズを決定します。それに従って、アプリケーションおよびネットワーク・バッファにByteBufferインスタンスを割り当てます。
  3. 接続とバッファを構成したら、beginHandshake()メソッドを呼び出して、SSLEngineを初期ハンドシェーク状態にします。
  4. 接続で使用するトランスポート・メカニズム(SocketChannelクラスやSocketクラスなど)を作成します。
  5. wrap()およびunwrap()メソッドを呼び出して、初期ハンドシェークを実行します。後でwrap()/unwrap()呼出しでアプリケーション・データを使用、生成および適切に保護するには、これらのメソッドを複数回呼び出す必要があります。

    ハンドシェーク・バイトは、トランスポートメカニズムを使用してピアと交換する必要があります。TLSハンドシェーク・メカニズムの詳細は、TLS RFCのいずれか(RFC 5246: Transport Layer Security (TLS)プロトコル: バージョン1.2など)を参照してください。

    たとえば、SSLEngineがクライアントとして機能し、TLSv1.2を使用してハンドシェークを実行する場合、次のことが発生する可能性があります:

    1. wrap()メソッドがTLS ClientHelloメッセージを生成し、アウトバウンド・ネットワーク・バッファに格納します。アプリケーションは、このメッセージのバイトをピアに正しく送信する必要があります。
    2. SSLEngineは、ハンドシェークを進めるためにピアのレスポンス(ServerHello、Certificate、ServerHelloDoneメッセージなど)を処理する必要があります。アプリケーションは、ネットワーク・トランスポートからレスポンス・バイトを取得し、インバウンド・ネットワーク・バッファに格納します。SSLEngineは、unwrap()メソッドを使用してこれらのバイトを処理します。
    3. SSLEngineは、さらにハンドシェーク・データ(ChangeCipherSuiteメッセージやFinishedメッセージなど)を送信します。wrap()は、メッセージのバイトをアウトバウンド・ネットワーク・バッファに格納します。アプリケーションは、以前と同様に、これらのバイトをピアに正しく送信する必要があります。
    4. SSLEngineは、ピアのChangeCipherSuiteまたはFinishedメッセージを待ちます。このメッセージのバイトは、ステップbと同じ経路に従います。
  6. ハンドシェークが完了すると、アプリケーション・データの流れが開始します。wrap()メソッドを呼び出してアウトバウンド・アプリケーション・バッファからバイトを取り出し、暗号化して保護し、ピアにトランスポートするためにネットワーク・バッファに格納します。同様に、unwrap()メソッドを呼び出して、インバウンド・ネットワーク・データを復号化および保護解除します。結果のアプリケーション・データは、インバウンド・アプリケーション・データ・バッファに格納されます。
  7. 2つのピア間でデータが交換されたら、SSLEngineのインバウンド側とアウトバウンド側の両方を閉じます。closeOutbound()メソッドを呼び出して、アプリケーションがこれ以上データを送信しないことをSSLEngineに通知します。closeInbound()メソッドを呼び出して、ネットワーク接続が閉じられ、これ以上データがないことをSSLEngineに通知します。
SSLEngineの操作のステータスについて

エンジンのステータスとアプリケーションの取る必要があるアクションを示すため、SSLEngine.wrap()メソッドとSSLEngine.unwrap()メソッドは、例8-5で示すようなSSLEngineResultインスタンスを返します。このSSLEngineResultオブジェクトには、エンジンの全体的なステータスとハンドシェークのステータスの2つのステータス情報が格納されます。

全体的なステータスは、SSLEngineResult.Status enumによって表されます。次のステータスがあります。

OK
エラーはありませんでした。
CLOSED
操作によってSSLEngineが閉じられたか、操作はすでに閉じられていたために完了できませんでした。
BUFFER_UNDERFLOW
入力バッファの処理データが不十分なため、(たとえば、ネットワークからより多くのデータを読み取って)アプリケーションがピアからさらにデータを取得し、操作を再試行する必要があることを示しています。
BUFFER_OVERFLOW
出力バッファの結果格納用の領域が不足しているため、アプリケーションで宛先バッファをクリアまたは拡張して、操作を再試行する必要があることを示しています。

例8-1は、SSLEngine.unwrap()メソッドのBUFFER_UNDERFLOWおよびBUFFER_OVERFLOWステータスの処理方法を示しています。SSLSession.getApplicationBufferSize()およびSSLSession.getPacketBufferSize()を使用して、バイト・バッファのサイズを決定します。

ハンドシェークのステータスは、SSLEngineResult.HandshakeStatus enumによって表されます。ハンドシェークが完了したかどうか、呼出し側がピアとの間でより多くのハンドシェーク・データを送受信する必要があるかどうかなどを表します。使用可能なステータスは次のとおりです。

FINISHED
SSLEngineがハンドシェークを完了したところです。
NEED_TASK
SSLEngineがハンドシェークを続行するには、事前に1つ(または複数の)委譲されたタスクの結果が必要です。
NEED_UNWRAP
SSLEngineがハンドシェークを続行するには、事前にリモート側からデータを受信する必要があります。
NEED_UNWRAP_AGAIN
SSLEngineは、ハンドシェークを続行するにはアンラップする必要があります。この値は、まだ解釈されていないデータをリモート側から以前に受け取っており再度受け取る必要がないことを示しています。このデータはJSSEフレームワークに受信されていますが、まだ処理されていません。
NEED_WRAP
SSLEngineがハンドシェークを続行するには、事前にリモート側にデータを送信する必要があるため、SSLEngine.wrap()を呼び出すようにしてください。
NOT_HANDSHAKING
SSLEngineは現在ハンドシェークしていません。

結果ごとに2つのステータスがあることにより、SSLEngineは、アプリケーションがハンドシェークへの応答におけるアクションとwrap()およびunwrap()メソッドの全体のステータスを表すアクションという2つのアクションを取る必要があることを示すことができます。たとえばエンジンは、1回のSSLEngine.unwrap()呼出しの結果として、SSLEngineResult.Status.OKを返すことで、入力データが正常に処理されたことを示し、SSLEngineResult.HandshakeStatus.NEED_UNWRAPを返すことで、ハンドシェークを継続するためにアプリケーションがピアからさらにTLS/DTLSエンコード・データを取得し、もう一度SSLEngine.unwrap()に供給する必要があることを示します。次に示す例は大幅に簡略化されています。これらすべてのステータスの組合せを適切に処理するには、これらを大幅に拡張する必要があります。

例8-2例8-3に、wrap()メソッドおよびunwrap()メソッドのハンドシェーク・ステータスと全体のステータスをチェックすることによって、ハンドシェーク・データを処理する方法を示します。

例8-1 BUFFER_UNDERFLOWおよびBUFFER_OVERFLOWの処理のサンプル・コード

次のコード例では、BUFFER_UNDERFLOWステータスとBUFFER_OVERFLOWステータスを処理する方法を示します。

    SSLEngineResult res = engine.unwrap(peerNetData, peerAppData);
    switch (res.getStatus()) {

        case BUFFER_OVERFLOW:
            // Maybe need to enlarge the peer application data buffer if
            // it is too small, and be sure you've compacted/cleared the
            // buffer from any previous operations.
            if (engine.getSession().getApplicationBufferSize() > peerAppData.capacity()) {
                // enlarge the peer application data buffer
            } else {
                // compact or clear the buffer
            }
            // retry the operation
         break;

         case BUFFER_UNDERFLOW:
             // Not enough inbound data to process. Obtain more network data
             // and retry the operation. You may need to enlarge the peer
             // network packet buffer, and be sure you've compacted/cleared
             // the buffer from any previous operations.
             if (engine.getSession().getPacketBufferSize() > peerNetData.capacity()) {
                 // enlarge the peer network packet buffer
             } else {
                 // compact or clear the buffer
             }
             // obtain more inbound network data and then retry the operation
             break; 

         // Handle other status: CLOSED, OK
         // ...
    }

例8-2 ハンドシェーク・ステータスと全体のステータスの確認と処理のサンプル・コード

次のコード例では、wrap()メソッドとunwrap()メソッドのハンドシェーク・ステータスと全体のステータスをチェックすることによって、ハンドシェーク・データを処理する方法を示します。

void doHandshake(SocketChannel socketChannel, SSLEngine engine,
    ByteBuffer myNetData, ByteBuffer peerNetData) throws Exception {

    // Create byte buffers to use for holding application data
    int appBufferSize = engine.getSession().getApplicationBufferSize();
    ByteBuffer myAppData = ByteBuffer.allocate(appBufferSize);
    ByteBuffer peerAppData = ByteBuffer.allocate(appBufferSize);

    // Begin handshake
    engine.beginHandshake();
    SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();

    // Process handshaking message
    while (hs != SSLEngineResult.HandshakeStatus.FINISHED &&
        hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {

        switch (hs) {

        case NEED_UNWRAP:
            // Receive handshaking data from peer
            if (socketChannel.read(peerNetData) < 0) {
                // The channel has reached end-of-stream
            }

            // Process incoming handshaking data
            peerNetData.flip();
            SSLEngineResult res = engine.unwrap(peerNetData, peerAppData);
            peerNetData.compact();
            hs = res.getHandshakeStatus();

            // Check status
            switch (res.getStatus()) {
            case OK :
                // Handle OK status
                break;

               // Handle other status: BUFFER_UNDERFLOW, BUFFER_OVERFLOW, CLOSED
               // ...
            }
            break;

        case NEED_WRAP:
            // Ensure that any previous net data in myNetData has been sent
            // to the peer (not shown here), then generate more.

            // Empty/clear the local network packet buffer.        
            myNetData.clear();

            // Generate more data to send if possible.
            res = engine.wrap(myAppData, myNetData);
            hs = res.getHandshakeStatus();

            // Check status
            switch (res.getStatus()) {
            case OK :
                myNetData.flip();

                // Send the handshaking data to peer
                while (myNetData.hasRemaining()) {
                    socketChannel.write(myNetData);
                }
                break;

                // Handle other status:  BUFFER_OVERFLOW, BUFFER_UNDERFLOW, CLOSED
                // ...
            }
            break;

        case NEED_TASK :
            // Handle blocking tasks
            break;

            // Handle other status:  // FINISHED or NOT_HANDSHAKING
            // ...
        }
    }

    // Processes after handshaking
    // ...
}

例8-3 DTLSハンドシェーク・ステータスおよび全体的ステータスの処理のサンプル・コード

次のコード例では、DTLSハンドシェーク・ステータスを処理する方法を示します。

void handshake(SSLEngine engine, DatagramSocket socket,
               SocketAddress peerAddr) throws Exception {
    boolean endLoops = false;
    // private static int MAX_HANDSHAKE_LOOPS = 60;
    int loops = MAX_HANDSHAKE_LOOPS;
    engine.beginHandshake();
    while (!endLoops && (serverException == null) && (clientException == null)) {
        if (--loops < 0) {
            throw new RuntimeException("Too many loops to produce handshake packets");
        }
        SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
        if (hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ||
                hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN) {
            ByteBuffer iNet;
            ByteBuffer iApp;
            if (hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
                // receive ClientHello request and other SSL/TLS/DTLS records
                byte[] buf = new byte[1024];
                DatagramPacket packet = new DatagramPacket(buf, buf.length);
                try {
                    socket.receive(packet);
                } catch (SocketTimeoutException ste) {
                    // retransmit the packet if timeout
                    List <DatagramPacket> packets =
                        onReceiveTimeout(engine, peerAddr);
                    for (DatagramPacket p : packets) {
                        socket.send(p);
                    }
                    continue;
                }
                iNet = ByteBuffer.wrap(buf, 0, packet.getLength());
                iApp = ByteBuffer.allocate(1024);
            } else {
                iNet = ByteBuffer.allocate(0);
                iApp = ByteBuffer.allocate(1024);
            }
            SSLEngineResult r = engine.unwrap(iNet, iApp);
            SSLEngineResult.Status rs = r.getStatus();
            hs = r.getHandshakeStatus();
            if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) {
                // the client maximum fragment size config does not work?
                throw new Exception("Buffer overflow: " +
                                    "incorrect client maximum fragment size");
            } else if (rs == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
                // bad packet, or the client maximum fragment size
                // config does not work?
                if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
                    throw new Exception("Buffer underflow: " +
                                        "incorrect client maximum fragment size");
                } // otherwise, ignore this packet
            } else if (rs == SSLEngineResult.Status.CLOSED) {
                endLoops = true;
            }   // otherwise, SSLEngineResult.Status.OK:
            if (rs != SSLEngineResult.Status.OK) {
                continue;
            }
        } else if (hs == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
            List <DatagramPacket> packets =
                // Call a function to produce handshake packets
                produceHandshakePackets(engine, peerAddr);
            for (DatagramPacket p : packets) {
                socket.send(p);
            }
        } else if (hs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
            runDelegatedTasks(engine);
        } else if (hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
            // OK, time to do application data exchange.
            endLoops = true;
        } else if (hs == SSLEngineResult.HandshakeStatus.FINISHED) {
            endLoops = true;
        }
    }
    SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
    if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
        throw new Exception("Not ready for application data yet");
    }
}
TLSプロトコル用のSSLEngine

この項では、SSLEngineオブジェクトを作成して、TLSデータの生成および処理に使用する方法について説明します。

SSLEngineオブジェクトの作成

SSLContext.createSSLEngine()メソッドを使用してSSLEngineオブジェクトを作成します。

SSLEngineオブジェクトを使用する前に、クライアントまたはサーバーとして動作するようにエンジンを構成し、使用する暗号化方式群やクライアント認証が必要かどうかなど、他の構成パラメータも設定します。

例8-4 キーストアとしてJKSを使用してTLSのためのSSLEngineクライアントを作成するサンプル・コード

次のサンプル・コードでは、キーストアとしてJKSを使用するTLSのためのSSLEngineクライアントを作成します。

ノート:

この例で、サーバー名とポート番号は、サーバーとの通信には使用されません(すべてのトランスポートはアプリケーションが担当します)。それらは、TLSセッション・キャッシングのために使用するJSSEプロバイダの手掛かりとなります。
    import javax.net.ssl.*;
    import java.security.*;

    // Create and initialize the SSLContext with key material
    char[] passphrase = "passphrase".toCharArray();

    // First initialize the key and trust material
    KeyStore ksKeys = KeyStore.getInstance("JKS");
    ksKeys.load(new FileInputStream("testKeys"), passphrase);
    KeyStore ksTrust = KeyStore.getInstance("JKS");
    ksTrust.load(new FileInputStream("testTrust"), passphrase);

    // KeyManagers decide which key material to use
    KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
    kmf.init(ksKeys, passphrase);

    // TrustManagers decide whether to allow connections
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
    tmf.init(ksTrust);

    // Get an instance of SSLContext for TLS protocols
    sslContext = SSLContext.getInstance("TLS");
    sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

    // Create the engine
    SSLEngine engine = sslContext.createSSLengine(hostname, port);

    // Use as client
    engine.setUseClientMode(true);
TLSデータの生成と処理

2つの主要なSSLEngineメソッドはwrap()unwrap()です。それらは、それぞれネットワーク・データの生成と消費を担当します。SSLEngineオブジェクトの状態に応じて、このデータはハンドシェーク・データかアプリケーション・データになります。

SSLEngine.wrap()およびSSLEngine.unwrap()を使用したTLSハンドシェークの実行およびTLSデータの処理

それぞれのSSLEngineオブジェクトには、存続期間中に数種類の段階があります。アプリケーション・データを送信または受信できるようにするには、TLSプロトコルで、暗号化パラメータを確立するためのハンドシェークが必要です。このハンドシェークでは、SSLEngineオブジェクトによる一連のやり取りのステップが必要です。初期ハンドシェーク中に、wrap()およびunwrap()メソッドはハンドシェーク・データを生成および消費してから、アプリケーション・データの交換を開始します。

アプリケーションは、ピアとの間で(TCPを使用するなどして)データを確実にトランスポートします。つまり、アプリケーション(SSLEngineではない)は、wrap()メソッドによって生成されたデータをピアに確実に配信する必要があります。また、アプリケーション(SSLEngineではない)はピアからデータを確実に取得して、unwrap()メソッドを呼び出してデコードできるようにする必要があります。

それぞれのSSLEngine操作によりSSLEngineResultクラスのインスタンスが生成され、その中のSSLEngineResult.HandshakeStatusフィールドは、ハンドシェークを進めるために実行する必要のある次の動作を決定するために使用されます。

ハンドシェークの完了時に、wrap()をさらに呼び出すと、アプリケーション・データおよびパッケージを消費してトランスポートしようとします。unwrap()メソッドはその逆を試みます。

ピアにデータを送信するには、まずアプリケーションがSSLEngine.wrap()を介して送信するデータを提供し、対応するTLSエンコード・データを取得します。次にアプリケーションは、選択したトランスポート・メカニズムを使用してエンコード・データをピアに送信します。アプリケーションは、トランスポート・メカニズムを介してピアからTLSエンコード・データを受け取ると、SSLEngine.unwrap()を介してそのデータをSSLEngineに送り、ピアによって送信されたプレーン・テキスト・データを取得します。

表8-3に、一般的なTLSハンドシェーク時の状態マシンと、対応するメッセージおよびステータスを示します。

図8-3 TLSハンドシェーク中の状態マシン

図8-3の説明が続きます
「図8-3 TLSハンドシェーク中の状態マシン」の説明

TLSデータを処理するステップ

クライアントとサーバーの間で送信されるTLSデータを処理するとします。通常は、次のステップに従います:

  1. ByteBufferインスタンスを作成して、アプリケーション・データ・バッファおよびクライアントとサーバーのネットワーク・データ・バッファを表現します。クライアントおよびサーバーのアウトバウンド・アプリケーション・データ・バッファで、暗号化してネットワーク経由でサーバーおよびクライアントに送信するデータをそれぞれ指定します。

    ノート:

    wrap(ByteBuffer src, ByteBuffer dst)メソッドの場合、パラメータsrcはアプリケーション・データ・バッファで、dstはネットワーク・データ・バッファです。これに対して、unwrap(ByteBuffer src, ByteBuffer dst)メソッドの場合、パラメータsrcはネットワーク・データ・バッファで、dstはアプリケーション・データ・バッファです。wrap()unwrap()はどちらもSSLEngineResultのインスタンスを返します。このインスタンスには、ハンドシェークが完了しているかどうか、またはハンドシェークを進めるために次に必要な処理を示すSSLEngineResult.HandshakeStatusフィールドが含まれています。
  2. ループ内で、ハンドシェークが完了し、クライアントとサーバーの両方がアプリケーション・データを相互に送信するまで、クライアントとサーバーで次のようにwrap()およびunwrap()を呼び出します:

    1. クライアントおよびサーバーでwrap()を呼び出します。wrap()が返すSSLEngineResultインスタンスのSSLEngineResult.HandshakeStatusフィールドの値を確認します。

      • ハンドシェークが完了していない場合、パラメータdstには、ネットワークを介してピアに送信する必要があるハンドシェーク・データが含まれます。
      • ハンドシェークが完了した場合、dstにはSSLEngineで暗号化されたアプリケーション・データが含まれ、リモート・ピアに送信できるようになります。
    2. wrap()およびunwrap()メソッドによって返されるSSLEngineResult.HandshakeStatus値を処理するコードを追加します。詳細は、「SSLEngineの操作のステータスについて」を参照してください。

    3. wrap()メソッドによってネットワーク・データ・バッファにデータが生成された場合(ハンドシェーク・データまたは暗号化されたアプリケーション・データのいずれかを含めることが可能)、それをネットワーク経由でリモート・ピアに送信します。

      ノート:

      • ネットワーク・データ・バッファ内のデータをリモート・ピアに送信するのは、SSLEngineではなく、アプリケーションの役割です。
      • wrap()を呼び出した後、ネットワーク・データ・バッファ内のすべてのデータがピアに送信されたことを確認する必要があります。

      たとえば、例8-2は、SocketChannel.write()を呼び出してリモート・ピアにネットワーク・データを送信します。ByteBuffer.hasRemaining()を呼び出して、すべてのネットワーク・データが送信されたことを確認します。

                      while (myNetData.hasRemaining()) {
                          socketChannel.write(myNetData);
                      }
    4. リモート・ピアによってネットワーク経由で送信されたネットワーク・データを取得します。これを行うのは、SSLEngineではなく、アプリケーションの役割であることに注意してください。たとえば、例8-2は、SocketChannel.read()を呼び出してリモート・ピアからネットワーク・データを取得します。

              case NEED_UNWRAP:
                  // Receive handshaking data from peer
                  if (socketChannel.read(peerNetData) < 0) {
                      // The channel has reached end-of-stream
                  }
    5. リモート・ピアから取得したネットワーク・データを使用して、クライアントとサーバーでunwrap()を呼び出します。unwrap()が返すSSLEngineResultインスタンスのSSLEngineResult.HandshakeStatusフィールドの値を確認します:

      • ハンドシェークが完了していない場合は、srcパラメータに追加のハンドシェーク・パケットが含まれているか、またはハンドシェークを続行するためにピアからさらにパケットを取得する必要があります。
      • ハンドシェークが完了した場合は、dstにはSSLEngineによって復号化されたアプリケーション・データが含まれ、すぐにアプリケーションで処理できます。
    6. クライアントとサーバーが、unwrap()によって返されたSSLEngineResult.HandshakeStatus値を処理することを確認します。

例8-5 非ブロックSocketChannelの作成のサンプル・コード

次の例では、非ブロックSocketChannelを使用してピアと通信するSSLアプリケーションを示します。これは、例8-4で作成したSSLEngineを使用してエンコードすることにより、ピアに文字列helloを送信します。バイト・バッファの大きさを決定するために、SSLSessionからの情報を使用しています。

ノート:

この例は、非ブロックSocketChannelを組み込んだSelectorを使用することにより、堅牢性と拡張性を高めることができます。
    // Create a nonblocking socket channel
    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.configureBlocking(false);
    socketChannel.connect(new InetSocketAddress(hostname, port));

    // Complete connection
    while (!socketChannel.finishedConnect()) {
        // do something until connect completed
    }

    // Create byte buffers for holding application and encoded data

    SSLSession session = engine.getSession();
    ByteBuffer myAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
    ByteBuffer myNetData = ByteBuffer.allocate(session.getPacketBufferSize());
    ByteBuffer peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
    ByteBuffer peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());

    // Do initial handshake
    doHandshake(socketChannel, engine, myNetData, peerNetData);

    myAppData.put("hello".getBytes());
    myAppData.flip();

    while (myAppData.hasRemaining()) {
        // Generate TLS/DTLS encoded data (handshake or application data)
        SSLEngineResult res = engine.wrap(myAppData, myNetData);

        // Process status of call
        if (res.getStatus() == SSLEngineResult.Status.OK) {
            myAppData.compact();

            // Send TLS/DTLS encoded data to peer
            while(myNetData.hasRemaining()) {
                int num = socketChannel.write(myNetData);
                if (num == 0) {
                    // no bytes written; try again later
                }
            }
        }

        // Handle other status:  BUFFER_OVERFLOW, CLOSED
        ...
    }

例8-6 非ブロックSocketChannelからのデータの読取りのサンプル・コード

次のサンプル・コードでは、同じ非ブロックSocketChannelからデータを読み取り、例8-4で作成したSSLEngineを使用して、そのデータからプレーン・テキストを抽出する方法を示しています。このコードが反復されるごとに、ハンドシェーク処理が進行しているかどうかに応じて、プレーン・テキストが生成されたり、生成されなかったりします。
    // Read TLS/DTLS encoded data from peer
    int num = socketChannel.read(peerNetData);
    if (num == -1) {
        // The channel has reached end-of-stream
    } else if (num == 0) {
        // No bytes read; try again ...
    } else {
        // Process incoming data
        peerNetData.flip();
        res = engine.unwrap(peerNetData, peerAppData);

        if (res.getStatus() == SSLEngineResult.Status.OK) {
            peerNetData.compact();

        if (peerAppData.hasRemaining()) {
            // Use peerAppData
        }
    }

    // Handle other status:  BUFFER_OVERFLOW, BUFFER_UNDERFLOW, CLOSED
    ...
DTLSプロトコル用のSSLEngine

この項では、SSLEngineオブジェクトを作成して、DTLSハンドシェークの処理、DTLSデータの生成と処理、およびDTLS接続での再送信の処理に使用する方法について説明します。

DTLSのためのSSLEngineオブジェクトの作成

次の例では、DTLSのためのSSLEngineオブジェクトの作成方法を示します。

ノート:

サーバー名とポート番号は、サーバーとの通信には使用されません(すべてのトランスポートはアプリケーションが担当します)。それらは、DTLSセッション・キャッシングのためや、取得する必要があるサーバー・クレデンシャルを決定するためのKerberosベースの暗号化方式群の実装のために使用するJSSEプロバイダの手掛かりとなります。

例8-7 キーストアとしてPKCS12を使用してDTLSのためのSSLEngineクライアントを作成するサンプル・コード

次のサンプル・コードでは、キーストアとしてPKCS12を使用するDTLSのためのSSLEngineクライアントを作成します。

    import javax.net.ssl.*;
    import java.security.*;

    // Create and initialize the SSLContext with key material
    char[] passphrase = "passphrase".toCharArray();

    // First initialize the key and trust material
    KeyStore ksKeys = KeyStore.getInstance("PKCS12");
    ksKeys.load(new FileInputStream("testKeys"), passphrase);
    KeyStore ksTrust = KeyStore.getInstance("PKCS12");
    ksTrust.load(new FileInputStream("testTrust"), passphrase);

    // KeyManagers decide which key material to use
    KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
    kmf.init(ksKeys, passphrase);

    // TrustManagers decide whether to allow connections
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
    tmf.init(ksTrust);

    // Get an instance of SSLContext for DTLS protocols
    sslContext = SSLContext.getInstance("DTLS");
    sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

    // Create the engine
    SSLEngine engine = sslContext.createSSLengine(hostname, port);

    // Use engine as client
    engine.setUseClientMode(true);

例8-8 キーストアとしてPKCS12を使用してDTLSのためのSSLEngineサーバーを作成するサンプル・コード

次のサンプル・コードでは、キーストアとしてPKCS12を使用するDTLSのためのSSLEngineサーバーを作成します。
    import javax.net.ssl.*;
    import java.security.*;

    // Create and initialize the SSLContext with key material
    char[] passphrase = "passphrase".toCharArray();

    // First initialize the key and trust material
    KeyStore ksKeys = KeyStore.getInstance("PKCS12");
    ksKeys.load(new FileInputStream("testKeys"), passphrase);
    KeyStore ksTrust = KeyStore.getInstance("PKCS12");
    ksTrust.load(new FileInputStream("testTrust"), passphrase);

    // KeyManagers decide which key material to use
    KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
    kmf.init(ksKeys, passphrase);

    // TrustManagers decide whether to allow connections
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
    tmf.init(ksTrust);

    // Get an SSLContext for DTLS Protocol without authentication
    sslContext = SSLContext.getInstance("DTLS");
    sslContext.init(null, null, null);

    // Create the engine
    SSLEngine engine = sslContext.createSSLeEngine(hostname, port);

    // Use the engine as server
    engine.setUseClientMode(false);

    // Require client authentication
    engine.setNeedClientAuth(true);
DTLSデータの生成と処理

DTLSハンドシェークとTLSハンドシェークでは、データの生成と処理が同様に行われます。(「TLSデータの生成と処理」を参照。)それらは両方とも、それぞれSSLEngine.wrap()メソッドとSSLEngine.wrap()メソッドを使用してネットワーク・データを生成および使用します。

次の図では、一般的なDTLSハンドシェーク時の状態マシンと、対応するメッセージおよびステータスを示します。

図8-4 DTLSハンドシェーク中の状態マシン

図8-4の説明が続きます
「図8-4 DTLSハンドシェーク中の状態マシン」の説明

例8-9 DTLSハンドシェーク・ステータスおよび全体的ステータスの処理のサンプル・コード

この例では、DTLSハンドシェーク・ステータス(SSLEngine.getHandshakeStatusメソッドから取得)および全体的ステータス(SSLEngineResult.getStatusメソッドから取得)の処理方法を示します。

void handshake(SSLEngine engine, DatagramSocket socket, SocketAddress peerAddr) throws Exception {
    boolean endLoops = false;
    // private static int MAX_HANDSHAKE_LOOPS = 60;
    int loops = MAX_HANDSHAKE_LOOPS;
    engine.beginHandshake();
    while (!endLoops && (serverException == null) && (clientException == null)) {
        if (--loops < 0) {
            throw new RuntimeException("Too many loops to produce handshake packets");
        }
        SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
        if (hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ||
                hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP_AGAIN) {
            ByteBuffer iNet;
            ByteBuffer iApp;
            if (hs == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) {
                // Receive ClientHello request and other SSL/TLS/DTLS records
                byte[] buf = new byte[1024];
                DatagramPacket packet = new DatagramPacket(buf, buf.length);
                try {
                    socket.receive(packet);
                } catch (SocketTimeoutException ste) {
                    // Retransmit the packet if timeout
                    List <DatagramPacket> packets = onReceiveTimeout(engine, peerAddr);
                    for (DatagramPacket p : packets) {
                        socket.send(p);
                    }
                    continue;
                }
                iNet = ByteBuffer.wrap(buf, 0, packet.getLength());
                iApp = ByteBuffer.allocate(1024);
            } else {
                iNet = ByteBuffer.allocate(0);
                iApp = ByteBuffer.allocate(1024);
            }
            SSLEngineResult r = engine.unwrap(iNet, iApp);
            SSLEngineResult.Status rs = r.getStatus();
            hs = r.getHandshakeStatus();
            if (rs == SSLEngineResult.Status.BUFFER_OVERFLOW) {
                // The client maximum fragment size config does not work?
                throw new Exception("Buffer overflow: " +
                                    "incorrect client maximum fragment size");
            } else if (rs == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
                // Bad packet, or the client maximum fragment size
                // config does not work?
                if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
                    throw new Exception("Buffer underflow: " +
                                        "incorrect client maximum fragment size");
                } // Otherwise, ignore this packet
            } else if (rs == SSLEngineResult.Status.CLOSED) {
                endLoops = true;
            } // Otherwise, SSLEngineResult.Status.OK
            if (rs != SSLEngineResult.Status.OK) {
                continue;
            }
        } else if (hs == SSLEngineResult.HandshakeStatus.NEED_WRAP) {
            // Call a function to produce handshake packets
            List <DatagramPacket> packets = produceHandshakePackets(engine, peerAddr);
            for (DatagramPacket p : packets) {
                socket.send(p);
            }
        } else if (hs == SSLEngineResult.HandshakeStatus.NEED_TASK) {
            runDelegatedTasks(engine);
        } else if (hs == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
            // OK, time to do application data exchange
            endLoops = true;
        } else if (hs == SSLEngineResult.HandshakeStatus.FINISHED) {
            endLoops = true;
        }
    }
    SSLEngineResult.HandshakeStatus hs = engine.getHandshakeStatus();
    if (hs != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
        throw new Exception("Not ready for application data yet");
    }
}

TLSとDTLSのSSLEngine.wrap()メソッドの違い

DTLS用のSSLEngine.wrap()メソッドは、TLSとは次のように異なります。
  • SSLEngineのTLS実装では、SSLEngine.wrap()の出力バッファに、1つ以上のTLSレコードが含まれます(TLSv1 BEAST暗号ブロック連鎖の脆弱性が原因)。

  • SSLEngineのDTLS実装では、すべてのDTLSレコードをマーシャリングしデータグラム層に個別に配信できるようにSSLEngine.wrap()の出力バッファに最大で1つのレコードが含まれます。

ノート:

SSLEngine.wrap()によって生成された各レコードは、SSLParameters.getMaximumPacketSize()で指定された最大パケット・サイズ制限に従っている必要があります。
 
DTLS接続での再送信の処理

信頼できる接続でのSSL/TLSでは、データは正しい順序で到着することが保証されており、再送信は不要です。ただし、DTLSの場合は、信頼できないメディアで機能することが多く、不足または遅延しているハンドシェーク・メッセージを再送信する必要があります。

SSLEngineクラスはトランスポートにまったく依存しない方法で動作し、アプリケーション層がすべての入出力を実行します。SSLEngineクラスは入出力を担当しないため、かわりにアプリケーションが、タイマーの提供、および再送信が必要なときのSSLEngineへの通知を担当します。アプリケーション層は、正しいタイムアウト値、およびタイムアウト・イベントをトリガーする時期を決定する必要があります。ハンドシェーク中にSSLEngineオブジェクトがHandshakeStatus.NEED_UNWRAP状態の場合、SSLEngine.wrap()の呼出しは、前のパケットが失われ、再送信する必要があることを意味します。そのような場合、SSLEngineクラスのDTLS実装は、必要に応じて前の必要なハンドシェーク・メッセージをラップしなおす役割を担います。

ノート:

DTLSエンジンでは、ハンドシェーク・メッセージのみを正しく交換する必要があります。アプリケーション・データは、タイマーを必要とせずにパケット損失に対応できます。
アプリケーションでの再送信の処理

SSLEngine.unwrap()およびSSLEngine.wrap()は、アプリケーションで再送信を処理するために一緒に使用できます。

図8-5では、DTLSハンドシェークの再送信を処理する一般的なシナリオを示します。

図8-5 DTLSハンドシェークの再送信の状態のフロー

この図は、DTLSハンドシェークの再送信の状態のフローを示しています。このフローは、この図に続く番号付きのステップで説明されています。
  1. DTLS SSLEngineのインスタンスを作成し初期化します。
    SSLEngineオブジェクトの作成を参照してください。DTLSハンドシェークの処理が開始します。
  2. ハンドシェークのステータスがHandshakeStatus.NEED_UNWRAPである場合は、ネットワークからのデータを待機します。
  3. タイマーがタイムアウトした場合、これは、前に配信されたハンドシェーク・メッセージが失われた可能性があることを示します。

    ノート:

    DTLSハンドシェークの再送信では、SSLEngine.wrap()の呼出しに関する判定されるハンドシェーク・ステータスは、必ずしもHandshakeStatus.NEED_WRAPではありません。
  4. SSLEngine.wrap()を呼び出します。
  5. ラップされたパケットが配信されます。
アプリケーションでのバッファ済ハンドシェーク・メッセージの処理

データグラム・トランスポートでは、信頼できる順次のデータ配信は必要なく、提供されません。ハンドシェーク・メッセージは、失われることや、並替えが必要なことがあります。DTLS実装では、前のすべてのメッセージを受け取る前に、今後の処理のためにハンドシェーク・メッセージをバッファする必要があることがあります。

SSLEngineのDTLS実装は、ハンドシェーク・メッセージを並べ替える役割を担います。ハンドシェーク・メッセージのバッファリングと並替えは、アプリケーションに対して透過的です。

ただし、アプリケーションは、HandshakeStatus.NEED_UNWRAP_AGAINステータスを管理する必要があります。このステータスは、次のSSLEngine.unwrap()操作にはリモート側からのその他のデータが必要ないことを示します。

図8-6では、HandshakeStatus.NEED_UNWRAP_AGAINの使用の一般的なシナリオを示します。

図8-6 NEED_UNWRAP_AGAINでのDTLSバッファ済ハンドシェークの状態マシン

このフローチャートは、DTLSバッファ済ハンドシェークで交換される一連のメッセージを示しています。特定の状況下でだけ送信されるメッセージには「optional」と記されています。シーケンスは、この図に続く番号付きのリストで説明されています。
  1. DTLS SSLEngineのインスタンスを作成し初期化します。
    SSLEngineオブジェクトの作成を参照してください。
  2. オプション: ハンドシェークのステータスがHandshakeStatus.NEED_UNWRAPである場合は、ネットワークからのデータを待機します。
  3. オプション: ネットワーク・データを受け取った場合は、SSLEngine.unwrap()を呼び出します。
  4. 次の処理のハンドシェーク・ステータスを判定します。ハンドシェーク・ステータスは、HandshakeStatus.NEED_UNWRAP_AGAINHandshakeStatus.NEED_UNWRAPまたはHandshakeStatus.NEED_WRAPである可能性があります。
    • ハンドシェーク・ステータスがHandshakeStatus.NEED_UNWRAP_AGAINである場合は、SSLEngine.unwrap()を呼び出します。

    ノート:

    HandshakeStatus.NEED_UNWRAP_AGAINステータスの場合、SSLEngine.unwrap()操作のためにネットワークからのその他のデータは必要ありません。
  5. さらに次の処理のハンドシェーク・ステータスを判定します。ハンドシェーク・ステータスは、HandshakeStatus.NEED_UNWRAP_AGAINHandshakeStatus.NEED_UNWRAPまたはHandshakeStatus.NEED_WRAPである可能性があります。
ブロック・タスクの処理

ハンドシェーク時に、SSLEngineはブロックしたり処理に長い時間がかかったりするタスクに直面することがあります。たとえば、TrustManagerは、リモート証明書検証サービスに接続する必要があることがあります。またはKeyManagerは、クライアント認証の一環として使用する証明書を決定するようにユーザーに求める必要があることがあります。SSLEngineの非ブロック性を保持するため、エンジンはそれらのタスクに直面した場合にSSLEngineResult.HandshakeStatus.NEED_TASKを返します。このステータスを受け取ると、アプリケーションはSSLEngine.getDelegatedTask()を呼び出してタスクを取得し、その要件に適したスレッド・モデルを使用してタスクを処理すべきです。アプリケーションは、たとえばスレッド・プールからスレッドを入手してタスクを処理し、メイン・スレッドは他の入出力を処理できます。

次のコードは、新しく作成されたスレッドで各タスクを実行します。

if (res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) {
    Runnable task;
    while ((task = engine.getDelegatedTask()) != null) {
        new Thread(task).start();
    }
}

SSLEngineは、未処理のタスクすべてが完了するまで、将来のwrap()およびunwrap()呼出しをブロックします。

TLS/DTLS接続の停止

TLS/DTLS接続を正しい順序で停止するため、TLS/DTLSプロトコルではクローズ・メッセージを送信する必要があります。したがって、アプリケーションがTLS/DTLS接続を終了するときは、最初にSSLEngineからクローズ・メッセージを取得し、そのトランスポート・メカニズムを使用してピアに送信して、最後にトランスポート・メカニズムを停止する必要があります。例8-10に、これを示します。

SSLEngineを明示的に閉じるアプリケーションの他に、SSLEngineは、ピアによって(ハンドシェーク・データを処理している間のクローズ・メッセージの受取りによる)、またはアプリケーション・データやハンドシェーク・データを処理している間に、SSLExceptionをスローすることによって示される、エラーが発生しているSSLEngineによって閉じられることがあります。そのような場合、アプリケーションはSSLEngine.wrap()を呼び出してクローズメッセージを取得し、SSLEngine.isOutboundDone()trueを返すまで(例8-10に示すように)、またはSSLEngineResult.getStatus()CLOSEDを返すまで、それをピアに送信する必要があります。

正常なシャットダウンに加え、クローズメッセージが交換される前にトランスポート・リンクを切断する非常シャットダウンもあります。前の例では、アプリケーションは非ブロックSocketChannelからの読取りを実行しようとすると-1またはIOExceptionを受け取り、非ブロックSocketChannelへの書込みを実行しようとするとIOExceptionを受け取ります。入力データの最後に達したら、engine.closeInbound()を呼び出して、SSLEngineによって、リモートのピアがTLS/DTLSの観点から正常に閉じたことを検証する必要があります。次に、アプリケーションが例8-10の手順を使用して正常な停止を試みる必要があります。SSLSocketと異なり、SSLEngineを使用するアプリケーションは、多くの状態移行、ステータスおよびプログラミングを処理する必要があります。SSLEngineの使用を表すサンプル・コードを参照してください。

例8-10 SSL/TLS/DTLS接続の停止のサンプル・コード

次のコード例では、TLS/DTLS接続の停止方法を示します。

// Indicate that application is done with engine
engine.closeOutbound();

while (!engine.isOutboundDone()) {
    // Get close message
    SSLEngineResult res = engine.wrap(empty, myNetData);

    // Check res statuses

    // Send close message to peer
    while(myNetData.hasRemaining()) {
        int num = socketChannel.write(myNetData);
        if (num == 0) {
            // no bytes written; try again later
        }
        myNetData().compact();
    }
}

// Close transport
socketChannel.close();

SSLSessionとExtendedSSLSession

javax.net.ssl.SSLSessionインタフェースは、SSLSocketまたはSSLEngine接続の2つのピアの間でネゴシエーションされたセキュリティ・コンテキストを表します。一度確立されたセッションは、同じ2つのピアの間で接続されるそのあとのSSLSocketまたはSSLEngineオブジェクトによっても共有できます。

場合によっては、ハンドシェーク中に取り決めたパラメータが、後のハンドシェークでトラストについて判断を下す際に必要になることもあります。たとえば、有効な署名アルゴリズムのリストによって、認証に使用できる証明書タイプが制限されることがあります。SSLSessionはハンドシェークに、SSLSocketまたはSSLEnginegetHandshakeSession()を呼び出すことによって取得できます。TrustManagerまたはKeyManagerの実装により、getHandshakeSession()メソッドを使用して、それらが判断を下す際に役立つセッション・パラメータに関する情報を取得できます。

完全に初期化されたSSLSessionには暗号化方式群が含まれ、これは、リモート・ピアのネットワーク・アドレスに関する権限のないヒントと同様、セキュアなソケットの通信でも使用され、作成や最後の使用の時点などで、管理情報としても使用されます。セッションには、SSLSocketまたはSSLEngine接続による通信を暗号化して整合性を保証する暗号キーを作成するために使用される、ピア間でネゴシエーションされた共用マスターとなる秘密も含まれます。このマスターとなる秘密の値は、基盤となるセキュアなソケット実装のみに伝えられ、SSLSession APIによって公開されません。

ExtendedSSLSessionは、追加のセッション属性をサポートするようにSSLSessionインタフェースを拡張します。ExtendedSSLSessionクラスは、ローカル実装およびピアによってサポートされる署名アルゴリズムを記述するメソッドを追加します。ExtendedSSLSessionインスタンスで呼び出されるgetRequestedServerNames()メソッドは、要求されたServer Name Indication (SNI)拡張でSNIServerNameオブジェクトのリストを取得するために使用されます。サーバーは、要求されたサーバー名を使用して、適切な認証証明書の選択やセキュリティ・ポリシーのその他の側面をガイドする必要があります。クライアントは、要求されたサーバー名を使用して、ピアの識別情報のエンドポイントの識別やセキュリティ・ポリシーのその他の側面をガイドする必要があります。

SSLSessionでのgetPacketBufferSize()およびgetApplicationBufferSize()メソッドの呼出しは、SSLEngineによって使用される適切なバッファ・サイズを決定するために使用します。

ノート:

TLSプロトコルは、実装が最大16Kバイト(KB)のプレーン・テキストを含むパケットを生成することを指定します。ただし、一部の実装はこの指定に違反し、32Kバイトまでの大きいレコードを生成します。SSLEngine.unwrap()コードが大きいインバウンド・パケットを検出した場合、SSLSessionから返されるバッファ・サイズは動的に更新されます。アプリケーションは常に、必要に応じてBUFFER_OVERFLOWおよびBUFFER_UNDERFLOWのステータスをチェックして、対応するバッファを拡大するべきです。SSLEngineの操作のステータスについてを参照してください。SunJSSE初値に標準に準拠した16Kバイトのレコードを送信し、32Kバイトの着信レコードを許可します。回避策については、「JSSEのカスタマイズ」のシステム・プロパティjsse.SSLEngine.acceptLargeFragmentsを参照してください。

HttpsURLConnectionクラス

javax.net.ssl.HttpsURLConnectionjava.net.HttpURLConnectionクラスを拡張し、HTTPS固有の機能のサポートを追加します。

HTTPSプロトコルはHTTPプロトコルに似ていますが、データを要求または受信する前に、まずTLSソケットを利用してセキュアなチャネルを確立してピアの識別情報を検証します(「符号化方式の選択とリモート・エンティティの検証」を参照)。javax.net.ssl.HttpsURLConnectionjava.net.HttpURLConnectionクラスを拡張し、HTTPS固有の機能のサポートを追加します。HTTPS URLの構築方法と使用方法の詳細は、java.net.URLjava.net.URLConnectionjava.net.HttpURLConnectionおよびjavax.net.ssl.HttpsURLConnectionクラスを参照してください。

HttpsURLConnectionインスタンスを取得する際、URLConnection.connect()メソッドを使用して実際にネットワーク接続を開始する前に、複数のHTTPおよびHTTPSパラメータを構成できます。これらについては、次を参照してください。

割当て済のSSLSocketFactoryの設定

状況によっては、HttpsURLConnectionのインスタンスによって使用されるSSLSocketFactoryを指定した方がよい場合があります。たとえば、デフォルト実装ではサポートされないプロキシ・タイプを使用してトンネリングを行いたいと考える場合があります。新しいSSLSocketFactoryは、すでに必要なトンネリングの完了したソケットを返すことができます。このため、HttpsURLConnectionは追加のプロキシを使用できます。

HttpsURLConnectionクラスには、ロード時に割り当てられたデフォルトのSSLSocketFactoryがあります(これはSSLSocketFactory.getDefault()メソッドによって返されるファクトリです)。以降、HttpsURLConnectionのインスタンスは、staticメソッドHttpsURLConnection.setDefaultSSLSocketFactoryによってクラスに新しいデフォルトのSSLSocketFactoryが割り当てられるまで、現在のデフォルトのSSLSocketFactoryを継承します。HttpsURLConnectionのインスタンスが作成された後、setSSLSocketFactory()メソッドへの呼出しにより、このインスタンス上の継承されたSSLSocketFactoryをオーバーライドできます。

ノート:

デフォルトのstatic SSLSocketFactoryの変更はHttpsURLConnectionの既存のインスタンスに影響しません。既存のインスタンスを変更するには、setSSLSocketFactory()メソッドの呼出しが必要です。

getSSLSocketFactory()メソッドまたはgetDefaultSSLSocketFactory()メソッドを呼び出すことにより、インスタンスごと、またはクラスごとにSSLSocketFactoryを取得できます。

割当て済のHostnameVerifierの設定

URLのホスト名がTLSハンドシェークの一部として受け取ったクレデンシャル内のホスト名と一致しない場合、URL不正行為が発生した可能性があります。実装で、十分な確信を持ってホスト名の一致を判断できない場合、TLS実装で、インスタンスの割当て済HostnameVerifierのコールバックを実行して、より詳しいチェックを行います。ホスト名ベリファイアは、ホスト名パターン・マッチングを実行したり、対話型のダイアログ・ボックスを表示したりなど、判定を下すために必要なあらゆるステップを取ることができます。ホスト名ベリファイアによる検証に失敗した場合は、接続が切断されます。ホスト名の検証に関する詳細については、RFC 2818: HTTP over TLSを参照してください。

setHostnameVerifier()メソッドおよびsetDefaultHostnameVerifier()メソッドの動作は、インスタンスごと、またはクラスごとにHostnameVerifierオブジェクトが割り当てられ、現在の値がgetHostnameVerifier()メソッドまたはgetDefaultHostnameVerifier()メソッドの呼出しによって取得できる点で、setSSLSocketFactory()メソッドおよびsetDefaultSSLSocketFactory()メソッドと似ています。

サポート・クラスとインタフェース

このセクションのクラスとインタフェースは、SSLSocketFactorySSLServerSocketFactoryおよびSSLEngineオブジェクトを作成するために使用されるSSLContextオブジェクトの作成と初期化をサポートするために提供されます。サポート・クラスとインタフェースはjavax.net.sslパッケージに含まれています。

この項で説明する3つのクラス(SSLContextクラスKeyManagerFactoryクラスおよびTrustManagerFactoryクラス)はエンジン・クラスです。エンジン・クラスとは、特定のアルゴリズムのAPIクラス(SSLContextの場合はプロトコル)です。その実装では1つまたは複数の暗号化サービス・プロバイダ(プロバイダ)パッケージで提供されることがあります。JCAの設計の原則およびエンジン・クラスとアルゴリズムを参照してください。

JSSEに標準で付属するSunJSSEプロバイダは、SSLContextKeyManagerFactoryおよびTrustManagerFactory実装を提供し、標準のjava.security APIではエンジン・クラスの実装も提供します。表8-1では、SunJSSEによって提供される実装をリストします。

表8-1 SunJSSEによって提供される実装

実装されるエンジン・クラス アルゴリズムまたはプロトコル
KeyStore PKCS12
KeyManagerFactory PKIX、SunX509
TrustManagerFactory PKIX (X509またはSunPKIX)、SunX509
SSLContext SSLv3<a id="fn_1" name="fn_1" href="#fn_1" onclick='footdisplay(1, "Starting with JDK 8u31, the SSLv3 protocol (Secure Socket Layer) has been deactivated and is not available by default. See the java.security.Security property jdk.tls.disabledAlgorithms in the 脚注1、TLSv1、TLSv1.1、TLSv1.2、TLSv1.3、DTLSv1.0、DTLSv1.2

脚注1 JDK 8u31リリース以降、SSLv3プロトコル(Secure Socket Layer)は無効になっており、デフォルトでは使用できません。<java_home>/conf/security/java.securityファイルのjava.security.Securityプロパティjdk.tls.disabledAlgorithmsを参照してください。SSLv3が絶対に必要な場合は、java.securityファイルのjdk.tls.disabledAlgorithmsプロパティからSSLv3を削除するか、JSSEが初期化される前にこのセキュリティ・プロパティを動的に設定すれば、プロトコルを再度アクティブにできます。

SSLContextクラス

javax.net.ssl.SSLContextクラスは、セキュアなソケット・プロトコルの実装のエンジン・クラスです。このクラスのインスタンスは、SSLSocketSSLServerSocketおよびSSLEngineのファクトリの役目を果たします。SSLContextオブジェクトは、そのコンテキストの下で作成されたすべてのオブジェクトで共有される状態情報をすべて保持します。たとえば、セッションの状態は、ソケット・ファクトリにより作成され、コンテキストにより提供されたソケットによってハンドシェーク・プロトコルが取り決められると、SSLContextと関連付けられます。キャッシュに書き込まれたこれらのセッションは、同じコンテキストで作成された別のソケットで再利用したり共有することができます。

各インスタンスは、認証の実行に必要なキー、証明書チェーン、および信頼されたルートCA証明書を使ってinitメソッドで構成されます。この構成は、キーとトラスト・マネージャの形で提供されます。これらのマネージャは認証をサポートし、コンテキストによってサポートされる暗号群のキー合意を提供します。

現在は、X.509ベースのマネージャだけがサポートされています。

SSLContextクラスの取得と初期化

SSLContextクラスは、SSLSocketFactoryクラスまたはSSLServerSocketFactoryクラスを作成するために使用されます。

SSLContextを取得して初期化するには、次の2つの方法があります。

  • 最も簡単な方法は、SSLSocketFactoryまたはSSLServerSocketFactoryクラスでSSLContext.getDefault staticメソッドを呼び出すことです。このメソッドは、デフォルトのKeyManagerTrustManagerおよびSecureRandom (セキュアな乱数ジェネレータ)を使用してデフォルトのSSLContextを作成します。デフォルトのKeyManagerFactoryおよびTrustManagerFactoryを使用すると、KeyManagerおよびTrustManagerがそれぞれ作成されます。使用するキー・データは、「デフォルトのキーストアとトラストストア、ストア・タイプ、およびストア・パスワードのカスタマイズ」で説明するシステム・プロパティで指定されるデフォルトのキーストアおよびトラストストアにあります。
  • 作成したコンテキストの動作を呼出し側で最も厳密に制御できるようにする方法は、SSLContextクラスでstaticメソッドSSLContext.getDefaultを呼び出してから、そのインスタンスの適切なinit()メソッドを呼び出してコンテキストを初期化することです。init()メソッドの1つのバリアントは、KeyManagerオブジェクトの配列、TrustManagerオブジェクトの配列、およびSecureRandomオブジェクトの3つの引数を取ります。適切なインタフェースを実装するか、実装を生成するKeyManagerFactoryクラスとTrustManagerFactoryクラスを使用して、KeyManagerオブジェクトとTrustManagerオブジェクトが作成されます。その後、KeyManagerFactoryTrustManagerFactoryを、TrustManagerFactoryまたはKeyManagerFactoryクラスのinit()メソッドへの引数として渡されたKeyStoreに含まれるキー・データでそれぞれ初期化できます。最後に、TrustManagerFactorygetTrustManagers()メソッドとKeyManagerFactorygetKeyManagers()メソッドを呼び出して、トラスト・データまたはキー・データの型ごとに1つずつトラスト・マネージャまたはキー・マネージャの配列を取得できます。

TLS接続が確立されると、SSLSessionが作成され、これには設定した識別情報、使用する暗号化方式群などの様々な情報が含まれます。次にSSLSessionを使用して、2つのエンティティ間の進行中の関係と状態情報が記述されます。各TLS接続には、一度に1つのセッションが含まれますが、そのセッションがエンティティ間の接続に、同時に、または連続して何度も使用されることがあります。

SSLContextオブジェクトの作成

他のJCAプロバイダ・ベースのエンジン・クラスと同様に、SSLContextオブジェクトは、SSLContextクラスのgetInstance()ファクトリ・メソッドを使用して作成されます。このようなstaticメソッドは、最低限要求されたセキュアなソケット・プロトコルを実装するインスタンスを返します。返されるインスタンスはその他のプロトコルも実装できます。たとえば、getInstance("TLSv1")はTLSv1、TLSv1.1およびTLSv1.2を実装するインスタンスを返すことができます。getSupportedProtocols()メソッドは、このコンテキストからSSLSocketSSLServerSocketまたはSSLEngineが作成されたときに、サポート対象のプロトコルのリストを返します。実際のSSL接続でどのプロトコルを有効にするかは、setEnabledProtocols(String[] protocols)メソッドを使用して制御できます。

ノート:

SSLSocketFactory.getDefault()メソッドを呼び出すと、SSLContextオブジェクトが自動的に作成され、初期化され、SSLSocketFactoryクラスに静的に割り当てられます。したがって、デフォルト動作をオーバーライドする場合を除き、SSLContextオブジェクトを直接作成したり初期化したりする必要はありません。

getInstance()ファクトリ・メソッドを呼び出してSSLContextオブジェクトを作成するには、プロトコル名を指定する必要があります。または、要求されたプロトコルの実装を提供するプロバイダを次のように指定することもできます。

  • public static SSLContext getInstance(String protocol);
  • public static SSLContext getInstance(String protocol, String provider);
  • public static SSLContext getInstance(String protocol, Provider provider);

プロトコル名のみを指定すると、システムは、要求されたプロトコルの実装がその環境で利用できるかどうかを判断します。複数の実装がある場合、より望ましいものがあるかどうかを判断します。

プロトコル名とプロバイダの両方を指定すると、システムは、要求されたプロトコルの実装が要求されたプロバイダにあるかどうかを判断します。実装がない場合は、例外がスローされます。

プロトコルは、希望するセキュアなソケット・プロトコルを記述する文字列(TLSなど)です。SSLContextオブジェクトの一般的なプロトコル名は、Javaセキュリティ標準アルゴリズム名で定義されています。

SSLContextは次のように取得できます。

SSLContext sc = SSLContext.getInstance("TLS");

新しく作成されたSSLContextは、initメソッドを呼び出すことによって初期化すべきです。

public void init(KeyManager[] km, TrustManager[] tm, SecureRandom random);

KeyManager[]パラメータがnullの場合、このコンテキストには空のKeyManagerが定義されます。TrustManager[]パラメータがnullの場合、インストールされたセキュリティ・プロバイダは、TrustManagerFactoryクラスの最も優先度の高い実装で検索され(TrustManagerFactoryクラスを参照)、そこから適切なTrustManagerが取得されます。同様に、SecureRandomパラメータもNULLにできます。その場合、デフォルト実装が使用されます。

内部のデフォルト・コンテキストが使用される場合(SSLContextSSLSocketFactory.getDefault()またはSSLServerSocketFactory.getDefault()によって作成されるなど)、デフォルトのKeyManagerとTrustManagerが作成されます。また、デフォルトのSecureRandom実装も選択されます。

TrustManagerインタフェース

TrustManagerは、提示された認証クレデンシャルの信頼性を判定します。信頼できないクレデンシャルの場合、接続は切断されます。セキュアなソケット・ピアのリモート識別情報を認証するには、1つまたは複数のTrustManagerオブジェクトでSSLContextオブジェクトを初期化する必要があります。サポートされる認証メカニズムのそれぞれに対し、TrustManagerを1つずつ渡す必要があります。SSLContextの初期化にnullが渡されると、自動的にトラスト・マネージャが作成されます。通常、単一のトラスト・マネージャは、X.509公開キー証明書(X509TrustManagerなど)に基づく認証をサポートしています。セキュアなソケット実装には、共有の秘密キー、Kerberos、またはほかのメカニズムに基づく認証をサポートするものもあります。

TrustManagerオブジェクトはTrustManagerFactoryによって、またはインタフェースの具体的な実装を提供することによって作成されます。

TrustManagerFactoryクラス

javax.net.ssl.TrustManagerFactoryはプロバイダ・ベースのサービスのエンジン・クラスで、1つまたは複数の型のTrustManagerオブジェクトのファクトリとして動作します。これはプロバイダ・ベースなので、追加のファクトリを実装して構成し、より高度なサービスを提供したり、インストール固有の認証ポリシーを実装する追加または代替のトラスト・マネージャを提供できます。

TrustManagerFactoryの作成

このクラスのインスタンスはSSLContextと同様の方法で作成しますが、getInstanceメソッドにプロトコル名ではなくアルゴリズム名の文字列を渡す点が異なります。

TrustManagerFactory tmf = TrustManagerFactory.getInstance(String algorithm);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(String algorithm, String provider);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(String algorithm, Provider provider);

呼出しの例を次に示します。

TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX", "SunJSSE");

前の呼出しは、SunJSSEプロバイダのPKIXトラスト・マネージャ・ファクトリのインスタンスを作成します。このファクトリを使用して、X.509 PKIXベースの証明書パス妥当性検査を実行するトラスト・マネージャを作成できます。

SSLContextを初期化する場合は、トラスト・マネージャ・ファクトリから作成したトラスト・マネージャを使用するか、CertPath APIなどを使用して独自のトラスト・マネージャを記述できます。Java PKIプログラマーズ・ガイドを参照してください。X509TrustManagerインタフェースを使用してトラスト・マネージャを実装する場合は、トラスト・マネージャ・ファクトリを使用する必要はありません。

新しく作成されたファクトリは、init()メソッドの1つを呼び出すことによって初期化してください。

public void init(KeyStore ks);
public void init(ManagerFactoryParameters spec);

使用するTrustManagerFactoryに適したinit()メソッドを呼び出します。不明な場合は、プロバイダのベンダーに問い合わせてください。

SunX509 TrustManagerFactoryなど、SunJSSEプロバイダが提供するファクトリは多数ありますが、TrustManagerFactoryを初期化するために必要な情報はKeyStoreのみであるため、最初にinitメソッドを呼び出すのが適切です。TrustManagerFactoryKeyStoreに、認証チェック中に信頼すべきリモート証明書の情報を問い合わせます。

プロバイダでは、KeyStore以外の初期化パラメータを必要とすることがあります。そのプロバイダのユーザーは、プロバイダによる定義に従って、適切なManagerFactoryParametersの実装を渡すことが期待されます。そのあと、プロバイダはManagerFactoryParameters実装の特定のメソッドを呼び出し、必要な情報を取得できます。

たとえば、TrustManagerFactoryプロバイダが、そのプロバイダを使用するアプリケーションからの初期化パラメータB、RおよびSを必要としているとします。KeyStore以外の初期化パラメータを必要とするすべてのプロバイダと同様に、プロバイダはアプリケーションがManagerFactoryParametersの特定のサブインタフェースを実装するクラスのインスタンスを提供することを必要とします。たとえば、プロバイダは呼出し側のアプリケーションがMyTrustManagerFactoryParamsのインスタンスを実装して作成し、2つ目のinit()メソッドに渡すことを必要としているとします。次の例に、MyTrustManagerFactoryParamsがどのように見えるかを示します。

public interface MyTrustManagerFactoryParams extends ManagerFactoryParameters {
  public boolean getBValue();
  public float getRValue();
  public String getSValue();
}

一部のトラスト・マネージャでは、KeyStoreオブジェクトやその他のパラメータで明示的に初期化されなくても、信頼性を判定できます。たとえば、LDAP経由でローカル・ディレクトリ・サービスのトラスト・データにアクセスしたり、オンラインの証明書ステータスチェックサーバーをリモートで使用したり、または標準のローカル位置からデフォルトのトラスト・データにアクセスすることもできます。

TrustManagerFactoryオブジェクトの証明書の失効日の確認

メソッドfilterTrustAnchorsは、信頼できるアンカーのセットをフィルタし、指定した日付までに証明書が失効するものを削除してから、このセットでTrustManagerFactoryオブジェクトを初期化します。

    public static void filterTrustAnchors (
        String truststore, String password, String validityDate)
        throws FileNotFoundException, KeyStoreException, IOException,
            ParseException, NoSuchAlgorithmException,
            InvalidAlgorithmParameterException, CertificateException,
            KeyManagementException {
            
        FileInputStream is = new FileInputStream(truststore);
        KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
        keystore.load(is, password.toCharArray());
        PKIXParameters params = new PKIXParameters(keystore);
        
        // Obtain CA root certificates        
        Set<TrustAnchor> myTrustAnchors = params.getTrustAnchors();
        
        // Create new set of CA certificates that are still valid for
        // specified date        
        Set<TrustAnchor> validTrustAnchors =
            myTrustAnchors.stream().filter(
                ta -> {
                    try {
                        ta.getTrustedCert().checkValidity(
                            DateFormat.getDateInstance().parse(validityDate));
                    } catch (CertificateException | ParseException e) {
                        return false;
                    }
                    return true; }).collect(Collectors.toSet());
             
        // Create PKIXBuilderParameters parameters               
        PKIXBuilderParameters pkixParams =
            new PKIXBuilderParameters(validTrustAnchors, new X509CertSelector());
    
        // Wrap PKIX parameters as trust manager parameters
        ManagerFactoryParameters trustParams =
            new CertPathTrustManagerParameters(pkixParams);
            
        // Create TrustManagerFactory for PKIX-compliant trust managers
        TrustManagerFactory factory = TrustManagerFactory.getInstance("PKIX");

        // Pass parameters to factory to be passed to CertPath implementation
        factory.init(trustParams);

        // Use factory
        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(null, factory.getTrustManagers(), null);              
    }
PKIX TrustManagerのサポート

デフォルトのトラスト・マネージャのアルゴリズムは「PKIX」です。これは、java.securityファイルのssl.TrustManagerFactory.algorithmプロパティを編集することによって変更できます。

PKIXトラスト・マネージャ・ファクトリでは、インストールされたセキュリティ・プロバイダのCertPath PKIX実装(「PKIプログラマーズ・ガイドの概要」を参照)を使用します。トラスト・マネージャ・ファクトリを初期化するには、通常のinit(KeyStores)メソッドを使用するか、CertPathTrustManagerParametersクラスを使用して、CertPathパラメータをPKIXトラスト・マネージャに渡します。

例8-11では、トラスト・マネージャを取得して特定のLDAP証明書ストアを使用する方法と、失効チェックを有効にする方法を示します。

TrustManagerFactory.init(KeyStore)メソッドが使用される場合は、失効チェックが無効にされる点を除いて、デフォルトのPKIXパラメータが使用されます。有効にするには、com.sun.net.ssl.checkRevocationシステム・プロパティをtrueに設定します。この設定では、CertPath実装自身が失効情報を見つけられる必要があります。プロバイダのPKIX実装では多くの場合にこの動作を実行できますが、システム・プロパティcom.sun.security.enableCRLDPtrueに設定する必要があります。TrustManagerFactory.init(ManagerFactoryParameters)メソッドではデフォルトで失効チェックが有効になっているということに注意してください。

PKIXクラスおよびCertPathクラスを参照してください。

例8-11 LDAP証明書を使用して失効チェックを有効にするサンプル・コード

次の例に、トラスト・マネージャを取得して特定のLDAP証明書ストアを使用する方法と、失効チェックを有効にする方法を示します。


    import javax.net.ssl.*;
    import java.security.cert.*;
    import java.security.KeyStore;
    import java.io.FileInputStream;
    ...
    
    // Obtain Keystore password
    char[] pass = System.console().readPassword("Password: ");

    // Create PKIX parameters
    KeyStore anchors = KeyStore.getInstance("JKS");
    anchors.load(new FileInputStream(anchorsFile, pass));
    PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(anchors, new X509CertSelector());
    
    // Specify LDAP certificate store to use
    LDAPCertStoreParameters lcsp = new LDAPCertStoreParameters("ldap.imc.org", 389);
    pkixParams.addCertStore(CertStore.getInstance("LDAP", lcsp));
    
    // Specify that revocation checking is to be enabled
    pkixParams.setRevocationEnabled(true);
    
    // Wrap PKIX parameters as trust manager parameters
    ManagerFactoryParameters trustParams = new CertPathTrustManagerParameters(pkixParams);
    
    // Create TrustManagerFactory for PKIX-compliant trust managers
    TrustManagerFactory factory = TrustManagerFactory.getInstance("PKIX");
    
    // Pass parameters to factory to be passed to CertPath implementation
    factory.init(trustParams);
    
    // Use factory
    SSLContext ctx = SSLContext.getInstance("TLS");
    ctx.init(null, factory.getTrustManagers(), null);
X509TrustManagerインタフェース

javax.net.ssl.X509TrustManagerインタフェースは、汎用のTrustManagerインタフェースを拡張したものです。X. 509ベースの認証を使用する場合、トラスト・マネージャによって実装される必要があります。

JSSEを使用したリモート・ソケット・ピアのX.509認証をサポートするには、このインタフェースのインスタンスをSSLContextオブジェクトのinitメソッドに渡す必要があります。

X509TrustManagerの作成

このインタフェースは、ユーザーが直接実装することも、SunJSSEプロバイダによって提供されているものなど、プロバイダ・ベースのTrustManagerFactoryから取得することもできます。また、独自のインタフェースを実装して、ファクトリで生成されたトラスト・マネージャに委譲することもできます。たとえば、結果の信頼性の判定をフィルタし、グラフィカル・ユーザー・インタフェースからエンド・ユーザーに問い合わせる場合に、これを実行できます。

nullのKeyStoreパラメータがSunJSSEのPKIXまたはSunX509 TrustManagerFactoryに渡された場合、ファクトリは次のプロセスを使用してトラスト・データを検索します。

  1. javax.net.ssl.trustStoreプロパティが定義されている場合、TrustManagerFactoryは、このシステム・プロパティで指定されたファイル名を使用してファイルを検索し、KeyStoreパラメータにそのファイルを使用しようとします。javax.net.ssl.trustStorePasswordシステム・プロパティも定義されている場合は、ファイルを開く前に、その値を使用してトラストストアのデータの整合性をチェックします。

    javax.net.ssl.trustStoreプロパティが定義されているが、指定したファイルが存在しない場合、空のキーストアを使用するデフォルトのTrustManagerが作成されます。

  2. javax.net.ssl.trustStoreシステム・プロパティが指定されていない場合:
    • java-home/lib/security/jssecacertsファイルが存在すれば、そのファイルが使用されます。
    • java-home/lib/security/cacertsファイルが存在すれば、そのファイルが使用されます。
    • これらのどちらのファイルも存在しない場合、TLS暗号化方式群が匿名であり、認証を行わないので、トラストストアは必要ありません。

java-homeが何を示すかについては、用語と定義を参照してください。

ファクトリは、cacertsファイルをチェックする前に、javax.net.ssl.trustStoreセキュリティ・プロパティによって指定されたファイルまたはjssecacertsファイルを検索します。そのため、コード署名の目的で、cacerts内に存在するものとは別に、JSSE固有の一連の信頼されたルート証明書を提供できます。

独自のX509TrustManagerの作成

指定したX509TrustManagerの動作が状況に適していない場合は、独自のTrustManagerFactoryを作成して登録するか、X509TrustManagerインタフェースを直接実装して、独自のX509TrustManagerを作成できます。

例8-12では、デフォルトのX509TrustManagerが失敗した場合に代替の認証ロジックを提供することによってデフォルトのSunJSSE X509TrustManagerの動作を拡張する、MyX509TrustManagerクラスを示します。

このようなトラスト・マネージャを作成できたら、次の例のように、init()メソッドを使用して、これをSSLContextに割り当てます。以降、このSSLContextから作成されたSocketFactoriesは、ユーザー独自のTrustManagerを使用して信頼性を判定するようになります。

TrustManager[] myTMs = new TrustManager[] { new MyX509TrustManager() };
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, myTMs, null);

例8-12 X509TrustManagerの作成のサンプル・コード

次のコード例では、デフォルトのX509TrustManagerが失敗した場合に代替の認証ロジックを提供することによってデフォルトのSunJSSE X509TrustManagerの動作を拡張する、MyX509TrustManagerクラスを示します。


class MyX509TrustManager implements X509TrustManager {

     /*
      * The default PKIX X509TrustManager9.  Decisions are delegated
      * to it, and a fall back to the logic in this class is performed
      * if the default X509TrustManager does not trust it.
      */
     X509TrustManager pkixTrustManager;

     MyX509TrustManager() throws Exception {
         // create a "default" JSSE X509TrustManager.

         KeyStore ks = KeyStore.getInstance("JKS");
         ks.load(new FileInputStream("trustedCerts"), "passphrase".toCharArray());

         TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
         tmf.init(ks);

         TrustManager tms [] = tmf.getTrustManagers();

         /*
          * Iterate over the returned trust managers, looking
          * for an instance of X509TrustManager.  If found,
          * use that as the default trust manager.
          */
         for (int i = 0; i < tms.length; i++) {
             if (tms[i] instanceof X509TrustManager) {
                 pkixTrustManager = (X509TrustManager) tms[i];
                 return;
             }
         }

         /*
          * Find some other way to initialize, or else the
          * constructor fails.
          */
         throw new Exception("Couldn't initialize");
     }

     /*
      * Delegate to the default trust manager.
      */
     public void checkClientTrusted(X509Certificate[] chain, String authType)
                 throws CertificateException {
         try {
             pkixTrustManager.checkClientTrusted(chain, authType);
         } catch (CertificateException excep) {
             // do any special handling here, or rethrow exception.
         }
     }

     /*
      * Delegate to the default trust manager.
      */
     public void checkServerTrusted(X509Certificate[] chain, String authType)
                 throws CertificateException {
         try {
             pkixTrustManager.checkServerTrusted(chain, authType);
         } catch (CertificateException excep) {
             /*
              * Possibly pop up a dialog box asking whether to trust the
              * cert chain.
              */
         }
     }

     /*
      * Merely pass this through.
      */
     public X509Certificate[] getAcceptedIssuers() {
         return pkixTrustManager.getAcceptedIssuers();
     }
}
keyStoreの動的更新

MyX509TrustManagerを拡張して、キーストアの動的更新処理を行うことができます。checkClientTrustedまたはcheckServerTrustedのテストに失敗し、信頼できる証明書チェーンを確立できなかった場合、キーストアに対して、要求された信頼できる証明書を追加できます。更新されたキーストアを使用して初期化されたTrustManagerFactoryから新しいpkixTrustManagerを作成する必要があります。以前に初期化したSSLContextを使って新しい接続を確立すると、信頼性の判定を行うときに、新しく追加された証明書が使用されます。

X509ExtendedTrustManagerクラス

X509ExtendedTrustManagerクラスはX509TrustManagerインタフェースの抽象実装です。これは、接続を考慮したトラスト管理のためのメソッドを追加します。さらに、TLSレイヤーでのエンド・ポイントの検証を可能にします。

TLS 1.2以降では、クライアントとサーバーの両方が、受け入れるハッシュ・アルゴリズムと署名アルゴリズムを指定できます。リモート側を認証するには、認証の決定が、X509証明書と、ローカルで受け入れられるハッシュ・アルゴリズムおよび署名アルゴリズムの両方に基づいていることが必要です。ローカルで受け入れられるハッシュ・アルゴリズムおよび署名アルゴリズムはExtendedSSLSession.getLocalSupportedSignatureAlgorithms()メソッドを使用して取得できます。

ExtendedSSLSessionオブジェクトは、SSLSocket.getHandshakeSession()メソッドまたはSSLEngine.getHandshakeSession()メソッドを呼び出すことによって取得できます。

X509TrustManagerインタフェースは、接続を考慮しません。SSLSocketまたはSSLEngineセッション・プロパティにアクセスする方法を提供しません。

X509ExtendedTrustManagerクラスはTLS 1.2以降のサポート以外に、アルゴリズムの制約とSSLレイヤーのホスト名の検証もサポートします。JSSEプロバイダおよびトラスト・マネージャの実装については、レガシーのX509TrustManagerインタフェースよりもX509ExtendedTrustManagerクラスの方が強く推奨されます。

X509ExtendedTrustManagerの作成

X509ExtendedTrustManagerサブクラスを自分自身で作成するか(概略は次のセクションに記載)、プロバイダ・ベースのTrustManagerFactoryから取得できます(SunJSSEプロバイダによって提供されるものなど)。Java SE 7では、PKIXまたはSunX509 TrustManagerFactoryX509ExtendedTrustManagerインスタンスを返します。

独自のX509ExtendedTrustManagerの作成

このセクションでは、X509TrustManagerについて記載されているのとほぼ同じ方法でサブクラスX509ExtendedTrustManagerを作成する方法を示します。

例8-13では、PKIX TrustManagerFactoryを使用して、信頼性についての判定を下すために使用するデフォルトのX509ExtendedTrustManagerを見つけるクラスの作成方法を示します。

例8-13 PKIX TrustManagerFactoryの作成のサンプル・コード

次のコード例では、PKIX TrustManagerFactoryを使用して、信頼性についての判定を下すために使用するデフォルトのX509ExtendedTrustManagerを見つけるクラスの作成方法を示します。なんらかの理由でデフォルトのトラスト・マネージャに障害が発生した場合、サブクラスは他の動作を追加できます。このサンプルでは、これらの場所はcatch節内のコメントによって示されています。

import java.io.*;
import java.net.*;
import java.security.*;
import java.security.cert.*;
import javax.net.ssl.*;
    
public class MyX509ExtendedTrustManager extends X509ExtendedTrustManager {

  /*
   * The default PKIX X509ExtendedTrustManager.  Decisions are
   * delegated to it, and a fall back to the logic in this class is
   * performed if the default X509ExtendedTrustManager does not
   * trust it.
   */
  
  X509ExtendedTrustManager pkixTrustManager;
    
  MyX509ExtendedTrustManager() throws Exception {
    // create a "default" JSSE X509ExtendedTrustManager.
    
    KeyStore ks = KeyStore.getInstance("JKS");
    ks.load(new FileInputStream("trustedCerts"), "passphrase".toCharArray());
    
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
    tmf.init(ks);
    
    TrustManager tms [] = tmf.getTrustManagers();
    
    /*
     * Iterate over the returned trust managers, looking
     * for an instance of X509ExtendedTrustManager. If found,
     * use that as the default trust manager.
     */
    for (int i = 0; i < tms.length; i++) {
      if (tms[i] instanceof X509ExtendedTrustManager) {
        pkixTrustManager = (X509ExtendedTrustManager) tms[i];
        return;
      }
    }
    
    /*
     * Find some other way to initialize, or else we have to fail the
     * constructor.
     */
    throw new Exception("Couldn't initialize");
  }
    
  /*
   * Delegate to the default trust manager.
   */
  public void checkClientTrusted(X509Certificate[] chain, String authType)
    throws CertificateException {
    try {
      pkixTrustManager.checkClientTrusted(chain, authType);
    } catch (CertificateException excep) {
      // do any special handling here, or rethrow exception.
    }
  }
    
  /*
   * Delegate to the default trust manager.
   */
  public void checkServerTrusted(X509Certificate[] chain, String authType)
    throws CertificateException {
    try {
      pkixTrustManager.checkServerTrusted(chain, authType);
    } catch (CertificateException excep) {
      /*
       * Possibly pop up a dialog box asking whether to trust the
       * cert chain.
       */
    }
  }
    
  /*
   * Connection-sensitive verification.
   */
  public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
    throws CertificateException {
    try {
      pkixTrustManager.checkClientTrusted(chain, authType, socket);
    } catch (CertificateException excep) {
      // do any special handling here, or rethrow exception.
    }
  }
    
  public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
    throws CertificateException {
    try {
      pkixTrustManager.checkClientTrusted(chain, authType, engine);
    } catch (CertificateException excep) {
      // do any special handling here, or rethrow exception.
    }
  }
    
  public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)
    throws CertificateException {
    try {
      pkixTrustManager.checkServerTrusted(chain, authType, socket);
    } catch (CertificateException excep) {
      // do any special handling here, or rethrow exception.
    }
  }
    
  public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
    throws CertificateException {
    try {
      pkixTrustManager.checkServerTrusted(chain, authType, engine);
    } catch (CertificateException excep) {
      // do any special handling here, or rethrow exception.
    }
  }
         
  /*
   * Merely pass this through.
   */
  public X509Certificate[] getAcceptedIssuers() {
    return pkixTrustManager.getAcceptedIssuers();
  }
}
KeyManagerインタフェース

KeyManagerは、最終的にリモート・ホストに送信される認証クレデンシャルを選択します。自分自身(ローカルのセキュアなソケット・ピア)をリモート・ピアに対して認証させるには、1つまたは複数のKeyManagerオブジェクトでSSLContextオブジェクトを初期化する必要があります。サポートされる認証メカニズムごとに、KeyManagerを1つ渡す必要があります。SSLContextの初期化中にnullが渡されると、空のKeyManagerが作成されます。内部のデフォルト・コンテキストが使用される場合(SSLSocketFactory.getDefault()またはSSLServerSocketFactory.getDefault()によって作成されるSSLContextなど)は、デフォルトのKeyManagerが作成されます。デフォルトのキーストアとトラストストア、ストア・タイプおよびストア・パスワードのカスタマイズを参照してください。通常、単一のキー・マネージャは、X.509公開キー証明書に基づく認証をサポートしています。セキュアなソケット実装には、共有の秘密キー、Kerberos、またはほかのメカニズムに基づく認証をサポートするものもあります。

KeyManagerオブジェクトはKeyManagerFactoryによって、またはインタフェースの固定実装を提供することによって作成されます。

KeyManagerFactoryクラス

javax.net.ssl.KeyManagerFactoryクラスはプロバイダ・ベースのサービスのエンジン・クラスで、1つまたは複数の型のKeyManagerオブジェクトのファクトリとして動作します。SunJSSEプロバイダは、基本となるX.509キー・マネージャを返すことができるファクトリを実装します。これはプロバイダ・ベースであるため、追加のファクトリを実装し、構成することにより、追加の、または代替のキー・マネージャを提供できます。

KeyManagerFactoryの作成

このクラスのインスタンスはSSLContextと同様の方法で作成しますが、getInstanceメソッドにプロトコル名ではなくアルゴリズム名の文字列を渡す点が異なります。

KeyManagerFactory kmf = getInstance(String algorithm);
KeyManagerFactory kmf = getInstance(String algorithm, String provider);
KeyManagerFactory kmf = getInstance(String algorithm, Provider provider);

呼出しの例を次に示します。

KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509", "SunJSSE");

前の呼出しでSunJSSEプロバイダのデフォルトのキー・マネージャ・ファクトリのインスタンスが作成され、これは、基本となるX.509ベースの認証キーを提供します。

新しく作成されたファクトリは、initメソッドの1つを呼び出すことによって初期化してください。

public void init(KeyStore ks, char[] password);
public void init(ManagerFactoryParameters spec);

使用するKeyManagerFactoryに適したinitメソッドを呼び出します。不明な場合は、プロバイダのベンダーに問い合わせてください。

SunX509 KeyManagerFactoryなど、SunJSSEプロバイダが提供するファクトリは多数ありますが、KeyManagerFactoryを初期化するために必要な情報はKeyStoreのみであるため、最初にinitメソッドを呼び出すのが適切です。KeyManagerFactoryKeyStoreに、リモートのソケット・ピアを認証するために使用すべき秘密キー、および対応する公開キー証明書について問い合わせます。パスワード・パラメータは、KeyStoreのキーにアクセスするメソッドで使用するパスワードを指定します。KeyStoreのキーはすべて、同じパスワードで保護する必要があります。

プロバイダでは、KeyStoreとパスワード以外の初期化パラメータを必要とすることがあります。そのプロバイダのユーザーは、プロバイダによる定義に従って、適切なManagerFactoryParametersの実装を渡すことが期待されます。そのあと、プロバイダはManagerFactoryParameters実装の特定のメソッドを呼び出し、必要な情報を取得できます。

一部のファクトリでは、KeyStoreオブジェクトやその他のパラメータで初期化されなくても、認証データにアクセスできます。たとえば、Java認証・承認サービス(JAAS)などのログイン・メカニズムの一部としてキー・データにアクセスできる場合があります。

前に述べたように、SunJSSEプロバイダは、KeyStoreパラメータで初期化されている必要があるSunX509ファクトリをサポートします。

X509KeyManagerインタフェース

javax.net.ssl.X509KeyManagerインタフェースは、汎用のKeyManagerインタフェースを拡張したものです。X.509ベースの認証を行うキー・マネージャで実装します。JSSEを使用したリモート・ソケット・ピアのX.509認証をサポートするには、このインタフェースのインスタンスをSSLContextオブジェクトのinit()メソッドに渡す必要があります。

X509KeyManagerの作成

このインタフェースは、ユーザーが直接実装することも、SunJSSEプロバイダによって提供されているものなど、プロバイダ・ベースのKeyManagerFactoryから取得することもできます。また、独自のインタフェースを実装して、ファクトリで生成されたキー・マネージャに委譲することもできます。たとえば、結果のキーをフィルタし、グラフィカル・ユーザー・インタフェースを使用してエンド・ユーザーに問い合わせる場合に、これを実行できます。

独自のX509KeyManagerの作成

X509KeyManagerのデフォルトの動作が状況に適していない場合は、独自のX509TrustManagerの作成に示す同様の方法で独自のX509KeyManagerを作成できます。

X509ExtendedKeyManagerクラス

X509ExtendedKeyManager抽象クラスは、接続に固有のキーの選択が可能なX509KeyManagerインタフェースの実装です。これは、キーのタイプ、許可される発行者および現在のSSLEngineに基づいて、クライアントまたはサーバー用のキーの別名を選択する2つのメソッドを追加します。

  • public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine)
  • public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine)

キー・マネージャがX509ExtendedKeyManagerクラスのインスタンスでない場合、SSLEngineクラスと連携して動作しません。

JSSEプロバイダおよびトラスト・マネージャの実装については、レガシーのX509KeyManagerインタフェースよりもX509ExtendedKeyManagerクラスの方が強く推奨されます。

TLS 1.2以降では、クライアントとサーバーの両方が、受け入れるハッシュ・アルゴリズムと署名アルゴリズムを指定できます。リモート側から要求される認証に合格するには、ローカルのキー選択の決定が、X509証明書と、リモート側で受け入れられるハッシュ・アルゴリズムおよび署名アルゴリズムの両方に基づいていることが必要です。リモート側で受け入れられるハッシュ・アルゴリズムおよび署名アルゴリズムはExtendedSSLSession.getPeerSupportedSignatureAlgorithms()メソッドを使用して取得できます。

独自のX509TrustManagerの作成に示す同様の方法で、独自のX509ExtendedKeyManagerサブクラスを作成できます。

サーバー側でのServer Name Indication (SNI)拡張のサポートにより、キー・マネージャはサーバー名をチェックし、それに従って適切なキーを選択できます。たとえば、キーストアに証明書付きのキー・エントリが3つあるとします。

  • cn=www.example.com
  • cn=www.example.org
  • cn=www.example.net

ClientHelloメッセージで、SNI拡張のwww.example.netに接続するように要求する場合、サーバーはサブジェクトcn=www.example.netで証明書を選択できる必要があります。

TrustManagerとKeyManagerの関係

これまでは、TrustManagerKeyManagerの機能に関して混乱がありました。

TrustManagerはリモート認証クレデンシャル(すなわち接続)が信頼できるかどうかを判定します。

KeyManagerはリモート・ホストに送信される認証クレデンシャルを決定します。

二次サポート・クラスおよびインタフェース

二次サポート・クラスは、セキュアなソケットの作成、使用、および管理をサポートするJSSE APIの一部として提供されます。このクラスは、セキュアなソケット・アプリケーションでは、コア・クラスやサポート・クラスほどには使用されません。セカンダリ・サポート・クラスおよびインタフェースはjavax.net.sslおよびjavax.security.certパッケージに含まれています。

SSLParametersクラス

SSLParametersクラスは、SSL/TLS/DTLS接続に影響する次のパラメータをカプセル化します。

  • TLS/DTLSハンドシェークで受け付けられる暗号化方式群のリスト
  • 許可されるプロトコルのリスト
  • TLS/DTLSハンドシェーク中のエンドポイント識別アルゴリズム
  • サーバー名とサーバー名のマッチャ(Server Name Indication (SNI)拡張を参照)
  • TLS/DTLSハンドシェークで使用される暗号化方式群の優先順位
  • TLS/DTLSハンドシェーク中のアルゴリズム
  • Server Name Indication (SNI)
  • 最大ネットワーク・パケット・サイズ
  • アルゴリズム制約、およびTLS//DTLSサーバーがクライアント認証を要求する必要があるか必要とするかどうか

次のメソッドを使用して、SSLSocketまたはSSLEngineの現在のSSLParametersを取得できます。

  • SSLSocketSSLServerSocketおよびSSLEngine内のgetSSLParameters()
  • SSLContext内のgetDefaultSSLParameters()およびgetSupportedSSLParamters()

SSLSocketSSLServerSocketおよびSSLEnginesetSSLParameters()メソッドで、SSLParametersを割り当てることができます。

SSLParameters.setServerNames()メソッドによって、サーバー名の表示を明示的に設定できます。クライアント・モードでのサーバー名の表示はエンドポイントの識別にも影響します。X509ExtendedTrustManagerの実装で、それはExtendedSSLSession.getRequestedServerNames()メソッドによって取得されたサーバー名の表示を使用します。例8-14を参照してください。

例8-14 Server Name Indicationを設定するサンプル・コード

この例では、サーバー名表示のホスト名(www.example.com)を使用して、エンド・エンティティのX.509証明書に提示されるピアのIDに対してエンドポイントを識別します。

    SSLSocketFactory factory = ...
    SSLSocket sslSocket = factory.createSocket("172.16.10.6", 443);
    // SSLEngine sslEngine = sslContext.createSSLEngine("172.16.10.6", 443);

    SNIHostName serverName = new SNIHostName("www.example.com");
    List<SNIServerName> serverNames = new ArrayList<>(1);
    serverNames.add(serverName);
 
    SSLParameters params = sslSocket.getSSLParameters();
    params.setServerNames(serverNames);
    sslSocket.setSSLParameters(params);
    // sslEngine.setSSLParameters(params);
暗号化方式群の優先順位

TLSハンドシェーク時に、クライアントは、それがサポートする暗号化オプションのリストの、最優先順位から暗号化方式群をネゴシエーションすることを要求します。次に、サーバーはクライアントによって要求された暗号化方式群のリストから、単一の暗号化方式群を選択します。この選択では、デフォルトで、最もセキュアな設定であるサーバーの優先順位に従います。ただし、サーバーはSSLParameters.setUseCipherSuitesOrder(false)メソッドを呼び出して、独自の優先順位ではなく、クライアントの優先順位に従うように選択できます。

SSLSessionContextインタフェース

javax.net.ssl.SSLSessionContextインタフェースは、1つのエンティティに関連付けられているSSLSessionオブジェクトのグループです。たとえば、多数のセッションに同時に参加するサーバーやクライアントに関連付けることができます。このインタフェースのメソッドを使用すると、コンテキストの全セッションを列挙したり、セッションIDで特定のセッションを検索したりできます。

SSLSessionContextは、オプションで、SSLSessionのgetSessionContext()メソッドを呼び出してSSLSessionから取得することもできます。一部の環境では、コンテキストが使用できないことがあり、その場合、getSessionContextメソッドはnullを返します。

SSLSessionBindingListenerインタフェース

javax.net.ssl.SSLSessionBindingListenerインタフェースは、SSLSessionからバインドまたはアンバインドされるときに通知を受けるオブジェクトによって実装されます。

SSLSessionBindingEventクラス

javax.net.ssl.SSLSessionBindingEventクラスは、SSLSession (「SSLSessionとExtendedSSLSession」を参照)からバインドまたはアンバインドされるときにSSLSessionBindingListener (「SSLSessionBindingListenerインタフェース」を参照)に伝えられるイベントを定義します。

HandShakeCompletedListenerインタフェース

javax.net.ssl.HandShakeCompletedListenerインタフェースは、指定のSSLSocket接続時にSSLプロトコル・ハンドシェークの完了通知を受け取る任意のクラスに実装されるインタフェースです。

HandShakeCompletedEventクラス

javax.net.ssl.HandShakeCompletedEventクラスは、指定のSSLSocket接続のSSLプロトコル・ハンドシェークの完了時にHandShakeCompletedListener (「HandShakeCompletedListenerインタフェース」を参照)に伝えられるイベントを定義します。

HostnameVerifierインタフェース

SSL/TLS実装の標準ホスト名検証ロジックが失敗した場合、実装は、このインタフェースを実装し、このHttpsURLConnectionインスタンスに割り当てられたクラスのverifyメソッドを呼び出します。コールバック・クラスは、指定のパラメータでホスト名が受け付け可能であると判断できる場合、接続を許可すべきであると報告します。応答が受け付けられない場合、接続は切断されます。例8-15を参照してください。

HostnameVerifierHttpsURLConnectionに割り当てる方法の詳細は、HttpsURLConnectionを参照してください。

例8-15 HostnameVerifierインタフェース実装のサンプル・コード

次の例では、HostnameVerifierインタフェースを実装するクラスを示します。

    public class MyHostnameVerifier implements HostnameVerifier {
    
        public boolean verify(String hostname, SSLSession session) {
            // pop up an interactive dialog box
            // or insert additional matching logic
            if (good_address) {
                return true;
            } else {
                return false;
            }
        }
    }
    
    //...deleted...
    
    HttpsURLConnection urlc = (HttpsURLConnection)
      (new URL("https://www.example.com/")).openConnection();
    urlc.setHostnameVerifier(new MyHostnameVerifier());
X509Certificateクラス

セキュアなソケット・プロトコルの多くは、X.509証明書という公開キー証明書を使って認証を行います。これは、TLSプロトコルのデフォルト認証メカニズムです。

java.security.cert.X509Certificate抽象クラスは、X.509証明書の属性にアクセスする標準的な方法を提供します。

ノート:

javax.security.cert.X509Certificateクラスは、以前のバージョン(1.0.xおよび1.1.x)のJSSEとの後方互換性のためにのみサポートされています。新しいアプリケーションでは、かわりにjava.security.cert.X509Certificateクラスを使用してください。
AlgorithmConstraintsインタフェース

java.security.AlgorithmConstraintsインタフェースは、許可される暗号化アルゴリズムを制御するために使用します。AlgorithmConstraintsは3つのpermits()メソッドを定義します。これらのメソッドは、ある暗号化関数について許可されるアルゴリズム名またはキーを指定します。暗号化関数は一連のCryptoPrimitiveで表現され、これはSTREAM_CIPHERMESSAGE_DIGESTSIGNATUREなどのフィールドを含む列挙です。

したがって、AlgorithmConstraints実装は、「このキーとこのアルゴリズムを暗号化操作の目的で使用できるのか」といった質問に答えることができます。

新しいsetAlgorithmConstraints()メソッドを使用して、AlgorithmConstraintsオブジェクトをSSLParametersオブジェクトと関連付けることができます。SSLParametersオブジェクトに対する現在のAlgorithmConstraintsオブジェクトは、getAlgorithmConstraints()メソッドを使用して取得します。

StandardConstantsクラス

StandardConstantsクラスは、JSSEでの標準の定数定義を表すために使用します。

StandardConstants.SNI_HOST_NAMEServer Name Indication (SNI)拡張でのドメイン・ネーム・サーバー(DNS)ホスト名を表し、SNIServerNameまたはSNIMatcherオブジェクトのインスタンス化時に使用できます。

SNIServerNameクラス

抽象SNIServerNameクラスのインスタンスはServer Name Indication (SNI)拡張のサーバー名を表します。それは、指定したサーバー名のタイプとエンコード値を使用してインスタンス化されます。

getType()およびgetEncoded()メソッドを使用して、サーバー名のタイプとエンコードされたサーバー名の値のコピーをそれぞれ返します。equals()メソッドは他のオブジェクトがこのサーバー名と「等しい」かどうかをチェックするために使用できます。hashCode()メソッドはこのサーバー名のハッシュ・コード値を返します。サーバー名(サーバー名のタイプとエンコードされたサーバー名の値)の文字列表現を取得するには、toString()メソッドを使用します。

SNIMatcherクラス

抽象SNIMatcherクラスのインスタンスはSNIServerNameオブジェクトの一致操作を実行します。サーバーはServer Name Indication (SNI)拡張からの情報を使用して、特定のSSLSocketまたはSSLEngineで接続を受け付けるべきであるかどうかを判定できます。たとえば、単一の基礎となるネットワーク・アドレスで複数の「仮想」または「名前ベース」のサーバーがホストされている場合、サーバー・アプリケーションは、SNI情報を使用して、このサーバーが、クライアントがアクセスしようとしている正しいサーバーであるかどうかを判断できます。このクラスのインスタンスは、サーバーによって、ホスト名などの特定のタイプの受け付け可能なサーバー名を確認するために使用できます。

SNIMatcherクラスは、一致操作が実行される指定したサーバー名タイプを使用してインスタンス化されます。指定のSNIServerNameを照合するには、matches()メソッドを使用します。指定のSNIMatcherオブジェクトのサーバー名タイプを返すには、getType()メソッドを使用します。

SNIHostNameクラス

SNIHostNameクラス(SNIServerNameクラスを拡張する)のインスタンスは、Server Name Indication (SNI)拡張のタイプhost_name(「StandardConstantsクラス」を参照)のサーバー名を表します。SNIHostNameをインスタンス化するには、String引数で、サーバーの完全修飾DNSホスト名(クライアントから理解できる)を指定します。この引数は、次の場合に不正です。

  • 引数が空である。
  • 引数の末尾がピリオドで終わっている。
  • 引数がRFC 3490仕様に準拠した有効なInternationalized Domain Name (IDN)でない。

エンコードされたホスト名値をバイト配列で指定して、SNIHostNameをインスタンス化することもできます。このメソッドは一般に、要求されたSNI拡張でエンコードされた名前値を解釈するために使用します。そうでない場合、SNIHostName(String hostname)コンストラクタを使用します。encoded引数は、次の場合に不正です。

  • 引数が空である。
  • 引数の末尾がピリオドで終わっている。
  • 引数がRFC 3490仕様に準拠した有効なInternationalized Domain Name (IDN)でない。
  • 引数がUTF-8またはUS-ASCIIでエンコードされていない。

ノート:

引数として渡されたencodedバイト配列は、以降の変更から保護するために、複製が作成されます。

US-ASCIIエンコーディングのSNIHostNameオブジェクトのホスト名を返すには、getAsciiName()メソッドを使用します。サーバー名を他のオブジェクトと比較するには、equals()メソッドを使用します(比較は大文字と小文字が区別されません)。SNIHostNameのハッシュ・コード値を返すには、hashCode()メソッドを使用します。DNSホスト名を含むSNIHostNameの文字列表現を返すには、toString()メソッドを使用します。

createSNIMatcher()メソッドに、1つ以上の照合するホスト名を表す正規表現を渡すことによって、SNIHostNameオブジェクトのSNIMatcherオブジェクトを作成します。

JSSEのカスタマイズ

JSSEには、様々な実装をプラグインしたり、デフォルトのキーストアを指定したりして、カスタマイズ可能な標準実装が含まれます。

表8-2表8-3では、カスタマイズが可能な側面、デフォルトの内容、およびカスタマイズを提供するために使用するメカニズムを要約しています。

一部の機能は、システム・プロパティやセキュリティ・プロパティの値を設定してカスタマイズできます。表に続くセクションでは、プロパティ値の設定方法について説明します。

ノート:

この表に示すプロパティの多くは、現在JSSE実装で使用されていますが、それらの名前や型(システムまたはセキュリティ)が引き続き同じで、それらが将来のリリースにも存在するという保証はありません。それらのすべてのプロパティは「*」でフラグ付けされています。ここでは、JSSE実装で使用する場合の参考として、それらに言及しています。

表8-2では、java.security.Securityプロパティの設定によってカスタマイズされる項目を示します。java.security.Securityプロパティの指定方法を参照してください。

表8-2 セキュリティ・プロパティとカスタマイズされる項目

セキュリティ・プロパティ カスタマイズされる項目 デフォルト値 ノート
cert.provider.x509v1 X509証明書実装のカスタマイズ OracleからのX509Certificate実装

なし

SunJSSEプロバイダが使用するJCE暗号化アルゴリズム 代替のJCEアルゴリズム・プロバイダにSunJCEプロバイダより古い高い優先順位を与えます。 SunJCE実装

なし

jdk.certpath.disabledAlgorithms* 無効化された証明書検証暗号化アルゴリズム(無効化された制限付き暗号化アルゴリズムを参照)

MD2、MD5、SHA1 jdkCA & usage TLSServer、RSA keySize < 1024、DSA keySize < 1024、EC keySize < 224、SHA1 usage SignedJAR & denyAfter 2019-01-01脚注 2

なし

jdk.tls.disabledAlgorithms* 無効化された制限付き暗号化アルゴリズム

SSLv3、TLSv1、TLSv1.1、RC4、DES、MD5withRSA、DH keySize < 1024、EC keySize < 224、3DES_EDE_CBC、anon、NULL、include jdk.disabled.namedCurves脚注2

特定のアルゴリズム(プロトコル・バージョン、暗号化方式群、キー交換メカニズムなど)を無効化し、アプリケーションで明示的に有効化されてもTLS//DTLS接続でネゴシエーションされないようにします
jdk.tls.keyLimits* アルゴリズムでキー・セットを使用して暗号化可能なデータ量の制限 AES/GCM/NoPadding KeyUpdate 2^37 アルゴリズムで特定のキー・セットを使用して暗号化可能なデータ量を制限します。この制限に達すると、KeyUpdateポストハンドシェーク・メッセージが送信され、現在のキー・セットを更新するように要求します。
jdk.tls.legacyAlgorithms* レガシーの暗号化アルゴリズム

K_NULL、C_NULL、M_NULL、DH_anon、ECDH_anon、RC4_128、RC4_40、DES_CBC、DES40_CBC、3DES_EDE_CBC脚注2

レガシー・アルゴリズムとみなされるアルゴリズムを指定します。これらのアルゴリズムは、他に候補がない場合を除き、TLS/DTLSセキュリティ・パラメータのネゴシエーション中にネゴシエートされません。
jdk.tls.server.defaultDHEParameters Diffie-Hellmanグループ OpenJDK TLS/DTLS実装での安全な主要Diffie-Hellmanグループ (Datagram) Transport Layer Security ((D)TLS)処理のためにデフォルトの有限フィールドDiffie-Hellman ephemeral (DHE)パラメータを定義します
ocsp.enable* クライアント主導型OCSPとOCSPステープリング false クライアント主導型オンライン証明書ステータス・プロトコル(OCSP)を有効にします。

失効チェックも有効にする必要があります(「クライアント主導型OCSPを使用するためのJavaクライアントの設定」を参照)。

security.provider.n 暗号化サービス・プロバイダ。プロバイダ実装のカスタマイズ暗号化アルゴリズム・プロバイダのカスタマイズを参照してください。 優先度順の上位5つのプロバイダは、次のとおりです。
  1. SUN
  2. SunRsaSign
  3. SunEC
  4. SunJSSE
  5. SunJCE
セキュリティ・プロパティ・ファイル内のsecurity.provider.n=の行でプロバイダを指定します。ここでは、nは1以上の整数値です。
ssl.KeyManagerFactory.algorithm デフォルトのキー・マネージャ・ファクトリ・アルゴリズム名(デフォルトのキー・マネージャおよびトラスト・マネージャのカスタマイズを参照) SunX509

なし

ssl.ServerSocketFactory.provider* デフォルトのSSLServerSocketFactory実装 OracleからのSSLServerSocketFactory実装

なし

ssl.SocketFactory.provider * デフォルトのSSLSocketFactory実装 OracleからのSSLSocketFactory実装

なし

ssl.TrustManagerFactory.algorithm デフォルトのトラスト・マネージャ・ファクトリ・アルゴリズム名(デフォルトのキー・マネージャおよびトラスト・マネージャのカスタマイズを参照) PKIX

なし

脚注2 これらのセキュリティ・プロパティで指定されている制限付き、無効およびレガシーの各アルゴリズムのリストは、変更される可能性があります。最新の値は、JDKインストールのjava.securityファイルを参照してください。

* このセキュリティ・プロパティは現在JSSE実装で使用されていますが、他の実装で調査され、使用されている保証はありません。他の実装で調査する場合は、その実装で、JSSE実装と同じ方法でそれを処理すべきです。プロパティが今後も存在すること、またはシステム型やセキュリティ型が将来のリリースでも変更されないことの保証はされません。

表8-3では、java.lang.Systemプロパティの設定によってカスタマイズされる項目を示します。java.lang.Systemプロパティの指定方法を参照してください。

表8-3 システム・プロパティとカスタマイズされる項目

システム・プロパティ カスタマイズされる項目 デフォルト ノート
com.sun.net.ssl.checkRevocation* 失効チェック false クライアント主導型OCSPを有効化するには、失効チェックを有効にする必要があります(「クライアント主導型OCSPとOCSPステープリング」を参照)。
HTTPS URL内のportフィールドによってカスタマイズします* デフォルトのHTTPSポート 443

なし

https.cipherSuites* HTTPS接続用のデフォルトの暗号化方式群 ソケット・ファクトリによって決定

HttpsURLConnectionで使用できる暗号群を指定する暗号群名リスト(カンマ区切り形式)を含む。SSLSocket.setEnabledCipherSuites(String[])メソッドを参照してください。このメソッドは、渡されたString配列から直接ClientHello暗号化方式群の優先順位を設定することに注意してください。

https.protocols* HTTPS接続用のデフォルトのハンドシェーク・プロトコル ソケット・ファクトリによって決定

HttpsURLConnectionで有効にするプロトコルを指定するプロトコル名リスト(カンマ区切り形式)を含む。SSLSocket.setEnabledProtocols(String[])を参照してください

https.proxyHost* デフォルトのプロキシ・ホスト

なし

なし

https.proxyPort* デフォルトのプロキシ・ポート 80

なし

java.protocol.handler.pkgs HTTPSプロトコルの代替実装の指定 Oracleからの実装

なし

javax.net.ssl.keyStore* デフォルトのキーストア(「デフォルトのキーストアとトラストストア、ストア・タイプおよびストア・パスワードのカスタマイズ」を参照)

なし

NONEを指定できます。この設定は、キーストアがファイルベースでない場合に適切です(ハードウェア・トークンに存在する場合など)
javax.net.ssl.keyStorePassword* デフォルトのキーストア・パスワード(「デフォルトのキーストアとトラストストア、ストア・タイプおよびストア・パスワードのカスタマイズ」を参照)

なし

他のユーザーに見つかるような方法でパスワードを指定することはお薦めできません。

たとえば、コマンド行でのパスワードの指定です。パスワードのセキュリティを維持するには、アプリケーションでパスワードの入力を求めるか、適切に保護されたオプション・ファイルにパスワードを指定します

javax.net.ssl.keyStoreProvider* デフォルトのキーストア・プロバイダ(デフォルトのキーストアとトラストストア、ストア・タイプおよびストア・パスワードのカスタマイズを参照)

なし

なし

javax.net.ssl.keyStoreType* デフォルトのキーストア・タイプ(デフォルトのキーストアとトラストストア、ストア・タイプおよびストア・パスワードのカスタマイズを参照) KeyStore.getDefaultType()

なし

javax.net.ssl.sessionCacheSize SSLセッション・キャッシュ内の最大エントリ数のデフォルト値

20480

セッション・キャッシュ・サイズを設定するには、SSLSessionContext.setSessionCacheSizeメソッドを呼び出すか、javax.net.ssl.sessionCachSizeシステム・プロパティを設定します。キャッシュ・サイズが未設定の場合はデフォルト値が使用されます。

javax.net.ssl.trustStore* デフォルトのトラストストア(「デフォルトのキーストアとトラストストア、ストア・タイプおよびストア・パスワードのカスタマイズ」を参照) 存在する場合は、jssecacerts

そうでない場合、cacerts

NONEを指定できます。この設定は、トラストストアがファイルベースでない場合に適切です(ハードウェア・トークンに存在する場合など)
javax.net.ssl.trustStorePassword* デフォルトのトラストストア・パスワード(「デフォルトのキーストアとトラストストア、ストア・タイプおよびストア・パスワードのカスタマイズ」を参照) なし

他のユーザーに見つかるような方法でパスワードを指定することはお薦めできません。

たとえば、コマンド行でのパスワードの指定です。パスワードのセキュリティを維持するには、アプリケーションでパスワードの入力を求めるか、適切に保護されたオプション・ファイルにパスワードを指定します

javax.net.ssl.trustStoreProvider* デフォルトのトラストストア・プロバイダ(「デフォルトのキーストアとトラストストア、ストア・タイプおよびストア・パスワードのカスタマイズ」を参照) なし なし
javax.net.ssl.trustStoreType* デフォルトのトラストストア・タイプ(「デフォルトのキーストアとトラストストア、ストア・タイプおよびストア・パスワードのカスタマイズ」を参照) KeyStore.getDefaultType() なし
jdk.tls.acknowledgeCloseNotify* 受信時にclose_notifyアラートを送信するように指定 false trueの場合、クライアントまたはサーバーがclose_notifyアラートを受信すると、対応するclose_notifyアラートが送信され、接続が二重クローズされます。
jdk.tls.allowUnsafeServerCertChange* SSL/TLS再ネゴシエーションで安全でないサーバー証明書の変更を制限するかどうかを定義します。「SSL/TLS再ネゴシエーションにおける安全でないサーバー証明書変更の許可」を参照してください false 注意: 安全でないサーバー証明書の変更による脆弱性が再び確立されてしまうため、本当に必要な場合を除き、このシステム・プロパティをtrueに設定しないでください。
jdk.tls.client.cipherSuites* クライアント側の、デフォルトで有効な暗号化方式群。デフォルトで有効な暗号化方式群の指定を参照してください このJDKリリースで現在実装されているSunJSSE暗号化方式群の優先度順のリストは、「SunJSSE暗号化方式群」を参照してください 注意: これらのシステム・プロパティを使用して弱い暗号化方式群を構成できる、つまり、構成された暗号化方式群は将来的に脆弱である可能性があります。このリスクを理解せずにこれらのシステム・プロパティを使用することはお薦めしません。
jdk.tls.client.disableExtensions* デフォルトの拡張機能の構成 なし クライアント側で使用される拡張機能をブロックします。
jdk.tls.client.protocols* TLS/DTLSクライアント用のデフォルトのハンドシェーク・プロトコル。SunJSSEプロバイダを参照してください

なし

クライアント上で特定のSunJSSEプロトコルを有効化するには、引用符で囲んでカンマ区切りリスト形式で指定します。それ以外のサポート対象プロトコルはクライアント上では有効になりません。

たとえば、
  • jdk.tls.client.protocols="TLSv1,TLSv1.1"の場合は、TLSv1およびTLSv1.1用のクライアント上のデフォルトのプロトコル設定が有効になり、SSLv3、TLSv1.2、TLSv1.3およびSSLv2Helloは有効になりません

  • jdk.tls.client.protocols="DTLSv1.2"の場合は、DTLS1.2用のクライアント上のプロトコル設定が有効になり、DTLS1.0は有効になりません。

jdk.tls.client.SignatureSchemes* クライアント側のTLS接続に使用できる署名スキームを指定する、サポートされている署名スキーム名のカンマ区切りリストが含まれます。 定義されていません 認識されない、またはサポートされない署名スキーム名がプロパティに指定された場合は無視されます。このシステム・プロパティが定義されていないか空である場合、プロバイダ固有のデフォルトが使用されます。この名前は大/小文字が区別されません。署名スキーム名のリストは、Javaセキュリティ標準アルゴリズム名仕様の署名スキームに関する項を参照してください。
jdk.tls.ephemeralDHKeySize* エフェメラルDiffie-Hellmanキーのサイズのカスタマイズ 2048ビット

なし

jdk.tls.maxCertificateChainLength* 証明書チェーンの処理 10 TLS/DTLSハンドシェークでの証明書チェーンの最大許容長を指定します。
jdk.tls.maxHandshakeMessageSize* 証明書チェーンの処理 32768 (32KB) TLS/DTLSハンドシェークでのハンドシェーク・メッセージの最大許容サイズをバイト単位で指定します。
jdk.tls.namedGroups*

TLS/DTLSキー交換でサポートされている名前付きグループのカスタマイズ

このシステム・プロパティが定義されていないか値が空の場合は、実装のデフォルトのグループおよび設定が使用されます。

これには、引用符で囲まれたカンマ区切りリスト形式で、有効な名前付きグループが優先順位に従って格納されています。たとえば:

jdk.tls.namedGroups="secp521r1,secp256r1, ffdhe2048"

jdk.tls.server.cipherSuites* サーバー側の、デフォルトで有効な暗号化方式群。「デフォルトで有効な暗号化方式群の指定」を参照してください デフォルトで有効になっている暗号化方式群を確認するには、「SunJSSE暗号化方式群」を参照してください 注意: これらのシステム・プロパティを使用して弱い暗号化方式群を構成できる、つまり、構成された暗号化方式群は将来的に脆弱である可能性があります。このリスクを理解せずにこれらのシステム・プロパティを使用することはお薦めしません。
jdk.tls.server.disableExtensions* デフォルトの拡張機能の構成 サーバー側で使用される拡張機能をブロックします。  
jdk.tls.server.protocols* TLS/DTLSサーバー用のデフォルトのハンドシェーク・プロトコル。SunJSSEプロバイダを参照してください なし

SunJSSEプロバイダのサーバー側のデフォルトで有効なプロトコルを構成するには、引用符で囲まれたカンマ区切りリスト形式でプロトコルを指定します。

このリスト内のプロトコルは、Javaセキュリティ標準アルゴリズム名に記載されている標準のSSLプロトコル名です。

このシステム・プロパティは、デフォルトのプロトコル(SSL、TLSおよびDTLSアルゴリズムのSSLContext)にのみ影響します。アプリケーションでバージョン固有のSSLContext (SSLv3、TLSv1、TLSv1.1、TLSv1.2、TLSv1.3、DTLSv1.0またはDTLSv1.2)が使用されているか、有効なプロトコル・バージョンが明示的に設定されている場合、このシステム・プロパティは影響しません。

jdk.tls.server.SignatureSchemes* サーバー側のTLS接続に使用できる署名スキームを指定する、サポートされている署名スキーム名のカンマ区切りリストが含まれます。 定義されていません 認識されない、またはサポートされない署名スキーム名がプロパティに指定された場合は無視されます。このシステム・プロパティが定義されていないか空である場合、プロバイダ固有のデフォルトが使用されます。この名前は大/小文字が区別されません。署名スキーム名のリストは、Javaセキュリティ標準アルゴリズム名仕様の署名スキームに関する項を参照してください。
jsse.enableFFDHE*

TLS/DTLSキー交換用のFinite Field Diffie-Hellman Ephemeral (FFDHE)パラメータの有効化または無効化

true

FFDHEはRFC 7919で定義されているTLS/DTLS拡張です。これを使用すると、TLS/DTLS接続で既知の有限フィールドDiffie-Hellmanグループを使用できます。かなり古い一部のTLSベンダーは、TLS拡張を処理できないことがあります。この場合、このプロパティをfalseに設定してFFDHE拡張を無効化します。

jsse.enableMFLNExtension* 最大断片長ネゴシエーション(MFLN)拡張のカスタマイズ false

なし

jsse.enableSNIExtension* Server Name Indicationオプション true Server Name Indication (SNI)はTLS拡張で、RFC 6066で定義されています。これは仮想サーバーへのTLS接続を可能にし、さまざまなネットワーク名に対して複数のサーバーが単一の基本ネットワーク・アドレスでホスティングされます。かなり古い一部のTLSベンダーは、TLS拡張を処理できないことがあります。この場合、このプロパティをfalseに設定してSNI拡張を無効化します
jsse.SSLEngine.acceptLargeFragments* 大きいTLSパケット用のバッファのデフォルトのサイズ設定

なし

このシステム・プロパティをtrueに設定すると、SSLSessionはデフォルトで大きいデータ・パケットを処理するようにバッファをサイズ設定します。これにより、アプリケーションが不要な大きいSSLEngineバッファを割り当てる場合があります。代わりに、アプリケーションはバッファ・オーバーフロー条件を動的にチェックして、バッファを適宜サイズ変更する必要があります

sun.security.ssl.allowLegacyHelloMessages* 従来のHelloメッセージを許可します。「SunJSSE再ネゴシエーションの相互運用性モード」を参照してください false

このシステム・プロパティをtrueに設定すると、適切なRFC 5746メッセージを必要とすることなくピアがハンドシェークを実行できます。

このシステム・プロパティは推奨されておらず、将来のJDKリリースで削除される可能性があります。

sun.security.ssl.allowUnsafeRenegotiation* 安全でないSSL/TLS再ネゴシエーションを許可します。「SunJSSE再ネゴシエーションの相互運用性モード」を参照してください false

このシステム・プロパティをtrueに設定すると、完全な(安全でない)レガシーの再ネゴシエーションが許可されます。

このシステム・プロパティは推奨されておらず、将来のJDKリリースで削除される可能性があります。

* このシステム・プロパティは現在JSSE実装で使用されていますが、他の実装で調査され、使用されている保証はありません。他の実装で調査する場合は、その実装で、JSSE実装と同じ方法でそれを処理すべきです。プロパティが今後も存在すること、またはシステム型やセキュリティ型が将来のリリースでも変更されないことの保証はされません。

java.lang.Systemプロパティの指定方法

JSSEの一部の側面は、システム・プロパティを設定してカスタマイズできます。次のいくつかの方法によって、これらのプロパティを設定できます。
  • システム・プロパティを静的に設定するには、javaコマンドの-Dオプションを使用します。たとえば、MyAppというアプリケーションを実行して、javax.net.ssl.trustStoreシステム・プロパティを設定し、MyCacertsFileというトラストストアを指定します。トラストストアを参照してください。次のように入力します。
    
            java -Djavax.net.ssl.trustStore=MyCacertsFile MyApp
                    
    
  • システム・プロパティを動的に設定するには、次のコードでjava.lang.System.setProperty()メソッドを呼び出します。
    
            System.setProperty("propertyName", "propertyValue");
                    
    
    たとえば、システム・プロパティjavax.net.ssl.trustStoreを設定してMyCacertsFileというトラストストアを指定する、前の例に対応したsetProperty呼出しでは:
    
            System.setProperty("javax.net.ssl.trustStore", "MyCacertsFile");
                    
    

java.security.Securityプロパティの指定方法

JSSEの一部の側面は、セキュリティ・プロパティを設定してカスタマイズできます。セキュリティ・プロパティは静的または動的に設定できます。
  • セキュリティ・プロパティを静的に設定するには、セキュリティ・プロパティ・ファイルに1行追加します。セキュリティ・プロパティ・ファイルは、java-home/conf/security/java.securityにあります。
    java-home
    用語と定義を参照してください。

    セキュリティ・プロパティ・ファイルでセキュリティ・プロパティ値を指定するには、次の行を追加します。

    propertyName=propertyValue

    たとえば、デフォルトのSunX509以外のキー・マネージャ・ファクトリのアルゴリズム名を指定するとします。これを実行するには、ssl.KeyManagerFactory.algorithmというセキュリティ・プロパティの値として、アルゴリズム名を指定します。たとえば、値をMyX509に設定するには、次の行をセキュリティ・プロパティ・ファイルに追加します。

    ssl.KeyManagerFactory.algorithm=MyX509

    ノート:

    通常、java.securityファイル内のプロパティは1回のみ解析されます。このファイル内のプロパティを変更した場合は、アプリケーションを再起動して、変更が正しく反映されていることを確認します。
  • セキュリティ・プロパティを動的に設定するには、自分のコードでjava.security.Security.setPropertyメソッドを呼び出します。
    Security.setProperty("propertyName," "propertyValue");
    たとえば、キー・マネージャ・ファクトリのアルゴリズム名を指定する、前の例に対応したsetProperty()メソッドの呼出しでは:
    Security.setProperty("ssl.KeyManagerFactory.algorithm", "MyX509");

X509証明書実装のカスタマイズ

X509Certificate.getInstance()メソッドで返されたX509証明書実装は、デフォルトでJSSE実装の実装です。

別の実装を返すようにするには、次の手順に従います。
他の実装のクラスの名前(およびパッケージ)を、cert.provider.x509v1という名前のjava.security.Securityプロパティの指定方法の値として指定します。
たとえば、クラスがMyX509CertificateImplと呼ばれ、com.cryptoxパッケージにある場合、セキュリティ・プロパティ・ファイルに次の行を追加してください。

    cert.provider.x509v1=com.cryptox.MyX509CertificateImpl

デフォルトで有効な暗号化方式群の指定

デフォルトで有効な暗号化方式群は、アプリケーション内で、またはシステム・プロパティjdk.tls.client.cipherSuitesjdk.tls.server.cipherSuitesを使用して指定できます。

ノート:

有効な暗号化方式群の実際の使用は、アルゴリズム制約によって制限されます。

デフォルトで有効化する暗号化方式群のセットは、この優先度順に従い、次のいずれかの方法で決定されます。

  1. アプリケーションによる明示的な設定
  2. システム・プロパティによる指定
  3. JSSEプロバイダのデフォルトによる指定

たとえば、デフォルトで有効な暗号化方式群をアプリケーション内で明示的に設定すると、これにより、jdk.tls.client.cipherSuitesまたはjdk.tls.server.cipherSuitesおよびJSSEプロバイダのデフォルトで指定された設定がオーバーライドされます。

アプリケーションによる明示的な設定

次のいずれかのメソッドを使用して、どの暗号化方式群が有効であるかを設定できます。

システム・プロパティによる指定

システム・プロパティjdk.tls.client.cipherSuitesでは、クライアント側でのデフォルトで有効な暗号化方式群を指定します。jdk.tls.server.cipherSuitesではサーバー側のものを指定します。

これら2つのシステム・プロパティの値の構文は、サポートされている暗号化方式群の名前をカンマで区切ったリストです。これらのプロパティで指定された暗号化方式群名のうち、認識されていないまたはサポートされていないものは無視されます。JSSE暗号化方式群の標準名は、Javaセキュリティ標準アルゴリズムを参照してください。

ノート:

これらのシステム・プロパティは、現在、Oracle JDKおよびOpenJDKでサポートされています。これらが他のJDK実装によってサポートされることは保証されていません。

注意:

これらのシステム・プロパティを使用することで、弱い暗号化方式群が構成される、つまり、構成された暗号化方式群は将来的に脆弱になる可能性があります。このリスクを理解せずにこれらのシステム・プロパティを使用することはお薦めしません。

JSSEプロバイダのデフォルトによる指定

各JSSEプロバイダは、デフォルトで有効な暗号化方式群を独自に設定しています。SunJSSEプロバイダでサポートされている暗号化方式群名、およびデフォルトで有効になっている暗号化方式群名は、JDKプロバイダ・ドキュメントSunJSSEプロバイダを参照してください。

HTTPSプロトコルの代替実装の指定

java.net.URLクラスにHTTPS URLスキームを使用することで、SSL対応のWebサーバーでセキュアに通信できます。JDKは、デフォルトのHTTPS URL実装を提供します。

別のHTTPSプロトコル実装を使用する必要がある場合は、新しいクラス名を含めるようにjava.protocol.handler.pkgsjava.lang.Systemプロパティの指定方法を設定します。その結果、JDKのデフォルト・クラスより前に、指定したクラスが検索され、ロードされます。詳細は、URLクラスを参照してください。

ノート:

過去のJSSEリリースでは、JSSEのインストール中にjava.protocol.handler.pkgsシステム・プロパティを設定する必要がありました。このステップは、com.sun.net.ssl.HttpsURLConnectionのインスタンスを取得する場合以外は不要になりました。

プロバイダ実装のカスタマイズ

JDKには、SunJSSEというJSSE暗号化サービス・プロバイダ(略称はプロバイダ)が付属しています。基本的に、プロバイダは特定の暗号化アルゴリズムのエンジン・クラスを実装するパッケージです。

JSSEのエンジン・クラスはSSLContextKeyManagerFactory、およびTrustManagerFactoryです。プロバイダとエンジン・クラスの詳細は、Java暗号化アーキテクチャ(JCA)リファレンス・ガイドを参照してください。

使用する前に、プロバイダを静的または動的に登録する必要があります。SunJSSEプロバイダは登録済なので、登録する必要はありません。ほかのプロバイダを使用する場合は、後述のセクションでプロバイダの登録方法を確認してください。

暗号化プロバイダの静的な登録

セキュリティ・プロパティ・ファイル<java-home>/conf/security/java.securityに次のような1行を追加することで、プロバイダを静的に登録します。

security.provider.n=provName|className 

これはプロバイダを宣言し、その優先順位nを指定します。優先順位とは、特定プロバイダの指定がないときに、要求されたアルゴリズムについてプロバイダを検索する順序です。順序は1が基準になり、1が最も優先されます。その後、2、3、...と続きます。

provNameはプロバイダの名前であり、classNameはプロバイダの完全修飾クラス名です。

標準のセキュリティ・プロバイダは、java.securityセキュリティ・プロパティ・ファイルに自動的に登録されます。

他のJSSEプロバイダを使用する場合は、行を追加して他のプロバイダを登録し、目的の優先順位を指定します。

複数のJSSEプロバイダを同時に登録できます。登録されたプロバイダには、様々なエンジン・クラスの、様々なアルゴリズムの様々な実装が含まれる場合があり、同じ型のアルゴリズムおよびエンジン・クラスの一部または全部をサポートする場合もあります。特定のアルゴリズムの特定のエンジン・クラス実装を検索するとき、その検索に特定のプロバイダが指定されていない場合、プロバイダは優先順位で検索され、指定したアルゴリズムの実装を提供する最初のプロバイダの実装が使用されます。

プロバイダの実装および統合までのステップステップ8.1: プロバイダの構成を参照してください。

暗号化サービス・プロバイダを動的に登録する

プロバイダを静的に登録するかわりに、SecurityクラスのaddProviderまたはinsertProviderAtメソッドを呼び出すことで実行時に動的にプロバイダを追加できます。こうした登録は持続的なものではなく、実行できるのはinsertProvider.<provider name>権限を付与されたコードのみです。

プロバイダ構成

一部のプロバイダは、構成が必要な場合があります。これは、SecurityクラスのaddProviderメソッドを呼び出す前に、Providerクラスのconfigureメソッドを使用して行われます。一例として、SunPKCS11の構成を参照してください。Provider.configure()メソッドは、Java SE 9の新機能です。

特定の複数のアルゴリズムのための推奨プロバイダの構成

jdk.security.provider.preferredセキュリティ・プロパティで特定のアルゴリズムのための推奨プロバイダを指定します。推奨プロバイダを指定することで、特定のアルゴリズムのパフォーマンスを向上させるプロバイダを構成できますが、それらは、他のアルゴリズムにとっては最善のプロバイダではありません。security.provider.nプロパティを使用して指定された順序付きプロバイダ・リストは、特定のアルゴリズムのパフォーマンスを向上させるが他のアルゴリズムにとっては最善ではないプロバイダを順序付けるには十分ではありません。パフォーマンスを向上させるには、プロバイダ・リストの順序付けの構成をより柔軟に行う必要があります。

jdk.security.provider.preferredセキュリティ・プロパティでは、登録済プロバイダのリストにアクセスする前に、特定のアルゴリズム、またはサービスの型を一連の推奨プロバイダから選択できます。java.security.Securityプロパティの指定方法を参照してください。

jdk.security.provider.preferredセキュリティ・プロパティでは、プロバイダは登録されません。順序付きプロバイダ・リストは、security.provider.nプロパティを使用して暗号化プロバイダを静的に登録している必要があります。登録されていないプロバイダは無視されます。

1つのアルゴリズムのための推奨プロバイダの指定

jdk.security.provider.preferredセキュリティ・プロパティで推奨プロバイダ文字列を指定するための構文は、ServiceType.Algorithm:Providerのカンマ区切りのリストです

この構文の説明は次のとおりです。

ServiceType

サービスの型の名前(たとえば: "MessageDigest")。ServiceTypeはオプションです。指定しない場合は、そのアルゴリズムがすべてのサービス型に適用されます。

Algorithm

標準アルゴリズム名。Javaセキュリティ標準アルゴリズム名を参照してください。アルゴリズムは、標準名全体(AES/CBC/PKCS5Padding)または一部分(AES、AES/CBC、AES//PKCS5Padding)として指定できます。

Provider

プロバイダの名前。登録済リストに示されていないプロバイダは無視されます。JDKプロバイダを参照してください。

解析エラーなど、エラーを含むエントリは無視されます。エラーをデバッグするには、コマンドjava -Djava.security.debug=jcaを使用します。

推奨プロバイダとFIPS

FIPSプロバイダをsecurity.provider.nプロパティに追加し、jdk.security.provider.preferredプロパティで推奨プロバイダの順序付けを指定する場合は、jdk.security.provider.preferredで指定された推奨プロバイダが最初に選択されます。

したがって、FIPSプロバイダ構成のjdk.security.provider.preferredプロパティは構成しないようにすることをお薦めします。

jdk.security.provider.preferredのデフォルト値

jdk.security.provider.preferredプロパティは、デフォルトでは設定されておらず、アプリケーションのパフォーマンス・チューニングのためにのみ使用されます。

例8-16 jdk.security.provider.preferredプロパティのサンプル

jdk.security.provider.preferred プロパティを指定するための構文は、次のとおりです。

jdk.security.provider.preferred=AES/GCM/NoPadding:SunJCE, MessageDigest.SHA-256:SUN

この構文の説明は次のとおりです。
ServiceType
MessageDigest
Algorithm
AES/GCM/NoPadding、SHA-256
Provider
SunJCE、SUN

デフォルトのキーストアとトラストストア、ストア・タイプおよびストア・パスワードのカスタマイズ

SSLSocketFactory.getDefaultまたはSSLServerSocketFactory.getDefaultを呼び出すことでデフォルトのSSLSocketFactoryまたはSSLServerSocketFactoryが作成され、このデフォルトのSSLSocketFactory (またはSSLServerSocketFactory)がJSSEリファレンス実装に由来するものであれば、デフォルトのSSLContextは必ずソケット・ファクトリに関連付けられます。デフォルトのソケット・ファクトリは、JSSE実装に由来します。

デフォルトのSSLContextは、デフォルトのKeyManagerおよびデフォルトのTrustManagerで初期化されます。javax.net.ssl.keyStoreシステム・プロパティおよび適切なjavax.net.ssl.keyStorePasswordシステム・プロパティでキーストアを指定すると(java.lang.Systemプロパティの指定方法を参照)、デフォルトのSSLContextで作成したKeyManagerは、指定したキーストアを管理するKeyManager実装になります。(実際にはデフォルトのキー・マネージャおよびトラスト・マネージャのカスタマイズで説明したとおりに実装されます。)システム・プロパティが指定されない場合は、KeyManagerが管理するキーストアは新しい空のキーストアです。

一般に、ハンドシェークでサーバーとして動作するピアには、クライアントへの認証の資格を取得するため、KeyManagerのキーストアが必要です。ただし、匿名の暗号化方式群のいずれかを選択する場合、サーバーのKeyManagerキーストアは必要ありません。また、サーバーがクライアント認証を要求しないかぎり、クライアントとして動作するピアには、KeyManagerキーストアは必要ありません。したがって、このような状況では、javax.net.ssl.keyStoreのシステム・プロパティ値が定義されていなくてもかまわない場合があります。

同様に、トラスト・ストアをjavax.net.ssl.trustStoreシステム・プロパティで指定すると、デフォルトのSSLContextで作成したTrustManagerが、指定したトラスト・ストアを管理するTrustManager実装になります。この場合、そのようなプロパティが存在しても指定するファイルが存在しなければ、トラストストアは使用されません。javax.net.ssl.trustStoreプロパティが存在しない場合は、デフォルトのトラスト・ストアを検索します。java-home/lib/security/jssecacertsという名前のトラストストアが見つかった場合、それが使用されます。そうでない場合、java-home/lib/security/cacertsというトラストストアが検索され、それが存在していれば使用されます。トラスト・ストアが見つからない場合、TrustManagerは新しい空のトラスト・ストアを管理します。

ノート:

JDKには、java-home/lib/security/cacertsファイルで、限定された数の信頼されたルート証明書が付属しています。Java Platform, Standard Editionツール・リファレンスkeytoolに記載したとおり、このファイルをトラストストアとして使用する場合は、ユーザーにこのファイルに含まれる証明書を管理(追加または削除)する責任があります。

接続先のサーバーの証明書構成によっては、ルート証明書を追加する必要がある場合もあります。適切なベンダーから必要な特定のルート証明書を入手してください。

javax.net.ssl.keyStoreTypeまたはjavax.net.ssl.keyStorePasswordシステム・プロパティも指定されている場合、それぞれがデフォルトのKeyManagerキーストア・タイプとパスワードとして扱われます。タイプが指定されていない場合、デフォルトのタイプはKeyStore.getDefaultType()メソッドが返すもので、keystore.typeセキュリティ・プロパティの値か、または、そうしたセキュリティ・プロパティが指定されていない場合はjksになります。キーストアのパスワードが指定されていない場合は空白の文字列「」とみなされます。

同様に、javax.net.ssl.trustStoreTypeまたはjavax.net.ssl.trustStorePasswordシステム・プロパティも指定されている場合、それぞれがデフォルトのトラストストア・タイプとパスワードとして扱われます。タイプが指定されていない場合、デフォルトのタイプは、KeyStore.getDefaultType()メソッドによって返されるタイプです。トラストストアのパスワードが指定されていない場合は空白の文字列「」とみなされます。

ノート:

この項では、現在のJSSEリファレンス実装の動作を説明します。このセクションで説明するシステム・プロパティの名前と型(システムまたはセキュリティ)が引き続き同じで、将来のリリースにも存在するという保証はありせん。また、他のJSSE実装での検証や使用も保証されていません。実装で検証された場合、ここで説明するように、実装では、JSSEリファレンス実装と同じ方法でそれらを処理してください。

デフォルトのキー・マネージャおよびトラスト・マネージャのカスタマイズ

デフォルトのキーストアとトラストストア、ストア・タイプおよびストア・パスワードのカスタマイズで説明したように、デフォルトのSSLSocketFactoryまたはSSLServerSocketFactoryが作成され、このデフォルトのSSLSocketFactory (またはSSLServerSocketFactory)がJSSEリファレンス実装に由来する場合は、デフォルトのSSLContextは常にソケット・ファクトリに関連付けられます。

デフォルトのSSLContextは、KeyManagerおよびTrustManagerで初期化されます。デフォルトのSSLContextに提供されたKeyManagerTrustManagerの両方またはどちらか一方は、前のセクションで説明したように、指定したキーストアまたはトラストストアを管理するための実装になります。

選択されるKeyManager実装は、まずssl.KeyManagerFactory.algorithmセキュリティ・プロパティを調査することによって決定されます。そのようなプロパティ値が指定されていると、指定したアルゴリズムのKeyManagerFactory実装が検索されます。実装を提供する最初のプロバイダの実装が使用されます。そのgetKeyManagers()メソッドが呼び出され、デフォルトのSSLContextに提供するKeyManagerが決定されます。技術的には、getKeyManagers()は、キー・データの型ごとに1つずつのKeyManagerで、KeyManagerオブジェクトの配列を返します。そのようなセキュリティ・プロパティ値が指定されていない場合、SunX509のデフォルト値を使用して検索を実行します。

ノート:

SunX509アルゴリズムのKeyManagerFactory実装はSunJSSEプロバイダによって提供されます。それが指定するKeyManagerjavax.net.ssl.X509KeyManager実装です。

同様に、選択されるTrustManager実装は、まずssl.TrustManagerFactory.algorithmセキュリティ・プロパティを調査することによって決定されます。そのようなプロパティ値が指定されていると、指定したアルゴリズムのTrustManagerFactory実装が検索されます。実装を提供する最初のプロバイダの実装が使用されます。そのgetTrustManagersメソッドが呼び出され、デフォルトのSSLContextに提供するTrustManagerが決定されます。技術的には、getTrustManagers()は、トラスト・データの型ごとに1つずつのTrustManagerで、TrustManagerオブジェクトの配列を返します。そのようなセキュリティ・プロパティ値が指定されていない場合、PKIXのデフォルト値を使用して検索を実行します。

ノート:

PKIXアルゴリズムのTrustManagerFactory実装はSunJSSEプロバイダによって提供されます。それが指定するTrustManagerjavax.net.ssl.X509TrustManager実装です。

ノート:

この項では、現在のJSSEリファレンス実装の動作を説明します。このセクションで説明するシステム・プロパティの名前と型(システムまたはセキュリティ)が引き続き同じで、将来のリリースにも存在するという保証はありせん。また、他のJSSE実装での検証や使用も保証されていません。実装で検証された場合、ここで説明するように、実装では、JSSEリファレンス実装と同じ方法でそれらを処理してください。

無効化された制限付き暗号化アルゴリズム

一部の環境では、TLS/DTLSの使用時は、特定のアルゴリズムまたはキーの長さが不適切である場合があります。Oracle JDKでは、jdk.certpath.disabledAlgorithmsおよびjdk.tls.disabledAlgorithmセキュリティ・プロパティを使用して、バージョンのネゴシエーション、暗号化方式群の選択、ピア認証およびキー交換メカニズムなど、TLS/DTLSプロトコルのネゴシエーション中にアルゴリズムを無効にします。これらのセキュリティ・プロパティが他のJDK実装によって使用される保証はないことに注意してください。これらのセキュリティ・プロパティの構文とその現在アクティブな値については、<java-home>/conf/security/java.securityファイルを参照してください。

  • jdk.certpath.disabledAlgorithmsプロパティ: CertPathコードでは、jdk.certpath.disabledAlgorithmsセキュリティ・プロパティを使用して、CertPathチェック中に許可しないようにする必要があるアルゴリズムが決定されます。たとえば、TLSサーバーによって識別証明書チェーンが送信される場合、受け取ったチェーンをCertPath実装を使用して検証するクライアントTrustManagerでは、示された条件は許可されません。たとえば、次の行は、cacaertsキーストアにプレインストールされている信頼できるアンカーに連鎖するSHA1 TLSServer証明書と同様に、MD2ベースの証明書をブロックします。さらに、この行は、1024ビット未満のRSAキーをブロックします。

    jdk.certpath.disabledAlgorithms=MD2, SHA1 jdkCA & usage TLSServer, RSA keySize < 1024
  • jdk.tls.disabledAlgorithmsプロパティ: SunJSSEコードでは、jdk.tls.disabledAlgorithmsセキュリティ・プロパティを使用して、TLS/DTLSプロトコル、暗号化方式群、キーなどが無効にされます。この構文は、jdk.certpath.disabledAlgorithmsセキュリティ・プロパティに似ています。たとえば、次の行は、SSLv3アルゴリズムおよびすべてのTLS_*_RC4_*暗号化方式群を無効にします。

    jdk.tls.disabledAlgorithms=SSLv3, RC4

ノート:

これらのセキュリティ・プロパティで指定されるアルゴリズム制限は、トラスト・アンカーまたは自己署名証明書には適用されません。

特定の条件が必要な場合は、java.securityファイル内のセキュリティ・プロパティで関連する値を削除するか、JSSEの初期化前に正しいセキュリティ・プロパティを動的に設定することで、それを再アクティブ化できます。

ノート:

これらのセキュリティ・プロパティを変更したり、有効になっていない暗号化方式群を有効化する場合は、事前にセキュリティ・アーキテクトに連絡してください。この操作を行うと、保護の弱い暗号化方式群の使用が許可されます。

これらのセキュリティ・プロパティでは暗号化方式群の3番目のセットDisabledが事実上作成されることに注意してください。次のリストで、これら3つのセットを説明します。

  • Disabled: 暗号化方式群に無効リスト内のコンポーネント(RC4など)が含まれている場合(たとえば、RC4jdk.tls.disabledAlgorithmsセキュリティ・プロパティで指定されている)、その暗号化方式群は無効になり、接続ハンドシェークに考慮されません

  • Enabled: 接続に考慮される特定の暗号化方式群のリストです。

  • Not Enabled: 接続に考慮されない、無効でない暗号化方式群のリストです。これらの暗号化方式群を再度有効にするには、適切なsetEnabledCipherSuites()またはsetSSLParameters()メソッドを呼び出します。

jdk.tls.disabledAlgorithmsセキュリティ・プロパティで無効化されている暗号化方式群を、アプリケーションでsetEnabledCipherSuites()またはsetSSLParameters()メソッドを使用して再度有効にしようとすると、JSSEによってメソッドの呼出しは許可されますが、ハンドシェーク時の無効化された暗号化方式群の使用は許可されません。

このJDKリリースで現在実装されているSunJSSE暗号化方式群のリストは「SunJSSE暗号化方式群」を参照してください。

レガシーの暗号化アルゴリズム

一部の環境では、特定のアルゴリズムが望ましくない可能性があっても、それがレガシー・アプリケーションで使用されている場合は無効化できません。レガシー・アルゴリズムは引き続きサポートされますが、通常、レガシー・アルゴリズムはセキュリティ強度が十分でないため、アプリケーションでは使用しないでください。TLS/DTLSセキュリティ・パラメータのネゴシエーション中に、他に候補がない場合を除き、レガシー・アルゴリズムはネゴシエートされません。セキュリティ・プロパティjdk.tls.legacyAlgorithmsでは、Oracle JDKによってレガシー・アルゴリズムとみなされるアルゴリズムを指定します。このセキュリティ・プロパティの構文は、<java-home>/conf/security/java.securityファイルを参照してください。

ノート:

  • レガシー・アルゴリズムがjdk.tls.disabledAlgorithmsプロパティまたはjava.security.AlgorithmConstraints API (javax.net.ssl.SSLParameters.setAlgorithmConstraintsメソッドを参照)でも制限されている場合、そのアルゴリズムは完全に無効化されてネゴシエートされません。

  • セキュリティ・プロパティjdk.tls.legacyAlgorithmsで指定されたアルゴリズムをアプリケーションで使用している場合は、できるだけ早く別のアルゴリズムを使用してください。将来のJDKリリースでは、レガシー・アルゴリズムが制限付きアルゴリズムに指定される可能性があります。

暗号化アルゴリズム・プロバイダのカスタマイズ

SunJSSEプロバイダは、その暗号化のすべてニーズに対してSunJCE実装を使用します。プロバイダは通常の位置に置くことが推奨されていますが、SunJCEプロバイダより前に登録することにより、他のJCAまたはJCEプロバイダからの実装を使用できます。

セキュリティ・プロパティ・ファイル<java-home>/conf/security/java.securityを介して静的に、またはjava.security.SecurityクラスのaddProvider()またはinsertProviderAt()メソッドを介して動的に、標準JCAメカニズム(プロバイダ実装の要求および獲得方法を参照)を使用してプロバイダを構成できます。

エフェメラルDiffie-Hellmanキーのサイズのカスタマイズ

TLS/DTLS接続では、ハンドシェーク中に内部的にephemeral Diffie-Hellman (DH)キーが使用される場合があります。SunJSSEプロバイダでは、TLS/DTLSハンドシェーク中にephemeral DHキー・サイズの強度を柔軟にカスタマイズできます。

2048ビット未満のサイズのDiffie-Hellman (DH)キーは強度が不十分なため、非推奨になりました。システム・プロパティjdk.tls.ephemeralDHKeySizeによって、ephemeral DHキーのサイズをカスタマイズできます。このシステム・プロパティは、エクスポート可能な暗号化方式群のServerKeyExchangeメッセージ内のDHキーのサイズには影響しません。これは、JSSE OracleプロバイダのDHE_RSA、DHE_DSSおよびDH_anonベースの暗号化方式群にのみ影響します。

このプロパティには次のいずれかの値を指定できます。

  • 未定義: 2048ビットのサイズのDHキーは、常にエクスポート不可能な暗号化方式群に使用されます。これが、このプロパティのデフォルト値です。

  • legacy: JSSE Oracleプロバイダは、JDK 7以前のリリースのレガシーの動作を保持しています(512ビットおよび768ビットのサイズのephemeral DHキーの使用など)。

  • matched:

    • エクスポート不可能な匿名の暗号化方式群の場合、ServerKeyExchangeメッセージ内のDHキーのサイズは2048ビットです。

    • X.509証明書ベースの認証(エクスポート不可能な暗号化方式群の)の場合、対応する認証キーに一致するDHキーのサイズが使われます。ただし、1024ビット未満のすべてのキーでは固定サイズ1024ビットが使用され、2048ビットを超えるすべてのキーでは固定サイズ2048ビットが使用されます。

      たとえば、認証証明書の公開キー・サイズが2048ビットの場合、暗号化方式群がエクスポート可能でないかぎり、ephemeral DHキー・サイズは2048ビットです。このキー・サイズ設定スキームにより、認証キーとキー交換キー間の暗号化強度の整合性を確保しています。

  • 1024以上8192以下の64の倍数である有効な整数: エクスポート不可能な暗号化方式群に、指定した値の固定のephemeral DHキー・サイズ(ビット単位)が使われます。

次の表に、システム・プロパティjdk.tls.ephemeralDHKeySizeの可能性のある各値について、DHキーの最小および最大許容サイズをまとめています:

表8-4 システム・プロパティjdk.tls.ephemeralDHKeySizeのDHキー・サイズ

jdk.tls.ephemeralDHKeySizeの値 未定義 legacy matched 整数値(固定)
エクスポート可能なDHキー・サイズ 512 512 512 512
エクスポート不可能な匿名暗号化方式群 2048 768 2048 1024以上8192以下の64の倍数である有効な整数: エクスポート不可能な暗号化方式群に、指定した値の固定のephemeral DHキー・サイズ(ビット単位)が使われます。
認証証明書 2048 768

キーが1024ビット未満か2048ビットを超える場合を除き、キー・サイズは認証証明書と同じです。キーが1024ビット未満の場合は、1024ビットのDHキーが使用されます。キーが2048ビットを超える場合は、2048ビットのDHキーが使用されます。

結果として、使用できる値は1024または2048のみになります。

固定キー・サイズは有効な整数プロパティ値で指定し、1024以上8192以下の64の倍数である必要があります。

最大断片長ネゴシエーション(MFLN)拡張のカスタマイズ

より小さい最大断片長をネゴシエーションするために、クライアントは、ClientHelloメッセージにmax_fragment_lengthタイプの拡張情報を含めることを選択できます。システム・プロパティjsse.enableMFLNExtensionを使用して、TLS/DTLS用のMFLN拡張を有効または無効にできます。

最大断片長ネゴシエーション

制約があるTLS/DTLSクライアントでは、メモリー制限や帯域幅制限が原因で、より小さい最大断片長をネゴシエーションしたほうが望ましい場合があります。より小さい最大断片長をネゴシエーションするために、クライアントは、(拡張された) ClientHelloメッセージにmax_fragment_lengthタイプの拡張情報を含めることを選択できます。RFC 6066を参照してください。

最大断片長が正常にネゴシエーションされると、TLS/DTLSクライアントおよびサーバーで、すぐにメッセージ(ハンドシェーク・メッセージを含む)の断片化を開始して、ネゴシエーションされた長さより長い断片が送信されないようにすることができます。

システム・プロパティjsse.enableMFLNExtension

システム・プロパティjsse.enableMFLNExtensionは、MFLN拡張を有効または無効にするために定義されます。jsse.enableMFLNExtensionは、デフォルトでは無効になっています。

このシステム・プロパティの値は、次のように設定できます。

表8-5 jsse.enableMFLNExtensionシステム・プロパティ

システム・プロパティ 説明
jsse.enableMFLNExtension=true MFLN拡張を有効にします。SSLParameters.getMaximumPacketSize()で返された値が(212 + header-size)未満の場合、最大断片長ネゴシエーション拡張が有効になります。
jsse.enableMFLNExtension=false MFLN拡張を無効にします。

最大および最小パケット・サイズの構成

SSLParameters.setMaximumPacketSizeメソッドを使用して、TLS/DTLSレコードのための予期される最大ネットワーク・パケット・サイズ(バイト単位)を設定します。

HelloVerifyRequestsなど、小さいハンドシェーク・メッセージが断片化されないように、パケット・サイズは256バイト未満にしないようにすることをお薦めします。

アルゴリズムでキー・セットを使用して暗号化可能なデータ量の制限

jdk.tls.keyLimitsセキュリティ・プロパティを使用すると、アルゴリズムで特定のキー・セットを使用して暗号化可能なデータ量に制限を指定できます。この制限に達すると、KeyUpdateポストハンドシェーク・メッセージが送信され、現在のキー・セットを更新するように要求します。このセキュリティ・プロバイダは、TLS 1.3での対称暗号化にのみ使用できます。

このプロパティの構文は次のとおりです。

jdk.tls.keyLimits=KeyLimit { , KeyLimit }
KeyLimit
AlgorithmName KeyUpdate Length
AlgorithmName
アルゴリズムの完全変換
Length
KeyUpdateメッセージが送信されるまでに、セッション内で暗号化されるデータの量。この値は、整数値(バイト単位)または2の累乗で表します(たとえば、2^37)。

たとえば、次の例では、AES/GCM/NoPaddingアルゴリズムで237バイトを暗号化するとKeyUpdateメッセージが送信されるように指定します。

jdk.tls.keyLimits=AES/GCM/NoPadding KeyUpdate 2^37

受信時にclose_notifyアラートを送信するように指定

jdk.tls.acknowledgeCloseNotifyシステム・プロパティがtrueに設定されている場合、クライアントまたはサーバーがclose_notifyアラートを受信すると、対応するclose_notifyアラートが送信され、接続が二重クローズされます。

TLS 1.2以前のバージョンでは、duplex-closeポリシーが使用されています。しかし、TLS 1.3では、half-closeポリシーが使用されます。つまり、インバウンドおよびアウトバウンドのclose_notifyアラートは独立しています。TLS 1.3にアップグレードすると、アプリケーションによって接続の各側でSSLEngine.closeInbound()またはSSLEngine.closeOutbound()メソッドの(両方ではなく)いずれかのみを使用してTLS/DTLS接続が停止された場合、予期しない動作が発生する可能性があります。基礎となるTLS/DTLS転送が二重クローズされておらず、アプリケーションが予期せずハングまたはタイムアウトする場合、このプロパティをtrueに設定する必要がある場合があります。

TLS/DTLS接続が不要になった場合は、クライアントおよびサーバーの各アプリケーションがそれぞれの接続の両側を閉じるようにしてください。

サーバー証明書選択のためのcertificate_authorities拡張の有効化

certificate_authorities拡張は、TLS 1.3で導入されたオプションの拡張機能です。これは、エンドポイントがサポートする認証局(CA)を示し、受信エンドポイントが証明書の選択をガイドするために使用する必要があります。

この拡張機能は、クライアント証明書の選択では常に存在しますが、サーバー証明書の選択ではオプションです。

システム・プロパティjdk.tls.client.enableCAExtensiontrueに設定して、サーバー証明書の選択に対してこの拡張機能を有効にします。このプロパティのデフォルト値はfalseです。

ノート:

クライアントまたはサーバーが、拡張機能のサイズ制限(2^16バイト未満)を超えるように、より多くのCAを信頼する場合、拡張機能は有効になりません。また、一部のサーバー実装では、ハンドシェーク・メッセージが2^14バイトを超えることは許可されません。したがって、jdk.tls.client.enableCAExtensiontrueに設定されており、クライアントがサーバー実装の制限を超えるほど多くのCAを信頼している場合は、相互運用性の問題が発生する可能性があります。

SunJSSE再ネゴシエーションの相互運用性モード

SunJSSE実装は、RFC 5746に準拠したピアへの接続について、再ネゴシエーションをデフォルトで再び有効にします。つまり、セキュアに再ネゴシエーションを行うには、クライアントとサーバーの両方がRFC 5746をサポートしている必要があります。(RFC 5746は、SSL/TLSプロトコルで検出された問題に対処します。)まだアップグレードされていないピアとの接続について、SunJSSEではある程度の相互運用性モードが提供されていますが、ユーザーがクライアントとサーバーの両方の実装をできるだけ早く更新することを強く推奨します

SunJSSEには、再ネゴシエーションの相互運用性モードが3つあります。どのモードもRFC 5746: Transport Layer Security (TLS)再ネゴシエーション表示拡張を完全にサポートしていますが、アップグレードされていないピアとの通信時に次のセマンティクスが追加されます:

  • 厳密モード: クライアントとサーバーの両方がRFC 5746にアップグレードされていること、および適切なRFC 5746メッセージを送信することが求められます。そうでない場合、初期の(または後続の)ハンドシェークが失敗して接続が切断されます。

  • 相互運用モード(デフォルト): 正しいRFC 5746メッセージの使用はオプションですが、適切なメッセージが使用されない場合はレガシーの(元のSSL/TLS仕様の)再ネゴシエーションが無効になります。最初のレガシー接続は許可されますが、レガシーの再ネゴシエーションは無効化されます。これはセキュリティと相互運用性の最適な組み合わせであるため、デフォルト設定です。

  • セキュアでないモード: レガシーの再ネゴシエーションを完全に許可します。レガシーのピアとの相互運用性がもっとも高いですが、本来のMITM攻撃に対して脆弱です。

3つのモード区分は、アップグレードされていないピアとの接続にのみ影響します。すべてのクライアントおよびサーバーで厳密モード(完全なRFC 5746モード)を使用することが望ましいのですが、配備されているすべてのSSL/TLS実装がRFC 5746をサポートするようになるまである程度時間がかかるため、今のところは相互運用モードがデフォルトになっています。

次の表に、クライアントまたはサーバー(あるいはその両方)がRFC 5746をサポートするように更新されている場合と更新されていない場合の様々な例でのモードに関する相互運用性情報を示しています。

表8-6 相互運用性情報

クライアント サーバー モード
更新済 更新済 すべてのモードで再ネゴシエーションがセキュリティ保護されます。
レガシー脚注3 更新済
  • 厳密クライアントが適切なRFC 5746メッセージを送信しない場合、最初の接続がサーバーによってすぐに切断されます(SSLHandshakeExceptionまたはhandshake_failure)。
  • 相互運用可能 レガシー・クライアントからの初期接続(RFC 5746メッセージが欠落している)は許可されますが、再ネゴシエーションはサーバーによって許可されません。脚注4脚注5
  • セキュアでないレガシー・クライアントとの接続および再ネゴシエーションは許可されますが、元のMITM攻撃に対して脆弱です。
更新済 レガシー脚注3
  • 厳密サーバーが正しいRFC 5746メッセージで応答しない場合、クライアントは接続をすぐに切断します(SSLHandshakeExceptionまたはhandshake_failure)。
  • 相互運用可能レガシー・サーバーからの初期接続(RFC 5746メッセージが欠落している)は許可されますが、再ネゴシエーションはサーバーによって許可されません。脚注4脚注5
  • セキュアでないレガシー・サーバーとの接続および再ネゴシエーションは許可されますが、元のMITM攻撃に対して脆弱です。
レガシー脚注3 レガシー脚注3 既存のSSL/TLS動作を行い、MITM攻撃に対して脆弱です。

脚注3 「レガシー」とは元のSSL/TLS仕様を意味します(つまり、RFC 5746でない)。

脚注4 再ネゴシエーションが再度有効化された場合、再ネゴシエーションでは正しいRFC 5746メッセージが送信されないため、RFC 5746に準拠したピアからはレガシーとみなされます。

脚注5 SSL/TLSでは、再ネゴシエーションをいずれの側からでも開始できます。アップグレードされていないピアと相互運用性モードで通信しているアプリケーションが(SSLSocket.startHandshake()またはSSLEngine.beginHandshake()を使用して)再ネゴシエーションを開始しようとすると、アプリケーションはSSLHandshakeException (IOException)を受け取り、接続は停止(handshake_failure)されます。まだアップグレードされていないピアから再ネゴシエーション要求を受け取ったアプリケーションは、現在の接続のタイプに応じて応答します。

  • TLSv1no_renegotiation(100)タイプの警告メッセージがピアに送信され、接続は開いたままになります。古いバージョンのSunJSSEは、no_renegotiationアラートを受け取ると接続を停止します。
  • SSLv3 アプリケーションはSSLHandshakeExceptionを受け取り、接続は閉じられます(handshake_failure)。no_renegotiationアラートはSSLv3仕様に定義されていません。

次のシステム・プロパティを使用してモードを設定します(java.lang.Systemプロパティの指定方法を参照)。

  • sun.security.ssl.allowUnsafeRenegotiationは、レガシー(安全でない)再ネゴシエーションを許可するかどうかを制御します。
  • sun.security.ssl.allowLegacyHelloMessagesは、適切なRFC 5746メッセージを必要とすることなく、ピアによるハンドシェーク・プロセスの実行を許可します。

ノート:

システム・プロパティsun.security.ssl.allowUnsafeRenegotiationおよびsun.security.ssl.allowLegacyHelloMessages推奨されておらず、将来のJDKリリースで削除される可能性があります。

表8-7 相互運用性モードを設定するためのシステム・プロパティの値

モード allowLegacyHelloMessages allowUnsafeRenegotiation
厳密 false false
相互運用(デフォルト) true false
非セキュア true true

注意:

SSL/TLSプロトコルで検出された脆弱性が再確立されてしまうため、安全でないSSL/TLS再ネゴシエーションを再度有効化しないでください。
SSL/TLS再ネゴシエーションに対する回避方法と代替方法

すべてのピアは、できるだけ早くRFC 5746準拠の実装に更新する必要があります。このRFC 5746修正を適用しても、再ネゴシエーションが必要な場合は、アップグレードされていないピアとの通信に影響が生じます。推奨されるいくつかの方法を次に示します。

  • ピアを再構築して再ネゴシエーションを要求しないようにする。

    再ネゴシエーションは通常、Webサーバーが最初は匿名のクライアント・ブラウズを許可するが、後でSSL/TLS認証済クライアントを要求する場合や、Webサーバーが最初は弱い暗号化方式群を許可するが、後で強い暗号化方式群を必要とする場合に、Webサーバーによって使用されます。代替策は、最初のネゴシエーション中にクライアント認証および強い暗号化方式群を必要とすることです。これを行うには、2つの選択肢があります。

    • アプリケーションに、あるポイントに到達して、再ネゴシエーションが必要になるまでの「ブラウズ・モード」がある場合、「ブラウズ・モード」を削除し、すべての初期接続を強化するようにサーバーを再構築できます。

    • サーバーを2つのエンティティに分割して、1つのエンティティ上でブラウズ・モードを実行し、2つ目のエンティティをよりセキュアなモードで使用します。ネゴシエーション・ポイントに到達したら、関連情報をサーバー間で転送します。

    これらのどちらのオプションも、ある程度の作業が必要ですが、元のセキュリティ上の欠陥が再度発生することはありません。

  • システム・プロパティを使用して、再ネゴシエーション相互運用性モードを「セキュアでない」に設定します。

    「SunJSSE再ネゴシエーションの相互運用性モード」を参照してください。

SSL/TLS再ネゴシエーションにおける安全でないサーバー証明書変更の許可

SSL/TLS再ネゴシエーションでのサーバー証明書の変更は、次のすべてに該当する場合、安全ではない可能性があります:

  • エンドポイント識別がSSL/TLSハンドシェークで有効になっていない。
  • 前のハンドシェークが、セッション再開が省略された初期ハンドシェークである。
  • 両方の証明書で表されるアイデンティティが、別のものであると判断できる。次のステップに従って、2つの証明書が同じアイデンティティを表しているかどうかを判断します:
    1. IPアドレスのサブジェクトの代替名が両方の証明書に存在する場合、それらは同一です。
    2. それ以外の場合、DNS名のサブジェクトの代替名が両方の証明書に存在する場合、それらは同一です。
    3. それ以外の場合、サブジェクト・フィールドが両方の証明書に存在する場合、証明書のサブジェクトと発行人は同一です。

SSL/TLS再ネゴシエーションで安全でないサーバー証明書の変更は、デフォルトでは許可されていません。SSL/TLS再ネゴシエーションにおける安全でないサーバー証明書の変更を制限するかどうかを定義するには、システム・プロパティjdk.tls.allowUnsafeServerCertChangeを使用します。このシステム・プロパティのデフォルト値はfalseです。

注意:

安全でないサーバー証明書の変更による脆弱性が再び確立されてしまうため、本当に必要な場合を除き、このシステム・プロパティをtrueに設定しないでください。

クライアント主導型OCSPとOCSPステープリング

Transport Layer Security (TLS)のハンドシェーク中にX.509証明書失効ステータスを判定するには、オンライン証明書ステータス・プロトコル(OCSP)を使用します。

TLSで使用されるX.509証明書は、証明書に欠陥があるとみなす理由がある場合は、発行元の証明書発行局(CA)によって無効にされる可能性があります。次のいずれかの手法を使用することで、TLSハンドシェーク中に証明書の失効ステータスを確認できます。
  • 証明書失効リスト(CRL): CRLは、失効した証明書の単純なリストです。証明書を受け取るアプリケーションは、CRLサーバーからCRLを取得し、受け取った証明書がリストにあるかどうかを確認します。CRLの使用には、証明書を失効できるという意味で、不便な点が2つあります。

    • CRLは非常に大きくなる可能性があるため、ネットワーク・トラフィックがかなり増加する可能性があります。

    • 多くのCRLは長い有効期間で作成されます。これにより、証明書がその有効期間内に失効し次のCRL更新までリストに含まれなくなる可能性が高くなります。

    「Java PKIプログラマーズ・ガイド」「証明書/CRLストレージ・クラス」を参照してください。

  • クライアント主導型OCSP: クライアント主導型OCSPでは、クライアントはOCSPを使用してOCSPレスポンダに連絡し、証明書の失効ステータスを確認します。必要なデータ量はCRLの場合よりも通常は少なく、OCSPレスポンダは、CRLよりも最新の失効ステータスを把握している可能性があります。サーバーに接続する各クライアントは、確認する証明書ごとに1つのOCSP応答が必要となります。サーバーがよく使用されるものであり、クライアントの多くでクライアント主導型OCSPが使用されている場合、これらのOCSP要求は、OCSPレスポンダのパフォーマンスに悪影響を及ぼす可能性があります。

  • OCSPステープリング: OCSPステープリングにより、クライアントではなくサーバーで、OCSPレスポンダへの要求を行うことができます。サーバーは、証明書へのOCSP応答をステープリングして、TLSハンドシェーク中にそれをクライアントに返します。この手法では、発行元のCAではなく、証明書の提示者が、OCSP応答の提供のリソース・コストを負担できます。また、サーバーでOCSP応答をキャッシュし、それらをすべてのクライアントに提供できるようになります。これにより、応答をキャッシュし各クライアントによってではなくサーバーによって定期的に更新できるため、OCSPレスポンダの負荷が大幅に軽減されます。

クライアント主導型OCSPと証明書失効

クライアント主導型オンライン証明書ステータス・プロトコル(OCSP)では、クライアントが、Transport Layer Security (TLS)ハンドシェーク中にOCSPレスポンダに接続することで、証明書失効ステータスを確認できます。

クライアント主導型OCSPの要求は、TLSハンドシェーク中の、クライアントがサーバーから証明書を受け取りそれを検証したすぐ後に発生します。

クライアント主導型OCSPを使用するTLSハンドシェーク

クライアント主導型OCSPは、サーバーの証明書失効ステータスを確認するために、クライアントとサーバーとの間のTLSハンドシェーク中に使用されます。クライアントは証明書を受け取った後、証明書検証を実行します。検証が成功すると、クライアントは、証明書が発行者によって失効にされていないことを確認します。これは、OCSPレスポンダにOCSP要求を送信することで行われます。OCSP応答を受け取ると、クライアントは、TLSハンドシェークが完了する前にこの応答を確認します。

通常、クライアントは証明書のAuthority Information Access (AIA)拡張情報を調べてOCSPレスポンダのURLを見つけますが、システム・プロパティの使用によってそれを静的URLに設定できます。

クライアント主導型OCSPを使用するためのJavaクライアントの設定

クライアント主導型OCSPは、失効チェックとOCSPを有効にすることで有効になります。

クライアント主導型OCSPを使用するようにJavaクライアントを構成するには、Javaクライアントを、TLSを使用してサーバーに接続するように設定してある必要があります。
  1. 失効チェックを有効にします。これを行うには、異なる2通りの方法があります。
    • システム・プロパティcom.sun.net.ssl.checkRevocationtrueに設定します。
    • PKIXParameterssetRevocationEnabledメソッドを使用します。PKIXParametersクラスを参照してください。
  2. クライアント主導型OCSPを有効にします。

    セキュリティ・プロパティocsp.enabletrueに設定します。

両方のステップが必要となります。失効チェックが有効になっている場合以外は、ocsp.enable設定に効果はありません。
OCSPステープリングと証明書失効

オンライン証明書ステータス・プロトコル(OCSP)ステープリングでは、発行元の証明書発行局(CA)ではなく、証明書の提示者が、証明書の失効ステータスを含むOCSP応答の提供のリソース・コストを負担できます。

OCSPステープリングを使用するTLSハンドシェーク

OCSPステープリングは、サーバーの証明書失効ステータスを確認するために、クライアントとサーバーとの間のTransport Layer Security (TLS)ハンドシェーク中に使用されます。サーバーはOCSPレスポンダへのOCSP要求を行い、クライアントに返された証明書にOCSP要求をステープリングします。サーバーにOCSPレスポンダへの要求を行わせることで、応答をキャッシュし、多数のクライアントのために複数回使用できます。

ステープリングされたOCSP応答とともに証明書を受け取るクライアントは、各証明書を検証してから、ハンドシェークを続行する前にそのOCSP応答を確認します。クライアントの視点から述べると、証明書のための、サーバーからのステープリング済OCSP応答がない場合、次に当てはまると、クライアントはクライアント主導型OCSPまたは証明書失効リスト(CRL)を使用して失効情報を取得しようとします。

  • PKIXParameters.setRecovcationEnabledメソッドでRevocationEnabledフラグがtrueに設定されている。

  • ocsp.enableセキュリティ・プロパティをtrueに設定して、OCSPチェックが有効化されている。

OCSPチェックは、失効チェック中にCRLと連動して機能します。「Java PKIプログラマーズ・ガイド」「付録C: OCSPサポート」を参照してください。

ステータス要求と複数ステータス要求との比較

OCSPステープリング機能は、TLS Certificate Status Request拡張機能(RFC 6066の第8項)およびMultiple Certificate Status Request拡張機能(RFC 6961)を実装します。

TLS Certificate Status Request拡張機能は、証明書チェーン内のサーバー証明書のみのために失効情報を要求しますが、Multiple Certificate Status Request拡張機能は、証明書チェーン内のすべての証明書のために失効情報を要求します。サーバー証明書の失効情報のみがクライアントに送信される場合、チェーン内の他の証明書を、証明書失効リスト(CRL)またはクライアント主導型OCSPを使用して検証できます(ただし、クライアントを、これを行うように設定する必要があります)。

TLSによって、サーバーがクライアントの証明書も要求するようにできますが、クライアントが適切なOCSPレスポンダに連絡して、サーバーに送信された証明書に応答をステープリングできるようにする、OCSPステープリングでの用意はできていません。

OCSPの要求と応答

OCSPの要求メッセージと応答メッセージは、通常は、暗号化されていないHTTPを介して送信されます。応答は、CAによって署名されます。

必要な場合は、ステープリングされた応答は、クライアント・コードで、ExtendedSSLSessionオブジェクトでgetStatusResponsesメソッドを呼び出すことで取得できます。メソッド・シグネチャは次のとおりです。

public List<byte[]> getStatusResponses();

OCSP応答は、RFC 6960内のASN.1で示されている形式でDistinguished Encoding Rules (DER)を使用してエンコードされます。

OCSPステープリングを使用するためのJavaクライアントの設定

オンライン証明書ステータス・プロトコル(OCSP)ステープリングは、クライアント側で、システム・プロパティjdk.tls.client.enableStatusRequestExtensiontrue(そのデフォルト値)に設定することで有効になります。

サーバーから返された証明書にステープリングされたOCSP応答を使用するようにJavaクライアントを構成するには、Javaクライアントを、TLSを使用してサーバーに接続するように設定してある必要があり、サーバーを、TLSハンドシェークの一環でそれが返す証明書にOCSP応答をステープリングするように設定する必要があります。
  1. クライアントでOCSPステープリングを有効にします。

    必要な場合は、システム・プロパティjdk.tls.client.enableStatusRequestExtensiontrueに設定します。

  2. 失効チェックを有効にします。これを行うには、異なる2通りの方法があります。
    • システム・プロパティcom.sun.net.ssl.checkRevocationtrueに設定します。これは、コマンド行から、またはコードで実行できます。
    • PKIXParametersクラスでsetRevocationEnabledメソッドを使用します。PKIXParametersクラスを参照してください。

    クライアントで、証明書検証にサーバーから受け取ったステープリング済応答を含める場合は、失効チェックをtrueに設定する必要があります。失効チェックがtrueに設定されていない場合、失効情報の存在またはステータスに関係なく、接続を継続できます。

OCSPステープリングを使用するためのJavaサーバーの設定

オンライン証明書ステータス・プロトコル(OCSP)ステープリングは、サーバーで、システム・プロパティjdk.tls.server.enableStatusRequestExtensiontrueに設定することで有効になります。(デフォルトでは、falseに設定されています。)

Javaサーバーを、OCSPレスポンダに接続しOCSP応答を証明書にステープリングしてクライアントに返すように構成するには、次のステップを使用します。Javaサーバーを、TLSを使用してクライアントに応答するように設定してある必要があります。
  1. サーバーでOCSPステープリングを有効にします。

    システム・プロパティjdk.tls.server.enableStatusRequestExtensiontrueに設定します。

  2. オプション: 必要に応じて他のプロパティを設定します。有効なプロパティのリストは、OCSPステープリングの構成プロパティを参照してください。
OCSPステープリングの構成プロパティ

このトピックでは、オンライン証明書ステータス・プロトコル(OCSP)使用時の各種プロパティの設定の効果をリストします。クライアント主導型OCSPとOCSPステープリングの両方で使用されるプロパティを示します。

サーバー側のプロパティ

大部分のプロパティは、SSLContextのインスタンス化時に読み取られます。これは、プロパティを設定した場合に、新しいSSLContextオブジェクトを取得して、そのSSLContextオブジェクトから取得するSSLSocketまたはSSLEngineオブジェクトがそのプロパティ設定を反映するようにする必要があることを意味します。1つの例外は、jdk.tls.stapling.responseTimeoutプロパティです。このプロパティは、ServerHandshakerオブジェクトの作成時に評価されます(基本的に、SSLSocketまたはSSLEngineオブジェクトの作成と同時)。

表8-8 サーバー側のOCSPステープリングのプロパティ

プロパティ 説明 デフォルト値
jdk.tls.server.enableStatusRequestExtension OCSPステープリングのサーバー側サポートを有効にします。 False
jdk.tls.stapling.responseTimeout

キャッシュから取得するかOCSPレスポンダに連絡することで取得するかに関係なく、サーバーでOCSP応答の取得に使用される最長時間を制御します。

受取済の応答は、該当する場合は実行中のステープリングのタイプに基づいて、CertificateStatusメッセージで送信されます。

5000 (ミリ秒単位の整数値)
jdk.tls.stapling.cacheSize

エントリ内の最大キャッシュ・サイズを制御します。

キャッシュがいっぱいで、新しい応答をキャッシュする必要がある場合は、最も長い間使用されていないキャッシュ・エントリが、新しいものに置き換えられます。このプロパティの値がゼロ以下の場合は、キャッシュに含めることができる応答の数に上限がないことを意味します。

256個のオブジェクト
jdk.tls.stapling.cacheLifetime

キャッシュされた応答の最長存続時間を制御します。

応答にキャッシュ存続時間より早く期限切れになるnextUpdateフィールドがある場合は、応答の存続時間を、このプロパティで設定されている値よりも短くできます。このプロパティの値がゼロ以下の場合は、キャッシュ存続時間が無効になります。オブジェクトにnextUpdate値がなく、キャッシュ存続時間が無効になっている場合、応答はキャッシュされません。

3600秒(1時間)
jdk.tls.stapling.responderURI

TLSのために使用される証明書にAuthority Info Access (AIA)拡張情報がない場合は、管理者がデフォルトURIを設定できるようになります。

jdk.tls.stapling.responderOverrideプロパティが設定されている場合を除き、Authority Info Access拡張情報の値はオーバーライドされません。

未設定
jdk.tls.stapling.responderOverride

jdk.tls.stapling.responderURIプロパティによって提供されるURIで、AIA拡張情報の値をオーバーライドできるようになります。

False
jdk.tls.stapling.ignoreExtensions

status_requestまたはstatus_request_v2 TLS拡張情報で指定されているOCSP拡張情報の転送を無効にします。

False

クライアント側の設定

表8-9 OCSPステープリングで使用されるクライアント側設定

PKIXBuilderParameters checkRevocationプロパティ PKIXRevocationChecker 結果
デフォルト デフォルト デフォルト 失効チェックは無効です。
デフォルト True デフォルト 失効チェックは有効です。[1]
インスタンス化済 デフォルト デフォルト 失効チェックは有効です。[1]
インスタンス化済 デフォルト インスタンス化され、PKIXBuilderParametersオブジェクトに追加されています。 失効チェックは有効であり、[1]PKIXRevocationCheckerの設定に従って動作します。

脚注1 ocsp.enableセキュリティ・プロパティがtrueに設定されている場合のみクライアント側のOCSPのフォールバックが起こるということに注意してください。

開発者は、OCSPステープリングによって提供された応答をいくらか柔軟に処理できます。OCSPステープリングによって、証明書パス・チェックおよび失効チェックに関わる現在の方法論が変更されることはありません。これは、クライアントとサーバーの両方でstatus_request拡張情報をアサートし、CertificateStatusメッセージによってOCSP応答を取得し、ユーザーが失効情報またはその不足に柔軟に対応できるようになることを意味します。

呼出し側によってPKIXBuilderParametersが提供されない場合は、失効チェックは無効になります。呼出し側がPKIXBuilderParametersオブジェクトを作成し、setRevocationEnabledメソッドを使用して失効チェックを有効にする場合は、ステープリングされたOCSP応答が評価されます。これは、com.sun.net.ssl.checkRevocationプロパティがtrueに設定されている場合にも当てはまります。

デフォルトの拡張機能の構成

TLS実装によっては、不明な拡張機能を正しく処理できないことがあります。そのため、JDKで新しい拡張機能が導入されると、予期しない相互運用性の問題が発生する可能性があります。2つのシステム・プロパティを使用すると、デフォルトの拡張機能をカスタマイズできます:

  • jdk.tls.client.disableExtensions: クライアント側で使用される拡張機能をブロックします。
  • jdk.tls.server.disableExtensions: サーバー側で使用される拡張機能をブロックします。

拡張機能が無効になっている場合、ハンドシェーク・メッセージで拡張機能は生成も処理もされません。

これらのシステム・プロパティの値は、カンマ区切りの標準TLS拡張名のリストです。これらの名前のリストは、トランスポート・レイヤー・セキュリティ(TLS)の拡張を参照してください。拡張名は大文字と小文字が区別され、不明な、サポートされていないスペルミスや重複した名前は無視されます。

ノート:

特定のTLS拡張(jsse.enableMFLNExtensionjsse.enableSNIExtensionjsse.enableSNIExtensionなど)を有効または無効にするシステム・プロパティが存在しますが、jdk.tls.client.disableExtensionsまたはjdk.tls.server.disableExtensionsを介して無効にされた拡張は、対応するシステム・プロパティで有効にできる場合でも有効にはなりません。

ハードウェア高速化およびスマート・カードのサポート

Java暗号化アーキテクチャ(JCA)は、暗号化、キー生成とキー合意およびメッセージ認証コード(MAC)アルゴリズム用のフレームワークと実装を提供するパッケージ・セットです。(Java暗号化アーキテクチャ(JCA)リファレンス・ガイドを参照。)SunJSSEプロバイダは、すべての暗号化操作にJCAを排他的に使用するので、JCAのRSA PKCS#11のサポートを含め、JCEの機能や拡張機能を自動的に利用できます。このサポートによりSunJSSEプロバイダは、ハードウェア暗号化アクセラレータを使用してパフォーマンスを大幅に向上させ、キーストアとしてスマート・カードを使用し、キーおよび信頼性管理の柔軟性を高めることができます。

基盤となるアクセラレータ・ハードウェアを使用するようにOracle PKCS#11プロバイダが構成され、そのPKCS#11プロバイダを使用するようにJCAが構成されていれば、ハードウェア暗号化アクセラレータは自動的に使用されます。プロバイダは、プロバイダ・リストにある他のJCAプロバイダより前に構成する必要があります。Oracle PKCS#11プロバイダの構成方法の詳細は、PKCS#11リファレンス・ガイドを参照してください。

スマートカードをキーストアおよびトラストストアとして使用するためのJSSEの構成

JCAでのPKCS#11のサポートにより、キーストアとしてスマートカードにアクセスすることもできます。JSSEによって使用されるキーストアのタイプと場所の構成方法の詳細は、JSSEのカスタマイズを参照してください。スマートカードをキーストアまたはトラストストアとして使用するには、javax.net.ssl.keyStoreTypeおよびjavax.net.ssl.trustStoreTypeシステム・プロパティをそれぞれpkcs11に設定し、javax.net.ssl.keyStoreおよびjavax.net.ssl.trustStoreシステム・プロパティをそれぞれNONEに設定します。特定のプロバイダを使用するように指定するには、javax.net.ssl.keyStoreProviderおよびjavax.net.ssl.trustStoreProviderシステム・プロパティを使用します(たとえば、それらをSunPKCS11-joeに設定します)。これらのプロパティを使用することにより、以前はファイルベースのキーストアにアクセスするためにこれらのプロパティに依存していたアプリケーションを構成して、アプリケーションに変更を加えずにスマートカードのキーストアを使用できます。

アプリケーションによっては、キーストアをプログラムで使用する必要があります。こうしたアプリケーションでは、引き続き既存のAPIを使用してKeystoreをインスタンス化し、キー・マネージャとトラスト・マネージャに渡すことができます。Keystoreインスタンスがスマートカードに基づくPKCS#11キーストアを参照する場合は、JSSEアプリケーションがスマートカードのキーにアクセスすることになります。

複数の動的キーストア

スマートカードおよびその他の取り外し可能トークンには、X509KeyManagerについて追加の要件があります。Javaアプリケーションの存続期間の間、異なるスマートカードをスマートカード・リーダーに入れることができ、それらのスマートカードは異なるパスワードを使用して保護できます。

KeyStore.Builderクラスは、KeyStoreオブジェクトの構造と初期化を抽象化します。パスワードのプロンプト用にCallbackHandlerの使用をサポートし、そのサブクラスを使用して、アプリケーションに望まれる追加機能をサポートできます。たとえば、Builderを実装して、個々のKeyStoreエントリを異なるパスワードで保護するようにすることが可能です。その後、KeyStoreBuilderParametersクラスを使用し、これらのBuilderオブジェクトを1つ以上使用してKeyManagerFactoryを初期化できます。

NewSunX509と呼ばれる、SunJSSEプロバイダのX509KeyManager実装は、これらのパラメータをサポートしています。複数の証明書が使用可能な場合は、適切なキーを使用する証明書を選択し、期限切れの証明書より有効な証明書を優先させます。

例8-17では、PKCS#11キーストア(スマートカードを使用できる)とPKCS#12ファイルベース・キーストアの両方を使用するようJSSEに指示する方法を示します。

例8-17 PKCS#11およびPKCS#12ファイルベース・キーストアを使用するサンプル・コード

import javax.net.ssl.*;
import java.security.KeyStore.*;
// ...

// Specify keystore builder parameters for PKCS#11 keystores
Builder scBuilder = Builder.newInstance("PKCS11", null,
    new CallbackHandlerProtection(myGuiCallbackHandler));

// Specify keystore builder parameters for a specific PKCS#12 keystore
Builder fsBuilder = Builder.newInstance("PKCS12", null,
    new File(pkcsFileName), new PasswordProtection(pkcsKsPassword));

// Wrap them as key manager parameters
ManagerFactoryParameters ksParams = new KeyStoreBuilderParameters(
    Arrays.asList(new Builder[] { scBuilder, fsBuilder }) );

// Create KeyManagerFactory
KeyManagerFactory factory = KeyManagerFactory.getInstance("NewSunX509");

// Pass builder parameters to factory
factory.init(ksParams);

// Use factory
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(factory.getKeyManagers(), null, null);

その他のキーストア形式(PKCS12)

PKCS#12 (Personal Information Exchange Syntax Standard)では、移植可能な保存形式、およびユーザーの秘密キー、証明書、その他の秘密およびほかの項目の転送について規定されています。SunJSSEプロバイダは、PKCS12ファイルの読取りおよび書込みのためのPKCS12 java.security.KeyStore形式の完全な実装を提供します。この形式は、キーと証明書のインポートおよびエクスポートのために、Mozilla Firefox、Microsoft Internet ExplorerおよびOpenSSLなど、他のツールキットやアプリケーションでもサポートされています。たとえば、これらの実装は、クライアントの証明書とキーを.p12ファイル名拡張子を使用してファイルにエクスポートできます。

SunJSSEプロバイダでは、PKCS12のキーストア・タイプを使用して、KeyStore APIを介してPKCS12キーにアクセスできます。さらにkeytoolコマンドと、pkcs12に設定された-storetypeオプションを使用して、インストールされたキーおよび関連する証明書を表示できます。『Java Platform, Standard Editionツール・リファレンス』keytoolを参照してください。

Server Name Indication (SNI)拡張

SNI拡張は、TLS/DTLSプロトコルを拡張し、クライアントがハンドシェーク時に接続を試みるサーバー名を示す機能です。サーバーはServer Name Indicationの情報を使って、特定のSSLSocketまたはSSLEngineインスタンスが接続を受け入れる必要があるかどうかを判定できます。たとえば、単一の基礎となるネットワーク・アドレスで複数の仮想または名前ベースのサーバーがホストされている場合、サーバー・アプリケーションは、SNI情報を使用して、このサーバーが、クライアントがアクセスしようとしている正しいサーバーであるかどうかを判断できます。このクラスのインスタンスは、サーバーによって、ホスト名などの特定のタイプの受け付け可能なサーバー名を確認するために使用できます。TLS拡張(RFC 6066)の第3項を参照してください。

クライアント・アプリケーションの開発者は、SSLParameters.setServerNames(List<SNIServerName> serverNames)メソッドを使用して、サーバー名の表示を明示的に設定できます。例8-18を参照してください。

サーバー・アプリケーションの開発者はSNIMatcherクラスを使用して、サーバー名の表示を認識する方法を決定できます。例8-19例8-20では、この機能を示します。

例8-18 Server Name Indicationを設定するサンプル・コード

次のコード例では、メソッドSSLParameters.setServerNames(List<SNIServerName> serverNames)を使用してサーバー名表示を設定する方法を示します。

SSLSocketFactory factory = ...
SSLSocket sslSocket = factory.createSocket("172.16.10.6", 443);
// SSLEngine sslEngine = sslContext.createSSLEngine("172.16.10.6", 443);

SNIHostName serverName = new SNIHostName("www.example.com");
List<SNIServerName> serverNames = new ArrayList<>(1);
serverNames.add(serverName);

SSLParameters params = sslSocket.getSSLParameters();
params.setServerNames(serverNames);
sslSocket.setSSLParameters(params);
// sslEngine.setSSLParameters(params);

例8-19 SSLSocketクラスを使用してSNIを認識するサンプル・コード

次のコード例では、サーバー・アプリケーションでSNIMatcherクラスを使用してサーバー名表示の認識方法を決定する方法を示します。

SSLSocket sslSocket = sslServerSocket.accept();

SNIMatcher matcher = SNIHostName.createSNIMatcher("www\\.example\\.(com|org)");
Collection<SNIMatcher> matchers = new ArrayList<>(1);
matchers.add(matcher);

SSLParameters params = sslSocket.getSSLParameters();
params.setSNIMatchers(matchers);
sslSocket.setSSLParameters(params);

例8-20 SSLServerSocketクラスを使用してSNIを認識するサンプル・コード

次のコード例では、サーバー・アプリケーションでSNIMatcherクラスを使用してサーバー名表示の認識方法を決定する方法を示します。


 
SSLServerSocket sslServerSocket = ...;

SNIMatcher matcher = SNIHostName.createSNIMatcher("www\\.example\\.(com|org)");
Collection<SNIMatcher> matchers = new ArrayList<>(1);
matchers.add(matcher);

SSLParameters params = sslServerSocket.getSSLParameters();
params.setSNIMatchers(matchers);
sslServerSocket.setSSLParameters(params);

SSLSocket sslSocket = sslServerSocket.accept();

次のリストは、ClientHelloメッセージで様々なサーバー名表示要求を受け取った場合のSNIMatcherの動作の例を示しています。

  • www\\.example\\.comに構成されたマッチャ:

    • 要求されたホスト名がwww.example.comの場合、それは受け付けられ、ServerHelloメッセージで確認が送信されます。
    • 要求されたホスト名がwww.example.orgの場合、それはunrecognized_name致命的エラーで拒否されます。
    • 要求されたホスト名がないか、空の場合、要求は受け付けられますが、ServerHelloメッセージで確認が送信されません。
  • www\\.invalid\\.comに構成されたマッチャ:

    • 要求されたホスト名がwww.example.comの場合、それはunrecognized_name致命的エラーで拒否されます。
    • 要求されたホスト名がwww.example.orgの場合、それは受け付けられ、ServerHelloメッセージで確認が送信されます。
    • 要求されたホスト名がないか、空の場合、要求は受け付けられますが、ServerHelloメッセージで確認が送信されません。
  • マッチャが構成されていない:

    要求されたすべてのホスト名が受け付けられますが、ServerHelloメッセージで確認は送信されません。

SNI拡張を実装する新しいクラスの説明については、次を参照してください。

たとえば、「Server Name Indication (SNI)拡張の使用」を参照してください。

TLSのアプリケーション層プロトコル・ネゴシエーション

アプリケーション層プロトコル・ネゴシエーション(ALPN)を使用してTLS接続のためにアプリケーション・プロトコルをネゴシエーションします。

ALPNとは

アプリケーションによっては、TLSハンドシェークが完了する前に、共有アプリケーション・レベル値のネゴシエーションが必要な場合があります。たとえば、HTTP/2では、特定のTCPまたはUDPポートで使用されるか使用できるHTTPバージョン(h2、spdy/3、http/1.1)の確立に役立つように、アプリケーション層プロトコル・ネゴシエーションのメカニズムが使用されます。ALPN (RFC 7301)では、クライアントとサーバーとの間のネットワーク・ラウンドトリップを増やすことなく、これが行われます。HTTP/2の場合、プロトコルは、接続をネゴシエーションする前に確立される必要があります。これは、クライアントとサーバーが、通信を開始する前に、使用するHTTPのバージョンを把握する必要があるためです。ALPNを使用しないと、アプリケーション・プロトコルHTTP/1とHTTP/2を同じポートで使用できません。

クライアントは、TLSハンドシェークの開始時にALPN拡張を使用して、サポートされているアプリケーション・プロトコルのリストをClientHelloの一部としてサーバーに送信します。サーバーは、ClientHello内のサポートされているアプリケーション・プロトコルのリストを読み取り、サポートされているプロトコルのうちどれが望ましいかを判断します。次に、ネゴシエーション結果とともにServerHelloメッセージをクライアントに送り返します。メッセージには、選択されているプロトコルの名前、またはプロトコルが選択されていないことが示されている場合があります。

そのようにして、アプリケーション・プロトコル・ネゴシエーションは、ネットワーク・ラウンドトリップを増やすことなく、TLSハンドシェーク内で行うことができ、必要に応じてサーバーで異なる証明書を各アプリケーション・プロトコルに関連付けることができるようにします。

他の多くのTLS拡張と異なり、この拡張では、セッションのプロパティは確立されず、接続のプロパティのみとなります。それが、ネゴシエーションした値がSSLSessionではなくSSLSocket/SSLEngineで見つかる理由です。セッション再開またはセッション・チケットが使用される場合(サーバー側の状態なしでのTLSセッション再開を参照)、前にネゴシエーションした値は無関係であり、新しいハンドシェーク・メッセージ内の値のみが考慮されます。

クライアントでのALPNの設定

クライアントがサポートしているアプリケーション層プロトコル・ネゴシエーション(ALPN)値を設定してサーバーに送信するには、SSLParameters.setApplicationProtocols(String[])メソッドに続いて、SSLSocketまたはSSLEnginesetSSLParametersメソッドを呼び出します。サーバーとのハンドシェーク中に、サーバーは、クライアントのアプリケーション・プロトコル・リストを読み取り、どれが最も適切かを判断します。

例8-21 JavaクライアントでのALPN値の設定および取得のサンプル・コード

たとえば、ここでは、クライアントでthreetwoというALPN値を設定するステップを示します。

コードを実行するには、プロパティjavax.net.ssl.trustStoreを有効なルート証明書に設定する必要があります。(これはコマンド行で実行できます)。

import java.io.*; 
import java.util.*;
import javax.net.ssl.*; 
public class SSLClient {
    public static void main(String[] args) throws Exception {

        // Code for creating a client side SSLSocket
        SSLSocketFactory sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault();
        SSLSocket sslSocket = (SSLSocket) sslsf.createSocket("localhost", 9999);

        // Get an SSLParameters object from the SSLSocket
        SSLParameters sslp = sslSocket.getSSLParameters();

        // Populate SSLParameters with the ALPN values
        // On the client side the order doesn't matter as
        // when connecting to a JDK server, the server's list takes priority
        String[] clientAPs = {"three", "two"};
        sslp.setApplicationProtocols(clientAPs);

        // Populate the SSLSocket object with the SSLParameters object
        // containing the ALPN values
        sslSocket.setSSLParameters(sslp);

        sslSocket.startHandshake();

        // After the handshake, get the application protocol that has been negotiated
        String ap = sslSocket.getApplicationProtocol();
        System.out.println("Application Protocol client side: \"" + ap + "\"");

        // Do simple write/read
        InputStream sslIS = sslSocket.getInputStream();
        OutputStream sslOS = sslSocket.getOutputStream();
        sslOS.write(280);
        sslOS.flush();
        sslIS.read();
        sslSocket.close();
    }
}

このコードが実行されると、ALPN値onetwoおよびthreeが設定されているJavaサーバーにClientHelloメッセージが送信されます。コードの出力内容は次のとおりです:

Application Protocol client side: two

ハンドシェーク中にネゴシエーションの結果を確認することもできます。ハンドシェーク中のネゴシエーション済ALPN値の特定を参照してください。

サーバーでのデフォルトALPNの設定

サーバーでALPN値を設定することで、デフォルトのALPNメカニズムを使用して、適切なアプリケーション・プロトコルを決定します。

サーバーでALPNのデフォルト・メカニズムを使用するには、クライアントでALPNを設定したときに行ったように(クライアントでのALPNの設定の項を参照)、設定するALPN値をSSLParametersオブジェクトに移入してから、このSSLParametersオブジェクトを使用してSSLSocketオブジェクトかSSLEngineオブジェクトにこれらのパラメータを移入します。サーバーで設定されたALPN値のうち、ClientHelloに含まれているALPN値のいずれかに一致する最初の値が選択され、ServerHelloの一部としてクライアントに返されます。

例8-22 サーバーでのデフォルトALPN値ネゴシエーションのサンプル・コード

次に、プロトコル・ネゴシエーションにデフォルトの手法を使用するJavaサーバー用コードを示します。コードを実行するには、プロパティjavax.net.ssl.keyStoreを有効なキーストアに設定する必要があります。(これはコマンド行で実行できます。JSSEで使用するキーストアの作成を参照)。

import java.util.*; 
import javax.net.ssl.*; 
public class SSLServer {
    public static void main(String[] args) throws Exception {

        // Code for creating a server side SSLSocket
        SSLServerSocketFactory sslssf = 
            (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
        SSLServerSocket sslServerSocket = 
            (SSLServerSocket) sslssf.createServerSocket(9999);
        SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();

        // Get an SSLParameters object from the SSLSocket
        SSLParameters sslp = sslSocket.getSSLParameters();

        // Populate SSLParameters with the ALPN values
        // As this is server side, put them in order of preference
        String[] serverAPs ={ "one", "two", "three" };
        sslp.setApplicationProtocols(serverAPs);

        // If necessary at any time, get the ALPN values set on the 
        // SSLParameters object with:
        // String serverAPs = sslp.setApplicationProtocols();

        // Populate the SSLSocket object with the ALPN values
        sslSocket.setSSLParameters(sslp);

        sslSocket.startHandshake();

        // After the handshake, get the application protocol that 
        // has been negotiated

        String ap = sslSocket.getApplicationProtocol();
        System.out.println("Application Protocol server side: \"" + ap + "\"");

        // Continue with the work of the server
        InputStream sslIS = sslSocket.getInputStream();
        OutputStream sslOS = sslSocket.getOutputStream();
        sslIS.read();
        sslOS.write(85);
        sslOS.flush();
        sslSocket.close();
    }
}
このコードを実行し、Javaクライアントによって、ALPN値threeおよびtwoを含むClientHelloが送信されると、出力は次のようになります。
Application Protocol server side: two
ハンドシェーク中にネゴシエーションの結果を確認することもできます。ハンドシェーク中のネゴシエーション済ALPN値の特定を参照してください。

サーバーでのカスタムALPNの設定

コールバック・メソッドを設定することで、カスタムのALPNメカニズムを使用して、適切なアプリケーション・プロトコルを決定します。

サーバーのデフォルト・ネゴシエーション・プロトコルを使用しない場合は、SSLEngineまたはSSLSocketsetHandshakeApplicationProtocolSelectorメソッドを使用して、現在までのハンドシェーク状態を確認できるBiFunction (ラムダ)コールバックを登録し、クライアントのアプリケーション・プロトコル・リストおよびその他の関連情報に基づいて選択できます。たとえば、推奨されている暗号化方式群、または選択に当たって取得できるServer Name Indication (SNI)やその他のデータの使用を検討できます。カスタム・ネゴシエーションを使用する場合は、setApplicationProtocolsメソッドによって設定された値(デフォルト・ネゴシエーション)は無視されます。

例8-23 サーバーでのカスタムALPN値ネゴシエーションのサンプル・コード

次に、プロトコル・ネゴシエーションにカスタム・メカニズムを使用するJavaサーバー用コードを示します。コードを実行するには、プロパティjavax.net.ssl.keyStoreを有効な証明書に設定する必要があります。(これはコマンド行で実行できます。JSSEで使用するキーストアの作成を参照)。

import java.util.*; 
import javax.net.ssl.*; 

public class SSLServer {
    public static void main(String[] args) throws Exception {

        // Code for creating a server side SSLSocket

        SSLServerSocketFactory sslssf =
            (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
        SSLServerSocket sslServerSocket = 
            (SSLServerSocket) sslssf.createServerSocket(9999);
        SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();

        // Code to set up a callback function
        // Pass in the current SSLSocket to be inspected and client AP values

        sslSocket.setHandshakeApplicationProtocolSelector(
            (serverSocket, clientProtocols) -> {
                SSLSession handshakeSession = serverSocket.getHandshakeSession();

                // callback function called with current SSLSocket and client AP values
                // plus any other useful information to help determine appropriate
                // application protocol. Here the protocol and ciphersuite are also
                // passed to the callback function.

                return chooseApplicationProtocol(
                    serverSocket,
                    clientProtocols,
                    handshakeSession.getProtocol(),
                    handshakeSession.getCipherSuite());
         }); 

        sslSocket.startHandshake();

        // After the handshake, get the application protocol that has been
        // returned from the callback method.

        String ap = sslSocket.getApplicationProtocol();
        System.out.println("Application Protocol server side: \"" + ap + "\"");

        // Continue with the work of the server

        InputStream sslIS = sslSocket.getInputStream();
        OutputStream sslOS = sslSocket.getOutputStream();
        sslIS.read();
        sslOS.write(85);
        sslOS.flush();
        sslSocket.close();
    }

    // The callback method. Note how the parameters match the call within 
    // the setHandshakeApplicationProtocolSelector method.

    public static String chooseApplicationProtocol(SSLSocket serverSocket,
            List<String> clientProtocols, String protocol, String cipherSuite ) {
        // For example, check the cipher suite and return an application protocol
        // value based on that.
        if (cipherSuite.equals("<--a_particular_ciphersuite-->")) { 
            return "three";
        } else {
            return "";
        }
    } 
}

暗号化方式群が、このコードの実行時に条件文で指定されたものと一致する場合は、値threeが返されます。そうでない場合は、空の文字列が返されます。

BiFunctionオブジェクトの戻り値がStringであることに注意してください。これは、アプリケーション・プロトコル名になるか、通知された値をどれも受け入れられないことを示すnullになります。戻り値が空のStringである場合は、アプリケーション・プロトコルの指示は使用されません。戻り値がnull (選択値なし)であるか、ピアによって通知されていない値である場合は、基になるプロトコルで、実行するアクションが決定されます。(たとえば、サーバー・コードでno_application_protocolアラートが送信され、接続が終了されます。)

クライアントとサーバーの両方でハンドシェークが完了した後、SSLSocketオブジェクトまたはSSLEngineオブジェクトでgetApplicationProtocolメソッドを呼び出すことで、ネゴシエーションの結果を確認できます。

ハンドシェーク中のネゴシエーション済ALPN値の特定

ハンドシェーク中にネゴシエーションされたALPN値を特定するには、カスタムのKeyManagerまたはTrustManagerクラスを作成し、このカスタム・クラスにgetHandshakeApplicationProtocolメソッドの呼出しを含めます。

選択したALPN値とSNI値がKeyManagerまたはTrustManagerによる選択内容に影響するユースケースがいくつかあります。たとえば、アプリケーションが、サーバーの属性および選択されたALPN/SNI/暗号化方式群値に応じて、異なる証明書/公開キーのセットを選択する必要がある場合があります。

与えられたサンプル・コードでは、KeyManagerオブジェクトとして作成し登録するカスタムX509ExtendedKeyManager内から、getHandshakeApplicationProtocolメソッドを呼び出す方法を示します。

例8-24 カスタムKeyManagerのサンプル・コード

この例では、X509ExtendedKeyManagerを拡張するカスタムKeyManagerのコード全体を示します。大部分のメソッドは、このMyX509ExtendedKeyManagerクラスによってラップされているKeyManagerクラスから返された値を返すのみです。ただし、chooseServerAliasメソッドは、SSLSocketオブジェクトでgetHandshakeApplicationProtocolを呼び出し、それにより、ネゴシエーションされた現在のALPN値を特定できます。



import java.net.Socket;
import java.security.*;
import javax.net.ssl.*;

public class MyX509ExtendedKeyManager extends X509ExtendedKeyManager {

    // X509ExtendedKeyManager is an abstract class so your new class 
    // needs to implement all the abstract methods in this class. 
    // The easiest way to do this is to wrap an existing KeyManager
    // and call its methods for each of the methods you need to implement.   

    X509ExtendedKeyManager akm;
    
    public MyX509ExtendedKeyManager(X509ExtendedKeyManager akm) {
        this.akm = akm;
    }

    @Override
    public String[] getClientAliases(String keyType, Principal[] issuers) {
        return akm.getClientAliases(keyType, issuers);
    }

    @Override
    public String chooseClientAlias(String[] keyType, Principal[] issuers, 
        Socket socket) {
        return akm.chooseClientAlias(keyType, issuers, socket);
    }

    @Override
    public String chooseServerAlias(String keyType, Principal[] issuers, 
        Socket socket) {
        
        // This method has access to a Socket, so it is possible to call the
        // getHandshakeApplicationProtocol method here. Note the cast from 
        // a Socket to an SSLSocket
        String ap = ((SSLSocket) socket).getHandshakeApplicationProtocol();
        System.out.println("In chooseServerAlias, ap is: " + ap);
        return akm.chooseServerAlias(keyType, issuers, socket);
    }

    @Override
    public String[] getServerAliases(String keyType, Principal[] issuers) {
        return akm.getServerAliases(keyType, issuers);
    }

    @Override
    public X509Certificate[] getCertificateChain(String alias) {
        return akm.getCertificateChain(alias);
    }

    @Override
    public PrivateKey getPrivateKey(String alias) {
        return akm.getPrivateKey(alias);
    }
}
このコードがJavaサーバー用のKeyManagerとして登録されており、Javaクライアントによって、ALPN値を含むClientHelloが送信される場合、出力は次のようになります。
    In chooseServerAlias, ap is: <negotiated value>

例8-25 JavaサーバーでのカスタムKeyManagerの使用のサンプル・コード

この例では、デフォルトのALPNネゴシエーション戦略、および前のコード例で示されたカスタムKeyManagerMyX509ExtendedKeyManagerを使用する単純なJavaサーバーを示します。

import java.io.*;
import java.util.*;
import javax.net.ssl.*;
import java.security.KeyStore;

public class SSLServerHandshake {
    
    public static void main(String[] args) throws Exception {
        SSLContext ctx = SSLContext.getInstance("TLS");

        // You need to explicitly create a create a custom KeyManager

        // Keystores
        KeyStore keyKS = KeyStore.getInstance("PKCS12");
        keyKS.load(new FileInputStream("serverCert.p12"), 
            "password".toCharArray());

        // Generate KeyManager
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
        kmf.init(keyKS, "password".toCharArray());
        KeyManager[] kms = kmf.getKeyManagers();

        // Code to substitute MyX509ExtendedKeyManager
        if (!(kms[0] instanceof X509ExtendedKeyManager)) {
            throw new Exception("kms[0] not X509ExtendedKeyManager");
        }

        // Create a new KeyManager array and set the first index 
        // of the array to an instance of MyX509ExtendedKeyManager.
        // Notice how creating this object is done by passing in the 
        // existing default X509ExtendedKeyManager 
        kms = new KeyManager[] { 
            new MyX509ExtendedKeyManager((X509ExtendedKeyManager) kms[0])};

        // Initialize SSLContext using the new KeyManager
        ctx.init(kms, null, null);

        // Instead of using SSLServerSocketFactory.getDefault(), 
        // get a SSLServerSocketFactory based on the SSLContext
        SSLServerSocketFactory sslssf = ctx.getServerSocketFactory();
        SSLServerSocket sslServerSocket = 
            (SSLServerSocket) sslssf.createServerSocket(9999);
        SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
        SSLParameters sslp = sslSocket.getSSLParameters();
        String[] serverAPs ={"one","two","three"};
        sslp.setApplicationProtocols(serverAPs);
        sslSocket.setSSLParameters(sslp);
        sslSocket.startHandshake();

        String ap = sslSocket.getApplicationProtocol();
        System.out.println("Application Protocol server side: \"" + ap + "\"");

        InputStream sslIS = sslSocket.getInputStream();
        OutputStream sslOS = sslSocket.getOutputStream();
        sslIS.read();
        sslOS.write(85);
        sslOS.flush();

        sslSocket.close();
        sslServerSocket.close();
    }
}

カスタムX509ExtendedKeyManagerの準備が整い、ハンドシェーク中にchooseServerAliasが呼び出されると、KeyManagerは、ネゴシエーションされたアプリケーション・プロトコル値を確認できるようになります。示されている例の場合、この値はコンソールに出力されます。

たとえば、このコードを実行し、Javaクライアントによって、ALPN値threeおよびtwoを含むClientHelloが送信されると、出力は次のようになります。
Application Protocol server side: two

SunJSSEプロバイダを使用したALPN値の読取りおよび書込み

ALPNは、バイト配列でデータを転送します。これは、US-ASCIIなどのシングルバイト文字エンコーディングでのテキストのエンコードが期待されることを意味します。Java ALPN APIではテキストにStringクラスを使用しますが、Java SE 16/11.0.2/8u301より前では、SunJSSEプロバイダはStringインスタンスをUTF-8のバイト配列に変換します。ただし、UTF-8は可変幅文字エンコーディングです。U+007Fよりも上にある文字を、複数のバイトでエンコードします。これはALPNピアで予期されない可能性があります。

Java SE 16/11.0.2/8u301以降では、SunJSSEプロバイダはString文字を8ビットのISO_8859_1/LATIN-1文字としてエンコードおよびデコードします。

ALPN値は、ピアが期待するネットワーク・バイト表現を使用して表現されるようになり、標準の7ビットASCIIベースのStringインスタンスに変更を加える必要はありません。

javax.net.ssl.SSLSocketおよびjavax.net.ssl.SSLEngineのメソッドは、ピアによって送信されたネットワーク・バイト表現のApplicationProtocol String値を返します。

ただし、U+007Fより上の文字を含むUnicodeデータがある場合、アプリケーションは、SunJSSEプロバイダに依存してUnicode文字を自動的にエンコードまたはデコードするのではなく、送信または受信する前にUnicodeデータをバイト配列に正しくエンコードまたはデコードする必要があります。または、セキュリティ・プロパティjdk.tls.alpnCharsetをUTF-8に設定して、前の動作に戻すこともできます。

ALPN値と予想される値を比較するには、それらをバイト配列に変換して比較できます。

次の例で期待されるALPN値は、文字列http/1.1とUTF-8でエンコードされた文字列(16進) 0xABCD0xABCE0xABCF (Meetei Mayek文字"HUK UN I")です。この例では、ALPN値をISO-8859-1のバイト配列に変換し、http/1.1をUTF-8のバイト配列に変換して、0xABCD0xABCE0xABCFのバイト配列表現を手動で指定します。

    // Get the ALPN value negotiated by the TLS handshake currently
    // in progress

    String networkString = sslEngine.getHandshakeApplicationProtocol();
    
    // Encode the ALPN value into a byte array with the ISO-8859-1
    // character encoding
        
    byte[] bytes = networkString.getBytes(StandardCharsets.ISO_8859_1);
  
    String HTTP1_1 = "http/1.1";
    
    // Encode the String "http/1.1" into a byte array with the
    // UTF-8 character set
    
    byte[] HTTP1_1_BYTES = HTTP1_1.getBytes(StandardCharsets.UTF_8);
    
    // Create a byte array representing the Unicode characters 0xABCD,
    // 0xABCE, and 0xABCF, which are the Meetei Mayek letters "HUK UN I"

    byte[] HUK_UN_I_BYTES = new byte[] {
        (byte) 0xab, (byte) 0xcd,
        (byte) 0xab, (byte) 0xce,
        (byte) 0xab, (byte) 0xcf};
        
    // Test whether the APLN value is equal to "http/1.1" or
    // 0xABCD0xABCE0xABCF

    if ((Arrays.compare(bytes, HTTP1_1_BYTES) == 0 ) ||
        Arrays.compare(bytes, HUK_UN_I_BYTES) == 0) {
        // ...
    }

または、ALPN値が特定の文字セット(UTF-8など)を使用してStringからエンコードされたことがわかっている場合は、ALPN値をメソッドString.equals()と比較できます。比較する前に、ALPN値をUnicode Stringにデコードする必要があります。

    String unicodeString = new String(bytes, StandardCharsets.UTF_8);
    if (unicodeString.equals(HTTP1_1) ||
        unicodeString.equals("\uabcd\uabce\uabcf")) {
        // ...
    }

メソッドjavax.net.ssl.SSLParameters.setApplicationProtocols(String[] protocols)の場合、そのString引数をピアが期待するネットワーク・バイト表現に変換する必要があります。たとえば、ピアがUTF-8のALPN値を期待している場合は、それをUTF-8のバイト配列に変換してから、バイト指向のStringとして格納する必要があります:

// Convert Meetei Mayek letters "HUK UN I" (in hexadecimal, 0xABCD0xABCE0xABCF)
// to a byte array with UTF-8
byte[] bytes = "\uabcd\uabce\uabcf".getBytes(StandardCharsets.UTF_8);

// Create a byte-oriented String with ISO-8859-1
String HUK_UN_I = new String(bytes, StandardCharsets.ISO_8859_1);

// GREASE value {0x8A, 0x8A}
String rfc7301Grease8A = "\u008A\u008A";
SSLParameters p = sslSocket.getSSLParameters();
p.setApplicationProtocols(new String[] {"h2", "http/1.1", rfc7301Grease8A, HUK_UN_I});
sslSocket.setSSLParameters(p);

TLSハンドシェイクの開始時に、クライアントがALPN値のリストをサーバーに送信します。サーバーは、使用できる値を選択し、認識できない値を無視します。ただし、欠陥のあるTLS実装では、認識されないALPN値が拒否されることがあり、これによりハンドシェイクが続行できなくなる可能性がありますが、ALPN値が認識されているクライアントやサーバーは引き続き接続できるため、開発者または管理者はこの欠陥に気付かない場合があります。

そのため、TLS仕様では、Generate Random Extensions And Sustain Extensibility (GREASE)値が導入されました。これは、認識されない値をピアが正しく処理できるようにTLS実装がランダムにアドバタイズできる、予約済みの一連のTLSプロトコル値です。

前述の例では、メソッドsetApplicationProtocolsrfc7301Grease8Aに渡された値の1つがGREASE値です。ピアはそれを拒否するのではなく無視します。

ALPN関連のクラスとメソッド

これらのクラスとメソッドは、アプリケーション層プロトコル・ネゴシエーション(ALPN)の使用時に使用されます。

使用するクラスとメソッド

SSLEngineSSLSocketには同じALPN関連メソッドが含まれており、それらには同じ機能があります。

クラス メソッド 用途
SSLParameters public String[] getApplicationProtocols(); クライアント側とサーバー側: 各プロトコル・セットを含むString配列を返すには、このメソッドを使用します。
SSLParameters public void setApplicationProtocols([] protocols);

クライアント側: サーバーに選択可能なプロトコルを設定するには、このメソッドを使用します。

サーバー側: サーバーで使用可能なプロトコルを設定するには、このメソッドを使用します。String配列には、プロトコルが優先度順に含まれている必要があります。

SSLEngine

SSLSocket
public String getApplicationProtocol(); クライアント側とサーバー側: 接続のために選択されているプロトコルを含むStringを返すには、TLSプロトコル・ネゴシエーションが完了したに、このメソッドを使用します。
SSLEngine

SSLSocket
public String getHandshakeApplicationProtocol(); クライアント側とサーバー側: 接続のために選択されているプロトコルを含むStringを返すには、ハンドシェークにこのメソッドを使用します。ハンドシェークの前または後にこのメソッドが呼び出された場合は、nullが返されます。このメソッドの呼出し方法の詳細は、ハンドシェーク中のネゴシエーション済ALPN値の特定を参照してください。
SSLEngine

SSLSocket
public void setHandshakeApplicationProtocolSelector(BiFunction,String> selector) サーバー側: コールバック関数を登録するには、このメソッドを使用します。その後、プロトコルや暗号化方式群など、利用可能な任意の情報に基づいて、アプリケーション・プロトコル値をコールバック内で設定できます。このメソッドの使用方法の詳細は、サーバーでのカスタムALPNの設定を参照してください。

JSSEのトラブルシューティング

このセクションにはJSSEのトラブルシューティングに関する情報が含まれます。まず、一般的な構成の問題とそれらの解決方法を説明し、次に、役に立つデバッグ・ユーティリティについて説明します。

構成の問題

構成に関するいくつかの一般的問題のための解決策。

SSLHandshakeException: 使用可能な認証方式なし、ハンドシェーク失敗

問題: サーバーが次の例外をスローします。

javax.net.ssl.SSLHandshakeException: No available authentication scheme

クライアントは次の致命的アラートを受信します。

javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure

原因: TLSv1.3がプロトコル・バージョンとして選択されている場合、サーバーのキー・マネージャで使用できるのがDSA証明書のみであると、サーバーはこのSSLHandshakeExceptionをスローします。これをkeytoolコマンドで確認します。testkeys.dsaは、使用中のキーストア名に変更してください。

keytool -list -keystore testkeys.dsa -v

Enter keystore password:
Keystore type: PKCS12
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: localhost
Creation date: Sep 19, 2018
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]: Owner: CN=localhost, OU=Widget, O=Ficticious, L=Sunnyvale, ST=CA, C=US Issuer: CN=localhost, OU=Widget, O=Ficticious, L=Sunnyvale, ST=CA, C=US

...deleted...

Signature algorithm name: SHA256withDSA
Subject Public Key Algorithm: 2048-bit DSA key

...deleted...

解決法: RSAまたはEC公開キーを含むように証明書を更新します。

ハンドシェーク時のCertificateException

問題: TLS/DTLS接続のネゴシエーション中に、クライアントまたはサーバーがCertificateExceptionをスローします。

原因1: 一般に、リモート側がローカル側に不明な証明書を送信することが原因です。

解決法1: このタイプの問題をデバッグするもっともよい方法は、デバッグをオンにして(「デバッグ・ユーティリティ」を参照)、証明書のロード時およびネットワーク接続経由での証明書の受信時に観察することです。多くの場合、間違ったトラスト・ファイルをロードしたため、受信した証明書がトラスト・メカニズムにとって不明です。

次の項を参照してください。

原因2: システム・クロックが正しく設定されていません。この場合、認識された時間が証明書の有効期間外になっている可能性があります。トラストストアからの有効な証明書と置き換えないかぎり、システムはこの証明書を無効とみなすため、例外をスローします。

解決法2: システム・クロックの時間を修正します。

実行時例外: SSL Service Not Available

問題: JSSEを使用するプログラムを実行すると、SSLサービスが利用できないという例外が発生します。たとえば、次のような例外がスローされます。

    Exception in thread "main" java.net.SocketException:
        no SSL Server Sockets
    
    Exception in thread "main":
        SSL implementation not available

原因: キーストア上の誤ったパスワードやキーストアの破損などのため、SSLContextの初期化で問題が発生しました(かつてあるベンダーが不明な形式のキーストアを出荷し、それが原因でこのタイプのエラーが発生しました。)

解決法: 初期化パラメータを確認します。指定したキーストアが有効であり、指定したパスワードが正しいことを確認します。これをチェックできる1つの方法は、keytoolを使用して、キーストアと関連の内容を調べることです。Java Platform, Standard Editionツール・リファレンスkeytoolを参照してください。

実行時例外: "No available certificate corresponding to the SSL cipher suites which are enabled"

問題: 単純なSSLサーバー・プログラムを実行しようとすると、次の例外がスローされます。

    Exception in thread "main" javax.net.ssl.SSLException:
        No available certificate corresponding to the SSL cipher suites which are enabled...

原因: 様々な暗号化方式群では特定のタイプのキー・データが必要です。たとえば、RSA暗号化方式群が有効になっている場合、キーストアでRSAのkeyEntryを有効にする必要があります。該当するキーを使用できない場合、その暗号化方式群を使用することはできません。有効になっているすべての暗号化方式群のキー・エントリが使用できない場合、この例外がスローされます。

解決法: 様々な暗号化方式群タイプのキー・エントリを作成するか、匿名の暗号化方式群を使用します。匿名の暗号化方式群には、MITM (man-in-the-middle)攻撃に対して脆弱であるため、潜在的に危険です。『RFC 5246: The Transport Layer Security (TLS) Protocol, Version 1.2』を参照してください。

正しいキーストアおよび証明書を渡す方法を学習するには、次のセクションを参照してください。

実行時例外: No Cipher Suites in Common

問題1: ハンドシェーク時にクライアントやサーバーがこの例外をスローします。

原因1: TLS接続の両側が共通の暗号化方式群について合意している必要があります。クライアントの暗号化方式群セットとサーバーの暗号化方式群セットの共通点がない場合、この例外がスローされます。

解決法1: 有効な暗号化方式群を構成し、共通の暗号化方式群を含めます。さらに、非対称の暗号化方式群に適切なkeyEntryが提供されるようにします。この項の実行時例外: "No available certificate corresponding to the SSL cipher suites which are enabled"も参照してください。

問題2: DSAベースの証明書しかないサーバーのファイルにMozilla FirefoxやMicrosoft Internet Explorerでアクセスすると、共通の暗号化方式群がないことを示す実行時例外が発生します。

原因2: デフォルトでは、keytoolで作成されたkeyEntriesはDSA公開キーを使用します。キーストア内にDSAのkeyEntriesのみが存在する場合、使用できるのはDSAベースの暗号化方式群のみです。デフォルトでは、FirefoxとInternet ExplorerはRSAベースの暗号化方式群のみを送信します。クライアントとサーバーの暗号化方式群セットの共通点がないため、この例外がスローされます。

解決法2: FirefoxやInternet Explorerと対話するには、RSAベースのキーを使用する証明書を作成してください。そのためには、keytool使用時に-keyalg RSAオプションを指定します。たとえば:

keytool -genkeypair -alias duke -keystore testkeys -keyalg rsa
ClientHelloメッセージの送信後ソケットが切断される

問題: ソケットが接続を試み、ClientHelloメッセージを送信すると、ただちに切断されます。

原因: TLS/TLSサーバーの中には、理解できない形式や、サポートしていないプロトコル・バージョン番号でClientHelloメッセージを受信した場合、接続を切断するものがあります。

解決法: クライアント側で、有効なプロトコルの調整を試みてください。これには、次のシステム・プロパティやメソッドの変更や呼び出しが含まれます。

後方互換性のため、一部のTLS実装(SunJSSEなど)はTLSのClientHelloメッセージをSSLv2のClientHello形式にカプセル化して送信できます。SunJSSEプロバイダはこの機能をサポートしています。この機能を使用する場合は、必要に応じて「SSLv2Hello」を有効なプロトコル・リストに追加します。(「JDKプロバイダ」で「プロトコル」を参照してください。ここでは、SunJSSEプロバイダでデフォルトで有効になっているプロトコルがリストされています。)

TLS RFC標準で、実装は、両側で使用されている最新バージョンとネゴシエーションする必要がありますが、認識できないバージョンを提示されると、一部の非準拠実装は単にハングアップします。たとえば、SSLv3のみを認識する一部の古いサーバー実装は、TLSv1.2を要求されると、シャットダウンします。この状況では、TLSバージョンのフォールバック・スキームを使用することを検討してください。

  1. サーバーでTLSv1.2を理解しない場合、TLSv1.2からTLSv1.1にフォールバックします。
  2. 前のステップが機能しない場合は、TLSv1.1からTLSv1.0にフォールバックします。

たとえば、クライアントの有効なプロトコル・リストがTLSv1、TLSv1.1およびTLSv1.2の場合、一般的なTLSバージョンのフォールバック・スキームが次のように見えます。

  1. サーバーに接続を試みます。サーバーがTLS接続要求をただちに拒否した場合は、ステップ2に進みます。
  2. 有効なプロトコル・リスト内の最高のプロトコル・バージョン(最初の失敗の場合TLSv1.2など)を削除して、バージョン・フォールバック・スキームを試行します。
  3. 再度サーバーに接続を試みます。サーバーが接続を拒否した場合、サーバーがフォールバックできるバージョンがない場合を除いて、ステップ2に進みます。
  4. 接続が失敗し、SSLv2Helloが有効なプロトコル・リストにない場合は、有効なプロトコル・リストを復元して、SSLv2Helloを有効にします。(たとえば、有効なプロトコル・リストは、SSLv2Hello、TLSv1、TLSv1.1およびTLSv1.2となる必要があります。)ステップ1から再度開始します。

ノート:

以前のバージョンにフォールバックすることは、通常、セキュリティ強度が弱いプロトコルにダウングレードすることを意味します。本当に必要で、サーバーが上位のプロトコル・バージョンをサポートしていないことが確実にわかっている場合でないかぎり、フォールバック・スキームを使用することはお薦めしません。

ノート:

一部のサーバーはSSLv3無効化の過程でSSLv2Helloも無効にしていますが、それは、SSLv2Helloがアクティブなクライアント(JDK 6u95)との通信が失敗することを意味します。JDK 7以降では、デフォルトで、SSLv2Helloはクライアント上では無効、サーバー上では有効です。
必要なアルゴリズムをサポートするJCAプロバイダをSunJSSEが見つけられず、NoSuchAlgorithmExceptionが発生する

問題: ハンドシェークが試行され、必要なアルゴリズムが見つからない場合は失敗します。例は次のとおりです。

Exception in thread ...deleted...
    ...deleted...
    Caused by java.security.NoSuchAlgorithmException: Cannot find any
        provider supporting RSA/ECB/PKCS1Padding

または

Caused by java.security.NoSuchAlgorithmException: Cannot find any
    provider supporting AES/CBC/NoPadding

原因: SunJSSEは、その暗号化アルゴリズムすべてでJCEを使用します。SunJCEプロバイダがProviderメカニズムから登録解除されており、JCEからの代替実装を利用できない場合、この例外がスローされます。

解決法: プロバイダがProviderインタフェースで登録されていることをチェックして、SunJCEが使用可能であることを確認します。SSL接続のコンテキストで次のコードを実行してみます。

import javax.crypto.*;

System.out.println("=====Where did you get AES=====");
Cipher c = Cipher.getInstance("AES/CBC/NoPadding");
System.out.println(c.getProvider());
SNI拡張が必要な仮想ホストのWebサーバーからアプリケーション・リソースを取得すると例外がスローされる

問題: WebサーバーからTLSでアプリケーション・リソースを取得しようとしたときにExceptionを受け取り、使用中のWebサーバーが仮想ホストとして実装されていて、その仮想ホストを識別するために有効なServer Name Indication (SNI)拡張が必要な場合(Apache HTTPサーバーなど)、Webサーバーが正しく構成されていないことがあります。

原因: Java SE では、JSSEクライアントのSNI拡張がサポートされているため、要求された仮想サーバーのホスト名は、TLSハンドシェーク中にクライアントからサーバーに送信された最初のメッセージに含まれています。要求されたホスト名(Server Name Indication)が、仮想ホストの構成に指定されているはずの期待されるサーバー名と一致しない場合、サーバーはクライアントの接続要求を拒否することがあります。これにより、TLSハンドシェークの認識されない名前の警告がトリガーされ、Exceptionがスローされます。

解決策: 問題の原因がjavax.net.ssl.SSLProtocolException: handshake alert: unrecognized_nameの場合、SNIのための仮想ホスト構成が正しくない可能性があります。Apache HTTPサーバーを使用している場合、仮想ホストの構成については、名前ベースの仮想ホストのサポートを参照してください。特に、<VirtualHost>ブロック内でServerNameディレクティブが正しく構成されていることを確認してください。

次を参照してください。

RC4暗号化方式群がDTLS用に構成されている場合のIllegalArgumentException

問題: SSLEngine.setEnabledCipherSuites(String[] suites)メソッドでRC4暗号化方式群アルゴリズムが指定されており、SSLEngineがDTLSエンジンである場合に、IllegalArgumentException例外がスローされます。

sslContext = SSLContext.getInstance("DTLS");

// Create the engine
SSLEngine engine = sslContext.createSSLengine(hostname, port);

String enabledSuites[] = { "SSL_RSA_WITH_RC4_128_SHA" };
engine.setEnabledCipherSuites(enabledSuites);

原因: DTLSバージョン1.0DTLSバージョン1.2によると、RC4暗号化方式群はDTLSとは使用できません。

解決法: DTLS接続にはRC4ベースの暗号化方式群は使用しないでください。Javaセキュリティ標準アルゴリズム名JSSE暗号化方式群の名前を参照してください。

デバッグ・ユーティリティ

SunJSSEプロバイダは動的なデバッグ・トレースをサポートしています。これは、セキュリティ・ライブラリの問題をデバッグするメカニズムに似ています。一般的なJava動的デバッグ・トレースのサポートにはjava.security.debugシステム・プロパティを使用してアクセスしますが、JSSE固有の動的デバッグ・トレースのサポートにはjavax.net.debugシステム・プロパティを使用してアクセスします。

ノート:

現在、SunJSSEプロバイダではデバッグ・ユーティリティが使用されています。他のプロバイダがデバッグ・ユーティリティを使用する保証はありません。他のプロバイダでデバッグ・ユーティリティがサポートされていても、実装および出力は異なる場合があります。デバッグ・ユーティリティが今後も存在すること、または将来のリリースでも変更されない(たとえば、同じオプションや出力形式が使用される)ことの保証はされません。

JSSE動的デバッグ・ユーティリティのオプションを表示するには、javaコマンドで次のコマンド行オプションを使用します(ここで、MyAppは既存のJavaアプリケーションです)。

java -Djavax.net.debug=help MyApp

ノート:

  • MyAppアプリケーションは、デバッグのヘルプ情報が表示されると動作しなくなりますが、これはヘルプコードによりアプリケーションが終了するためです。

  • ユーティリティがデバッグするように設計されたクラスを使用しないプログラムの実行中に、いずれかの動的デバッグ・ユーティリティで値helpを指定しても、デバッグ・オプションは得られません。

現在のオプションは次のとおりです。

  • all: すべてのデバッグを有効にします
  • ssl: SSLデバッグを有効にします

次をsslオプションとともに使用して、出力するデバッグ情報の種類を選択できます。

  • defaultctx: デフォルトのSSL初期化を出力します
  • handshake: 各ハンドシェーク・メッセージを出力します
  • keygen: キー生成データを出力します
  • keymanager: キー・マネージャのトレースを出力します
  • pluggability: プラグイン可能性のトレースを出力します
  • record: レコードごとのトレースを有効にします
  • respmgr: ステータス・レスポンス・マネージャのトレースを出力します
  • session: セッション・アクティビティを出力します
  • sessioncache: セッション・キャッシュのトレースを出力します
  • sslctx: SSLContextのトレースを出力します
  • trustmanager: トラスト・マネージャのトレースを出力します

handshakeオプションによって生成されるメッセージは、次のオプションで拡張できます:

  • data: 各ハンドシェーク・メッセージの16進ダンプ
  • verbose: ハンドシェーク・メッセージの詳細出力

recordオプションによって生成されるメッセージは、次のオプションで拡張できます:

  • plaintext: レコードのプレーン・テキストの16進ダンプ
  • packet: raw SSL/TLSパケットを出力します

JSSE固有の動的デバッグ・トレースを有効にするには、javax.net.debugシステム・プロパティ(「java.security.Securityプロパティの指定方法」を参照)の値をallまたはsslに設定します。sslオプションで追加のオプションを指定するには、sslオプションの後に指定します。オプションをセパレータで区切る必要はありませんが、コロン(:)やカンマ(,)などの区切り文字を使用すると読みやすくなります。どのセパレータでも使用でき、オプション・キーワードの順序も重要ではありません。

このデバッグ情報の参照方法の概要は、「TLS接続のデバッグ」を参照してください。

次に、javax.net.debugシステム・プロパティの使用例を示します。

  • デバッグ・メッセージをすべて表示するには:

    java -Djavax.net.debug=all MyApp        
  • 各ハンドシェーク・メッセージを16進ダンプで表示するには、次のように入力します(コロンはオプションです)。

    java -Djavax.net.debug=ssl:handshake:data MyApp
    
  • 各ハンドシェーク・メッセージを16進ダンプで表示し、トラスト・マネージャのトレースを出力するには、次のように入力します(コロンはオプションです)。

    java -Djavax.net.debug=ssl,handshake,data,trustmanager MyApp
    
TLS接続のデバッグ

TLS接続における問題は、理解するのが難しい場合があり、実際に送受信されたメッセージが明確でない場合は特に面倒です。JSSEにはデバッグ機能が組み込まれており、javax.net.debugシステム・プロパティによって起動されます。javax.net.debugシステム・プロパティの詳細は、デバッグ・ユーティリティを参照してください。

この項では、基本的なTLS 1.3ハンドシェークのデバッグ出力の概要を説明します。TLSプロトコルの詳細は、『RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3』を参照してください。

ノート:

  • TLSハンドシェークの使用可能なすべての組合せとプロトコルに関するデバッグ出力情報は、このガイドの対象外です。TLSの特定バージョンについての詳細情報は、関連するRFCを参照してください。サポートされているSSL/TLS/DTLSプロトコルとそれぞれの関連するRFCへのリンク一覧は、「TLSおよびDTLSプロトコル」を参照してください。

  • 出力は標準的なものではなく、リリースによって変化することがあります。

この例では、デフォルトのJSSE X509KeyManagerとX509TrustManagerを使用して、接続時に使用されたキーおよび信頼できる証明書に関するデバッグ情報も出力しています。Java SE 8ドキュメントのJSSEサンプル・コードからClassFileServerおよびSSLSocketClientWithClientAuthサンプル・アプリケーションを使用します。ClassFileServerは、クライアント認証を必要とする簡単なHTTPSサーバーです。SSLSocketClientWithClientAuthでは、SSLSocketクラスをHTTP要求を送信するクライアントとして使用し、HTTPSサーバーから応答を受け取る方法を示します。より簡単にするため、ClassFileServerSSLSocketClientWithClientAuthはいずれも同じホストから実行します。

ローカルホストでのClassFileServerの実行

次のコマンドは、ClassFileServerアプリケーションをlocalhost、ポート2002で実行します。

java \
  -Djavax.net.ssl.trustStore=/my_home_directory/jssesamples/samples/samplecacerts \
  -Djavax.net.ssl.trustStorePassword=changeit \
  ClassFileServer 2002 \
  /my_home_directory/jssesamples/samples/ \
  TLS true

ローカルホストでのSSLSocketClientWithClientAuthの実行

次のコマンドは、SSLSocketClientWithClientAuthアプリケーションをlocalhost、ポート2002で実行します。アプリケーションが、前のコマンドで起動したHTTPSサーバーに接続します。HTTPSリクエストをサーバーに送信し、応答を受信します。このコマンドでは、システム・プロパティjavax.net.debugの値をallに設定して、すべてのデバッグを有効にします。

java -Djavax.net.debug=all -Djavax.net.ssl.trustStore=/my_home_directory/jssesamples/samples/samplecacerts SSLSocketClientWithClientAuth localhost 2002 /index.html

デバッグ出力形式

デバッグ出力の各行には次の情報が含まれ、各行は縦棒(|)で区切られています。

  • ロガー名(System.getLogger("javax.net.ssl"))
  • デバッグ・レベル(System.Logger.Level)
  • スレッドID(Thread.currentThread().getId())
  • スレッド名(Thread.currentThread().getName())
  • 日付と時刻
  • 呼出し側(ロギング呼出しの場所)
  • メッセージ

クライアント側とサーバー側で有効な暗号化方式群の特定

システム・プロパティjdk.tls.client.cipherSuitesおよびjdk.tls.server.cipherSuitesの値を確認して、デフォルトで有効な暗号化方式群を特定します。これらのシステム・プロパティの詳細は、「デフォルトで有効な暗号化方式群の指定」を参照してください。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:46.990 EDT|SSLContextImpl.java:427|System property jdk.tls.client.cipherSuites is set to 'null'
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.026 EDT|SSLContextImpl.java:427|System property jdk.tls.server.cipherSuites is set to 'null'
...

これらのシステム・プロパティの値はnullであるため、デフォルトで有効な暗号化方式群は、SunJSSEプロバイダがデフォルトで有効化する暗号化方式群です(「JDKプロバイダ・ドキュメント」「SunJSSEプロバイダ」を参照)。

jdk.tls.keyLimitsの値を確認して、アルゴリズムで特定のキー・セットを使用して暗号化できるデータ量の制限を特定します(「アルゴリズムでキー・セットを使用して暗号化可能なデータ量の制限」を参照)。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.124 EDT|SSLCipher.java:436|jdk.net.keyLimits:  entry = AES/GCM/NoPadding KeyUpdate 2^37. AES/GCM/NOPADDING:KEYUPDATE = 137438953472
...

デバッグ出力には、サポート対象外および無効の暗号化方式群がリストされます。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.150 EDT|SSLContextImpl.java:401|Ignore disabled cipher suite: TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
javax.net.ssl|ALL|01|main|2018-08-18 01:04:47.150 EDT|SSLContextImpl.java:410|Ignore unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.152 EDT|SSLContextImpl.java:401|Ignore disabled cipher suite: TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
...

X509KeyManagerの初期化

X509KeyManagerが初期化されます。「duke」と呼ばれる被認証者のKeyStoreに1つのkeyEntryがあることが検出されます。このアプリケーションが自分自身を認証する必要がある場合、X509KeyManagerはkeyEntryのリストを検索して適切なクレデンシャルを見つけます。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.224 EDT|SunX509KeyManagerImpl.java:164|found key for : duke (
  "certificate" : {
    "version"            : "v1",
    "serial number"      : "3B 0A FA 66",
    "signature algorithm": "MD5withRSA",
    "issuer"             : "CN=Duke, OU=Java Software, O="Sun Microsystems, Inc.", L=Cupertino, ST=CA, C=US",
    "not before"         : "2001-05-22 19:46:46.000 EDT",
    "not  after"         : "2011-05-22 19:46:46.000 EDT",
    "subject"            : "CN=Duke, OU=Java Software, O="Sun Microsystems, Inc.", L=Cupertino, ST=CA, C=US",
    "subject public key" : "RSA"}
)
...

TrustManagerの初期化

TrustManagerが初期化され、様々な認証局(CA)の複数の証明書がトラストストア内で検出されます。localhostという識別名の自己署名付き証明書も検出されます。トラストストア内の信頼できる証明書のチェーンに戻る有効なクレデンシャル(証明書)を提示するサーバーは、そのサーバー自身が信頼されます。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.229 EDT|TrustStoreManager.java:112|trustStore is: /my_home_directory/jssesamples/samples/samplecacerts
trustStore type is: pkcs12
trustStore provider is: 
the last modified time is: Tue Dec 11 06:43:38 EST 2012
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.229 EDT|TrustStoreManager.java:311|Reload the trust store
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.264 EDT|TrustStoreManager.java:318|Reload trust certs
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.266 EDT|TrustStoreManager.java:323|Reloaded 32 trust certs
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.322 EDT|X509TrustManagerImpl.java:79|adding as trusted certificates (
  "certificate" : {
    "version"            : "v1",
    "serial number"      : "00 9B 7E 06 49 A3 3E 62 B9 D5 EE 90 48 71 29 EF 57",
    "signature algorithm": "SHA1withRSA",
    "issuer"             : "CN=VeriSign Class 3 Public Primary Certification Authority - G3, OU="(c) 1999 VeriSign, Inc. - For authorized use only", OU=VeriSign Trust Network, O="VeriSign, Inc.", C=US",
    "not before"         : "1999-09-30 20:00:00.000 EDT",
    "not  after"         : "2036-07-16 19:59:59.000 EDT",
    "subject"            : "CN=VeriSign Class 3 Public Primary Certification Authority - G3, OU="(c) 1999 VeriSign, Inc. - For authorized use only", OU=VeriSign Trust Network, O="VeriSign, Inc.", C=US",
    "subject public key" : "RSA"},
  "certificate" : {
    "version"            : "v1",
    "serial number"      : "61 70 CB 49 8C 5F 98 45 29 E7 B0 A6 D9 50 5B 7A",
    "signature algorithm": "SHA1withRSA",
    "issuer"             : "CN=VeriSign Class 2 Public Primary Certification Authority - G3, OU="(c) 1999 VeriSign, Inc. - For authorized use only", OU=VeriSign Trust Network, O="VeriSign, Inc.", C=US",
    "not before"         : "1999-09-30 20:00:00.000 EDT",
    "not  after"         : "2036-07-16 19:59:59.000 EDT",
    "subject"            : "CN=VeriSign Class 2 Public Primary Certification Authority - G3, OU="(c) 1999 VeriSign, Inc. - For authorized use only", OU=VeriSign Trust Network, O="VeriSign, Inc.", C=US",
    "subject public key" : "RSA"},
...
  "certificate" : {
    "version"            : "v1",
    "serial number"      : "41 00 44 46",
    "signature algorithm": "MD5withRSA",
    "issuer"             : "CN=localhost, OU=Widget Development Group, O="Ficticious Widgets, Inc.", L=Sunnyvale, ST=CA, C=US",
    "not before"         : "2004-07-22 18:48:38.000 EDT",
    "not  after"         : "2011-05-22 18:48:38.000 EDT",
    "subject"            : "CN=localhost, OU=Widget Development Group, O="Ficticious Widgets, Inc.", L=Sunnyvale, ST=CA, C=US",
    "subject public key" : "RSA"},
...

その他の初期化の実行

次の例では、その他の初期化コードを実行してから、サーバーに接続します。

javax.net.ssl|ALL|01|main|2018-08-18 01:04:47.326 EDT|SSLContextImpl.java:115|trigger seeding of SecureRandom
javax.net.ssl|ALL|01|main|2018-08-18 01:04:47.524 EDT|SSLContextImpl.java:119|done seeding of SecureRandom
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.562 EDT|HandshakeContext.java:291|Ignore unsupported cipher suite: TLS_AES_128_GCM_SHA256 for TLS12
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.563 EDT|HandshakeContext.java:291|Ignore unsupported cipher suite: TLS_AES_256_GCM_SHA384 for TLS12
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.577 EDT|HandshakeContext.java:291|Ignore unsupported cipher suite: TLS_AES_128_GCM_SHA256 for TLS11
...

デバッグ出力には、無効、サポート対象外、または使用不可の拡張および署名アルゴリズムも表示されます。

javax.net.ssl|WARNING|01|main|2018-08-18 01:04:47.695 EDT|ServerNameExtension.java:255|Unable to indicate server name
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.695 EDT|SSLExtensions.java:235|Ignore, context unavailable extension: server_name
javax.net.ssl|WARNING|01|main|2018-08-18 01:04:47.703 EDT|SignatureScheme.java:282|Signature algorithm, ed25519, is not supported by the underlying providers
javax.net.ssl|WARNING|01|main|2018-08-18 01:04:47.704 EDT|SignatureScheme.java:282|Signature algorithm, ed448, is not supported by the underlying providers
javax.net.ssl|ALL|01|main|2018-08-18 01:04:47.724 EDT|SignatureScheme.java:358|Ignore disabled signature sheme: rsa_md5
javax.net.ssl|INFO|01|main|2018-08-18 01:04:47.724 EDT|AlpnExtension.java:161|No available application protocols
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.724 EDT|SSLExtensions.java:235|Ignore, context unavailable extension: application_layer_protocol_negotiation
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.725 EDT|SSLExtensions.java:235|Ignore, context unavailable extension: cookie
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.763 EDT|SSLExtensions.java:235|Ignore, context unavailable extension: renegotiation_info
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.763 EDT|PreSharedKeyExtension.java:606|No session to resume.
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.763 EDT|SSLExtensions.java:235|Ignore, context unavailable extension: pre_shared_key
...

ClientHelloメッセージの送信

クライアントは、サーバーにClientHelloメッセージを送信します。このメッセージでは、次の項目を指定します。

  • クライアント・バージョン: TLS 1.3の場合、この値は固定値TLSv1.2です。TLS 1.3では、このフィールドではなく、supported_versions拡張を使用してプロトコル・バージョンをネゴシエートします

  • ランダム: 暗号化アルゴリズムの初期化に使用される乱数値

  • セッションID: 以前のバージョンのTLSは、このIDを使用してセッション再開機能をサポートします

  • 暗号化方式群: クライアントが要求する暗号化方式群のリスト。有効な暗号化方式群に応じて、暗号化方式群名には様々な組合せがあり、一部はTLSv1.3専用、その他はTLSv1.2以前を対象としています

  • 圧縮方法: TLS 1.3の場合は、値0が指定されている必要があります

  • 拡張:

    • status_request: クライアントがOCSPを要求します(「クライアント主導型OCSPとOCSPステープリング」を参照)

    • supported_groups: クライアントがキー交換でサポートしている名前付きグループをリストします。これらの名前付きグループには、楕円曲線(ECDHE)グループと有限フィールド(DHE)グループが含まれます。ECDHEまたはDHEキー交換を使用している場合、ClientHelloメッセージにはこのメッセージが含まれている必要があります。

    • ec_point_formats: クライアントが解析可能な楕円曲線のポイント形式をリストします。この例で、クライアントが解析できるのは非圧縮ポイント形式のみです。その他の形式には、圧縮形式とansiX962_compressed_primeがあります。

    • 署名アルゴリズム: CertificateVerifyメッセージで使用できる署名アルゴリズムをリストします

    • signature_algorithms_cert: デジタル署名で使用できる署名アルゴリズムをリストします

    • status_request_v2: クライアントが複数の証明書ステータスのメソッドを指定およびサポートできるようにします。この拡張はTLS 1.3では非推奨です。

    • extended_master_secret: TLS 1.2以前では、この拡張を使用して、両方の側が元のプロトコル・バージョンで実行されていたよりも多くのハンドシェーク・トランスクリプトをマスター・シークレットにダイジェストすることを要求します(RFC 7627を参照)。この拡張は、TLS 1.2ハンドシェークがネゴシエートされる場合に備えて、TLS 1.3ハンドシェークに含まれています。

    • supported_versions: クライアントでサポートされているTLSバージョンをリストします。具体的には、クライアントがTLS 1.3を要求する場合、クライアント・バージョン・フィールドの値はTLSv1.2で、この拡張の値はTLSv1.3になります。クライアントがTLS 1.2を要求する場合、クライアント・バージョン・フィールドの値はTLSv1.2で、この拡張は存在しないか、値がTLSv1.3ではなくTLSv1.2になります。

    • psk_key_exchange_modes: 事前共有キー(PSK)で使用できるキー交換モードをリストします。この例で、クライアントがサポートしているモードは、(EC)DHEを伴うPSKキー確立(psk_dhe_ke)です。このモードでは、クライアントとサーバーがkey_share拡張の値を提供する必要があります。

    • key_share: キー交換の暗号化パラメータをリストします。client_sharesという名前のフィールドに、このリストが格納されています。このリストの各項目には、2つのフィールド(groupとkey_exchange)があります。この例には、楕円曲線secp256r1のキー交換情報が含まれています。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.769 EDT|ClientHello.java:633|Produced ClientHello handshake message (
"ClientHello": {
  "client version"      : "TLSv1.2",
  "random"              : "64 CF 68 A1 CF AB B1 6F 43 F6 DE 1B 49 49 DE 5A 42 9A 71 DD CB 9A E3 9F 32 00 E8 87 7A 00 DA C6",
  "session id"          : "02 0D BE 1B A4 5F F2 E8 B6 31 9D A4 EF F3 22 84 C3 58 0B 5C C0 57 0F A5 6D 8A 83 EB DC DA B1 B6",
  "cipher suites"       : "[TLS_AES_128_GCM_SHA256(0x1301), TLS_AES_256_GCM_SHA384(0x1302), TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xC02C), TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xC02B), TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384(0xC030), TLS_RSA_WITH_AES_256_GCM_SHA384(0x009D), TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384(0xC02E), TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384(0xC032), TLS_DHE_RSA_WITH_AES_256_GCM_SHA384(0x009F), TLS_DHE_DSS_WITH_AES_256_GCM_SHA384(0x00A3), TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(0xC02F), TLS_RSA_WITH_AES_128_GCM_SHA256(0x009C), TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256(0xC02D), TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256(0xC031), TLS_DHE_RSA_WITH_AES_128_GCM_SHA256(0x009E), TLS_DHE_DSS_WITH_AES_128_GCM_SHA256(0x00A2), TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384(0xC024), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384(0xC028), TLS_RSA_WITH_AES_256_CBC_SHA256(0x003D), TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384(0xC026), TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384(0xC02A), TLS_DHE_RSA_WITH_AES_256_CBC_SHA256(0x006B), TLS_DHE_DSS_WITH_AES_256_CBC_SHA256(0x006A), TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA(0xC00A), TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA(0xC014), TLS_RSA_WITH_AES_256_CBC_SHA(0x0035), TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA(0xC005), TLS_ECDH_RSA_WITH_AES_256_CBC_SHA(0xC00F), TLS_DHE_RSA_WITH_AES_256_CBC_SHA(0x0039), TLS_DHE_DSS_WITH_AES_256_CBC_SHA(0x0038), TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256(0xC023), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256(0xC027), TLS_RSA_WITH_AES_128_CBC_SHA256(0x003C), TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256(0xC025), TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256(0xC029), TLS_DHE_RSA_WITH_AES_128_CBC_SHA256(0x0067), TLS_DHE_DSS_WITH_AES_128_CBC_SHA256(0x0040), TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA(0xC009), TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xC013), TLS_RSA_WITH_AES_128_CBC_SHA(0x002F), TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA(0xC004), TLS_ECDH_RSA_WITH_AES_128_CBC_SHA(0xC00E), TLS_DHE_RSA_WITH_AES_128_CBC_SHA(0x0033), TLS_DHE_DSS_WITH_AES_128_CBC_SHA(0x0032), TLS_EMPTY_RENEGOTIATION_INFO_SCSV(0x00FF)]",
  "compression methods" : "00",
  "extensions"          : [
    "status_request (5)": {
      "certificate status type": ocsp
      "OCSP status request": {
        "responder_id": <empty>
        "request extensions": {
          <empty>
        }
      }
    },
    "supported_groups (10)": {
      "versions": [secp256r1, secp384r1, secp521r1, sect283k1, sect283r1, sect409k1, sect409r1, sect571k1, sect571r1, secp256k1, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192]
    },
    "ec_point_formats (11)": {
      "formats": [uncompressed]
    },
    "signature_algorithms (13)": {
      "signature schemes": [ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp512r1_sha512, rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512, rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512, dsa_sha256, ecdsa_sha1, rsa_pkcs1_sha1, dsa_sha1]
    },
    "signature_algorithms_cert (50)": {
      "signature schemes": [ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp512r1_sha512, rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512, rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512, dsa_sha256, ecdsa_sha1, rsa_pkcs1_sha1, dsa_sha1]
    },
    "status_request_v2 (17)": {
      "cert status request": {
        "certificate status type": ocsp_multi
        "OCSP status request": {
          "responder_id": <empty>
          "request extensions": {
            <empty>
          }
        }
      }
    },
    "extended_master_secret (23)": {
      <empty>
    },
    "supported_versions (43)": {
      "versions": [TLSv1.3, TLSv1.2, TLSv1.1, TLSv1]
    },
    "psk_key_exchange_modes (45)": {
      "ke_modes": [psk_dhe_ke]
    },
    "key_share (51)": {
      "client_shares": [  
        {
          "named group": secp256r1
          "key_exchange": {
            0000: 04 1F 80 50 D9 C6 03 45   7B 59 0F A7 B6 9E AE 39  ...P...E.Y.....9
            0010: 37 BE B0 5B 09 D8 91 37   72 5D 2B 8E 01 0A 84 56  7..[...7r]+....V
            0020: 99 0D 37 49 8F 92 61 A9   D6 54 E1 3B EE D1 E8 D2  ..7I..a..T.;....
            0030: 92 22 F9 17 CE A7 F8 51   47 C9 1E 5C D6 59 0F 4F  .".....QG..\.Y.O
            0040: 55 
          }
        },
      ]
    }
  ]
}
)
...

実際の送信データと読取りデータの表示

デバッグ出力には、raw出力オブジェクト(この場合はOutputStream)に送信された実際のデータが表示されます。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.770 EDT|SSLSocketOutputRecord.java:217|WRITE: TLS13 handshake, length = 405
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.774 EDT|SSLSocketOutputRecord.java:231|Raw write (
  0000: 16 03 03 01 95 01 00 01   91 03 03 64 CF 68 A1 CF  ...........d.h..
  0010: AB B1 6F 43 F6 DE 1B 49   49 DE 5A 42 9A 71 DD CB  ..oC...II.ZB.q..
  0020: 9A E3 9F 32 00 E8 87 7A   00 DA C6 20 02 0D BE 1B  ...2...z... ....
...

次に、処理が実行される前に入力デバイス(InputStream)から読み取ったrawデータがデバッグ出力に表示されます。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.862 EDT|SSLSocketInputRecord.java:215|READ: TLSv1.2 handshake, length = 155
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.863 EDT|SSLSocketInputRecord.java:474|Raw read (
  0000: 02 00 00 97 03 03 66 24   0F F6 6D 4A 0C 5A A1 23  ......f$..mJ.Z.#
  0010: F6 5D 4B 87 B1 6E AC 13   BB 4D C1 A4 0F F0 2C EF  .]K..n...M....,.
  0020: D7 4F 03 11 19 B1 20 02   0D BE 1B A4 5F F2 E8 B6  .O.... ....._...
...

クライアントがメッセージの送信または読取りを行うときは必ず、送信または読み取られたrawデータと、メッセージ(およびその拡張)の処理内容がデバッグ出力に表示されます。次の各項では、その部分のデバッグ出力は省略されています。

ServerHelloメッセージの読取り

この時点で、TLS 1.3はネゴシエーション済です。サーバーがTLSバージョンを選択し、サーバー・バージョンとsupported_versions拡張の組合せを使用して応答します。この場合、TLSv1.3プロトコルが指定されました。

ServerHelloメッセージでは、次の項目を指定します。

  • サーバー・バージョン: TLS 1.3の場合、この値はTLSv1.2であることが必要です。TLS 1.3では、このフィールドではなく、supported_versions拡張を使用して、ネゴシエートするプロトコル・バージョンを指定します

  • ランダム: 暗号化アルゴリズムの初期化にも使用されます

  • セッションID: TLS 1.3では、ClientHelloメッセージの対応するフィールドと同じ値になります

  • 暗号化方式群: 選択した暗号化方式群(この例では、TLS_AES_128_GCM_SHA256)

  • 圧縮方法: TLS 1.3の場合は、値0が指定されている必要があります

  • 拡張

    • supported_versions: サーバーで使用するTLSバージョンを指定します。TLS 1.3では、サーバーはクライアント・バージョン・フィールドの値ではなく、ClientHelloメッセージのsupported_versions拡張の値を使用して、バージョンのネゴシエーションを行う必要があります。

    • key_share: ECDHEキー交換用の名前付きグループとキーの値

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.863 EDT|SSLSocketInputRecord.java:251|READ: TLSv1.2 handshake, length = 155
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.867 EDT|ServerHello.java:862|Consuming ServerHello handshake message (
"ServerHello": {
  "server version"      : "TLSv1.2",
  "random"              : "66 24 0F F6 6D 4A 0C 5A A1 23 F6 5D 4B 87 B1 6E AC 13 BB 4D C1 A4 0F F0 2C EF D7 4F 03 11 19 B1",
  "session id"          : "02 0D BE 1B A4 5F F2 E8 B6 31 9D A4 EF F3 22 84 C3 58 0B 5C C0 57 0F A5 6D 8A 83 EB DC DA B1 B6",
  "cipher suite"        : "TLS_AES_128_GCM_SHA256(0x1301)",
  "compression methods" : "00",
  "extensions"          : [
    "supported_versions (43)": {
      "selected version": [TLSv1.3]
    },
    "key_share (51)": {
      "server_share": {
        "named group": secp256r1
        "key_exchange": {
          0000: 04 DE 5B 20 0E FD EB 6E   DA 70 C2 D0 FA 0D 4C 53  ..[ ...n.p....LS
          0010: 6D E1 9E 67 77 65 36 AF   B5 EB E6 D2 88 92 9B EE  m..gwe6.........
          0020: E4 97 A3 B3 C1 FB D8 29   3B 92 87 D2 B3 9E 3D AA  .......);.....=.
          0030: 14 99 1E 84 8F C2 E9 E3   E1 AC 9A 12 95 F0 26 B5  ..............&.
          0040: 88 
        }
      },
    }
  ]
}
)
...

セッションが初期化されます。

javax.net.ssl|ALL|01|main|2018-08-18 01:04:47.873 EDT|SSLSessionImpl.java:203|Session initialized:  Session(1534568687873|TLS_AES_128_GCM_SHA256)
...

EncryptedExtensionsメッセージの読取り

ハンドシェークのこの時点で、十分な暗号化情報が交換されているので、ハンドシェークの残りの部分は暗号化して実行されます。

EncryptedExtensionsメッセージには、個々の証明書に固有のものを除いて、暗号化パラメータの決定に必要とされないClientHello拡張に対する応答が含まれます。この例では、クライアントがキー交換でサポートしている名前付きグループのリストが返されます。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.942 EDT|EncryptedExtensions.java:171|Consuming EncryptedExtensions handshake message (
"EncryptedExtensions": [
  "supported_groups (10)": {
    "versions": [secp256r1, secp384r1, secp521r1, sect283k1, sect283r1, sect409k1, sect409r1, sect571k1, sect571r1, secp256k1, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192]
  }
]
)
...

サーバーのCertificateRequestメッセージの読取り

証明書ベースのクライアント認証が必要な場合、サーバーはCertificateRequestメッセージを送信します。このメッセージには、その証明書に必要なパラメータが含まれています。次のことを指定します。

  • certificate_request_context: 証明書要求を識別する文字列。このフィールドの値は長さゼロです(ポストハンドシェーク認証で使用される場合を除く)

  • 拡張: 次の2つの拡張では、デジタル署名で使用できる署名アルゴリズムを指定します。

    • signature_algorithms: TLS 1.2で最初に導入されたもので、CertificateVerifyメッセージの署名に適用されます

    • signature_algorithms_cert: 証明書の署名に適用されます

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.947 EDT|CertificateRequest.java:864|Consuming CertificateRequest handshake message (
"CertificateRequest": {
  "certificate_request_context": "",
  "extensions": [
    "signature_algorithms (13)": {
      "signature schemes": [ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp512r1_sha512, rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512, rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512, dsa_sha256, ecdsa_sha1, rsa_pkcs1_sha1, dsa_sha1]
    },
    "signature_algorithms_cert (50)": {
      "signature schemes": [ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp512r1_sha512, rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512, rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512, dsa_sha256, ecdsa_sha1, rsa_pkcs1_sha1, dsa_sha1]
    }
  ]
}
)
...

サーバーのCertificateメッセージの読取り

Certificateメッセージには、認証証明書と、証明書チェーン内のサポートされているその他の証明書が含まれています。次のことを指定します。

  • certificate_request_context: サーバー認証では、このフィールドは空です
  • certificate_list: クライアントによって通知されている署名アルゴリズムで署名された証明書チェーンが格納されます。ただし、この例では、自己署名付き証明書(主体と発行者の名前が同じである証明書)が受信されました。この同じ自己署名付き証明書が初期化時にすでに検出されているので、TrustManagerを実際に呼び出して、受信した証明書を検証する際に、この証明書は信頼されます。

信頼関係を確立する方法は数多く存在するので、デフォルトのX509TrustManagerが必要とする信頼管理を実行しない場合は、独自のX509TrustManagerをSSLContextに設定できます。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:47.964 EDT|CertificateMessage.java:1148|Consuming server Certificate handshake message (
"Certificate": {
  "certificate_request_context": "",
  "certificate_list": [  
  {
    "certificate" : {
      "version"            : "v1",
      "serial number"      : "41 00 44 46",
      "signature algorithm": "MD5withRSA",
      "issuer"             : "CN=localhost, OU=Widget Development Group, O="Ficticious Widgets, Inc.", L=Sunnyvale, ST=CA, C=US",
      "not before"         : "2004-07-22 18:48:38.000 EDT",
      "not  after"         : "2011-05-22 18:48:38.000 EDT",
      "subject"            : "CN=localhost, OU=Widget Development Group, O="Ficticious Widgets, Inc.", L=Sunnyvale, ST=CA, C=US",
      "subject public key" : "RSA"}
    "extensions": {
      <no extension>
    }
  },
]
}
)
...

クライアントは、この証明書を認識し、信頼することができます。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.165 EDT|X509TrustManagerImpl.java:242|Found trusted certificate (
  "certificate" : {
    "version"            : "v1",
    "serial number"      : "41 00 44 46",
    "signature algorithm": "MD5withRSA",
    "issuer"             : "CN=localhost, OU=Widget Development Group, O="Ficticious Widgets, Inc.", L=Sunnyvale, ST=CA, C=US",
    "not before"         : "2004-07-22 18:48:38.000 EDT",
    "not  after"         : "2011-05-22 18:48:38.000 EDT",
    "subject"            : "CN=localhost, OU=Widget Development Group, O="Ficticious Widgets, Inc.", L=Sunnyvale, ST=CA, C=US",
    "subject public key" : "RSA"}
)
...

サーバーのCertificateVerifyメッセージの読取り

サーバーから送信された証明書は、CertificateVerifyメッセージで検証します。このメッセージを使用して、サーバーがその証明書に対応する秘密キーを持っていることを明示的に証明します。このメッセージでは、次の項目を指定します。

  • 署名アルゴリズム: 使用された署名アルゴリズム(この例では、rsa_pss_rsae_sha256)。

  • 署名: Certificateメッセージ内の公開キーに対応する秘密キーを使用したハンドシェーク全体に対する署名

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.194 EDT|CertificateVerify.java:1128|Consuming CertificateVerify handshake message (
"CertificateVerify": {
  "signature algorithm": rsa_pss_rsae_sha256
  "signature": {
    0000: 0F 25 DD 62 03 6B 8C 8F   22 C7 8D 46 A2 A6 45 39  .%.b.k.."..F..E9
    0010: 08 8D 51 1E 48 52 66 A4   F8 28 D3 FD 18 93 70 C6  ..Q.HRf..(....p.
    0020: 32 74 C1 CC 0A C4 60 41   50 AF 7C DA 0C DB 92 F9  2t....`AP.......
    0030: 14 CB EF 15 7F 3E 52 16   F7 CC 8A 7C C9 1F 42 CA  .....>R.......B.
    0040: 90 8D FA B7 F2 3A 46 7E   F7 9F 43 CE C6 AA 15 59  .....:F...C....Y
    0050: EE AD 34 10 FF B7 BC FD   A2 F7 F3 1A FA 7F 26 61  ..4...........&a
    0060: 80 2B 50 3A 8A 9E 5C 0E   4C A6 24 DA E6 3D 71 FA  .+P:..\.L.$..=q.
    0070: AE 78 79 D2 DA 36 DE C1   A6 BC 18 46 04 CE 03 4E  .xy..6.....F...N
  }
}
)
...

サーバーのFinishedメッセージの読取り

サーバーがFinishedメッセージを送信します。このメッセージには、ハンドシェーク全体に対するMessage Authentication Code (MAC)が含まれています。

javax.net.ssl|DEBUG|01|main|2018-08-17 01:56:26.764 EDT|Finished.java:860|Consuming server Finished handshake message (
"Finished": {
  "verify data": {
    0000: CA 7B 74 A6 79 36 ED 62   A7 0E 14 9D 9F D0 4A 0F  ..t.y6.b......J.
    0010: 02 4C 78 BB E2 89 A2 C6   E8 BD 28 CA E7 D9 DB 68  .Lx.......(....h
  }'}
)
...

Certificateメッセージの送信

サーバーがCertificateRequestメッセージでクライアント認証を要求したので、クライアントはCertificateメッセージを送信します。このCertificateメッセージには、サーバーのCertificateメッセージと同様の情報が指定されます。クライアントはクレデンシャルをサーバーに返信し、そのX509KeyManagerが調べられるようにする必要があります。クライアントは、受け入れられる発行者のリストと、KeyStoreに保管された証明書が一致することを期待します。この場合は一致しました。クライアントは「duke」としてクレデンシャルを有することになります。これらのクレデンシャルが受け入れられるかどうかは、サーバーのX509TrustManagerしだいです。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.222 EDT|CertificateMessage.java:1116|Produced client Certificate message (
"Certificate": {
  "certificate_request_context": "",
  "certificate_list": [  
  {
    "certificate" : {
      "version"            : "v1",
      "serial number"      : "3B 0A FA 66",
      "signature algorithm": "MD5withRSA",
      "issuer"             : "CN=Duke, OU=Java Software, O="Sun Microsystems, Inc.", L=Cupertino, ST=CA, C=US",
      "not before"         : "2001-05-22 19:46:46.000 EDT",
      "not  after"         : "2011-05-22 19:46:46.000 EDT",
      "subject"            : "CN=Duke, OU=Java Software, O="Sun Microsystems, Inc.", L=Cupertino, ST=CA, C=US",
      "subject public key" : "RSA"}
    "extensions": {
      <no extension>
    }
  },
]
}
)
...

CertificateVerifyメッセージの送信

サーバーから送信されたCertificateVerifyメッセージと同様に、クライアントが送信した証明書はCertificateVerifyメッセージで検証します。このメッセージを使用して、クライアントがその証明書に対応する秘密キーを持っていることを明示的に証明します。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.268 EDT|CertificateVerify.java:1097|Produced client CertificateVerify handshake message (
"CertificateVerify": {
  "signature algorithm": rsa_pss_rsae_sha256
  "signature": {
    0000: 91 C2 F7 5D 8D 90 B4 82   E4 BA C6 23 08 E2 B4 DD  ...].......#....
    0010: 8D 95 8F 9F 31 4F 26 F3   97 3B FB 5B 10 4D AE F6  ....1O&..;.[.M..
    0020: 71 78 FB 7B 3A 4F F6 1B   BF D2 E3 FB BE 53 F6 70  qx..:O.......S.p
    0030: 7E 73 83 F4 9A 5E 08 19   63 C1 97 4C 10 B1 C7 3F  .s...^..c..L...?
    0040: 4A 7D EF 4A 30 44 15 9F   D0 F2 8B C4 D1 45 69 B1  J..J0D.......Ei.
    0050: D9 DB 45 83 C4 11 91 B3   81 5E 69 F4 5C 2A CF 69  ..E......^i.\*.i
    0060: D3 A6 7E 75 B4 C9 30 FB   5B AC BA 9F A3 C5 0C FD  ...u..0.[.......
    0070: 9A 62 A4 DA 5A 80 6B 72   CD F5 A5 53 AD 14 74 1C  .b..Z.kr...S..t.
  }
}
)

Finishedメッセージの送信

次に、クライアントは自身のFinishedメッセージをサーバーに送信します。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.271 EDT|Finished.java:658|Produced client Finished handshake message (
"Finished": {
  "verify data": {
    0000: 93 04 B5 23 8F 48 3A CF   4A 85 35 9E 5F E0 1D 4C  ...#.H:.J.5._..L
    0010: 9C 65 06 D4 E8 B4 ED 8F   01 6B 1E A2 DD 18 BD 78  .e.......k.....x
  }'}
)
...

クライアントとサーバーによって、それぞれのピアから受信したFinishedメッセージが検証されました。これで、両方の側は接続を介してアプリケーション・データを送受信できるようになりました。

アプリケーション・データの交換、クライアントからのGETコマンドの送信

サーバーとクライアントでアプリケーション・データを交換する準備が整いました。クライアントがGET /index.html HTTP1.0コマンドを送信します。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.375 EDT|SSLCipher.java:2019|Plaintext before ENCRYPTION (
  0000: 47 45 54 20 2F 69 6E 64   65 78 2E 68 74 6D 6C 20  GET /index.html 
  0010: 48 54 54 50 2F 31 2E 30   0D 0A 0D 0A 17 00 00 00  HTTP/1.0........
  0020: 00 00 00 00 00 00 00 00   00 00 00 00 00           .............
)
...

通信データは暗号化されます。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.385 EDT|SSLSocketOutputRecord.java:295|Raw write (
  0000: 17 03 03 00 3D 90 BF D1   81 E6 A3 E7 DA 50 A9 8B  ....=........P..
  0010: 18 F5 4B 30 AE 59 41 81   25 C4 9E 3E 70 29 5D C6  ..K0.YA.%..>p)].
  0020: 64 49 0B 4A 0E 93 E3 8F   DC 42 BA B5 21 42 38 88  dI.J.....B..!B8.
  0030: 62 4D 0C 86 FE 9A 8C B9   95 EF 89 93 61 3C 13 69  bM..........a<.i
  0040: 6C 45                                              lE
)
...

NewSessionTicketメッセージの読取り

クライアントのFinishedメッセージを受信すると、サーバーはクライアントが今後のハンドシェークを高速化するために使用できるPSKチケットが含まれているNewSessionTicketメッセージをいつでも送信できるようになります。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.517 EDT|NewSessionTicket.java:330|Consuming NewSessionTicket message (
"NewSessionTicket": {
  "ticket_lifetime"      : "86,400",
  "ticket_age_add"       : "<omitted>",
  "ticket_nonce"         : "01",
  "ticket"               : "A5 30 8C B6 AD 95 79 E8 2A D1 95 C0 F0 2F 6F AA 9E 97 58 AA 3D 19 82 2D 2C 47 C0 ED BF 64 48 AB",
  "extensions"           : [
    <no extension>
  ]
}
)

新しく生成されたPSK情報を追加して、SSLSessionの複製が作成されます。

javax.net.ssl|ALL|01|main|2018-08-18 01:04:48.517 EDT|SSLSessionImpl.java:203|Session initialized:  Session(1534568687873|TLS_AES_128_GCM_SHA256)
...

アプリケーション・データの交換、サーバーからのHTTPSヘッダーとデータの送信

クライアントはサーバーからアプリケーション・データ(最初にHTTPSヘッダー、その後で実際のデータ)を受信します。

javax.net.ssl|ALL|01|main|2018-08-18 01:04:48.517 EDT|SSLSessionImpl.java:203|Session initialized:  Session(1534568687873|TLS_AES_128_GCM_SHA256)
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.617 EDT|SSLSocketInputRecord.java:474|Raw read (
  0000: 17 03 03 00 63                                     ....c
)
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.618 EDT|SSLSocketInputRecord.java:215|READ: TLSv1.2 application_data, length = 99
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.618 EDT|SSLSocketInputRecord.java:474|Raw read (
  0000: 65 87 0E 1E 78 F7 AC C4   F7 C6 4D 55 91 6F 72 CC  e...x.....MU.or.
  0010: 18 2D 74 C3 B6 7B 2A F9   EB 2B F4 A8 C7 FD 09 FA  .-t...*..+......
  0020: 7E 36 9D F7 88 E7 44 DD   60 AF EB B0 F8 CF E1 64  .6....D.`......d
  0030: 0D 9B F4 B0 24 C2 BC B1   BF F7 F2 B6 CB E4 2E 39  ....$..........9
  0040: 78 B8 73 09 91 65 7A 0F   4C 49 DE 9A 7F 7B 42 86  x.s..ez.LI....B.
  0050: CA 33 87 DB 0D B2 E5 61   3C 70 6F F9 6A 15 A9 74  .3.....a<po.j..t
  0060: 64 E0 B0                                           d..
)
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.619 EDT|SSLSocketInputRecord.java:251|READ: TLSv1.2 application_data, length = 99
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.621 EDT|SSLCipher.java:1914|Plaintext after DECRYPTION (
  0000: 48 54 54 50 2F 31 2E 30   20 32 30 30 20 4F 4B 0D  HTTP/1.0 200 OK.
  0010: 0A 43 6F 6E 74 65 6E 74   2D 4C 65 6E 67 74 68 3A  .Content-Length:
  0020: 20 32 35 37 37 0D 0A 43   6F 6E 74 65 6E 74 2D 54   2577..Content-T
  0030: 79 70 65 3A 20 74 65 78   74 2F 68 74 6D 6C 0D 0A  ype: text/html..
  0040: 0D 0A                                              ..
)
...
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.626 EDT|SSLSocketInputRecord.java:215|READ: TLSv1.2 application_data, length = 2610
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.628 EDT|SSLSocketInputRecord.java:474|Raw read (
  0000: 69 8D F9 A3 E9 25 09 87   F0 E0 A1 63 12 9D 81 DF  i....%.....c....
  0010: 42 FC FA 7A 03 74 FD D5   ED 47 6C 5F 61 F2 BB 39  B..z.t...Gl_a..9
  0020: CF 64 0B B2 10 14 24 99   A3 66 8B D2 13 C9 66 FD  .d....$..f....f.
...
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.642 EDT|SSLSocketInputRecord.java:251|READ: TLSv1.2 application_data, length = 2610
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.647 EDT|SSLCipher.java:1914|Plaintext after DECRYPTION (
  0000: 3C 21 44 4F 43 54 59 50   45 20 68 74 6D 6C 20 50  <!DOCTYPE html P
  0010: 55 42 4C 49 43 20 22 2D   2F 2F 57 33 43 2F 2F 44  UBLIC "-//W3C//D
  0020: 54 44 20 58 48 54 4D 4C   20 31 2E 30 20 54 72 61  TD XHTML 1.0 Tra
  0030: 6E 73 69 74 69 6F 6E 61   6C 2F 2F 45 4E 22 0A 20  nsitional//EN". 
...

サーバーのAlertメッセージの読取り

サーバーは、このセッションではこれ以上メッセージを送信しないことをクライアントに通知するclose_notifyアラートを送信します。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.658 EDT|Alert.java:232|Received alert message (
"Alert": {
  "level"      : "warning",
  "description": "close_notify"
}
)

接続の終了

サーバーがソケット、TLS接続の順に終了します。

javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.661 EDT|SSLSocketImpl.java:1161|close the underlying socket
javax.net.ssl|DEBUG|01|main|2018-08-18 01:04:48.661 EDT|SSLSocketImpl.java:921|close the ssl connection (passive)
javax.net.ssl|ALL|01|main|2018-08-18 01:04:48.661 EDT|SSLSocketImpl.java:658|Closing input stream
javax.net.ssl|ALL|01|main|2018-08-18 01:04:48.661 EDT|SSLSocketImpl.java:728|Closing output stream

互換性のリスクと既知の問題

この項では、JSSEの機能拡張によって生じる可能性がある互換性の問題やその他の既知の問題について説明します。

TLS 1.3には以前のバージョンとの直接的な互換性がない

TLS 1.3には、以前のバージョンとの直接的な互換性がありません。TLS 1.3を下位互換性モードで実装することは可能ですが、TLS 1.3にアップグレードする際に考慮する互換性リスクはまだいくつかあります。

  • TLS 1.3ではhalf-closeポリシーを使用し、TLS 1.2以前ではduplex-closeポリシーを使用しています。duplex-closeポリシーに依存しているアプリケーションでは、TLS 1.3へのアップグレード時に互換性の問題が発生する場合があります。

  • signature_algorithms_cert拡張では、証明書認証に事前定義済の署名アルゴリズムを使用することが求められます。しかし実際には、サポートされていない署名アルゴリズムがアプリケーションで使用されている場合があります。

  • DSA署名アルゴリズムはTLS 1.3ではサポートされていません。サーバーがDSA証明書のみを使用するように構成されている場合、サーバーではTLS 1.3接続をネゴシエートできません。

  • TLS 1.3でサポートされている暗号化方式群は、TLS 1.2以前のものとは異なります。サポートされなくなった暗号化方式群がアプリケーションでハードコードされている場合、コードを修正せずにTLS 1.3を使用することはできない場合があります(たとえばTLS_AES_128_GCM_SHA256 (1.3以降)とTLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (1.2以前))。

  • TLS 1.3のセッション再開およびキー更新の動作はTLS 1.2以前とは異なります。互換性の影響はわずかですが、アプリケーションがTLSプロトコルのハンドシェークの詳細に依存している場合にはリスクになる可能性があります。

コード例

このセクションには次のコード例が含まれています。

セキュアでないソケットからセキュアなソケットへの変換

例8-26では、セキュアでないソケットを使用してクライアントとサーバーとの間の通信を設定するために使用できるサンプル・コードを示します。次に例8-27ではこのコードを変更し、JSSEを使用してセキュアなソケット通信を設定します。

例8-26 SSLを使用しないソケットの例

次の例では、セキュアでないソケット接続を設定するサーバー側およびクライアント側のコードを示します。

サーバーとして動作し、ソケットを使用してクライアントと通信するJavaプログラムでは、次のようなコードでソケット通信を設定します。


    import java.io.*;
    import java.net.*;
    
    . . .
    
    int port = availablePortNumber;
    
    ServerSocket s;
    
    try {
        s = new ServerSocket(port);
        Socket c = s.accept();
    
        OutputStream out = c.getOutputStream();
        InputStream in = c.getInputStream();
    
        // Send messages to the client through
        // the OutputStream
        // Receive messages from the client
        // through the InputStream
    } catch (IOException e) { }

ソケットを使ってサーバーとの通信を設定するクライアント・コードの例を、次に示します。


    import java.io.*;
    import java.net.*;
    
    . . .
    
    int port = availablePortNumber;
    String host = "hostname";
    
    try {
        s = new Socket(host, port);
    
        OutputStream out = s.getOutputStream();
        InputStream in = s.getInputStream();
    
        // Send messages to the server through
        // the OutputStream
        // Receive messages from the server
        // through the InputStream
    } catch (IOException e) { }

例8-27 SSLを使用するソケットの例

次の例では、セキュアなソケット接続を設定するサーバー側およびクライアント側のコードを示します。

サーバーとして動作し、セキュアなソケットを使用してクライアントと通信するJavaプログラムでは、次のようなコードでソケット通信を設定します。セキュアでないソケットを使った通信のプログラムとこのプログラムとの違いは、太字で示されています。


    import java.io.*;
    import javax.net.ssl.*;
    
    . . .
    
    int port = availablePortNumber;
    
    SSLServerSocket s;
    
    try {
        SSLServerSocketFactory sslSrvFact =
            (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
        s = (SSLServerSocket)sslSrvFact.createServerSocket(port);
    
        SSLSocket c = (SSLSocket)s.accept();
    
        OutputStream out = c.getOutputStream();
        InputStream in = c.getInputStream();
    
        // Send messages to the client through
        // the OutputStream
        // Receive messages from the client
        // through the InputStream
    }
    
    catch (IOException e) {
    }

セキュアなソケットを使ってサーバーとの通信を設定するクライアント・コードの例を、次に示します。セキュアでないソケットとの違いは、太字で示されています。


    import java.io.*;
    import javax.net.ssl.*;
    
    . . .
    
    int port = availablePortNumber;
    String host = "hostname";
    
    try {
        SSLSocketFactory sslFact =
            (SSLSocketFactory)SSLSocketFactory.getDefault();
        SSLSocket s = (SSLSocket)sslFact.createSocket(host, port);
    
        OutputStream out = s.getOutputStream();
        InputStream in = s.getInputStream();
    
        // Send messages to the server through
        // the OutputStream
        // Receive messages from the server
        // through the InputStream
    }
    
    catch (IOException e) {
    }

JSSEサンプル・コードの実行

JSSEサンプル・プログラムでは、JSSEの使用方法を示します。

ノート:

サンプル・コードを使用する場合、サンプル・プログラムはJSSEの使用法を示すために設計されていることに注意してください。堅牢なアプリケーションを開発するためのものではありません。

セキュアな通信を設定すると、アルゴリズムが複雑になります。サンプル・プログラムでは、設定プロセス中のフィードバックがありません。プログラムの実行時は、少し待ってください。しばらく出力が表示されない場合があります。javax.net.debugシステム・プロパティをallに設定して、プログラムを実行すると、フィードバックが表示されます。このデバッグ情報の参照方法の概要は、「TLS接続のデバッグ」を参照してください。

このセクションには次のトピックが含まれます:

サンプル・コードの場所

Java SE 8ドキュメントのJSSEサンプル・コードには、すべてのサンプル・コード・ファイルとテキスト・ファイルがリストされています。そのページには、すべてのサンプル・コード・ファイルをダウンロードできるZIPファイルへのリンクも記載されています。

サンプルの証明書とキー

JSSEのサンプルでは、次の証明書キーストア・ファイルを使用して、クライアントおよびサーバーを認証します:

  • */testkeys

    これらのファイルは、公開キー/秘密キーおよび証明書データのソースとしてコード・サンプルで使用されます。クライアント・プログラム・ディレクトリで、testkeysファイルには、JavaマスコットDukeの証明書エントリが含まれます。サーバー・プログラム・ディレクトリ(./sockets/serverおよびrmi)では、このファイルにはサーバーlocalhostの証明書エントリが含まれています。

    サンプル・コードでは、testkeysファイルが現在の作業ディレクトリに存在すると見なしています。

    ノート:

    これらは、非常に単純な証明書であり、本番環境に適していませんが、ここでサンプルを実行するには十分です。

    これらのキーストアのパスワードはpassphraseです

  • samplecacerts

    このトラストストア・ファイルはストックJDK cacertsファイルに非常に似ていますが、その中には、複数のベンダーからの信頼証明書が含まれています。Dukeおよびlocalhostからの信頼できる証明書も含まれています。

    このキーストアのパスワードは、JDK cacertの初期パスワードchangeitと同じです

    信頼できる証明書ファイルの場所を構成する方法は、プロバイダのドキュメントを参照してください。

    ノート:

    JDKのユーザーは、次のいずれかの方法を使用して、トラスト・ストアの場所を指定できます。

    1. システム・プロパティ:

      java -Djavax.net.ssl.trustStore=samplecacerts \
           -Djavax.net.ssl.trustStorePassword=changeit Application
    2. ファイルを次の場所にインストールします。

      <java-home>/lib/security/jssecacerts
    3. ファイルを次の場所にインストールします。

      <java-home>/lib/security/cacerts

    (2)または(3)を選択した場合、デプロイメントの前に、このファイルを本番用のcacertsファイルと必ず置き換えてください。

ユーティリティkeytoolを使用して、代替証明書およびキーストア・ファイルを生成できます。

ノート:

cacertsファイルを必ず検証してください。cacertsファイル内のCAは、証明書の署名や他のエンティティへの発行を行うエンティティとして信頼されるため、cacertsファイルの管理は慎重に行う必要があります。cacertsファイルには、信頼するエンティティおよびCAの証明書のみが含まれている必要があります。ユーザーは、自身の責任で、cacertsファイルにバンドルされている信頼できるルートCA証明書を検証し、信頼性に関する独自の決定を行います。信頼できないCA証明書をcacertsファイルから削除するには、keytoolユーティリティの-deleteコマンドを-cacertsオプションを指定して使用します。このファイルを編集するアクセス権がない場合は、システム管理者に連絡してください。

または、独自のトラストストアおよびキーストア・ファイルを使用することもできます。「JSSEで使用するキーストアの作成」を参照してください。

クライアントとサーバーのセキュアなソケット接続を表すサンプル・コード

samples/socketsディレクトリにあるサンプル・プログラムは、クライアントとサーバーとの間でセキュアなソケット接続を設定する方法を示しています。

サンプルのクライアント・プログラムを実行中に、Webサーバーなどの既存のサーバーと通信できます。または、サンプル・サーバー・プログラムClassFileServerと通信することもできます。サンプルのクライアント・プログラムとサーバー・プログラムは、同じネットワークに接続された別個のマシンで実行することも、1台のマシンで、ただし別のターミナル・ウィンドウから両方を実行することもできます。

amples/sockets/clientディレクトリのサンプルSSLSocketClient*プログラム(および「HTTPS接続を表すサンプル・コード」で説明するURLReader*プログラム)は、すべてClassFileServerサンプル・サーバー・プログラムで実行できます。これを実行する方法の例は、「ClassFileServerを使用したSSLSocketClientWithClientAuthの実行」に示しています。同様の変更をして、URLReaderSSLSocketClientまたはSSLSocketClientWithTunnelingClassFileServerで実行できます。

クライアントとサーバーとの間で通信中に認証エラーが発生する場合(WebサーバーとClassFileServerのどちらを使用しているか関係なく)、必要なキーがトラストストア(トラスト・キー・データベース)にないためである可能性があります。用語と定義を参照してください。たとえば、ClassFileServerは、SSLハンドシェーク中に必要なlocalhostの公開キーを格納するtestkeysというキーストアを使用します。testkeysキーストアはClassFileServerソースと同じsamples/sockets/serverディレクトリにあります。参照するトラストストアで、localhostの対応する公開キーの証明書をクライアントが見つけられない場合、認証エラーが発生します。次のセクションで説明するように、samplecacertsトラストストア(localhostの公開キーと証明書を格納する)を使用してください。

構成要件

クライアントとサーバーとの間のセキュアなソケット接続を作成するサンプル・プログラムを実行する場合は、適切な証明書ファイル(トラストストア)を利用できるようにしておく必要があります。クライアント・プログラムとサーバー・プログラムの両方で、samplesディレクトリのsamplecacerts証明書ファイルを使用してください。この証明書ファイルを使用すると、クライアントがサーバーを認証できるようになります。このファイルには、JDK (cacertsファイルにある)に付属する一般的な証明書発行局(CA)のすべての証明書と、サンプルサーバーClassFileServerとの通信時にクライアントがlocalhostを認証するために必要なlocalhostの証明書が含まれています。ClassFileServerは、samplecacertsの公開キーに対応するlocalhostの公開キーを含むキーストアを使用します。

クライアントとサーバーの両方でsamplecacertsファイルを使用できるようにするには、それを<java-home>/lib/security/jssecacertsファイルにコピーして名前をcacertsに変更し、<java-home>/lib/security/cacertsファイルと置き換えるか、クライアントとサーバーの両方に対してjavaコマンドを実行する場合に、次のオプションをコマンド行に追加します。

-Djavax.net.ssl.trustStore=path_to_samplecacerts_file

<java-home>の詳細は、「用語と定義」を参照してください。

samplecacertsトラスト・ストアのパスワードはchangeitです。keytoolユーティリティを使用して、サンプルの独自の証明書を置き換えることができます。

Mozilla FirefoxやMicrosoft Internet Explorerなどのブラウザを使用して、ClassFileServerの例で提供されているサンプルのSSLサーバーにアクセスすると、ダイアログ・ボックスが開いて、証明書が認識されないというメッセージが表示されます。これは、サンプル・プログラムで使用する証明書は自己署名付きのもので、テスト用にすぎないためです。現在のセッションで証明書に同意できます。SSLサーバーのテストが終了したあと、ブラウザを終了し、ブラウザの名前空間からテスト用証明書を削除します。

クライアント認証の場合、適切なディレクトリの別のduke証明書を使用できます。公開キーおよび証明書も、samplecacertsファイルに格納されています。

SSLSocketClientの実行

Java SE 8ドキュメント内のJSSEサンプル・コードSSLSocketClient.javaプログラムは、SSLSocketを使用してHTTPリクエストを送信し、HTTPSサーバーからレスポンスを受け取るクライアントの作成方法を示します。このプログラムの出力は、https://www.verisign.com/index.htmlのHTMLソースです。

ファイアウォールの内側から、このプログラムを提供されたとおりに実行しないでください。ファイアウォールの内側から実行すると、JSSEはファイアウォールを経由したwww.verisign.comへのパスを検出できないので、UnknownHostExceptionを受け取ります。ファイアウォールの外側から実行できる同等のクライアントを作成するには、サンプル・プログラムSSLSocketClientWithTunnelingで示すように、プロキシ・トンネリングを設定します。

SSLSocketClientWithTunnelingの実行

Java SE 8ドキュメント内のJSSEサンプル・コードSSLSocketClientWithTunneling.javaプログラムは、ファイアウォールを挟んでセキュアなWebサーバーにアクセスするプロキシ・トンネリングの方法を示します。このプログラムを実行するには、次のJavaシステム・プロパティに適切な値を設定する必要があります。

java -Dhttps.proxyHost=webproxy
-Dhttps.proxyPort=ProxyPortNumber
SSLSocketClientWithTunneling

ノート:

-Dオプションによるプロキシ指定はオプションです。webproxyは使用するプロキシ・ホスト名に、ProxyPortNumberは適切なポート番号に置き換えてください。

プログラムはhttps://www.verisign.com/index.htmlのHTMLソース・ファイルを返します。

SSLSocketClientWithClientAuthの実行

Java SE 8ドキュメント内のJSSEサンプル・コードSSLSocketClientWithClientAuth.javaプログラムは、サーバーから要求された場合にクライアント認証を行うように、キー・マネージャを設定する方法を示します。このプログラムも、クライアントがファイアウォールの外側にはいないことを前提にしています。SSLSocketClientWithTunnelingの例に従ってプログラムを変更すれば、ファイアウォールの内側から接続することもできます。

このプログラムを実行するには、ホスト名、ポート番号、および要求されたファイル・パスの3つのパラメータを指定する必要があります。前の例を反映させるには、ホストにwww.verisign.com、ポート番号に443、要求されたファイル・パスにhttps://www.verisign.com/を設定することで、このプログラムをクライアント認証なしで実行します。これらのパラメータを使用したときの出力が、Webサイトhttps://www.verisign.com/のHTMLソースです。

SSLSocketClientWithClientAuthを実行してクライアント認証を行うには、クライアント認証を要求するサーバーにアクセスする必要があります。このサーバーには、サンプル・プログラムClassFileServerを使用できます。これについては、次のセクションで説明します。

ClassFileServerの実行

ここでClassFileServerと呼んでいるプログラムは、Java SE 8ドキュメント内のJSSEサンプル・コードClassFileServer.javaClassServer.javaという2つのファイルで構成されています。

これらを実行するには、ClassFileServer.classを実行します。その際は次のパラメータが必要です。

  • portは、利用できる未使用のポート番号です。たとえば、2001のような数字を使用できます。
  • docrootは、取得するファイルを含むサーバーのディレクトリを表します。たとえば、Linuxでは、/home/userid/ (useridは特定のUIDを表す)を使用でき、Windowsではc:\を使用できます。
  • TLSは、サーバーがSSLを使用するかTLSを使用するかを示すオプションのパラメータです。
  • trueは、クライアント認証が必要であることを示すオプションのパラメータです。このパラメータは、TLSパラメータが設定されているかどうかだけを参照します。

ノート:

TLSパラメータとtrueパラメータはオプションです。それらを省略した場合は、TLSではない通常のファイルサーバーを認証なしで使用し、何も起こらないことを示します。これは、一方の側(クライアント)がTLSとネゴシエーションを行おうとしても、もう一方の側(サーバー)は行おうとしないため、通信ができないからです。

サーバーは、GET /path_to_fileの形式でのGET要求を予期しています。

ClassFileServerによるSSLSocketClientWithClientAuthの実行

Java SE 8ドキュメント内のJSSEサンプル・コードのサンプル・プログラムSSLSocketClientWithClientAuth.javaおよびClassFileServerを使用して、認証済の通信を設定できますが、この通信では、クライアントとサーバーが相互に認証します。サンプルのプログラムは、同じネットワークに接続された別個のマシンで実行することも、1台のマシンで、ただし別のターミナル・ウィンドウまたはコマンド・プロンプト・ウィンドウから両方を実行することもできます。クライアントとサーバーの両方を設定するには、次を実行します。

  1. ClassFileServerプログラムを1つのマシンまたはターミナル・ウィンドウから実行します。

    ClassFileServerの実行を参照してください。

  2. SSLSocketClientWithClientAuthプログラムを別のマシンやターミナル・ウィンドウで実行します。SSLSocketClientWithClientAuthには、次のパラメータが必要です。
    • hostは、ClassFileServerを実行するために使用するマシンのホスト名です。
    • portは、ClassFileServer用に指定したものと同じポートです。
    • requestedfilepathは、サーバーから取得するファイルのパスを示します。このパラメータには、/filepathと指定する必要があります。GET文の一部として使用されるので、ファイル・パスにはフォワード・スラッシュが必要です。GET文には、稼動中のオペレーティング・システムの種類にかかわらず、フォワード・スラッシュが必要です。文の構成は次のようになります。

      "GET " + requestedfilepath + " HTTP/1.0"
      

ノート:

ClassFileServerが動作しているローカル・マシンに接続するように、他のSSLClient*アプリケーションのGETコマンドを変更できます。

HTTPS接続を表すサンプル・コード

JSSEを介してセキュアな通信にアクセスするためのプライマリAPIは2つあります。1つの方法は、SSLSocketClientSSLSocketClientWithTunnelingおよびSSLSocketClientWithClientAuth (ClassFileServerを使用するか使用しないで)に示すように、任意のセキュアな通信に使用できるソケット・レベルのAPIを使用することです。

2つ目はもっと簡単な方法で、標準のJava URL APIを使用します。java.net.URLクラスを使用したHTTPS URLプロトコルまたはスキームを使用して、SSL対応のWebサーバーとセキュアに通信できます。

HTTPS URLスキームのサポートは一般的なブラウザの多くに実装されており、JSSEで提供されているソケット・レベルAPIを必要とせずにセキュアな通信にアクセスできます。

URLの例はhttps://www.verisign.comです。

HTTPS URL実装のトラストおよびキーの管理は、環境に固有です。JSSE実装は、HTTPS URL実装を提供します。別のHTTPSプロトコル実装を使用するには、java.protocol.handler.pkgsを設定します。パッケージ名に対するjava.lang.Systemプロパティの指定方法を参照してください。詳細については、java.net.URLクラスのドキュメントを参照してください。

JSSEでダウンロードできるサンプルには、HTTPS接続の作成方法を示すサンプル・プログラムが2つ含まれています。これらのサンプル・プログラム(URLReader.javaURLReaderWithOptions.java)は、どちらもsamples/urlsディレクトリにあります。

URLReaderの実行

Java SE 8ドキュメント内のJSSEサンプル・コードURLReader.javaプログラムは、セキュアなサイトにアクセスするURLクラスの使用方法を示します。このプログラムの出力は、https://www.verisign.com/のHTMLソースです。デフォルトで、JSSEに付属のHTTPSプロトコル実装が使用されます。別の実装を使用するには、システム・プロパティjava.protocol.handler.pkgsの値を、実装を含むパッケージ名に設定します。

ファイアウォールの内側でサンプル・コードを実行している場合は、https.proxyHostおよびhttps.proxyPortシステム・プロパティを設定する必要があります。たとえば、ポート8080でプロキシ・ホスト「webproxy」を使用する場合は、javaコマンドで次のオプションを使用できます。

-Dhttps.proxyHost=webproxy
-Dhttps.proxyPort=8080

または、java.lang.SystemのメソッドsetProperty()で、ソース・コード内のシステム・プロパティを設定することもできます。たとえば、コマンド行オプションを使用するかわりに、使用するプログラムに次の行を含めることができます。

System.setProperty("java.protocol.handler.pkgs", "com.ABC.myhttpsprotocol");
System.setProperty("https.proxyHost", "webproxy");
System.setProperty("https.proxyPort", "8080");

URLReaderWithOptionsの実行

Java SE 8ドキュメント内のJSSEサンプル・コードURLReaderWithOptions.javaプログラムは、基本的にはURLReader.javaプログラムと同じですが、実行時にプログラムの引数として次のシステム・プロパティのいずれかまたはすべてをオプションで入力できる点が異なります:

  • java.protocol.handler.pkgs
  • https.proxyHost
  • https.proxyPort
  • https.cipherSuites

URLReaderWithOptionsを実行するには、次のコマンドを入力します。

java URLReaderWithOptions [-h proxyhost -p proxyport] [-k protocolhandlerpkgs] [-c ciphersarray]

ノート:

複数のプロトコル・ハンドラを、縦線で区切った項目のリストでprotocolhandlerpkgs引数に含めることができます。複数のSSL暗号化方式群名を、カンマで区切った項目のリストでciphersarray引数に含めることができます。可能な暗号化方式群名はSSLSocket.getSupportedCipherSuites()メソッドで返されるものと同じです。暗号群はSSLおよびTLSプロトコルの仕様から命名されています。

protocolhandlerpkgs引数は、Oracleが提供するデフォルト以外のHTTPSプロトコル・ハンドラ実装を使用する場合にのみ必要です。

ファイアウォールの内側でサンプル・コード実行している場合は、プロキシ・ホストおよびプロキシ・ポートの引数を含める必要があります。また、使用できる暗号群のリストを含めることもできます。

次に、URLReaderWithOptionsの実行例と、ポート8080にプロキシ・ポート「webproxy」を指定する場合の例を示します。

java URLReaderWithOptions -h webproxy -p 8080

セキュアなRMI接続を表すサンプル・コード

samples/rmiディレクトリ内のサンプル・コードは、セキュアなJava Remote Method Invocation (RMI)接続の作成方法を示しています。このサンプル・コードは、基本的には、カスタムRMIソケット・ファクトリをインストールして使用するように変更されたHello Worldサンプルです。

SSLEngineの使用を表すサンプル・コード

SSLEngineは、アプリケーション開発者にI/Oおよび計算戦略を選択するときの柔軟性を提供します。SSLEngineは、SSL/TLS実装を特定のI/O抽象化(シングル・スレッドSSLSocketsなど)に結びつけるのではなく、I/Oおよび計算の制約をSSL/TLS実装から除外します。

前述したように、SSLEngineは高度なAPIであり、不用意に使用することはできません。ここでは、その使用を説明するのに役立つ入門用サンプル・コードを示します。最初のデモは、ほとんどのI/Oおよびスレッドの発行を除外し、SSLEngineメソッドの多くに重点を置きます。2番目のデモは、より現実的な例であり、SSLEngineがどのようにJava NIOと結合して基本的なHTTP/HTTPSサーバーを作成するかを示します。

SSLEngineSimpleDemoの実行

Java SE 8ドキュメント内のJSSEサンプル・コードSSLEngineSimpleDemo.javaプログラムは、I/Oとスレッドの問題が簡素化されている、SSLEngineの操作に重点を置いた非常に単純なアプリケーションです。このアプリケーションは、一般的なByteBufferオブジェクトによってSSL/TLSメッセージを交換する2つのSSLEngineオブジェクトを作成します。1つのループがすべてのエンジン操作を順番に実行して、セキュアな接続の確立(ハンドシェーク)、アプリケーション・データの転送、およびエンジンのクローズを示します。

SSLEngineResultは、SSLEngineの現在の状態に関して多くの情報を提供します。この例では、すべての状態を調べているわけではありません。I/Oおよびスレッドの発行を適度に単純化しているため本番稼動環境に適した例ではありませんが、SSLEngineの全体的な機能の説明に有用です。

JSSEサンプル・コードのトラブルシューティング

JSSEの使用に伴う最も一般的な問題の1つは、信頼性の判定を行うメカニズムにとって不明な証明書をJSSEが受け取った場合の対応です。不明な証明書を受信した場合、トラスト・メカニズムは、その証明書が信頼できないことを示す例外をスローします。正しいトラスト・ストアが使用されていること、およびJSSEが正しくインストールおよび構成されていることを確認してください。

localhostクレデンシャルまたはdukeクレデンシャルを使用する場合は、samplecacertsファイルの場所を正しく指定していないとアプリケーションが機能しないため、指定を確認します。(詳細は、「サンプルの証明書とキー」を参照してください。)

SSLデバッグ・メカニズムを使用して、このような信頼の問題を調査できます。詳細は、実装ドキュメントを参照してください。

JSSEで使用するキーストアの作成

この項では、keytoolユーティリティを使用して、JSSEでの使用に適した単純なPKCS12キーストアを作成する方法を説明します。

まずキーストア内に(公開キーおよび秘密キーを持つ) keyEntryを作成し、トラストストア内に対応するtrustedCertEntry (公開キーのみ)を作成します。クライアント認証の場合、クライアントの証明書に対して同様の処理に従う必要があります。

ノート:

ここでは、各ステップに関する詳しい解説は省略します。詳細は、『Java Platform, Standard Editionツール・リファレンス』keytoolに関する項を参照してください。

ユーザー入力は太字で表示されます。

  1. 対応する公開キーおよび秘密キーによって、新しいキーストアと自己署名付き証明書を作成します。

    
        % keytool -genkeypair -alias duke -keyalg RSA -validity 7 -keystore keystore 
        
        Enter keystore password:  <password>
        What is your first and last name?
        [Unknown]:  Duke
        What is the name of your organizational unit?
        [Unknown]:  Java Software
        What is the name of your organization?
        [Unknown]:  Oracle, Inc.
        What is the name of your City or Locality?
        [Unknown]:  Palo Alto
        What is the name of your State or Province?
        [Unknown]:  CA
        What is the two-letter country code for this unit?
        [Unknown]:  US
        Is CN=Duke, OU=Java Software, O="Oracle, Inc.",
        L=Palo Alto, ST=CA, C=US correct?
        [no]:  yes
    
  2. キーストアを調べます。エントリの型はPrivatekeyEntryで、これは、このエントリに秘密キーが関連付けられていることを示します。

    
        % keytool -list -v -keystore keystore
        
        Enter keystore password:  <password>
        
        Keystore type: PKCS12
        Keystore provider: SUN
    
        Your keystore contains 1 entry
    
        Alias name: duke
        Creation date: Jul 25, 2016
        Entry type: PrivateKeyEntry
        Certificate chain length: 1
        Certificate[1]:
        Owner: CN=Duke, OU=Java Software, O="Oracle, Inc.", L=Palo Alto, ST=CA, C=US
        Issuer: CN=Duke, OU=Java Software, O="Oracle, Inc.", L=Palo Alto, ST=CA, C=US
        Serial number: 210cccfc
        Valid from: Mon Jul 25 10:33:27 IST 2016 until: Mon Aug 01 10:33:27 IST 2016
        Certificate fingerprints:
             SHA1: 80:E5:8A:47:7E:4F:5A:70:83:97:DD:F4:DA:29:3D:15:6B:2A:45:1F
             SHA256: ED:3C:70:68:4E:86:35:9C:63:CC:B9:59:35:58:94:1F:7E:B8:B0:EE:D2:
        4B:9D:80:31:67:8A:D4:B4:7A:B5:12
        Signature algorithm name: SHA256withRSA
        Subject Public Key Algorithm: RSA (2048)
        Version: 3
    
        Extensions:
    
       #1: ObjectId: 2.5.29.14 Criticality=false
       SubjectKeyIdentifier [
       KeyIdentifier [
       0000: 7F C9 95 48 42 8D 68 91   BA 1E E6 5C 2C 6B FF 75  ...HB.h....\,k.u
       0010: 5F 19 78 43                                        _.xC
       ]
       ]
  3. 自己署名付き証明書をエクスポートし、内容を調べます。

    
        % keytool -export -alias duke -keystore keystore -rfc -file duke.cer
        Enter keystore password:  <password>
        Certificate stored in file <duke.cer>
        % cat duke.cer
        -----BEGIN CERTIFICATE-----
        MIIDdzCCAl+gAwIBAgIEIQzM/DANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJV
        UzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEVMBMGA1UEChMMT3Jh
        Y2xlLCBJbmMuMRYwFAYDVQQLEw1KYXZhIFNvZnR3YXJlMQ0wCwYDVQQDEwREdWtl
        MB4XDTE2MDcyNTA1MDMyN1oXDTE2MDgwMTA1MDMyN1owbDELMAkGA1UEBhMCVVMx
        CzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8xFTATBgNVBAoTDE9yYWNs
        ZSwgSW5jLjEWMBQGA1UECxMNSmF2YSBTb2Z0d2FyZTENMAsGA1UEAxMERHVrZTCC
        ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ7+Yeu6HDZgWwkGlG4iKH9w
        vGKrxXVR57FaFyheMevrgj1ovVnQVFhfdMvjPkjWmpqLg6rfTqU4bKbtoMWV6+Rn
        uQrCw2w9xNC93hX9PxRa20UKrSRDKnUSvi1wjlaxfj0KUKuMwbbY9S8x/naYGeTL
        lwbHiiMvkoFkP2kzhVgeqHjIwSz4HRN8vWHCwgIDFWX/ZlS+LbvB4TSZkS0ZcQUV
        vJWTocOd8RB90W3bkibWkWq166XYGE1Nq1L4WIhrVJwbav6ual69yJsEpVcshVkx
        E1WKzJg7dGb03to4agbReb6+aoCUwb2vNUudNWasSrxoEFArVFGD/ZkPT0esfqEC
        AwEAAaMhMB8wHQYDVR0OBBYEFH/JlUhCjWiRuh7mXCxr/3VfGXhDMA0GCSqGSIb3
        DQEBCwUAA4IBAQAmcTm2ahsIJLayajsvm8yPzQsHA7kIwWfPPHCoHmNbynG67oHB
        fleaNvrgm/raTT3TrqQkg0525qI6Cqaoyy8JA2fAp3i+hmyoGHaIlo14bKazaiPS
        RCCqk0J8vwY3CY9nVal1XlHJMEcYV7X1sxKbuAKFoAJ29E/p6ie0JdHtQe31M7X9
        FNLYzt8EpJYUtWo13B9Oufz/Guuex9PQ7aC93rbO32MxtnnCGMxQHlaHLLPygc/x
        cffGz5Xe5s+NEm78CY7thgN+drI7icBYmv4navsnr2OQaD3AfnJ4WYSQyyUUCPxN
        zuk+B0fbLn7PCCcQspmqfgzIpgbEM9M1/yav
        -----END CERTIFICATE-----    
    

    また、-certreqコマンドによって証明書署名要求(CSR)を生成し、証明書発行局(CA)に送付して署名を求めることもできます。例については、keytoolコマンドのCAに対する署名付き証明書の要求に関する項を参照してください。

  4. 証明書を新しいトラスト・ストアにインポートします。

    
        % keytool -import -alias dukecert -file duke.cer -keystore truststore
        Enter keystore password:  <password>
        Re-enter new password:
        Owner: CN=Duke, OU=Java Software, O="Oracle, Inc.", L=Palo Alto, ST=CA, C=US
        Issuer: CN=Duke, OU=Java Software, O="Oracle, Inc.", L=Palo Alto, ST=CA, C=US
        Serial number: 210cccfc
        Valid from: Mon Jul 25 10:33:27 IST 2016 until: Mon Aug 01 10:33:27 IST 2016
        Certificate fingerprints:
             SHA1: 80:E5:8A:47:7E:4F:5A:70:83:97:DD:F4:DA:29:3D:15:6B:2A:45:1F
             SHA256: ED:3C:70:68:4E:86:35:9C:63:CC:B9:59:35:58:94:1F:7E:B8:B0:EE:D2:
        4B:9D:80:31:67:8A:D4:B4:7A:B5:12
        Signature algorithm name: SHA256withRSA
        Subject Public Key Algorithm: RSA (2048)
        Version: 3
    
        Extensions:
    
        #1: ObjectId: 2.5.29.14 Criticality=false
        SubjectKeyIdentifier [
        KeyIdentifier [
        0000: 7F C9 95 48 42 8D 68 91   BA 1E E6 5C 2C 6B FF 75  ...HB.h....\,k.u
        0010: 5F 19 78 43                                        _.xC
        ]
        ]
    
        Trust this certificate? [no]:  yes
        Certificate was added to keystore
    
        
    
  5. トラストストアを調べます。エントリの型はtrustedCertEntryで、これは、秘密キーをこのエントリで使用できないことを示します。これは、このファイルがKeyManagerのキーストアとして適切でないことも意味します。

    
        % keytool -list -v -keystore truststore
        Enter keystore password:  <password>
        
        Keystore type: PKCS12
        Keystore provider: SUN
        
        Your keystore contains 1 entry
    
        Alias name: dukecert
        Creation date: Jul 25, 2016
        Entry type: trustedCertEntry
    
        Owner: CN=Duke, OU=Java Software, O="Oracle, Inc.", L=Palo Alto, ST=CA, C=US
        Issuer: CN=Duke, OU=Java Software, O="Oracle, Inc.", L=Palo Alto, ST=CA, C=US
        Serial number: 210cccfc
        Valid from: Mon Jul 25 10:33:27 IST 2016 until: Mon Aug 01 10:33:27 IST 2016
        Certificate fingerprints:
             SHA1: 80:E5:8A:47:7E:4F:5A:70:83:97:DD:F4:DA:29:3D:15:6B:2A:45:1F
             SHA256: ED:3C:70:68:4E:86:35:9C:63:CC:B9:59:35:58:94:1F:7E:B8:B0:EE:D2:
        4B:9D:80:31:67:8A:D4:B4:7A:B5:12
        Signature algorithm name: SHA256withRSA
        Subject Public Key Algorithm: RSA (2048)
        Version: 3
    
        Extensions:
    
        #1: ObjectId: 2.5.29.14 Criticality=false
        SubjectKeyIdentifier [
        KeyIdentifier [
        0000: 7F C9 95 48 42 8D 68 91   BA 1E E6 5C 2C 6B FF 75  ...HB.h....\,k.u
        0010: 5F 19 78 43                                        _.xC
        ]
        ]
    
    
    
        *******************************************
        *******************************************
    
  6. ここで、適切なキーストアを使用してアプリケーションを実行します。この例では、デフォルトのX509KeyManagerおよびX509TrustManagerを使用することを前提とするため、「JSSEのカスタマイズ」で説明したシステム・プロパティを使用してキーストアを選択します。

    % java -Djavax.net.ssl.keyStore=keystore -Djavax.net.ssl.keyStorePassword=password Server
    % java -Djavax.net.ssl.trustStore=truststore -Djavax.net.ssl.trustStorePassword=trustword Client

ノート:

この例で認証されるのは、サーバーのみです。クライアントの認証の場合、クライアントのキーに対して同様のキーストアを用意し、サーバーに対して適切なトラストストアを提供する必要があります。

Server Name Indication (SNI)拡張の使用

これらの例では、クライアント側アプリケーションとサーバー側アプリケーションでServer Name Indication (SNI)拡張を使用する方法およびそれを仮想インフラストラクチャに適用する方法を示します。

このセクションのすべての例で、パラメータの設定後にそれらを適用するには、対応するSSLSocketSSLEngineまたはSSLServerSocketオブジェクトで、setSSLParameters(SSLParameters)メソッドを呼び出します。

一般的なクライアント側使用例

次は、クライアント・アプリケーションを開発するためにSNI拡張の理解を必要とするユース・ケースのリストです。

  • ケース1.クライアントはwww.example.comにアクセスする必要があります。

    ホスト名を明示的に設定します。

        SNIHostName serverName = new SNIHostName("www.example.com");
        sslParameters.setServerNames(Collections.singletonList(serverName)); 

    クライアントは常にホスト名を明示的に指定してください。

  • ケース2.サーバーでサポートしていないため、クライアントはSNIを使用する必要はありません。

    空のサーバー名リストでSNIを無効にします。

        sslParameters.setServerNames(Collections.emptyList());        
    
  • ケース3.クライアントはURL https://www.example.comにアクセスする必要があります。

    OracleプロバイダはデフォルトでSNI拡張にホスト名を設定しますが、サード・パーティ・プロバイダがデフォルトのサーバー名表示をサポートしていないことがあります。アプリケーションでプロバイダに依存しないようにするには、常にホスト名を明示的に設定します。

  • ケース4.クライアントはソケットをサーバー・モードからクライアント・モードに切り替える必要があります。

    まず、メソッドsslSocket.setUseClientMode(true)でモードを切り替えます。次に、ソケットでサーバー名表示パラメータをリセットします。

一般的なサーバー側使用例

次は、サーバー・アプリケーションを開発するためにSNI拡張の理解を必要とするユース・ケースのリストです。

  • ケース1.サーバーはすべてのサーバー名表示タイプを受け付ける必要があります。

    SNI拡張を処理するコードがない場合、サーバーはすべてのサーバー名表示型を無視します。

  • ケース2.サーバーは型 host_nameのすべてのサーバー名表示型を拒否する必要があります。

    host_nameに無効なサーバー名パターンを設定します。

        SNIMatcher matcher = SNIHostName.createSNIMatcher("");
        Collection<SNIMatcher> matchers = new ArrayList<>(1);
        matchers.add(matcher);
        sslParameters.setSNIMatchers(matchers);        
    

    他の方法は、matches()メソッドで、常にfalseを返すSNIMatcherサブクラスを作成することです。

        class DenialSNIMatcher extends SNIMatcher {
            DenialSNIMatcher() {
                super(StandardConstants.SNI_HOST_NAME);
            }
        
            @Override
            public boolean matches(SNIServerName serverName) {
                return false;
            }
        }
        
        SNIMatcher matcher = new DenialSNIMatcher();
        Collection<SNIMatcher> matchers = new ArrayList<>(1);
        matchers.add(matcher);
        sslParameters.setSNIMatchers(matchers);        
    
  • ケース3.サーバーはexample.comドメイン内のすべてのホスト名への接続を受け付ける必要があります。

    すべての*.example.comアドレスを含むパターンとして、host_nameの認識可能なサーバー名を設定します。

        SNIMatcher matcher = SNIHostName.createSNIMatcher("(.*\\.)*example\\.com");
        Collection<SNIMatcher> matchers = new ArrayList<>(1);
        matchers.add(matcher);
        sslParameters.setSNIMatchers(matchers);
    
  • ケース4.サーバーはソケットをクライアント・モードからサーバー・モードに切り替える必要があります。

    まず、メソッドsslSocket.setUseClientMode(false)でモードを切り替えます。次に、ソケットでサーバー名表示パラメータをリセットします。

仮想インフラストラクチャの操作

このセクションでは、仮想インフラストラクチャ内からServer Name Indication (SNI)拡張を使用する方法について説明します。ここでは、ソケットからのClientHelloメッセージのパーサーの作成方法を示し、SSLSocketおよびSSLEngineを使用した仮想サーバー・ディスパッチャの例を示し、SNI拡張を使用できない場合に何が発生するかを説明して、フェールオーバーSSLContextを作成する方法を示します。

ClientHelloパーサーの準備

アプリケーションはソケットからのClientHelloメッセージを解析するAPIを実装する必要があります。次の例に、これらの機能を実行できるSSLCapabilitiesクラスとSSLExplorerクラスを示します。

SSLSocketClient.javaはハンドシェーク中にTLS/DTLSセキュリティ機能をカプセル化します(つまり、TLS/DTLSハンドシェークで受け付けられる暗号化方式群のリスト、レコード・バージョン、helloバージョンおよびサーバー名表示)。それは、SSLExplorer.explore()メソッドで、TLS/DTLS接続のネットワーク・データを調べることによって取得できます。

SSLExplorer.javaはTLSクライアントからの初期ClientHelloメッセージを調査しますが、ハンドシェークを開始したり、ネットワーク・データを消費したりしません。SSLExplorer.explore()メソッドはClientHelloメッセージを解析し、SSLCapabilitiesにセキュリティ・パラメータを取得します。このメソッドは、TLS接続でハンドシェークが行われる前に呼び出す必要があります。

SSLSocketに基づいた仮想サーバー・ディスパッチャ

このセクションでは、SSLSocketに基づいて、仮想サーバー・ディスパッチャを使用する手順を説明します。

  1. サーバー名ハンドラを登録します。

    このステップで、アプリケーションは様々なサーバー名表示の様々なSSLContextオブジェクトを作成したり、特定のサーバー名表示を指定した仮想マシンまたは分散システムにリンクしたりできます。

    たとえば、サーバー名がwww.example.orgの場合、登録されたサーバー名ハンドラはローカル仮想ホスティングWebサービス用などになります。ローカル仮想ホスティングWebサービスは指定したSSLContextを使用します。サーバー名がwww.example.comの場合、登録されたサーバー名ハンドラは10.0.0.36でホストしている仮想マシン用などになります。ハンドラはこの接続を仮想マシンにマッピングできます。

  2. ServerSocketを作成し、新しい接続を受け付けます。

    ServerSocket serverSocket = new ServerSocket(serverPort);
    Socket socket = serverSocket.accept();
    
  3. ソケット入力ストリームからバイトを読み取り、バッファして、バッファ済のバイトを調査します。

    InputStream ins = socket.getInputStream();
    byte[] buffer = new byte[0xFF];
    int position = 0;
    SSLCapabilities capabilities = null;
        
    // Read the header of TLS record
    while (position < SSLExplorer.RECORD_HEADER_SIZE) {
        int count = SSLExplorer.RECORD_HEADER_SIZE - position;
        int n = ins.read(buffer, position, count);
        if (n < 0) {
            throw new Exception("unexpected end of stream!");
        }
        position += n;
    }
        
    // Get the required size to explore the SSL capabilities
    int recordLength = SSLExplorer.getRequiredSize(buffer, 0, position);
    if (buffer.length < recordLength) {
        buffer = Arrays.copyOf(buffer, recordLength);
    }
        
    while (position < recordLength) {
        int count = recordLength - position;
        int n = ins.read(buffer, position, count);
        if (n < 0) {
            throw new Exception("unexpected end of stream!");
        }
        position += n;
    }
        
    // Explore
    capabilities = SSLExplorer.explore(buffer, 0, recordLength);
    if (capabilities != null) {
        System.out.println("Record version: " + capabilities.getRecordVersion());
        System.out.println("Hello version: " + capabilities.getHelloVersion());
    }
  4. 調査済の機能から要求されたサーバー名を取得します。

    List<SNIServerName> serverNames = capabilities.getServerNames();
    
  5. このサーバー名表示の登録済のサーバー名ハンドラを検索します。

    ホスト名のサービスが仮想マシンまたは他の分散システムに存在する場合、アプリケーションは接続を転送先に転送する必要があります。アプリケーションは、ソケット・ストリームからのSSLアプリケーションよりも、rawインターネット・データを読取りおよび書込みする必要があります。

    Socket destinationSocket = new Socket(serverName, 443);
    // Forward buffered bytes and network data from the current socket to the destinationSocket.
    

    ホスト名のサービスが同じプロセスに存在し、ホスト名サービスでSSLSocketを直接使用できる場合、アプリケーションはSSLSocketインスタンスをサーバーに設定する必要があります。

    // Get service context from registered handler
    // or create the context
    SSLContext serviceContext = ...
    
    SSLSocketFactory serviceSocketFac = serviceContext.getSSLSocketFactory();
    
    // wrap the buffered bytes
    ByteArrayInputStream bais = new ByteArrayInputStream(buffer, 0, position);
    SSLSocket serviceSocket = (SSLSocket)serviceSocketFac.createSocket(socket, bais, true);
    
    // Now the service can use serviceSocket as usual.
    

SSLEngineに基づいた仮想サーバー・ディスパッチャ

このセクションでは、SSLEngineに基づいて、仮想サーバー・ディスパッチャを使用する手順を説明します。

  1. サーバー名ハンドラを登録します。

    このステップで、アプリケーションは様々なサーバー名表示の様々なSSLContextオブジェクトを作成したり、特定のサーバー名表示を指定した仮想マシンまたは分散システムにリンクしたりできます。

    たとえば、サーバー名がwww.example.orgの場合、登録されたサーバー名ハンドラはローカル仮想ホスティングWebサービス用などになります。ローカル仮想ホスティングWebサービスは指定したSSLContextを使用します。サーバー名がwww.example.comの場合、登録されたサーバー名ハンドラは10.0.0.36でホストしている仮想マシン用などになります。ハンドラはこの接続を仮想マシンにマッピングできます。

  2. ServerSocketまたはServerSocketChannelを作成し、新しい接続を受け付けます。

    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.bind(...);
    ...
    SocketChannel socketChannel = serverSocketChannel.accept();
    
  3. ソケット入力ストリームからバイトを読み取り、バッファして、バッファ済のバイトを調査します。

    ByteBuffer buffer = ByteBuffer.allocate(0xFF);
    SSLCapabilities capabilities = null;
    while (true) {
        // ensure the capacity
        if (buffer.remaining() == 0) {
            ByteBuffer oldBuffer = buffer;
            buffer = ByteBuffer.allocate(buffer.capacity() + 0xFF);
            buffer.put(oldBuffer);
        }
    
        int n = sc.read(buffer);
        if (n < 0) {
            throw new Exception("unexpected end of stream!");
        }
    
        int position = buffer.position();
        buffer.flip();
        capabilities = explorer.explore(buffer);
        buffer.rewind();
        buffer.position(position);
        buffer.limit(buffer.capacity());
        if (capabilities != null) {
            System.out.println("Record version: " +
                capabilities.getRecordVersion());
            System.out.println("Hello version: " +
                capabilities.getHelloVersion());
            break;
        }
    }
    
    buffer.flip();  // reset the buffer position and limitation 
  4. 調査済の機能から要求されたサーバー名を取得します。

    List<SNIServerName> serverNames = capabilities.getServerNames();
    
  5. このサーバー名表示の登録済のサーバー名ハンドラを検索します。

    ホスト名のサービスが仮想マシンまたは他の分散システムに存在する場合、アプリケーションは接続を転送先に転送する必要があります。アプリケーションは、ソケット・ストリームからのSSLアプリケーションよりも、rawインターネット・データを読取りおよび書込みする必要があります。

    Socket destinationSocket = new Socket(serverName, 443);
    // Forward buffered bytes and network data from the current socket to the destinationSocket.
    

    ホスト名のサービスが同じプロセスに存在し、ホスト名サービスでSSLEngineを直接使用できる場合、アプリケーションは単にネット・データをSSLEngineインスタンスに提供します。

    // Get service context from registered handler
    // or create the context
    SSLContext serviceContext = ...
        
    SSLEngine serviceEngine = serviceContext.createSSLEngine();
    // Now the service can use the buffered bytes and other byte buffer as usual.
    

使用可能なSNI拡張がない

ClientHelloメッセージにサーバー名表示がない場合、SNIに従って正しいサービスを選択する方法がありません。そのような場合、アプリケーションは、サーバー名表示がない場合に、接続をそれに委譲できるように、デフォルトのサービスを指定する必要がある場合があります。

フェイルオーバーSSLContext

SSLExplorer.explore()メソッドはTLS/DTLSの内容の妥当性をチェックしません。レコード形式がTLS/DTLS仕様に準拠していない場合、またはハンドシェークの起動後に、explore()メソッドが呼び出された場合、メソッドはIOExceptionをスローすることがあり、ネットワーク・データを生成できません。そのような場合、TLS/DTLS接続のネゴシエーションに使用されないが適切な警告メッセージで接続を閉じるフェイルオーバーSSLContextを使用することで、SSLExplorer.explore()によってスローされた例外を処理します。次の例はフェールオーバーSSLContextを示しています。「一般的なサーバー側使用例」の「ケース2」で、DenialSNIMatcherクラスの例を参照できます。

byte[] buffer = ...       // buffered network data
boolean failed = true;    // SSLExplorer.explore() throws an exception

SSLContext context = SSLContext.getInstance("TLS");
// the failover SSLContext
    
context.init(null, null, null);
SSLSocketFactory sslsf = context.getSocketFactory();
ByteArrayInputStream bais = new ByteArrayInputStream(buffer, 0, position);
SSLSocket sslSocket = (SSLSocket)sslsf.createSocket(socket, bais, true);

SNIMatcher matcher = new DenialSNIMatcher();
Collection<SNIMatcher> matchers = new ArrayList<>(1);
matchers.add(matcher);
SSLParameters params = sslSocket.getSSLParameters();
params.setSNIMatchers(matchers);    // no recognizable server name
sslSocket.setSSLParameters(params);

try {
    InputStream sslIS = sslSocket.getInputStream();
    sslIS.read();
} catch (Exception e) {
    System.out.println("Server exception " + e);
} finally {
    sslSocket.close();
}

標準名

JDK Security APIは、アルゴリズム、証明書およびキーストアのタイプの一連の標準名を必要とし、これらを使用します。Javaセキュリティ標準アルゴリズム名の仕様を参照してください。特定のプロバイダ情報は、JDKプロバイダ・ドキュメントで参照できます。

プロバイダのプラグイン可能性

JSSEは完全にプラガブルであり、サード・パーティのJSSEプロバイダの使用には何の制限もありません。

JAXPセキュリティ処理

JAXPセキュリティ処理は、パーサーやトランスフォーマなどのJAXPコンポーネントにセキュアな方法で動作するよう指示します。セキュリティ・マネージャが存在する場合は、JAXPセキュリティ処理が自動的にオンになります。存在しない場合は、JAXPセキュリティ処理がデフォルトで無効になります。

JAXPセキュリティ処理のデフォルトの制限事項

次の表では、JAXPセキュリティ処理が有効な場合に無効になるXML関連ファクトリ・クラスと設定される処理制限について説明します。

表8-10 XML関連ファクトリ・クラスでJAXPセキュリティ処理によって設定されるデフォルトの制限事項

XML関連ファクトリ・クラス 有効? 処理制限
DocumentBuilderFactory true entityExpansionLimit = 64000

elementAttributeLimit = 1000

maxOccurLimit = 5000

SAXParserFactory true entityExpansionLimit = 64000

elementAttributeLimit = 10000

maxOccurLimit = 5000

SchemaFactory true maxOccurLimit = 5000
TransformerFactory false 拡張関数が無効です
XPathFactory false 拡張関数が無効です

entityExpansionLimitelementAttributeLimitmaxOccurLimitおよびその他のJAXP処理制限の詳細は、Javaチュートリアル処理制限に関する説明を参照してください。

次の各項では、この表の処理制限の詳細とそれらの変更方法について説明します。

エンティティ展開の制限

システム・プロパティentityExpansionLimitまたはパーサー・プロパティhttp://apache.org/xml/properties/entity-expansion-limitを設定して、エンティティ展開の数を制限します。どちらのプロパティもjava.lang.Integer値を受け入れます。エンティティ展開の上限に達すると、パーサーは致命的なエラーをスローします。デフォルトでは、entityExpansionLimitは64,000に設定されます。

次のコマンド行の例では、エンティティ展開制限が10,000に設定されます。

java -DentityExpansionLimit=10000 MyApp 

次のコード例では、エンティティ展開制限が10,000に設定されます。

    System.setProperty("entityExpansionLimit","10000");

次のコード例では、パーサー・プロパティhttp://apache.org/xml/properties/entity-expansion-limitが10,000に設定されます。

    DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
    dfactory.setAttribute(
        "http://apache.org/xml/properties/entity-expansion-limit",
        new Integer("10000"));
    DocumentBuilder docBuilder = dbFactory.newDocumentBuilder();

要素属性数の制限

システム・プロパティelementAttributeLimitを設定するか、パーサー・プロパティhttp://apache.org/xml/properties/elementAttributeLimitを設定して、要素内の属性の数を制限します。どちらのプロパティもInteger値を受け入れます。デフォルトでは、elementAttributeLimitは10,000に設定されます。パーサー・プロパティhttp://apache.org/xml/properties/elementAttributeLimitが設定されると、システム・プロパティがオーバーライドされます。要素内の属性数が制限を超えると、パーサーは致命的エラーをスローします。

次のコマンド行の例では、要素属性制限が20に設定されます。

java -DelementAttributeLimit=20 MyApp

次のコード例では、要素属性制限が20に設定されます。

System.setProperty("elementAttributeLimit","20");

次のコード例では、パーサー・プロパティhttp://apache.org/xml/properties/entity-expansion-limitが20に設定されます。

    DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
    dfactory.setAttribute(
        "http://apache.org/xml/properties/elementAttributeLimit",
        new Integer(20));
    DocumentBuilder docBuilder = dbFactory.newDocumentBuilder();

maxOccursを含むコンストラクトにより作成されるノード数の制限

xsd:sequenceなどの構成では、検証パーサーはmaxOccursオカレンス・インジケータの値に比例した領域(メモリー)を使用できます。これによってVMのメモリーが不足したり、非常に長時間実行される場合があります。この動作を悪用する潜在的攻撃を防ぐには、次のようにファクトリのセキュアな処理を有効にします。

factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);

xsd:elementおよびxsd:anyの場合、検証パーサーは一定量の領域を使用します。これはmaxOccursオカレンス・インジケータの値とは関係ありません。

maxOccursLimitのデフォルト値は5,000です。このシステム・プロパティは、"unbounded"以外の値を持つmaxOccursオカレンス・インジケータを含むW3C XMLスキーマの文法の構築時に作成できるコンテンツ・モデル・ノードの数を制限します。

XPathおよびXSLT拡張関数の無効化

デフォルトでは、JAXPセキュア処理を有効にすると、XPathおよびXSLT拡張関数が無効になります。次のコードはJAXPセキュア処理を有効化し、XPathFactoryのXPathおよびXSLT拡張関数を無効化します。

    XPathFactory xpf = xPathFactory.newInstance();
    xpf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);

次のコードはJAXPセキュア処理を有効化し、TransformerFactoryのXSLT拡張関数を無効化します。

    TransformerFactory tf = TransformerFactory.newInstance();
    tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);

入れ子にされたエンティティ定義によるセキュリティの問題

XMLでは、再帰的なエンティティ定義は認められませんが、入れ子にされたエンティティ定義は認められます。しかし、外部ソースからのXMLデータを許可するサーバーがサービス妨害攻撃を受ける可能性があります。たとえば、次のように非常に深く入れ子にされたエンティティ定義が含まれるSOAPドキュメントは、エンティティを展開するのにCPU時間の100%と大量のメモリーを消費してしまいます。

<?xml version="1.0" encoding ="UTF-8"?>
<!DOCTYPE foobar[
    <!ENTITY x100 "foobar">
    <!ENTITY  x99 "&x100;&x100;">
    <!ENTITY  x98 "&x99;&x99;">
    ...
    <!ENTITY   x2 "&x3;&x3;">
    <!ENTITY   x1 "&x2;&x2;">
]>
<SOAP-ENV:Envelope xmlns:SOAP-ENV=...>
    <SOAP-ENV:Body>
        <ns1:aaa xmlns:ns1="urn:aaa" SOAP-ENV:encodingStyle="...">
            <foobar xsi:type="xsd:string">&x1;</foobar>
        </ns1:aaa>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope> 

システムが外部のXMLデータに関与しない場合は、この問題について心配する必要はありませんが、関与するシステムでは、「エンティティ展開の制限」「要素属性数の制限」で説明するようにセキュア処理機能をオンにして制限をリセットする必要があります。

DTDの禁止

http://apache.org/xml/features/disallow-doctype-declパーサー・プロパティをtrueに設定すると、着信XMLドキュメントにDOCTYPE宣言が含まれる場合に致命的エラーがスローされます。このプロパティのデフォルト値はfalseです。SOAPメッセージには文書型定義を含めることができないため、一般にこのプロパティはSOAPベースのアプリケーションで便利です。

StAXを使用したセキュア処理

クラスXMLInputFactoryには、DTDをサポートしないプロセッサを要求するプロパティjavax.xml.stream.supportDTDが含まれています。StAXには、DTD処理を無効化するために使用できる類似のプロパティXMLInputFactory.SUPPORT_DTDがあります。

    XMLInputFactory xif = XMLInputFactory.newInstance();
    xif.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);

外部リソースの解決

次のシステム・プロパティは、XMLパーサーによる外部リソースの解決方法を制限します:

  • javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD
  • javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA
  • javax.xml.XMLConstants.ACCESS_EXTERNAL_STYLESHEET

これらのプロパティの詳細は、JavaチュートリアルJAXP 1.5および新規プロパティの説明を参照してください。

JAXPセキュア処理をオフにする

ファクトリに対してsetFeatureメソッドを呼び出して、JAXPのセキュア処理をオフにします。次のコード例では、SAXパーサーのJAXPのセキュア処理をオフにします。

    SAXParserFactory spf = SAXParserFactory.newInstance();
    spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING,false);

DOMまたはSAXパーサーのJAXPセキュア処理を無効にする場合は、entityExpansionLimitelementAttributeLimitおよびmaxOccursで指定されるデフォルトの制限事項を削除します。