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)の実装を提供する
- セキュアなチャネルを作成するためにインスタンス化可能なクラスを含む(
SSLSocket
、SSLServerSocket
、および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ドキュメント
-
javax.netパッケージ
-
javax.net.sslパッケージ
Java SEセキュリティ
-
Java SEセキュリティのホーム・ページ
-
JavaチュートリアルのJava SEのセキュリティ機能トレール
Transport Layer Security(TLS)
Datagram Transport Layer Security (DTLS)
米国の暗号化政策
JSSEクラスとインタフェース
セキュアな通信を行うには、接続の両側がSSL対応であることが必要です。JSSE APIの接続のエンドポイント・クラスは、SSLSocket
およびSSLEngine
です。図8-1では、SSLSocket
とSSLEngine
の作成に使用される主なクラスを論理的な順序で並べています。
SSLSocket
はSSLSocketFactory
またはイン・バウンド接続を受け取るSSLServerSocket
によって作成されます。SSLServerSocket
はSSLServerSocketFactory
で作成されます。SSLSocketFactory
およびSSLServerSocketFactory
オブジェクトはどちらもSSLContext
で作成されます。SSLEngine
は、SSLContext
によって直接作成され、アプリケーションに依存してすべての入出力を処理します。
ノート:
rawSSLSocket
クラスまたはSSLEngine
クラスを使用する場合は、データの送信前に必ずピアのクレデンシャルをチェックしてください。JDK 7以降は、エンドポイント識別/検証の手順は、SSL/TLSハンドシェーク中に処理できます。メソッドSSLParameters.setEndpointIdentificationAlgorithmを参照してください。
たとえば、URL内のホスト名は、ピアのクレデンシャル内のホスト名と一致する必要があります。ホスト名が検証されない場合、URL不正行為によってアプリケーションが悪用される可能性があります。
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のインスタンスによって作成できます。
- SSLSocketはSSLServerSocketクラスの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のメソッドに関するハンドシェーク・プロセスについて説明します:
- SSLEngineを作成した後、様々な
set*
メソッドを呼び出して、これから発生する接続のすべての側面(setEnabledProtocols()、setEnabledCipherSuites()、setUseClientMode()、setWantClientAuth()など)を構成します。SSLParametersクラスを使用して接続を構成することもできます。これにより、単一のメソッド呼出しで複数の設定を行うことができます。 - SSLEngineに対して現在は空のSSLSessionを取得し、getApplicationBufferSize()およびgetPacketBufferSize()メソッドで生成できるアプリケーションおよびネットワーク・バイトの最大バッファ・サイズを決定します。それに従って、アプリケーションおよびネットワーク・バッファにByteBufferインスタンスを割り当てます。
- 接続とバッファを構成したら、beginHandshake()メソッドを呼び出して、SSLEngineを初期ハンドシェーク状態にします。
- 接続で使用するトランスポート・メカニズム(SocketChannelクラスやSocketクラスなど)を作成します。
-
wrap()およびunwrap()メソッドを呼び出して、初期ハンドシェークを実行します。後でwrap()/unwrap()呼出しでアプリケーション・データを使用、生成および適切に保護するには、これらのメソッドを複数回呼び出す必要があります。
ハンドシェーク・バイトは、トランスポートメカニズムを使用してピアと交換する必要があります。TLSハンドシェーク・メカニズムの詳細は、TLS RFCのいずれか(RFC 5246: Transport Layer Security (TLS)プロトコル: バージョン1.2など)を参照してください。
たとえば、SSLEngineがクライアントとして機能し、TLSv1.2を使用してハンドシェークを実行する場合、次のことが発生する可能性があります:
- wrap()メソッドがTLS ClientHelloメッセージを生成し、アウトバウンド・ネットワーク・バッファに格納します。アプリケーションは、このメッセージのバイトをピアに正しく送信する必要があります。
- SSLEngineは、ハンドシェークを進めるためにピアのレスポンス(ServerHello、Certificate、ServerHelloDoneメッセージなど)を処理する必要があります。アプリケーションは、ネットワーク・トランスポートからレスポンス・バイトを取得し、インバウンド・ネットワーク・バッファに格納します。SSLEngineは、unwrap()メソッドを使用してこれらのバイトを処理します。
- SSLEngineは、さらにハンドシェーク・データ(ChangeCipherSuiteメッセージやFinishedメッセージなど)を送信します。wrap()は、メッセージのバイトをアウトバウンド・ネットワーク・バッファに格納します。アプリケーションは、以前と同様に、これらのバイトをピアに正しく送信する必要があります。
- SSLEngineは、ピアのChangeCipherSuiteまたはFinishedメッセージを待ちます。このメッセージのバイトは、ステップbと同じ経路に従います。
- ハンドシェークが完了すると、アプリケーション・データの流れが開始します。wrap()メソッドを呼び出してアウトバウンド・アプリケーション・バッファからバイトを取り出し、暗号化して保護し、ピアにトランスポートするためにネットワーク・バッファに格納します。同様に、unwrap()メソッドを呼び出して、インバウンド・ネットワーク・データを復号化および保護解除します。結果のアプリケーション・データは、インバウンド・アプリケーション・データ・バッファに格納されます。
- 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ハンドシェーク時の状態マシンと、対応するメッセージおよびステータスを示します。
TLSデータを処理するステップ
クライアントとサーバーの間で送信されるTLSデータを処理するとします。通常は、次のステップに従います:
-
ByteBuffer
インスタンスを作成して、アプリケーション・データ・バッファおよびクライアントとサーバーのネットワーク・データ・バッファを表現します。クライアントおよびサーバーのアウトバウンド・アプリケーション・データ・バッファで、暗号化してネットワーク経由でサーバーおよびクライアントに送信するデータをそれぞれ指定します。ノート:
wrap(ByteBuffer src, ByteBuffer dst)
メソッドの場合、パラメータsrc
はアプリケーション・データ・バッファで、dst
はネットワーク・データ・バッファです。これに対して、unwrap(ByteBuffer src, ByteBuffer dst)
メソッドの場合、パラメータsrc
はネットワーク・データ・バッファで、dst
はアプリケーション・データ・バッファです。wrap()
とunwrap()
はどちらもSSLEngineResult
のインスタンスを返します。このインスタンスには、ハンドシェークが完了しているかどうか、またはハンドシェークを進めるために次に必要な処理を示すSSLEngineResult.HandshakeStatus
フィールドが含まれています。 -
ループ内で、ハンドシェークが完了し、クライアントとサーバーの両方がアプリケーション・データを相互に送信するまで、クライアントとサーバーで次のように
wrap()
およびunwrap()
を呼び出します:-
クライアントおよびサーバーで
wrap()
を呼び出します。wrap()
が返すSSLEngineResult
インスタンスのSSLEngineResult.HandshakeStatus
フィールドの値を確認します。- ハンドシェークが完了していない場合、パラメータ
dst
には、ネットワークを介してピアに送信する必要があるハンドシェーク・データが含まれます。 - ハンドシェークが完了した場合、
dst
にはSSLEngine
で暗号化されたアプリケーション・データが含まれ、リモート・ピアに送信できるようになります。
- ハンドシェークが完了していない場合、パラメータ
-
wrap()
およびunwrap()
メソッドによって返されるSSLEngineResult.HandshakeStatus
値を処理するコードを追加します。詳細は、「SSLEngineの操作のステータスについて」を参照してください。 -
wrap()
メソッドによってネットワーク・データ・バッファにデータが生成された場合(ハンドシェーク・データまたは暗号化されたアプリケーション・データのいずれかを含めることが可能)、それをネットワーク経由でリモート・ピアに送信します。ノート:
- ネットワーク・データ・バッファ内のデータをリモート・ピアに送信するのは、
SSLEngine
ではなく、アプリケーションの役割です。 wrap()
を呼び出した後、ネットワーク・データ・バッファ内のすべてのデータがピアに送信されたことを確認する必要があります。
たとえば、例8-2は、
SocketChannel.write()
を呼び出してリモート・ピアにネットワーク・データを送信します。ByteBuffer.hasRemaining()
を呼び出して、すべてのネットワーク・データが送信されたことを確認します。while (myNetData.hasRemaining()) { socketChannel.write(myNetData); }
- ネットワーク・データ・バッファ内のデータをリモート・ピアに送信するのは、
-
リモート・ピアによってネットワーク経由で送信されたネットワーク・データを取得します。これを行うのは、
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 }
-
リモート・ピアから取得したネットワーク・データを使用して、クライアントとサーバーで
unwrap()
を呼び出します。unwrap()
が返すSSLEngineResult
インスタンスのSSLEngineResult.HandshakeStatus
フィールドの値を確認します:- ハンドシェークが完了していない場合は、
src
パラメータに追加のハンドシェーク・パケットが含まれているか、またはハンドシェークを続行するためにピアからさらにパケットを取得する必要があります。 - ハンドシェークが完了した場合は、
dst
にはSSLEngine
によって復号化されたアプリケーション・データが含まれ、すぐにアプリケーションで処理できます。
- ハンドシェークが完了していない場合は、
-
クライアントとサーバーが、
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-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()メソッドの違い
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実装では、前のすべてのメッセージを受け取る前に、今後の処理のためにハンドシェーク・メッセージをバッファする必要があることがあります。
SSLEngine
のDTLS実装は、ハンドシェーク・メッセージを並べ替える役割を担います。ハンドシェーク・メッセージのバッファリングと並替えは、アプリケーションに対して透過的です。
ただし、アプリケーションは、HandshakeStatus.NEED_UNWRAP_AGAIN
ステータスを管理する必要があります。このステータスは、次のSSLEngine.unwrap()操作にはリモート側からのその他のデータが必要ないことを示します。
図8-6では、HandshakeStatus.NEED_UNWRAP_AGAIN
の使用の一般的なシナリオを示します。
図8-6 NEED_UNWRAP_AGAINでのDTLSバッファ済ハンドシェークの状態マシン
ブロック・タスクの処理
ハンドシェーク時に、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
またはSSLEngine
のgetHandshakeSession()
を呼び出すことによって取得できます。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.HttpsURLConnection
はjava.net.HttpURLConnection
クラスを拡張し、HTTPS固有の機能のサポートを追加します。
HTTPSプロトコルはHTTPプロトコルに似ていますが、データを要求または受信する前に、まずTLSソケットを利用してセキュアなチャネルを確立してピアの識別情報を検証します(「符号化方式の選択とリモート・エンティティの検証」を参照)。javax.net.ssl.HttpsURLConnection
はjava.net.HttpURLConnection
クラスを拡張し、HTTPS固有の機能のサポートを追加します。HTTPS URLの構築方法と使用方法の詳細は、java.net.URL、java.net.URLConnection、java.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
をオーバーライドできます。
ノート:
デフォルトのstaticSSLSocketFactory
の変更はHttpsURLConnection
の既存のインスタンスに影響しません。既存のインスタンスを変更するには、setSSLSocketFactory()
メソッドの呼出しが必要です。
getSSLSocketFactory()
メソッドまたはgetDefaultSSLSocketFactory()
メソッドを呼び出すことにより、インスタンスごと、またはクラスごとにSSLSocketFactory
を取得できます。
割当て済のHostnameVerifierの設定
URLのホスト名がTLSハンドシェークの一部として受け取ったクレデンシャル内のホスト名と一致しない場合、URL不正行為が発生した可能性があります。実装で、十分な確信を持ってホスト名の一致を判断できない場合、TLS実装で、インスタンスの割当て済HostnameVerifierのコールバックを実行して、より詳しいチェックを行います。ホスト名ベリファイアは、ホスト名パターン・マッチングを実行したり、対話型のダイアログ・ボックスを表示したりなど、判定を下すために必要なあらゆるステップを取ることができます。ホスト名ベリファイアによる検証に失敗した場合は、接続が切断されます。ホスト名の検証に関する詳細については、RFC 2818: HTTP over TLSを参照してください。
setHostnameVerifier()メソッドおよびsetDefaultHostnameVerifier()メソッドの動作は、インスタンスごと、またはクラスごとにHostnameVerifierオブジェクトが割り当てられ、現在の値がgetHostnameVerifier()メソッドまたはgetDefaultHostnameVerifier()メソッドの呼出しによって取得できる点で、setSSLSocketFactory()メソッドおよびsetDefaultSSLSocketFactory()メソッドと似ています。
サポート・クラスとインタフェース
このセクションのクラスとインタフェースは、SSLSocketFactory
、SSLServerSocketFactory
およびSSLEngine
オブジェクトを作成するために使用されるSSLContext
オブジェクトの作成と初期化をサポートするために提供されます。サポート・クラスとインタフェースはjavax.net.ssl
パッケージに含まれています。
この項で説明する3つのクラス(SSLContextクラス、KeyManagerFactoryクラスおよびTrustManagerFactoryクラス)はエンジン・クラスです。エンジン・クラスとは、特定のアルゴリズムのAPIクラス(SSLContext
の場合はプロトコル)です。その実装では1つまたは複数の暗号化サービス・プロバイダ(プロバイダ)パッケージで提供されることがあります。JCAの設計の原則およびエンジン・クラスとアルゴリズムを参照してください。
JSSEに標準で付属するSunJSSEプロバイダは、SSLContext
、KeyManagerFactory
および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 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
クラスは、セキュアなソケット・プロトコルの実装のエンジン・クラスです。このクラスのインスタンスは、SSLSocket
、SSLServerSocket
およびSSLEngine
のファクトリの役目を果たします。SSLContext
オブジェクトは、そのコンテキストの下で作成されたすべてのオブジェクトで共有される状態情報をすべて保持します。たとえば、セッションの状態は、ソケット・ファクトリにより作成され、コンテキストにより提供されたソケットによってハンドシェーク・プロトコルが取り決められると、SSLContext
と関連付けられます。キャッシュに書き込まれたこれらのセッションは、同じコンテキストで作成された別のソケットで再利用したり共有することができます。
各インスタンスは、認証の実行に必要なキー、証明書チェーン、および信頼されたルートCA証明書を使ってinit
メソッドで構成されます。この構成は、キーとトラスト・マネージャの形で提供されます。これらのマネージャは認証をサポートし、コンテキストによってサポートされる暗号群のキー合意を提供します。
現在は、X.509ベースのマネージャだけがサポートされています。
SSLContextクラスの取得と初期化
SSLContext
クラスは、SSLSocketFactory
クラスまたはSSLServerSocketFactory
クラスを作成するために使用されます。
SSLContext
を取得して初期化するには、次の2つの方法があります。
- 最も簡単な方法は、
SSLSocketFactory
またはSSLServerSocketFactory
クラスでSSLContext.getDefault
staticメソッドを呼び出すことです。このメソッドは、デフォルトのKeyManager
、TrustManager
およびSecureRandom
(セキュアな乱数ジェネレータ)を使用してデフォルトのSSLContext
を作成します。デフォルトのKeyManagerFactory
およびTrustManagerFactory
を使用すると、KeyManager
およびTrustManager
がそれぞれ作成されます。使用するキー・データは、「デフォルトのキーストアとトラストストア、ストア・タイプ、およびストア・パスワードのカスタマイズ」で説明するシステム・プロパティで指定されるデフォルトのキーストアおよびトラストストアにあります。 - 作成したコンテキストの動作を呼出し側で最も厳密に制御できるようにする方法は、
SSLContext
クラスでstaticメソッドSSLContext.getDefault
を呼び出してから、そのインスタンスの適切なinit()
メソッドを呼び出してコンテキストを初期化することです。init()
メソッドの1つのバリアントは、KeyManager
オブジェクトの配列、TrustManager
オブジェクトの配列、およびSecureRandom
オブジェクトの3つの引数を取ります。適切なインタフェースを実装するか、実装を生成するKeyManagerFactory
クラスとTrustManagerFactory
クラスを使用して、KeyManager
オブジェクトとTrustManager
オブジェクトが作成されます。その後、KeyManagerFactory
とTrustManagerFactory
を、TrustManagerFactory
またはKeyManagerFactory
クラスのinit()
メソッドへの引数として渡されたKeyStore
に含まれるキー・データでそれぞれ初期化できます。最後に、TrustManagerFactory
のgetTrustManagers()
メソッドとKeyManagerFactory
のgetKeyManagers()
メソッドを呼び出して、トラスト・データまたはキー・データの型ごとに1つずつトラスト・マネージャまたはキー・マネージャの配列を取得できます。
TLS接続が確立されると、SSLSession
が作成され、これには設定した識別情報、使用する暗号化方式群などの様々な情報が含まれます。次にSSLSession
を使用して、2つのエンティティ間の進行中の関係と状態情報が記述されます。各TLS接続には、一度に1つのセッションが含まれますが、そのセッションがエンティティ間の接続に、同時に、または連続して何度も使用されることがあります。
SSLContextオブジェクトの作成
他のJCAプロバイダ・ベースのエンジン・クラスと同様に、SSLContext
オブジェクトは、SSLContext
クラスのgetInstance()
ファクトリ・メソッドを使用して作成されます。このようなstaticメソッドは、最低限要求されたセキュアなソケット・プロトコルを実装するインスタンスを返します。返されるインスタンスはその他のプロトコルも実装できます。たとえば、getInstance("TLSv1")
はTLSv1、TLSv1.1およびTLSv1.2を実装するインスタンスを返すことができます。getSupportedProtocols()
メソッドは、このコンテキストからSSLSocket
、SSLServerSocket
または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にできます。その場合、デフォルト実装が使用されます。
内部のデフォルト・コンテキストが使用される場合(SSLContext
はSSLSocketFactory.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
メソッドを呼び出すのが適切です。TrustManagerFactory
はKeyStore
に、認証チェック中に信頼すべきリモート証明書の情報を問い合わせます。
プロバイダでは、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.enableCRLDP
をtrue
に設定する必要があります。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
に渡された場合、ファクトリは次のプロセスを使用してトラスト・データを検索します。
-
javax.net.ssl.trustStore
プロパティが定義されている場合、TrustManagerFactory
は、このシステム・プロパティで指定されたファイル名を使用してファイルを検索し、KeyStoreパラメータにそのファイルを使用しようとします。javax.net.ssl.trustStorePassword
システム・プロパティも定義されている場合は、ファイルを開く前に、その値を使用してトラストストアのデータの整合性をチェックします。javax.net.ssl.trustStore
プロパティが定義されているが、指定したファイルが存在しない場合、空のキーストアを使用するデフォルトのTrustManager
が作成されます。 javax.net.ssl.trustStore
システム・プロパティが指定されていない場合:- java-home
/lib/security/jssecacerts
ファイルが存在すれば、そのファイルが使用されます。 - java-home
/lib/security/cacerts
ファイルが存在すれば、そのファイルが使用されます。 - これらのどちらのファイルも存在しない場合、TLS暗号化方式群が匿名であり、認証を行わないので、トラストストアは必要ありません。
- java-home
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 TrustManagerFactory
はX509ExtendedTrustManager
インスタンスを返します。
独自の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
メソッドを呼び出すのが適切です。KeyManagerFactory
はKeyStore
に、リモートのソケット・ピアを認証するために使用すべき秘密キー、および対応する公開キー証明書について問い合わせます。パスワード・パラメータは、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
で証明書を選択できる必要があります。
二次サポート・クラスおよびインタフェース
二次サポート・クラスは、セキュアなソケットの作成、使用、および管理をサポートするJSSE APIの一部として提供されます。このクラスは、セキュアなソケット・アプリケーションでは、コア・クラスやサポート・クラスほどには使用されません。セカンダリ・サポート・クラスおよびインタフェースはjavax.net.ssl
およびjavax.security.cert
パッケージに含まれています。
SSLParametersクラス
SSLParameters
クラスは、SSL/TLS/DTLS接続に影響する次のパラメータをカプセル化します。
- TLS/DTLSハンドシェークで受け付けられる暗号化方式群のリスト
- TLS/DTLSハンドシェイクで受け入れられる署名スキームのリスト
- 許可されるプロトコルのリスト
- TLS/DTLSハンドシェーク中のエンドポイント識別アルゴリズム
- サーバー名とサーバー名のマッチャ(Server Name Indication (SNI)拡張を参照)
- TLS/DTLSハンドシェークで使用される暗号化方式群の優先順位
- TLS/DTLSハンドシェーク中のアルゴリズム
- Server Name Indication (SNI)
- 最大ネットワーク・パケット・サイズ
- アルゴリズム制約、およびTLS//DTLSサーバーがクライアント認証を要求する必要があるか必要とするかどうか
次のメソッドを使用して、SSLSocket
またはSSLEngine
の現在のSSLParameters
を取得できます。
SSLSocket
、SSLServerSocket
およびSSLEngine
内のgetSSLParameters()
SSLContext
内のgetDefaultSSLParameters()
およびgetSupportedSSLParamters()
SSLSocket
、SSLServerSocket
およびSSLEngine
のsetSSLParameters()
メソッドで、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);
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を参照してください。
HostnameVerifier
をHttpsURLConnection
に割り当てる方法の詳細は、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_CIPHER
、MESSAGE_DIGEST
、SIGNATURE
などのフィールドを含む列挙です。
したがって、AlgorithmConstraints
実装は、「このキーとこのアルゴリズムを暗号化操作の目的で使用できるのか」といった質問に答えることができます。
新しいsetAlgorithmConstraints()
メソッドを使用して、AlgorithmConstraints
オブジェクトをSSLParameters
オブジェクトと関連付けることができます。SSLParameters
オブジェクトに対する現在のAlgorithmConstraints
オブジェクトは、getAlgorithmConstraints()
メソッドを使用して取得します。
StandardConstantsクラス
StandardConstants
クラスは、JSSEでの標準の定数定義を表すために使用します。
StandardConstants.SNI_HOST_NAME
Server 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、DTLSv1.0、RC4、DES、MD5withRSA、DH keySize < 1024、EC keySize < 224、3DES_EDE_CBC、anon、NULL、ECDH脚注2 |
特定のアルゴリズム(プロトコル・バージョン、暗号化方式群、キー交換メカニズムなど)を無効化し、アプリケーションで明示的に有効化されてもTLS//DTLS接続でネゴシエーションされないようにします |
jdk.tls.keyLimits *
|
アルゴリズムでキー・セットを使用して暗号化可能なデータ量の制限 | AES/GCM/NoPadding KeyUpdate 2^37 | アルゴリズムで特定のキー・セットを使用して暗号化可能なデータ量を制限します。この制限に達すると、KeyUpdateポストハンドシェーク・メッセージが送信され、現在のキー・セットを更新するように要求します。 |
jdk.tls.legacyAlgorithms *
|
レガシーの暗号化アルゴリズム |
SHA1、RSA keySize < 2048、DSA keySize < 2048、DES、DESede、MD5、RC2、ARCFOUR脚注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つのプロバイダは、次のとおりです。
|
セキュリティ・プロパティ・ファイル内の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実装と同じ方法でそれを処理すべきです。プロパティが今後も存在すること、またはシステム型やセキュリティ型が将来のリリースでも変更されないことの保証はされません。
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接続用のデフォルトの暗号化方式群 | ソケット・ファクトリによって決定 |
|
https.protocols *
|
HTTPS接続用のデフォルトのハンドシェーク・プロトコル | ソケット・ファクトリによって決定 |
|
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 |
セッション・キャッシュ・サイズを設定するには、 |
javax.net.ssl.trustStore *
|
デフォルトのトラストストア(「デフォルトのキーストアとトラストストア、ストア・タイプおよびストア・パスワードのカスタマイズ」を参照) | 存在する場合は、jssecacerts 。
そうでない場合、 |
値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.enableCAExtension *
|
サーバー証明書選択のためのcertificate_authorities拡張の有効化 | false |
クライアントまたはサーバーが、拡張機能のサイズ制限(2^16バイト未満)を超えるように、より多くのCAを信頼する場合、拡張機能は有効になりません。また、一部のサーバー実装では、ハンドシェーク・メッセージが2^14バイトを超えることは許可されません。したがって、jdk.tls.client.enableCAExtension がtrue に設定されており、クライアントがサーバー実装の制限を超えるほど多くのCAを信頼している場合は、相互運用性の問題が発生する可能性があります。
|
jdk.tls.client.enableSessionTicketExtension *
|
サーバー側の状態のないセッションの再開 | true |
この拡張により、サーバー側の状態のないTLSセッションの再開(RFC 5077)に関するサーバーのセッション状態が、クライアントに受け入れられるようになります。 |
jdk.tls.client.protocols *
|
TLS/DTLSクライアント用のデフォルトのハンドシェーク・プロトコル。SunJSSEプロバイダを参照してください |
なし |
クライアント上で特定の たとえば、
|
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キー交換でサポートされている名前付きグループのカスタマイズ | このシステム・プロパティが定義されていないか値が空の場合は、実装のデフォルトのグループおよび設定が使用されます。 |
これには、引用符で囲まれたカンマ区切りリスト形式で、有効な名前付きグループが優先順位に従って格納されています。たとえば:
キー交換の名前付きグループの標準リストは、Javaセキュリティ標準アルゴリズム名仕様の名前付きグループに関する項で定義されます。 |
jdk.tls.rejectClientInitiatedRenegotiation *
|
サーバー側でクライアントが開始した再ネゴシエーションを拒否します。このシステム・プロパティがtrue の場合、サーバーはクライアントが開始した再ネゴシエーションを受け入れず、致命的なhandshake_failure アラートで失敗します。クライアント側で初期化されたサーバー側再ネゴシエーションを拒否します。
|
false |
なし |
jdk.tls.server.cipherSuites *
|
サーバー側の、デフォルトで有効な暗号化方式群。「デフォルトで有効な暗号化方式群の指定」を参照してください | デフォルトで有効になっている暗号化方式群を確認するには、「SunJSSE暗号化方式群」を参照してください | 注意: これらのシステム・プロパティを使用して弱い暗号化方式群を構成できる、つまり、構成された暗号化方式群は将来的に脆弱である可能性があります。このリスクを理解せずにこれらのシステム・プロパティを使用することはお薦めしません。 |
jdk.tls.server.disableExtensions *
|
デフォルトの拡張機能の構成 | なし | サーバー側で使用される拡張機能をブロックします。 |
jdk.tls.server.enableSessionTicketExtension *
|
サーバー側の状態のないセッションの再開 | true |
ステートレス・セッション・チケットには暗号化されたサーバーの状態が含まれており、サーバーのリソースが節約されます。 |
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.sessionTicketTimeout *
|
サーバー・キャッシュ内のセッションまたは再開のステートレス・チケットを使用できる期間を指定します | 86400秒(24時間) |
このプロパティを使用して設定された値は、メソッドSSLSessionContext.setSessionTimeout()を使用して、実行時に変更できます。 |
jdk.tls.server.SignatureSchemes *
|
TLS/DTLSプロトコルで使用可能な署名スキームの指定。 | このシステム・プロパティが定義されていないか空である場合、プロバイダ固有のデフォルトが使用されます。 |
これは、サーバー側のTLS接続に使用できる署名スキームを指定する、サポートされている署名スキーム名のカンマ区切りリストです。たとえば:
認識されない、またはサポートされない署名スキーム名がプロパティに指定された場合は無視されます。この名前は大/小文字が区別されません。 署名スキーム名のリストは、Javaセキュリティ標準アルゴリズム名仕様の署名スキームに関する項を参照してください。 |
jsse.enableFFDHE *
|
TLS/DTLSキー交換用のFinite Field Diffie-Hellman Ephemeral (FFDHE)パラメータの有効化または無効化 |
true |
FFDHEはRFC 7919で定義されているTLS/DTLS拡張です。これを使用すると、TLS/DTLS接続で既知の有限フィールドDiffie-Hellmanグループを使用できます。かなり古い一部のTLSベンダーは、TLS拡張を処理できないことがあります。この場合、このプロパティを |
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パケット用のバッファのデフォルトのサイズ設定 |
なし |
このシステム・プロパティを |
sun.security.ssl.allowLegacyHelloMessages *
|
従来のHelloメッセージを許可します。「SunJSSE再ネゴシエーションの相互運用性モード」を参照してください | false |
このシステム・プロパティを このシステム・プロパティは推奨されておらず、将来のJDKリリースで削除される可能性があります。 |
sun.security.ssl.allowUnsafeRenegotiation *
|
安全でないSSL/TLS再ネゴシエーションを許可します。「SunJSSE再ネゴシエーションの相互運用性モード」を参照してください | false |
このシステム・プロパティを このシステム・プロパティは推奨されておらず、将来のJDKリリースで削除される可能性があります。 |
* このシステム・プロパティは現在JSSE実装で使用されていますが、他の実装で調査され、使用されている保証はありません。他の実装で調査する場合は、その実装で、JSSE実装と同じ方法でそれを処理すべきです。プロパティが今後も存在すること、またはシステム型やセキュリティ型が将来のリリースでも変更されないことの保証はされません。
java.security.Securityプロパティの指定方法
X509証明書実装のカスタマイズ
X509Certificate.getInstance()
メソッドで返されたX509証明書実装は、デフォルトでJSSE実装の実装です。
cert.provider.x509v1
という名前のjava.security.Securityプロパティの指定方法の値として指定します。
MyX509CertificateImpl
と呼ばれ、com.cryptox
パッケージにある場合、セキュリティ・プロパティ・ファイルに次の行を追加してください。
cert.provider.x509v1=com.cryptox.MyX509CertificateImpl
デフォルトで有効な暗号化方式群の指定
デフォルトで有効な暗号化方式群は、アプリケーション内で、またはシステム・プロパティjdk.tls.client.cipherSuites
とjdk.tls.server.cipherSuites
を使用して指定できます。
ノート:
有効な暗号化方式群の実際の使用は、アルゴリズム制約によって制限されます。デフォルトで有効化する暗号化方式群のセットは、この優先度順に従い、次のいずれかの方法で決定されます。
- アプリケーションによる明示的な設定
- システム・プロパティによる指定
- JSSEプロバイダのデフォルトによる指定
たとえば、デフォルトで有効な暗号化方式群をアプリケーション内で明示的に設定すると、これにより、jdk.tls.client.cipherSuites
またはjdk.tls.server.cipherSuites
およびJSSEプロバイダのデフォルトで指定された設定がオーバーライドされます。
アプリケーションによる明示的な設定
次のいずれかのメソッドを使用して、どの暗号化方式群が有効であるかを設定できます。
- SSLSocket.setEnabledCipherSuites(String[])
- SSLEngine.setEnabledCipherSuites(String[])
- SSLServerSocket.setEnabledCipherSuites(String[])
- SSLParameters(String[] cipherSuites)
- SSLParameters(String[] cipherSuites, String[] protocols)
- SSLParameters.setCipherSuites(String[])
- HttpsURLConnectionの
https.cipherSuites
システム・プロパティ
システム・プロパティによる指定
システム・プロパティ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.pkgs
のjava.lang.Systemプロパティの指定方法を設定します。その結果、JDKのデフォルト・クラスより前に、指定したクラスが検索され、ロードされます。詳細は、URLクラスを参照してください。
プロバイダ実装のカスタマイズ
JDKには、SunJSSEというJSSE暗号化サービス・プロバイダ(略称はプロバイダ)が付属しています。基本的に、プロバイダは特定の暗号化アルゴリズムのエンジン・クラスを実装するパッケージです。
JSSEのエンジン・クラスはSSLContext
、KeyManagerFactory
、および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
プロパティは、デフォルトでは設定されておらず、アプリケーションのパフォーマンス・チューニングのためにのみ使用されます。
デフォルトのキーストアとトラストストア、ストア・タイプおよびストア・パスワードのカスタマイズ
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 Development Kitツール仕様の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
に提供されたKeyManager
とTrustManager
の両方またはどちらか一方は、前のセクションで説明したように、指定したキーストアまたはトラストストアを管理するための実装になります。
選択されるKeyManager
実装は、まずssl.KeyManagerFactory.algorithm
セキュリティ・プロパティを調査することによって決定されます。そのようなプロパティ値が指定されていると、指定したアルゴリズムのKeyManagerFactory
実装が検索されます。実装を提供する最初のプロバイダの実装が使用されます。そのgetKeyManagers()
メソッドが呼び出され、デフォルトのSSLContext
に提供するKeyManager
が決定されます。技術的には、getKeyManagers()
は、キー・データの型ごとに1つずつのKeyManager
で、KeyManager
オブジェクトの配列を返します。そのようなセキュリティ・プロパティ値が指定されていない場合、SunX509のデフォルト値を使用して検索を実行します。
ノート:
SunX509アルゴリズムのKeyManagerFactory
実装はSunJSSEプロバイダによって提供されます。それが指定するKeyManager
はjavax.net.ssl.X509KeyManager
実装です。
同様に、選択されるTrustManager
実装は、まずssl.TrustManagerFactory.algorithm
セキュリティ・プロパティを調査することによって決定されます。そのようなプロパティ値が指定されていると、指定したアルゴリズムのTrustManagerFactory
実装が検索されます。実装を提供する最初のプロバイダの実装が使用されます。そのgetTrustManagers
メソッドが呼び出され、デフォルトのSSLContext
に提供するTrustManager
が決定されます。技術的には、getTrustManagers()
は、トラスト・データの型ごとに1つずつのTrustManager
で、TrustManager
オブジェクトの配列を返します。そのようなセキュリティ・プロパティ値が指定されていない場合、PKIXのデフォルト値を使用して検索を実行します。
ノート:
PKIXアルゴリズムのTrustManagerFactory
実装はSunJSSEプロバイダによって提供されます。それが指定するTrustManager
はjavax.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など)が含まれている場合(たとえば、
RC4
がjdk.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
サーバー側の状態のないセッションの再開
サーバー側の状態のないセッションの再開機能を使用すると、JSSEのサーバー側のステートレス操作が可能になります。TLS 1.2以前のRFC 5077 (サーバー側の状態のないTLSセッションの再開)、およびTLS 1.3のRFC 8446で説明されているように、TLSサーバーは、内部セッション情報を暗号化されたセッション・チケットの形式で、ステートレス操作をサポートしているクライアントに送信します。そのセッション・チケットが、TLSハンドシェーク中にサーバーに送られ、セッションが再開されます。これにより、セッション・キャッシュがほとんど使用されなくなるため、ワークロードが大量にあるTLSサーバーのパフォーマンスとメモリー使用量が改善されます。ただし、キャッシュされるセッション情報が少ないと、情報のないセッションが出てくる可能性があります。この機能はデフォルトで有効になっています。次の2つのシステム・プロパティを設定することでオフにできます:
jdk.tls.client.enableSessionTicketExtension
: TLS 1.2以前のクライアント側のClientHelloメッセージのセッション・チケット拡張を切り替えます。値がtrue
(デフォルト値)の場合は拡張が送信され、false
の場合は送信されません。jdk.tls.server.enableSessionTicketExtension
: クライアントでサポートされていれば、サーバーでのステートレス・セッション・チケットの使用が可能になります。ステートレス・セッション・チケットをサポートしていないクライアントでは、かわりにキャッシュが使用されます。値がtrue
(デフォルト値)の場合は、ステートレス・セッション・チケットの使用およびステートレス・セッション再開が可能になります: NewSessionTicketメッセージには、すべてのセッション情報が(暗号化形式で)含まれます。値がfalse
の場合は、ステートレス・セッション・チケットの使用が無効になります。つまり、セッション再開はステートフルです: NewSessionTicketメッセージには、セッション・キャッシュからセッション情報にアクセスするためにセッション再開時にサーバーで使用されるキーが含まれているだけです。
ノート:
TLS 1.3では、ステートレス・チケットで既存の事前共有キー(PSK)再開拡張が使用されます(事前共有キーを使用したセッション再開を参照)。そのため、サーバー側の状態のないセッションの再開には、これら2つのプロパティは必要ありません。ただし、ステートレス・チケットのコンテンツ、特にNewSessionTicketメッセージのコンテンツは、jdk.tls.server.enableSessionTicketExtension
の値により異なります: jdk.tls.server.enableSessionTicketExtension
がtrue
の場合、NewSessionTicketメッセージには暗号化されたセッション状態が含まれます。false
の場合、セッション状態はPSK再開拡張でキャッシュされます。
TLS 1.2では、ステートレス・セッション・チケットは、クライアントでサポートされている場合にのみ使用されます。
受信時に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.enableCAExtension
をtrue
に設定して、サーバー証明書の選択に対してこの拡張機能を有効にします。このプロパティのデフォルト値はfalse
です。
ノート:
クライアントまたはサーバーが、拡張機能のサイズ制限(2^16バイト未満)を超えるように、より多くのCAを信頼する場合、拡張機能は有効になりません。また、一部のサーバー実装では、ハンドシェーク・メッセージが2^14バイトを超えることは許可されません。したがって、jdk.tls.client.enableCAExtension
がtrue
に設定されており、クライアントがサーバー実装の制限を超えるほど多くの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 | 更新済 | |
更新済 | レガシー脚注3 | |
レガシー脚注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
)されます。まだアップグレードされていないピアから再ネゴシエーション要求を受け取ったアプリケーションは、現在の接続のタイプに応じて応答します。
- TLSv1
no_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つの証明書が同じアイデンティティを表しているかどうかを判断します:
- IPアドレスのサブジェクトの代替名が両方の証明書に存在する場合、それらは同一です。
- それ以外の場合、DNS名のサブジェクトの代替名が両方の証明書に存在する場合、それらは同一です。
- それ以外の場合、サブジェクト・フィールドが両方の証明書に存在する場合、証明書のサブジェクトと発行人は同一です。
SSL/TLS再ネゴシエーションで安全でないサーバー証明書の変更は、デフォルトでは許可されていません。SSL/TLS再ネゴシエーションにおける安全でないサーバー証明書の変更を制限するかどうかを定義するには、システム・プロパティjdk.tls.allowUnsafeServerCertChange
を使用します。このシステム・プロパティのデフォルト値はfalse
です。
注意:
安全でないサーバー証明書の変更による脆弱性が再び確立されてしまうため、本当に必要な場合を除き、このシステム・プロパティをtrue
に設定しないでください。
クライアント主導型OCSPとOCSPステープリング
Transport Layer Security (TLS)のハンドシェーク中にX.509証明書失効ステータスを判定するには、オンライン証明書ステータス・プロトコル(OCSP)を使用します。
-
証明書失効リスト(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ステープリングと証明書失効
オンライン証明書ステータス・プロトコル(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.enableStatusRequestExtension
をtrue
(そのデフォルト値)に設定することで有効になります。
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応答の取得に使用される最長時間を制御します。 受取済の応答は、該当する場合は実行中のステープリングのタイプに基づいて、 |
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 |
|
False |
jdk.tls.stapling.ignoreExtensions |
|
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.enableMFLNExtension
、jsse.enableSNIExtension
、jsse.enableSNIExtension
など)を有効または無効にするシステム・プロパティが存在しますが、jdk.tls.client.disableExtensions
またはjdk.tls.server.disableExtensions
を介して無効にされた拡張は、対応するシステム・プロパティで有効にできる場合でも有効にはなりません。
TLS/DTLSプロトコルで使用可能な署名スキームの指定
署名スキームは、TLS/DTLS接続のデジタル署名で使用されるアルゴリズムです。システム・プロパティjdk.tls.server.SignatureSchemes
には、サーバー側のTLS/DTLS接続に使用できる署名スキームを指定する、サポートされている署名スキーム名のカンマ区切りリストが含まれます。jdk.tls.client.SignatureSchemes
には、クライアント側の接続用であることを除いて、同じものが含まれています。
署名スキーム名のリストは、Javaセキュリティ標準アルゴリズム名仕様の署名スキームに関する項を参照してください。
アプリケーションでは、メソッドjavax.net.ssl.SSLParameters.setSignatureSchemes(String[])を使用して署名スキームを設定できます。SSL/TLS/DTLS接続での署名スキームの使用方法の具体的な詳細は、メソッドjavax.net.ssl.SSLParameters.getSignatureSchemes()に関する項を参照してください。
TLS/DTLSキー交換でサポートされている名前付きグループのカスタマイズ
名前付きグループは、キー交換用の事前定義された暗号化パラメータです。jdk.tls.namedGroups
システム・プロパティには、有効な名前付きグループを優先度の高い順に並べて引用符で囲んだカンマ区切りのリストが含まれています。
キー交換の名前付きグループの標準リストは、Javaセキュリティ標準アルゴリズム名仕様の名前付きグループに関する項で定義されます。
javax.net.ssl.SSLParameters.getNamedGroups()を呼び出して、TLS/DTLSプロトコルで使用できるキー交換の名前付きグループ名の優先度付き配列を取得します。javax.net.ssl.SSLParameters.setNamedGroups(String[])を呼び出して、キー交換の名前付きグループ名の優先度付き配列を指定できます。
ハードウェア高速化およびスマート・カードのサポート
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 Development Kitツール仕様の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
またはSSLEngine
のsetSSLParameters
メソッドを呼び出します。サーバーとのハンドシェーク中に、サーバーは、クライアントのアプリケーション・プロトコル・リストを読み取り、どれが最も適切かを判断します。
例8-21 JavaクライアントでのALPN値の設定および取得のサンプル・コード
たとえば、ここでは、クライアントでthree
とtwo
という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値one
、two
およびthree
が設定されているJavaサーバーにClientHello
メッセージが送信されます。コードの出力内容は次のとおりです:
Application Protocol client side: two
ハンドシェーク中にネゴシエーションの結果を確認することもできます。ハンドシェーク中のネゴシエーション済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();
}
}
three
およびtwo
を含むClientHello
が送信されると、出力は次のようになります。Application Protocol server side: two
ハンドシェーク中にネゴシエーションの結果を確認することもできます。ハンドシェーク中のネゴシエーション済ALPN値の特定を参照してください。
サーバーでのカスタムALPNの設定
コールバック・メソッドを設定することで、カスタムのALPNメカニズムを使用して、適切なアプリケーション・プロトコルを決定します。
サーバーのデフォルト・ネゴシエーション・プロトコルを使用しない場合は、SSLEngine
またはSSLSocket
のsetHandshakeApplicationProtocolSelector
メソッドを使用して、現在までのハンドシェーク状態を確認できる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);
}
}
KeyManager
として登録されており、Javaクライアントによって、ALPN値を含むClientHello
が送信される場合、出力は次のようになります。 In chooseServerAlias, ap is: <negotiated value>
例8-25 JavaサーバーでのカスタムKeyManagerの使用のサンプル・コード
この例では、デフォルトのALPNネゴシエーション戦略、および前のコード例で示されたカスタムKeyManager
、MyX509ExtendedKeyManager
を使用する単純な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
は、ネゴシエーションされたアプリケーション・プロトコル値を確認できるようになります。示されている例の場合、この値はコンソールに出力されます。
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プロトコル値です。
前述の例では、メソッドsetApplicationProtocols、rfc7301Grease8A
に渡された値の1つがGREASE値です。ピアはそれを拒否するのではなく無視します。
ALPN関連のクラスとメソッド
これらのクラスとメソッドは、アプリケーション層プロトコル・ネゴシエーション(ALPN)の使用時に使用されます。
使用するクラスとメソッド
SSLEngine
とSSLSocket
には同じ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 Development Kitツール仕様の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メッセージを受信した場合、接続を切断するものがあります。
解決法: クライアント側で、有効なプロトコルの調整を試みてください。これには、次のシステム・プロパティやメソッドの変更や呼び出しが含まれます。
クラスのシステム・プロパティHttpsURLConnection
https.protocols
- システム・プロパティjdk.tls.client.protocols
SSLContext.getInstance
メソッドSSLEngine.setEnabledProtocols
メソッドSSLSocket.setEnabledProtocols
メソッドSSLParameters.setProtocols
およびSSLEngine.setSSLParameters
メソッドSSLParameters.setProtocols
およびSSLSocket.setSSLParameters
メソッド
後方互換性のため、一部のTLS実装(SunJSSEなど)はTLSのClientHelloメッセージをSSLv2のClientHello形式にカプセル化して送信できます。SunJSSEプロバイダはこの機能をサポートしています。この機能を使用する場合は、必要に応じて「SSLv2Hello」を有効なプロトコル・リストに追加します。(「JDKプロバイダ」で「プロトコル」を参照してください。ここでは、SunJSSEプロバイダでデフォルトで有効になっているプロトコルがリストされています。)
TLS RFC標準で、実装は、両側で使用されている最新バージョンとネゴシエーションする必要がありますが、認識できないバージョンを提示されると、一部の非準拠実装は単にハングアップします。たとえば、SSLv3のみを認識する一部の古いサーバー実装は、TLSv1.2を要求されると、シャットダウンします。この状況では、TLSバージョンのフォールバック・スキームを使用することを検討してください。
- サーバーでTLSv1.2を理解しない場合、TLSv1.2からTLSv1.1にフォールバックします。
- 前のステップが機能しない場合は、TLSv1.1からTLSv1.0にフォールバックします。
たとえば、クライアントの有効なプロトコル・リストがTLSv1、TLSv1.1およびTLSv1.2の場合、一般的なTLSバージョンのフォールバック・スキームが次のように見えます。
- サーバーに接続を試みます。サーバーがTLS接続要求をただちに拒否した場合は、ステップ2に進みます。
- 有効なプロトコル・リスト内の最高のプロトコル・バージョン(最初の失敗の場合TLSv1.2など)を削除して、バージョン・フォールバック・スキームを試行します。
- 再度サーバーに接続を試みます。サーバーが接続を拒否した場合、サーバーがフォールバックできるバージョンがない場合を除いて、ステップ2に進みます。
- 接続が失敗し、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.0とDTLSバージョン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サーバーから応答を受け取る方法を示します。より簡単にするため、ClassFileServer
とSSLSocketClientWithClientAuth
はいずれも同じホストから実行します。
ローカルホストでの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接続のデバッグ」を参照してください。
このセクションには次のトピックが含まれます:
- サンプル・コードの場所
- サンプルの証明書とキー
- 次のトピックでは、サンプルについて説明します:
- JSSEサンプル・コードのトラブルシューティング
サンプル・コードの場所
Java SE 8ドキュメントのJSSEサンプル・コードには、すべてのサンプル・コード・ファイルとテキスト・ファイルがリストされています。そのページには、すべてのサンプル・コード・ファイルをダウンロードできるZIPファイルへのリンクも記載されています。
サンプルの証明書とキー
JSSEのサンプルでは、次の証明書キーストア・ファイルを使用して、クライアントおよびサーバーを認証します:
-
*/testkeys
これらのファイルは、公開キー/秘密キーおよび証明書データのソースとしてコード・サンプルで使用されます。クライアント・プログラム・ディレクトリで、
testkeys
ファイルには、JavaマスコットDuke
の証明書エントリが含まれます。サーバー・プログラム・ディレクトリ(./sockets/server
およびrmi
)では、このファイルにはサーバーlocalhost
の証明書エントリが含まれています。サンプル・コードでは、
testkeys
ファイルが現在の作業ディレクトリに存在すると見なしています。ノート:
これらは、非常に単純な証明書であり、本番環境に適していませんが、ここでサンプルを実行するには十分です。
これらのキーストアのパスワードは
passphrase
です -
samplecacerts
このトラストストア・ファイルはストックJDK
cacerts
ファイルに非常に似ていますが、その中には、複数のベンダーからの信頼証明書が含まれています。Dukeおよびlocalhostからの信頼できる証明書も含まれています。このキーストアのパスワードは
changeit
です信頼できる証明書ファイルの場所を構成する方法は、プロバイダのドキュメントを参照してください。
ノート:
JDKのユーザーは、次のいずれかの方法を使用して、トラスト・ストアの場所を指定できます。
-
システム・プロパティ:
java -Djavax.net.ssl.trustStore=samplecacerts \ -Djavax.net.ssl.trustStorePassword=changeit Application
-
ファイルを次の場所にインストールします。
<java-home>/lib/security/jssecacerts
-
ファイルを次の場所にインストールします。
<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の実行」に示しています。同様の変更をして、URLReader
、SSLSocketClient
またはSSLSocketClientWithTunneling
をClassFileServer
で実行できます。
クライアントとサーバーとの間で通信中に認証エラーが発生する場合(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.java
とClassServer.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台のマシンで、ただし別のターミナル・ウィンドウまたはコマンド・プロンプト・ウィンドウから両方を実行することもできます。クライアントとサーバーの両方を設定するには、次を実行します。
ClassFileServer
プログラムを1つのマシンまたはターミナル・ウィンドウから実行します。ClassFileServerの実行を参照してください。
SSLSocketClientWithClientAuth
プログラムを別のマシンやターミナル・ウィンドウで実行します。SSLSocketClientWithClientAuth
には、次のパラメータが必要です。host
は、ClassFileServer
を実行するために使用するマシンのホスト名です。port
は、ClassFileServer
用に指定したものと同じポートです。-
requestedfilepath
は、サーバーから取得するファイルのパスを示します。このパラメータには、/filepath
と指定する必要があります。GET文の一部として使用されるので、ファイル・パスにはフォワード・スラッシュが必要です。GET文には、稼動中のオペレーティング・システムの種類にかかわらず、フォワード・スラッシュが必要です。文の構成は次のようになります。"GET " + requestedfilepath + " HTTP/1.0"
ノート:
ClassFileServer
が動作しているローカル・マシンに接続するように、他のSSLClient*
アプリケーションのGET
コマンドを変更できます。
HTTPS接続を表すサンプル・コード
JSSEを介してセキュアな通信にアクセスするためのプライマリAPIは2つあります。1つの方法は、SSLSocketClient
、SSLSocketClientWithTunneling
および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.java
とURLReaderWithOptions.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
(公開キーのみ)を作成します。クライアント認証の場合、クライアントの証明書に対して同様の処理に従う必要があります。
ユーザー入力は太字で表示されます。
-
対応する公開キーおよび秘密キーによって、新しいキーストアと自己署名付き証明書を作成します。
% 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
-
キーストアを調べます。エントリの型は
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 ] ]
-
自己署名付き証明書をエクスポートし、内容を調べます。
% 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に対する署名付き証明書の要求に関する項を参照してください。 -
証明書を新しいトラスト・ストアにインポートします。
% 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
-
トラストストアを調べます。エントリの型は
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 ] ] ******************************************* *******************************************
-
ここで、適切なキーストアを使用してアプリケーションを実行します。この例では、デフォルトの
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)拡張を使用する方法およびそれを仮想インフラストラクチャに適用する方法を示します。
このセクションのすべての例で、パラメータの設定後にそれらを適用するには、対応するSSLSocket
、SSLEngine
または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
に基づいて、仮想サーバー・ディスパッチャを使用する手順を説明します。
-
サーバー名ハンドラを登録します。
このステップで、アプリケーションは様々なサーバー名表示の様々な
SSLContext
オブジェクトを作成したり、特定のサーバー名表示を指定した仮想マシンまたは分散システムにリンクしたりできます。たとえば、サーバー名が
www.example.org
の場合、登録されたサーバー名ハンドラはローカル仮想ホスティングWebサービス用などになります。ローカル仮想ホスティングWebサービスは指定したSSLContext
を使用します。サーバー名がwww.example.com
の場合、登録されたサーバー名ハンドラは10.0.0.36
でホストしている仮想マシン用などになります。ハンドラはこの接続を仮想マシンにマッピングできます。 -
ServerSocket
を作成し、新しい接続を受け付けます。ServerSocket serverSocket = new ServerSocket(serverPort); Socket socket = serverSocket.accept();
-
ソケット入力ストリームからバイトを読み取り、バッファして、バッファ済のバイトを調査します。
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()); }
-
調査済の機能から要求されたサーバー名を取得します。
List<SNIServerName> serverNames = capabilities.getServerNames();
-
このサーバー名表示の登録済のサーバー名ハンドラを検索します。
ホスト名のサービスが仮想マシンまたは他の分散システムに存在する場合、アプリケーションは接続を転送先に転送する必要があります。アプリケーションは、ソケット・ストリームからの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
に基づいて、仮想サーバー・ディスパッチャを使用する手順を説明します。
-
サーバー名ハンドラを登録します。
このステップで、アプリケーションは様々なサーバー名表示の様々な
SSLContext
オブジェクトを作成したり、特定のサーバー名表示を指定した仮想マシンまたは分散システムにリンクしたりできます。たとえば、サーバー名が
www.example.org
の場合、登録されたサーバー名ハンドラはローカル仮想ホスティングWebサービス用などになります。ローカル仮想ホスティングWebサービスは指定したSSLContext
を使用します。サーバー名がwww.example.com
の場合、登録されたサーバー名ハンドラは10.0.0.36
でホストしている仮想マシン用などになります。ハンドラはこの接続を仮想マシンにマッピングできます。 -
ServerSocket
またはServerSocketChannel
を作成し、新しい接続を受け付けます。ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(...); ... SocketChannel socketChannel = serverSocketChannel.accept();
-
ソケット入力ストリームからバイトを読み取り、バッファして、バッファ済のバイトを調査します。
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
-
調査済の機能から要求されたサーバー名を取得します。
List<SNIServerName> serverNames = capabilities.getServerNames();
-
このサーバー名表示の登録済のサーバー名ハンドラを検索します。
ホスト名のサービスが仮想マシンまたは他の分散システムに存在する場合、アプリケーションは接続を転送先に転送する必要があります。アプリケーションは、ソケット・ストリームからの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();
}
標準名
プロバイダのプラグイン可能性
JSSEは完全にプラガブルであり、サード・パーティのJSSEプロバイダの使用には何の制限もありません。
JAXPセキュリティ処理
JAXPセキュリティ処理は、パーサーやトランスフォーマなどのJAXPコンポーネントにセキュアな方法で動作するよう指示します。セキュリティ・マネージャが存在する場合は、JAXPセキュリティ処理が自動的にオンになります。存在しない場合は、JAXPセキュリティ処理がデフォルトで無効になります。
警告:
セキュリティ・マネージャおよびそれに関連するAPIは非推奨であり、今後のリリースでは削除されます。セキュリティ・マネージャの代わりとなるものはありません。詳細および代替手段については、JEP 411を参照してください。JAXPセキュリティ処理のデフォルトの制限事項
次の表では、JAXPセキュリティ処理が有効な場合に無効になるXML関連ファクトリ・クラスと設定される処理制限について説明します。
表8-10 XML関連ファクトリ・クラスでJAXPセキュリティ処理によって設定されるデフォルトの制限事項
XML関連ファクトリ・クラス | 有効? | 処理制限 |
---|---|---|
DocumentBuilderFactory | true | entityExpansionLimit = 64000
|
SAXParserFactory | true | entityExpansionLimit = 64000
|
SchemaFactory | true | maxOccurLimit = 5000
|
TransformerFactory | false | 拡張関数が無効です |
XPathFactory | false | 拡張関数が無効です |
entityExpansionLimit
、elementAttributeLimit
、maxOccurLimit
およびその他の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セキュア処理を無効にする場合は、entityExpansionLimit
、elementAttributeLimit
およびmaxOccurs
で指定されるデフォルトの制限事項を削除します。