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

このガイドでは次のトピックについて説明します。


はじめに

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

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

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

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

JSSE APIでは、SSLバージョン2.0および3.0の他、TLS バージョン 1.0をサポートできます。これらのセキュリティ・プロトコルは、通常の双方向のストリーム・ソケットをカプセル化し、JSSE APIは認証、暗号化および整合性保護の透過的なサポートを追加します。JDKに付属しているJSSE実装は、SSL 3.0、TLS (1.0、1.1および1.2)およびDTLS(バージョン1.0および1.2)をサポートしています。SSL 2.0は実装しません。

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

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

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

特長と利点

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

表1: JSSEで利用できる暗号化機能
暗号化アルゴリズム 脚注1 暗号化処理 鍵の長さ(ビット)
Rivest Shamir Adleman (RSA) 認証と鍵交換 512以上
Rivest Cipher 4 (RC4) バルク暗号化 128
128 (40が有効)
データ暗号化標準(DES) バルク暗号化 64 (56が有効)
64 (40が有効)
トリプルDES (3DES) バルク暗号化 192 (112が有効)
Advanced Encryption Standard (AES) バルク暗号化 256 脚注2
128
Diffie-Hellman (DH) 鍵合意 1024
512
デジタル署名アルゴリズム(DSA) 認証 1024

脚注1 SunJSSE実装では、そのすべての暗号化アルゴリズムでJCAを使用します。

脚注2 AES_256を使用する暗号化方式群では、Java暗号化拡張(JCE)の強度が無制限の管轄ポリシー・ファイルがインストールされている必要があります。「Java SEダウンロード・ページ」を参照してください。

JSSE標準API

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

SunJSSEプロバイダ

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

このプロバイダの詳細は、Oracleプロバイダ・ドキュメントの「SunJSSE」セクションで説明されています。

関連項目

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

Java Runtime Environment (JRE)インストール・ディレクトリ

このドキュメント全体で使われているjava-home変数プレースホルダは、Java Runtime Environment (JRE)がインストールされているディレクトリを表します。このディレクトリは、実行しているJSSEにJDKがインストールされているか、インストールされていないかに基づいて決定されます。JDKにはJREが含まれていますが、ファイル階層の異なるレベルにあります。

様々なインストールのjava-homeのデフォルトの場所を確認するには、表2を参照してください。

表2: JREインストール・ディレクトリ
オペレーティング・システム JDK JRE
Solaris/Linux ~/jdk1.8.0/jre ~/jre1.8.0
Windows C:\jdk1.8.0\jre C:\jre1.8.0

注: パス名内のチルド(~)は、Solaris、LinuxまたはMac OS Xオペレーティング・システムの現在のユーザーのホーム・ディレクトリを表します。


用語と定義

このドキュメントでは、暗号化に関するいくつかの用語が使用されています。このセクションでは、こうした用語を定義します。

認証

通信している相手側の識別情報を確認するプロセスです。

暗号化方式群

暗号化パラメータの組合わせで、認証、鍵合意、暗号化および整合性保護に使用するセキュリティ・アルゴリズムおよび鍵のサイズを定義します。

証明書

デジタル署名付きの文で、あるエンティティ(人や会社など)の識別情報および公開鍵の内容を保証します。証明書は、自己署名されるか証明書発行局(CA)(他のエンティティのために有効な証明書を発行する信頼されているエンティティ)によって発行されます。よく知られているCAにはVeriSign、Entrust、およびGTE CyberTrustがあります。X509は証明書の一般的な形式で、JDKのkeytoolで管理できます。

暗号化ハッシュ関数

データの任意のブロックから比較的小さな固定サイズのビットの文字列(ハッシュと呼ばれる)を生成するために使われるアルゴリズム。暗号化ハッシュ関数はチェックサムに似ており、3つの主な特性があります。一方向の関数であるため、ハッシュからオリジナルデータを生成することはできません。オリジナルデータをわずかに変更しても、ハッシュでは大きな変更になります。暗号化鍵は必要ありません。

暗号化サービス・プロバイダ

短縮形としてプロバイダとだけ呼ばれることもあり、Java暗号化アーキテクチャ(JCA)ではそれを、特定の暗号化アルゴリズムの1つまたは複数のエンジン・クラスを実装するパッケージ(または一連のパッケージ)と定義しています。エンジン・クラスは、具体的な実装のない抽象的な方法で暗号化サービスを定義します。

復号化

暗号化/復号化」を参照してください。

デジタル署名

デジタル署名とは、手書きの署名のデジタル版です。これは、ネットワークで伝送されるデータが、それを送信したと主張する人物からのものであり、送信中にデータが変更されていないことを保証するものです。たとえば、RSAベースのデジタル署名を計算するには、まずデータの暗号化ハッシュを計算し、次に送信者の非公開鍵でハッシュを暗号化します。

暗号化/複合化

暗号化は複雑なアルゴリズムを使用して、元のメッセージ(クリアテキスト)を、復号化しないかぎり、その内容を理解できないエンコードされたメッセージ(暗号テキスト)に変換するプロセスです。復号化とは、暗号テキストからクリアテキストを生成する逆のプロセスです。

データの暗号化および復号化に使用するアルゴリズムは一般に、秘密鍵(対称)暗号化と公開鍵(非対称)暗号化の2つのカテゴリに分けられます。

ハンドシェーク・プロトコル

2つのソケット同士が新しいセッションや既存のセッションの使用に同意するネゴシエーションのフェーズです。ハンドシェーク・プロトコルは、レコード・プロトコルを介して交換される一連のメッセージです。ハンドシェークの終了時に、セッションの接続に固有の暗号化鍵や、整合性を保護するための鍵が、鍵合意による秘密に基づいて新たに生成されます。

鍵合意

二者が協力して共通鍵を確立するための方法です。それぞれの側が一定のデータを生成して交換します。そのあと、2つのデータが組み合わされて、1つの鍵が生成されます。適正な非公開初期化データを保持しているユーザーのみが、最終的な鍵を取得できます。Diffie-Hellman (DH)は、一般的な鍵合意アルゴリズムの一例です。

鍵交換

鍵を交換する方法です。一方の側が秘密鍵を生成し、標準的にはRSAにより、ピアの公開鍵を使用して暗号化します。データがピアに送信され、ピアは対応する秘密鍵を使用して鍵を復号化します。

キー・マネージャ/トラスト・マネージャ

キー・マネージャとトラスト・マネージャは、それぞれの鍵データにキーストアを使用します。キー・マネージャはキーストアを管理し、ユーザーを他のユーザーに対して承認する場合に使用するなど必要に応じて、他のユーザーに公開鍵を提供します。トラスト・マネージャは、管理するトラストストアの情報に基づいて、トラストの対象者を決定します。

キーストア/トラストストア

キーストアは、鍵データのデータベースです。鍵データにはさまざまな用途があり、それには認証やデータ整合性も含まれます。利用できるキーストアには様々なタイプがあり、その中にはPKCS12やOracleのJKSも含まれます。

一般に、キーストア情報は、鍵エントリと信頼される証明書エントリ2つのカテゴリに分類できます。鍵エントリはエンティティの識別情報とその秘密鍵から構成されており、様々な暗号化の目的で使用できます。これとは対照的に、信頼される証明書のエントリには、公開鍵とそのエンティティの識別情報しか含まれていません。したがって、javax.net.ssl.KeyManagerの場合など、秘密鍵が必要な場合は、信頼される証明書エントリを使用することはできません。JKSのJDK実装では、キーストアに鍵のエントリと、信頼される証明書エントリの両方を含めることができます。

トラストストアとは、トラストの対象を決めるときに使用するキーストアです。すでに信頼しているエンティティからデータを受け取る場合、およびそのエンティティが発信元を名乗るエンティティであることを確認できる場合は、データは実際にそのエンティティから届いたものであると仮定できます。

ユーザーがそのエンティティを信頼する場合にのみ、エントリをトラストストアに追加する必要があります。ユーザーは、鍵のペアを生成するか、証明書をインポートすることにより、そのエントリにトラストを与えます。トラストストア内のすべてのエントリは信頼されたエントリとみなされます。

2つの異なるキーストア・ファイルを持つと便利な場合があります。1つは鍵エントリのみのファイル、もう1つはCA証明書を含む信頼された証明書エントリを含むファイルです。前者には機密性のある情報が含まれますが、後者には含まれません。単独のキーストア・ファイルではなく、2つのファイルを使用すると、独自の証明書(および対応する秘密鍵)と他の証明書を論理的に区別した明確な区分が提供されます。秘密鍵の保護を強化するには、アクセスが制限されたキーストアにそれらを保存し、必要に応じて、より公的にアクセスできるキーストアで信頼される証明書を提供することもできます。

メッセージ認証コード(MAC)

信頼できない媒体に送信または格納された情報の整合性を、秘密鍵に基づいてチェックする方法を提供します。通常、MACは秘密鍵を共有する2つの当事者間で、お互いが送信する情報を検証するために使用されます。

暗号化ハッシュ機能に基づくMACメカニズムは、HMACと呼ばれます。HMACは、共有する秘密鍵と組み合せて、Secure Hash Algorithm (SHA-256)などの暗号化ハッシュ関数とともに使用できます。HMACについては、RFC 2104で規定されています。

公開鍵暗号化

2つの鍵を生成する暗号化アルゴリズムを使用する鍵暗号化システムです。一方の鍵は公開されますが、他方は秘密のままです。公開鍵と非公開鍵では、逆の暗号化処理がなされ、一方の鍵で暗号化したものを他方の鍵で復号化します。公開鍵暗号化は、非対称暗号化とも呼ばれます。

レコード・プロトコル

すべてのデータ(アプリケーション・レベルであるかハンドシェーク・プロセスの一部であるかに関係なく)を独立したデータのレコードにパッケージ化するプロトコルで、TCPストリーム・ソケットがアプリケーション・バイト・ストリームをネットワーク・パケットに変換するのとよく似ています。個々のレコードは、現在の暗号化鍵と整合性保護鍵によって保護されます。

秘密鍵暗号化

データの暗号化と復号化に同じ鍵を使用する暗号化アルゴリズムを使用する暗号化システム。秘密鍵暗号化は対称暗号化とも呼ばれます。

セッション

認証されたピア識別情報、暗号化方式群、鍵合意の秘密を含む名前付きの状態情報のコレクションで、セキュアなソケット・ハンドシェークを通じてネゴシエーションが行われ、複数のセキュアなソケット・インスタンス間で共有できます。

トラスト・マネージャ

キー・マネージャ/トラスト・マネージャ」を参照してください。

トラストストア

キーストア/トラストストア」を参照してください。

Secure Sockets Layer (SSL)プロトコルの概要

Secure Sockets Layer (SSL)は、Webで暗号化を実装する場合にもっともよく使用されるプロトコルです。SSLは、ネットワークでセキュアな通信を行うために暗号化プロセスを組み合わせて使用します。このセクションでは、SSLおよびSSLが使用する暗号化プロセスについて簡単に説明します。

SSLは、インターネット通信で使用される標準的なTCP/IPソケットプロトコルをセキュアに拡張します。表3に示すように、Secure Sockets Layerは標準TCP/IPプロトコル・スタックのトランスポート層とアプリケーション層の間に追加されます。SSLとともにもっともよく使用されるアプリケーションは、インターネットWebページ用のプロトコルであるHypertext Transfer Protocol (HTTP)です。この他にも、Net News Transfer Protocol (NNTP)、Telnet、Lightweight Directory Access Protocol (LDAP)、Interactive Message Access Protocol (IMAP)、File Transfer Protocol (FTP)などのアプリケーションがあり、同様にSSLとともに使用します。


注: 現在のところ、セキュアなFTPの標準は存在しません。


表3: SSLによるTCP/IPプロトコル・スタック
TCP/IPの層 プロトコル
アプリケーション層 HTTP、NNTP、Telnet、FTPなど
Secure Sockets Layer SSL
トランスポート層 TCP
インターネット層 IP

SSLは1994年にNetscape社によって開発され、インターネットの世界で使用されるようになると、標準的な存在になりました。現在では、国際的な標準化機構であるInternet Engineering Task Force (IETF)が管理しています。IETFはSSLの名称をTransport Layer Security (TLS)に変更し、1999年1月に最初の仕様のバージョン1.0をリリースしました。TLS 1.0は、SSLの最新バージョン3.0を少しだけ変更したものです。SSL 3.0とTLS 1.0にはほとんど違いがありません。TLS 1.1は2006年4月にリリースされ、TLS 1.2は2008年8月にリリースされました。ただし、これらの更新されたバージョンはTLS 1.0やSSL 3.0ほど広くサポートされていません。

SSLを使用することの利点

次の3つの理由から、機密情報をネットワークで送信する際に危険が伴う場合があります。

SSLはこれらの各問題に対処します。最初の問題には、認証と呼ばれるプロセスで、通信の当事者双方に相手側の識別情報をオプションで確認させることで対応しています。両者が認証されると、SSLはセキュアなメッセージ伝送のために両者間の暗号化接続を提供します。両者の通信を暗号化することで機密性が保持されるため、2番目の問題に対処します。SSLで使用する暗号化アルゴリズムには、セキュアなハッシュ関数が含まれており、これはチェックサムに似ています。これにより、送信中にデータが変更されていないことが保証されます。セキュアなハッシュ関数により、3番目のデータの整合性の問題に対処します。


注: 認証も暗号化もオプションであり、2つのエンティティ間でネゴシエーションされた暗号化方式群に依存します。


SSLを使用する場合の明確な例は電子商取引です。電子商取引では、通信するサーバーの識別情報は保証されていると考えるべきではありません。クレジット・カードの番号を入力するだけですばらしいサービスが受けられるという偽のWebサイトを作成するのは簡単なことです。SSLを使うと、クライアントがサーバーの識別情報を認証することができます。また、サーバーもクライアントの情報を認証できますが、インターネット上の取引では、この方法はあまり使われていません。

クライアントとサーバーが互いの情報を認証すると、SSLは暗号化アルゴリズムを使用して機密性とデータの整合性を提供します。これにより、クレジット・カード番号のような機密情報をインターネット上でセキュアに送信することができます。

SSLは認証、機密性およびデータの整合性を提供しますが、非拒否サービスは提供しません。非拒否性とは、メッセージを送信したエンティティは、後になって送信を拒否することができないということを意味します。メッセージとデジタル署名が関連付けられていると、後になって通信内容を証明することができます。SSL単独では、非拒否性を提供しません。

SSLのしくみ

SSLが有効な理由の1つに、複数の暗号化プロセスを使用していることがあります。SSLは、公開鍵暗号化で認証を行い、秘密鍵暗号化とデジタル署名で機密性とデータの整合性を提供します。SSLについて理解する前に、暗号化の処理方法を理解しておくと役立ちます。

暗号化処理

暗号化の主な目的は、2者間の秘密の通信に権限のない第三者がアクセスしたり、その内容を理解するのを困難にすることです。暗号化のプロセスによって、データに対する権限のないすべてのアクセスを必ずしも制限できるわけではありませんが、権限のない者が秘密のデータを理解できないようにすることができます。暗号化では、複雑なアルゴリズムを使用して、元のメッセージ(クリアテキスト)をエンコードされたメッセージ(暗号テキスト)に変更します。ネットワーク上で転送されるデータの暗号化および復号化に使用するアルゴリズムは一般に、秘密鍵暗号化と公開鍵暗号化の2つのカテゴリに分けられます。これらの暗号化の形式については、次のサブセクションで説明します。

秘密鍵暗号化も公開鍵暗号化も、合意に基づく暗号鍵または暗号鍵のペアを使用します。は、データの暗号化プロセスおよび復号化プロセスで暗号化アルゴリズムが使用するビット文字列です。暗号化鍵は、錠の鍵と同様、錠を開けることができるのは正しい鍵のみです。

通信の当事者が互いに鍵を安全に送信するのは、些細な問題ではありません。公開鍵証明書を使用すると、公開鍵を安全に送信し、受信者に公開鍵の信頼性を保証できます。公開鍵証明書については、後のセクションで説明します。

次の暗号化プロセスの説明では、セキュリティ・コミュニティで広く使用されている規則を使用します。通信する両者がAliceとBobという名前でラベル付けされます。権限のない第三者は攻撃者とも呼ばれ、Charlieと名付けられます。

秘密鍵暗号化

秘密鍵暗号化では、通信するAliceとBobはメッセージの暗号化と復号化に同じ鍵を使用します。暗号化されたデータをネットワークで送信する前に、AliceとBobは鍵を持っていることが必要で、暗号化と復号化に使用する暗号化アルゴリズムに同意している必要があります。

秘密鍵暗号化で大きな問題の1つが、攻撃者にアクセスされずに一方から他方に鍵を渡す方法の問題です。AliceとBobが秘密鍵暗号化でデータを保護しても、Charlieがその鍵にアクセスできればAliceとBobの間で傍受した秘密メッセージを理解できます。CharlieはAliceとBobのメッセージを復号化できるだけではなく、Aliceになりすまして暗号化データをBobに送信することもできるのです。Bobには、メッセージがCharlieから届いたものかAliceから届いたものかはわかりません。

秘密鍵の配布の問題が解決すれば、秘密鍵暗号化はたいへん貴重なツールになります。そのアルゴリズムにより、優れたセキュリティと暗号化データが比較的迅速に提供できるからです。SSLセッションで送信される機密性の高いデータの多くは、秘密鍵暗号化で送信されます。

秘密鍵暗号化は、データの暗号化と復号化の両方に同じ鍵を使用するため、対称暗号化とも呼ばれます。よく知られている秘密鍵暗号化アルゴリズムには、Advanced Encryption Standard (AES)、Triple Data Encryption Standard (3DES)およびRivest Cipher 4 (RC4)があります。

公開鍵暗号化

公開鍵暗号化は、公開鍵と秘密鍵の両方を使用することで鍵の配布方法の問題を解決しました。公開鍵はネットワークを通じて公開し、送信できますが、非公開鍵は通信の1人の当事者にしか公開されません。公開鍵と非公開鍵は暗号化方式が逆で、一方の鍵で暗号化したものをもう一方の鍵で復号化します。

Bobが公開鍵暗号化を使用して、Aliceに秘密のメッセージを送信するとします。Aliceは公開鍵と非公開鍵をどちらも持っているので、非公開鍵は安全な場所に保管しておき、公開鍵をBobに送信します。BobはAliceの公開鍵を使ってAliceへの秘密のメッセージを暗号化します。Aliceは非公開鍵を使ってメッセージを復号化します。

Aliceが自分の秘密鍵を使用してメッセージを暗号化し、その暗号化されたメッセージをBobに送信すれば、Bobが受信するデータはAliceから届いたものだと考えることができます。BobがAliceの公開鍵でデータを復号化できれば、そのメッセージはAliceが自分の秘密鍵で暗号化したものに間違いなく、Aliceの秘密鍵を持っているのはAliceのみです。問題は、Aliceの公開鍵が公開されているために、だれもがメッセージを読めてしまうことです。このシナリオは、セキュアなデータ通信を考慮に入れていませんが、デジタル署名の基本を示しています。デジタル署名とは公開鍵証明書のコンポーネントの1つで、SSLでクライアントやサーバーを認証するために使用します。公開鍵証明書とデジタル署名については、後のセクションで説明します。

公開鍵暗号化は、データの暗号化と復号化に別の鍵を使用するので、非対称暗号化とも呼ばれます。SSLでよく使われる、よく知られている公開鍵暗号化アルゴリズムは、Rivest Shamir Adleman (RSA)アルゴリズムです。このほかにも、秘密鍵を交換するために設計されたSSLを使う公開鍵暗号化アルゴリズムには、Diffie-Hellman (DH)があります。公開鍵暗号化には膨大な計算が必要なため、速度が大幅に遅くなります。そこで、この方式は暗号化データ通信全体に使用するよりもむしろ、秘密鍵など少量のデータを暗号化する場合にだけ使用します。

秘密鍵暗号化と公開鍵暗号化の比較

秘密鍵暗号化と公開鍵暗号化のどちらにも、長所と短所があります。秘密鍵暗号化では、データの暗号化や復号化に時間はかかりませんが、通信者同士が同じ秘密鍵情報を共有する必要があり、鍵の交換の方法が問題になります。公開鍵暗号化では、鍵を秘密にする必要がないので鍵の交換は問題になりませんが、データの暗号化と復号化に使用するアルゴリズムには膨大な計算が必要で、著しく遅くなります。

公開鍵証明書

公開鍵証明書を使用すると、エンティティは非対称暗号化で使用する公開鍵を安全に配布できます。公開鍵証明書は、次の状況を回避します。Charlieが自分の公開鍵と秘密鍵を作成すれば、自分はAliceだと名乗ってBobに公開鍵を送信できます。BobはCharlieと通信できますが、データをAliceに送信していると思い込んでしまいます。

公開鍵証明書は電子的なパスポートだと考えることができます。これは、信頼できる組織によって発行され、所有者に識別情報を提供します。公開鍵証明書を発行する信頼できる組織を、証明書発行局(CA)と呼びます。CAは公証人にたとえることができます。CAから証明書を取得するには、識別情報の証拠となるものを提供する必要があります。CAは、申請者が申し立てる組織の代表であるとの確証が得られたら、証明書に含まれる情報の妥当性を証明する証明書に署名します。

公開鍵証明書には、次のようなフィールドがあります。

AliceがBobに公開鍵証明書で自分の公開鍵を送信するとき、BobがAliceの公開鍵のみを有効として受け付ける場合、CharlieがAliceになりすましたとしても、BobがだまされてCharlieに秘密情報を送信することはありません。

複数の証明書を証明書チェーンでリンクすることもできます。証明書チェーンを使用する場合、最初の証明書は必ず送信者の証明書です。次は送信者の証明書を発行したエンティティの証明書です。チェーン内に他の証明書がある場合、それぞれ直前の証明書を発行した証明書発行局の証明書です。チェーンの最後の証明書は、ルートCAの証明書です。ルートCAは、広く信頼されている公開証明書発行局です。複数のルートCAの情報は、通常、クライアントのインターネット・ブラウザに保存されています。この情報には、CAの公開鍵が含まれています。よく知られているCAにはVeriSign、Entrust、およびGTE CyberTrustがあります。

暗号化ハッシュ関数

暗号化されたデータを送信する場合、SSLは通常、暗号化ハッシュ関数を使ってデータの整合性を保証します。ハッシュ関数を使って、AliceがBobに送ったデータをCharlieが改ざんできないようにします。

暗号化ハッシュ関数はチェックサムに似ています。主な違いは、チェックサムがデータの偶発的変化を検出するのに対し、暗号化ハッシュ関数は故意による変更を検出するように設計されています。データが暗号化ハッシュ関数で処理されると、ハッシュと呼ばれる小さなビット文字列が生成されます。メッセージがごくわずかだけ変更された場合も、結果として生成されるハッシュは大きく変更されます。暗号化ハッシュ関数には、暗号化鍵が必要ありません。SSLとともによく使用されるハッシュ関数は、Secure Hash Algorithm (SHA)です。SHAは、U.S. National Institute of Standards and Technology (NIST)によって提案されました。

メッセージ認証コード

メッセージ認証コード(MAC)は暗号化ハッシュに似ていますが、秘密鍵をベースにしている点が異なります。秘密鍵情報が暗号化ハッシュ関数で処理したデータに含まれている場合、その結果生成されるハッシュはHMACと呼ばれます。

Aliceは、BobへのメッセージをCharlieが確実に改ざんしないようにする場合、メッセージのHMACを計算して元のメッセージにHMACを追加できます。次に、Bobと共有している秘密鍵を使用してメッセージとHMACを暗号化できます。Bobは、メッセージを復号化してHMACを計算すれば、送信中にメッセージが変更されたかどうかを知ることができます。SSLでは、HMACを使ってセキュアなデータを送信します。

デジタル署名

メッセージに暗号化ハッシュが作成されると、ハッシュは送信者の非公開鍵で暗号化されます。このような暗号化ハッシュをデジタル署名と呼びます。

SSLハンドシェーク

SSLを使った通信は、クライアントとサーバー間の情報交換から始まります。この情報交換をSSLハンドシェークと呼びます。SSLハンドシェークには、次のステージがあります。

  1. 暗号化方式群のネゴシエーション

    SSLセッションは、どの暗号群を使用するかについて、クライアントとサーバーがネゴシエーションを行うことから始まります。暗号化方式群とは、コンピュータがデータを暗号化するために使用する暗号化アルゴリズムと鍵のサイズのセットです。符号化方式には、公開鍵交換アルゴリズムまたは鍵合意アルゴリズム、および暗号化ハッシュ関数に関する情報が含まれます。クライアントは利用できる暗号群をサーバーに伝え、サーバーは、どちらにも適用できる暗号群を選択します。

  2. サーバーの識別情報の認証(オプション)

    SSLの認証ステップはオプションです。しかし、Web上の電子商取引の例では、一般にクライアントがサーバーを認証します。サーバーの認証により、サーバーが表すとクライアントが信じているエンティティを、そのサーバーが実際に表していることをクライアントが確認できます。

    サーバーは、自らが表すと唱える組織に属していることを証明するため、クライアントに公開鍵証明書を提示します。この証明書が有効であれば、クライアントはサーバーの識別情報について確信できます。

    クライアントとサーバーは、同じ秘密鍵について同意できる情報を交換します。たとえば、RSAを使う場合、クライアントは公開鍵証明書で取得したサーバーの公開鍵を使用して、秘密鍵情報を暗号化します。クライアントは暗号化された秘密鍵情報をサーバーに送信します。復号化にはサーバーの非公開鍵が必要なので、サーバーでだけ、このメッセージを復号化できます。

  3. 暗号化メカニズムの合意

    クライアントとサーバーは、同じ秘密鍵にアクセスします。それぞれのメッセージでは、ハンドシェークの最初のステップで選択した暗号化ハッシュ関数と、共有された秘密情報を使用して、メッセージに添付されるHMACを計算します。次に、秘密鍵と、ハンドシェークの最初のステップでネゴシエーションされた秘密鍵アルゴリズムを使用し、セキュアなデータとHMACを暗号化します。そのあと、クライアントとサーバーは、暗号化されハッシュ化されたデータを使ってセキュアに通信することができます。

SSLプロトコル

前のセクションでは、SSLハンドシェークについて概要を説明しました。それは、暗号化されたメッセージを送信する前にクライアントとサーバーで行われる情報の交換です。このセクションでは、さらに詳しく説明します。

図1に、SSLハンドシェークで交換される一連のメッセージを示しています。特定の状況下でだけ送信されるメッセージには「optional」と記されています。各SSLメッセージについては図の下で説明しています。

SSLハンドシェークで交換されるメッセージ・シーケンス。

SSLメッセージは、次の順序で送信されます。

  1. Client hello
    クライアントは、それがサポートする最上位バージョンのSSLと暗号化方式群のリスト(TLS 1.0はSSL 3.1と示される)を含むサーバー情報を送信します。暗号化方式群の情報には、暗号化アルゴリズムと鍵のサイズが含まれます。
  2. Server hello
    サーバーは、クライアントとサーバーの両方がサポートする最上位バージョンのSSLと最適な暗号化方式群を選択し、この情報をクライアントに送信します。
  3. Certificate (オプション)
    サーバーはクライアントに証明書または証明書チェーンを送信します。証明書チェーンは通常、サーバーの公開鍵証明書で始まり、認証局のルート証明書で終わります。このメッセージはオプションで、サーバー認証を求められた場合に使用します。
  4. Certificate request (オプション)
    サーバーがクライアントを認証する必要がある場合、クライアントに証明書要求を送信します。インターネット・アプリケーションでは、このメッセージが使われることはほとんどありません。
  5. Server key exchange (オプション)
    certificateからの公開鍵情報が鍵交換を行うのに不十分な場合、サーバーはクライアントにサーバー鍵交換メッセージを送信します。たとえば、Diffie-Hellman (DH)に基づく暗号化方式群では、このメッセージにはサーバーのDH公開鍵が含まれています。
  6. Server hello done
    サーバーは、最初のネゴシエーション・メッセージを終了したことをクライアントに伝えます。
  7. Certificate (オプション)
    サーバーがクライアントに証明書を要求すると、クライアントはサーバーが前に行ったように、その証明書チェーンを送信します。

    注: クライアントに証明書を要求するのは、ごく一部のインターネット・サーバー・アプリケーションのみです。


  8. Client key exchange
    クライアントは、対称暗号化で使用する鍵を作成するために使用される情報を生成します。RSAでは、クライアントはサーバーの公開鍵でこの鍵情報を暗号化してサーバーに送信します。DHに基づく暗号化方式群の場合、このメッセージにはクライアントのDH公開鍵が含まれています。
  9. Certificate verify (オプション)
    このメッセージは、上述のとおりクライアントが証明書を提示する場合にクライアントによって送信されます。このメッセージは、サーバーにクライアントの認証処理を完了させるためのものです。このメッセージが使用されると、クライアントは暗号化ハッシュ関数で電子的に署名した情報を送信します。サーバーがクライアントの公開鍵でこの情報を復号化すれば、サーバーはクライアントを認証できます。
  10. Change cipher spec
    クライアントはメッセージを送信し、暗号化モードに変更するようサーバーに伝えます。
  11. Finished
    クライアントはサーバーに、セキュアなデータ通信を開始する準備ができたことを伝えます。
  12. Change cipher spec
    サーバーはメッセージを送信し、暗号化モードを変更するようクライアントに伝えます。
  13. Finished
    サーバーはクライアントに、セキュアなデータ通信を開始する準備ができたことを伝えます。SSLハンドシェークが終了します。
  14. Encrypted data
    クライアントとサーバーは、client helloとserver hello時にネゴシエーションされた対称暗号化アルゴリズムと暗号化ハッシュ関数およびclient key exchange時にクライアントがサーバーに送信した秘密鍵を使用して通信します。このときハンドシェークの再ネゴシエーションを行うことができます。詳細は、次のセクションを参照してください。
  15. Close Messages
    接続の終わりに、それぞれの側がclose_notifyメッセージを送信し、接続が終了したことをピアに伝えます。

SSLセッションで生成したパラメータを保存しておけば、将来のSSLセッションでこれらのパラメータを再利用できます。SSLセッションのパラメータを保存しておけば、暗号化通信をすばやく開始できます。

ハンドシェークの再実行(再ネゴシエーション)

初期のハンドシェークが完了してアプリケーション・データが流れているとき、いずれの側からでも新しいハンドシェークをいつでも開始できます。特に重要な操作について、アプリケーションで強力な暗号化方式群を使用したり、サーバー・アプリケーションでクライアント認証が必要になる場合もあります。

理由は何であれ、新しいハンドシェークが既存の暗号化セッションに置き換わり、新しいセッションが確立されるまで、アプリケーション・データとハンドシェーク・メッセージが交互に配置されます。

アプリケーションで次のいずれかのメソッドを使用して、新しいハンドシェークを開始できます。

再ネゴシエーションに関するプロトコルの問題が2009年に見つかりました。プロトコルおよびJava SE実装はいずれも修正されています。詳細は、「Transport Layer Security (TLS)再ネゴシエーションの問題」を参照してください。

符号化方式の選択とリモート・エンティティの検証

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

raw SSLSocketおよびSSLEngineクラスを使用する場合は、データの送信前に必ずピアのクレデンシャルをチェックしてください。SSLSocketおよびSSLEngineクラスは、URL内のホスト名がピアのクレデンシャル内のホスト名と一致することを自動的に検証しません。ホスト名が検証されない場合、URL不正行為によってアプリケーションが悪用される可能性があります。

HTTPS (HTTP Over TLS)などのプロトコルでは、ホスト名検証が必要です。アプリケーションは、HostnameVerifierを使用してデフォルトのHTTPSホスト名規則をオーバーライドできます。詳細は「HttpsURLConnection」を参照してください。

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

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

SSLSocketおよびSSLEngineの作成に使用されるクラスの図

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


注: raw SSLSocketまたはSSLEngineクラスを使用する場合は、データの送信前に必ずピアのクレデンシャルをチェックしてください。SSLSocketおよびSSLEngineクラスは、たとえばURL内のホスト名がピアのクレデンシャル内のホスト名と一致することを自動的に検証しません。ホスト名が検証されない場合、URL不正行為によってアプリケーションが悪用される可能性があります。


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

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

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

コアJSSEクラスは、javax.netおよびjavax.net.sslパッケージの一部です。

SocketFactoryおよびServerSocketFactoryクラス

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

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

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

SSLSocketFactoryおよびSSLServerSocketFactoryクラス

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

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

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

SSLSocketFactoryの取得

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

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

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

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

SSLSocketおよびSSLServerSocketクラス

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

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

ピアの不正行為を防止するには、常にSSLSocketに提示されるクレデンシャルを検証してください。


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


SSLSocketの取得

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

SSLEngineクラス

SSL/TLSが利用される機会はますます増えています。広範な計算プラットフォームやデバイスを包含するさまざまなアプリケーションで使用されています。この普及に伴い、アプリケーションのパフォーマンス、拡張性、サイズおよびその他の要件を満たすため、様々な入出力モデルやスレッド・モデルでSSL/TLSを使用することが求められています。ブロックおよび非ブロック入出力チャネル、非同期入出力、任意の入力ストリームと出力ストリームおよびバイト・バッファでSSL/TLSの使用が求められています。また、数千のネットワーク接続を管理することが必要な、非常に拡張性の高いパフォーマンス重視の環境で使用することも求められています。

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

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

コア・クラスは、javax.net.ssl.SSLEngineです。これは、SSL/TLS状態マシンをカプセル化し、SSLEngineクラスのユーザーによって供給されるインバウンドとアウトバウンドのバイト・バッファ上で動作します。図3の図は、アプリケーションから、SSLEngineを経由して、トランスポート・メカニズムまで進み、戻ってくるデータのフローを示しています。

SSLEngineを通るデータのフロー

左側に示されるアプリケーションは、アプリケーションのプレーンテキスト・データをアプリケーション・バッファに供給し、それをSSLEngineに渡します。SSLEngineオブジェクトは、バッファに格納されているデータまたはハンドシェーク・データを処理してSSL/TLSエンコード・データを生成し、アプリケーションによって提供されるネットワーク・バッファに格納します。次にアプリケーションは、右側に示されている適切なトランスポートを使用して、ネットワーク・バッファの内容をピアに送信する役割を実行します。トランスポートを介してピアからSSL/TLSエンコード・データを受け取ると、アプリケーションはそのデータをネットワーク・バッファに格納し、SSLEngineに渡します。SSLEngineオブジェクトは、ネットワーク・バッファの内容を処理し、ハンドシェーク・データまたはアプリケーション・データを生成します。

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

SSLEngineオブジェクトの作成

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

例1SSLEngineオブジェクトの作成方法を示します。


注: サーバー名とポート番号は、サーバーとの通信には使用されません(すべてのトランスポートはアプリケーションが担当します)。それらは、SSLセッションキャッシングおよび取得すべきサーバー・クレデンシャルを決定するために、Kerberosベースの暗号化方式群の実装に使用する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("SunX509");
kmf.init(ksKeys, passphrase);

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

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);

SSL/TLSデータの生成と処理

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

それぞれのSSLEngineオブジェクトには、存続期間中に数種類の段階があります。アプリケーション・データを送信または受信できるようにするには、SSL/TLSプロトコルで、暗号化パラメータを確立するためのハンドシェークが必要です。このハンドシェークでは、SSLEngineオブジェクトによる一連のやり取りのステップが必要です。「SSLハンドシェーク」セクションで、ハンドシェーク自体の詳細について説明しています。

初期ハンドシェーク時に、wrap()およびunwrap()メソッドはハンドシェーク・データを生成および消費し、アプリケーションはデータのトランスポートを担当します。wrap()およびunwrap()メソッド・シーケンスは、ハンドシェークが終了するまで繰り返されます。それぞれのSSLEngine操作によりSSLEngineResultクラスのインスタンスが生成され、その中のSSLEngineResult.HandshakeStatusフィールドは、ハンドシェークを進めるために実行する必要のある次の動作を決定するために使用されます。

表4に、一般的なハンドシェーク時に呼び出されるメソッドのシーケンスと、対応するメッセージおよびステータスを示します。

表4: 一般的なハンドシェーク
クライアント SSL/TLSメッセージ HandshakeStatus
wrap() ClientHello NEED_UNWRAP
unwrap() ServerHello/Cert/ServerHelloDone NEED_WRAP
wrap() ClientKeyExchange NEED_WRAP
wrap() ChangeCipherSpec NEED_WRAP
wrap() Finished NEED_UNWRAP
unwrap() ChangeCipherSpec NEED_UNWRAP
unwrap() Finished FINISHED

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

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

例2に、非ブロックSocketChannelを使用してピアと通信するSSLアプリケーションを示します。


注: この例は、非ブロックSocketChannelを組み込んだSelectorを使用することにより、堅牢性と拡張性を高めることができます。


例2では、例1で作成したSSLEngineを使用してエンコードすることにより、ピアに文字列helloを送信します。これは、バイト・バッファの大きさを決定するために、SSLSessionからの情報を使用しています。

// 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 to use 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 SSL/TLS 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 SSL/TLS 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
    ...
}

例3では、同じ非ブロックSocketChannelからデータを読み取り、例1で作成したSSLEngineを使用して、そのデータからプレーン・テキストを抽出する方法を示しています。このコードが反復されるごとに、ハンドシェーク処理が進行しているかどうかに応じて、プレーン・テキストが生成されたり、生成されなかったりします。

// Read SSL/TLS 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
    ...
}

SSLEngineの操作のステータスについて

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

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

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

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

case BUFFER_OVERFLOW:
    // Maybe need to enlarge the peer application data buffer.
    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:
    // Maybe need to enlarge the peer network packet buffer
    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
    ...
}

ハンドシェークのステータスは、SSLEngineResult.HandshakeStatus enumによって表されます。それらは、ハンドシェークが完了しているかどうか、発信側はピアから追加のハンドシェーク・データを取得する必要があるかどうか、またピアに追加のハンドシェーク・データを送信する必要があるかどうかなどを表します。

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

例5に、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 :
            // Empty the local network packet buffer.
            myNetData.clear();

            // Generate handshaking data
            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
    ...
}

ブロック・タスクの処理

ハンドシェーク時に、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()呼出しをブロックします。

シャットダウン

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

// 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();

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

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

SSLSessionおよびExtendedSSLSession

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

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

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

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

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


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


HttpsURLConnectionクラス

HTTPSプロトコルはHTTPプロトコルに似ていますが、データを要求または受信する前に、まずSSL/TLSソケットを利用してセキュアなチャネルを確立してピアの識別情報を検証します。javax.net.ssl.HttpsURLConnectionクラスはjava.net.HttpsURLConnectionクラスを拡張し、HTTPSに固有の機能のサポートを追加します。HTTPS URLの構築および使用方法の詳細は、java.net.URLjava.net.URLConnectionjava.net.HttpURLConnectionおよびjavax.net.ssl.HttpURLConnectionクラスに関するAPIの仕様のセクションを参照してください。

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

割当て済のSSLSocketFactoryの設定

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

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


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


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

割当て済のHostnameVerifierの設定

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

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

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

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

このセクションで説明する3つのクラス(SSLContextKeyManagerFactoryおよびTrustManagerFactory)はエンジン・クラスです。エンジン・クラスとは、特定のアルゴリズムのAPIクラス(SSLContextの場合はプロトコル)です。その実装では1つまたは複数の暗号化サービス・プロバイダ(プロバイダ)パッケージで提供されることがあります。プロバイダとエンジン・クラスの詳細は、「Java暗号化アーキテクチャ・リファレンス・ガイド」の「設計方針」と「概念」のセクションを参照してください。

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

表5: SunJSSEによって提供される実装
実装されるエンジン・クラス アルゴリズムまたはプロトコル
KeyStore PKCS12
KeyManagerFactory PKIX、SunX509
TrustManagerFactory PKIX (X509またはSunPKIX)、SunX509
SSLContext SSLv3(1)、TLSv1、TLSv1.1、TLSv1.2

SSLContextクラス

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

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

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

SSLContextオブジェクトの作成

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


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

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

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

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

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

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

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

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

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

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

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

TrustManagerインタフェース

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

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

TrustManagerFactoryクラス

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

TrustManagerFactoryの作成

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

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

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

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

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

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

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

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

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

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

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

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

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

PKIX TrustManagerのサポート

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

PKIXトラスト・マネージャ・ファクトリはインストールされたセキュリティ・プロバイダのCertPath PKIX実装を使用します。トラスト・マネージャ・ファクトリを初期化するには、通常のinit(KeyStore ks)メソッドを使用するか、javax.net.ssl.CertPathTrustManagerParametersクラスを使用して、CertPathパラメータをPKIXトラスト・マネージャに渡します。

次の例に、トラスト・マネージャを取得して特定の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("PKCS12");
    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);

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

PKIXおよびCertPath APIの詳細は、「Java PKIプログラマーズ・ガイド」を参照してください。

X509TrustManagerインタフェース

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

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

X509TrustManagerの作成

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


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


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

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

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

java-homeが参照する情報については、「インストール・ディレクトリ」を参照してください。

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

独自のX509TrustManagerの作成

指定したX509TrustManagerの動作が状況に適していない場合は、独自のTrustManagerFactoryを作成して登録するか、X509TrustManagerインタフェースを直接実装して、独自の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("PKCS12");
         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();
     }
}

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

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

keyStoreの動的更新

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

X509ExtendedTrustManagerクラス

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

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

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

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

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

X509ExtendedTrustManagerの作成

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

独自のX509ExtendedTrustManagerの作成

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

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

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

KeyManagerインタフェース

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

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

KeyManagerFactoryクラス

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

KeyManagerFactoryの作成

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

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

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

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

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

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

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

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

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

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

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

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

X509KeyManagerインタフェース

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

X509KeyManagerの作成

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

独自のX509KeyManagerの作成

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

X509ExtendedKeyManagerクラス

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

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

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

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

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

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

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

TrustManagerとKeyManagerの関係

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

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

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

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

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

SSLParametersクラス

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

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

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

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

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);

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

暗号化方式群の優先順位

TLSハンドシェーク時に、クライアントは、それがサポートする暗号化オプションのリストの、最優先順位から暗号化方式群をネゴシエーションすることを要求します。次に、サーバーはクライアントによって要求された暗号化方式群のリストから、単一の暗号化方式群を選択します。通常、選択はクライアントの優先順位が尊重されます。ただし、弱い暗号化方式群の使用のリスクを緩和するため、サーバーはSSLParameters.setUseCipherSuitesOrder(true)メソッドを呼び出して、クライアントの優先順位ではなく、独自の優先順位に基づいて暗号化方式群を選択することがあります。

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からバインドまたはアンバインドされるときに、SSLSessionBindingListenerに伝えられるイベントを定義します。

HandShakeCompletedListenerインタフェース

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

HandShakeCompletedEventクラス

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

HostnameVerifierインタフェース

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

たとえば、

    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());

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

X509Certificateクラス

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

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


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


AlgorithmConstraintsインタフェース

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

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

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

StandardConstantsクラス

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

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

SNIServerNameクラス

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

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

SNIMatcherクラス

抽象SNIServerNameクラスのインスタンスは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ホスト名(クライアントから理解できる)を指定します。この引数は、次の場合に不正です。

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


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


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

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

JSSEのカスタマイズ

JSSEには、様々な実装をプラグインしたり、デフォルトのキーストアを指定したりして、カスタマイズ可能な標準実装が含まれます。表6では、カスタマイズが可能な側面、デフォルトの内容、およびカスタマイズを提供するために使用するメカニズムを要約しています。表の最初の列には、指定した機能と、カスタマイズ方法の詳細が説明されているサイトへのリンクが設定してあります。

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


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


表6: JSSEのカスタマイズ可能な項目
カスタマイズ項目 デフォルト カスタマイズ方法
X509証明書実装 OracleからのX509Certificate実装 cert.provider.x509v1セキュリティ・プロパティ
HTTPSプロトコル実装 Oracleからの実装 java.protocol.handler.pkgsシステム・プロパティ
プロバイダ実装 SunJSSE セキュリティ・プロパティ・ファイルのsecurity.provider.n=
デフォルトのSSLSocketFactory実装 OracleからのSSLSocketFactory実装 * ssl.SocketFactory.providerセキュリティ・プロパティ
デフォルトのSSLServerSocketFactory実装 OracleからのSSLServerSocketFactory実装 * ssl.ServerSocketFactory.providerセキュリティ・プロパティ
デフォルトのキーストア なし * javax.net.ssl.keyStoreシステム・プロパティ
システム・プロパティ値NONEを指定できます。この設定は、ハードウェア・トークンに存在する場合など、キーストアがファイルベースでない場合に適切。
デフォルトのキーストア・パスワード なし * javax.net.ssl.keyStorePasswordシステム・プロパティ。
パスワードをコマンド・ラインに指定するなど、他のユーザーに検出される方法でパスワードを指定することはお薦めできません。パスワードのセキュリティを維持するには、アプリケーションでパスワードの入力を求めるか、適切に保護されたオプション・ファイルにパスワードを指定します。
デフォルトのキーストア・プロバイダ なし * javax.net.ssl.keyStoreProviderシステム・プロパティ
デフォルトのキーストア・タイプ KeyStore.getDefaultType() * javax.net.ssl.keyStoreTypeシステム・プロパティ
デフォルトのトラストストア 存在する場合は、jssecacerts。そうでない場合、cacerts * javax.net.ssl.trustStoreシステム・プロパティ
デフォルトのトラストストア・パスワード なし * javax.net.ssl.trustStorePasswordシステム・プロパティ
パスワードをコマンド・ラインに指定するなど、他のユーザーに検出される方法でパスワードを指定することはお薦めできません。パスワードのセキュリティを維持するには、アプリケーションでパスワードの入力を求めるか、適切に保護されたオプション・ファイルにパスワードを指定します。
デフォルトのトラストストア・プロバイダ なし * javax.net.ssl.trustStoreProviderシステム・プロパティ
デフォルトのトラストストア・タイプ KeyStore.getDefaultType() * javax.net.ssl.trustStoreTypeシステム・プロパティ
NONEを指定可能。この設定は、ハードウェア・トークンに存在する場合など、トラストストアがファイルベースでない場合に適切。
デフォルトのキー・マネージャ・ファクトリのアルゴリズム名 SunX509 ssl.KeyManagerFactory.algorithmセキュリティ・プロパティ
デフォルトのトラスト・マネージャ・ファクトリのアルゴリズム名 PKIX ssl.TrustManagerFactory.algorithmセキュリティ・プロパティ
無効化された証明書検証暗号化アルゴリズム MD2, RSA keySize < 1024 jdk.certpath.disabledAlgorithmsセキュリティ・プロパティ
無効化済/制限付きアルゴリズム SSLv3 jdk.tls.disabledAlgorithmsセキュリティ・プロパティ。
特定のアルゴリズム(プロトコル・バージョン、暗号スイート、キー交換メカニズムなど)を無効化し、アプリケーションで明示的に有効化されてもSSL/TLS接続でネゴシエーションされないようにします。
デフォルトのプロキシ・ホスト なし * https.proxyHostシステム・プロパティ
デフォルトのプロキシ・ポート 80 * https.proxyPortシステム・プロパティ
Server Name Indicationオプション true * jsse.enableSNIExtensionシステム・プロパティ。
Server Name Indication (SNI)はTLS拡張で、RFC 6066で定義されています。これは仮想サーバーへのTLS接続を可能にし、さまざまなネットワーク名に対して複数のサーバーが単一の基本ネットワーク・アドレスでホスティングされます。
かなり古い一部のSSL/TLSベンダーは、SSL/TLS拡張を処理できないことがあります。この場合、このプロパティをfalseに設定してSNI拡張を無効化します。
デフォルトの暗号化方式群 ソケット・ファクトリによって決定 * https.cipherSuitesシステム・プロパティ。HttpsURLConnectionで使用できる暗号群を指定する暗号群名リスト(カンマ区切り形式)を含む。SSLSocket.setEnabledCipherSuites(String[])メソッドを参照してください。このメソッドは、渡されたString配列から直接ClientHello暗号スイートの優先順位を設定することに注意してください。
デフォルトのハンドシェーク・プロトコル ソケット・ファクトリによって決定 * https.protocolsシステム・プロパティ。
HttpsURLConnectionで有効にするプロトコルを指定するプロトコル名リスト(カンマ区切り形式)を含む。SSLSocket.setEnabledProtocols(String[])メソッドを参照してください
デフォルトのHTTPSポート 443 * HTTPS URL内のportフィールドによってカスタマイズします。
SunJSSEプロバイダが使用するJCE暗号化アルゴリズム SunJCE実装 代替のJCEアルゴリズム・プロバイダにSunJCEプロバイダより古い高い優先順位を与えます。
大きいSSL/TLSパケット用のバッファのデフォルトのサイズ設定 なし * jsse.SSLEngine.acceptLargeFragmentsシステム・プロパティ。
このシステム・プロパティをtrueに設定すると、SSLSessionはデフォルトで大きいデータ・パケットを処理するようにバッファをサイズ設定します。これにより、アプリケーションが不要な大きいSSLEngineバッファを割り当てる場合があります。代わりに、アプリケーションはバッファ・オーバーフロー条件を動的にチェックして、バッファを適宜サイズ変更する必要があります。
安全でないSSL/TLSネゴシエーションを許可 false * sun.security.ssl.allowUnsafeRenegotiationシステム・プロパティ。
このシステム・プロパティをtrueに設定すると、完全な(安全でない)レガシーの再ネゴシエーションが許可されます。
レガシーのHelloメッセージの許可(再ネゴシエーション) true * sun.security.ssl.allowLegacyHelloMessagesシステム・プロパティ。
このシステム・プロパティをtrueに設定すると、適切なRFC 5746メッセージを必要とすることなくピアがハンドシェークを実行できます。
デフォルトで有効にされているTLSプロトコル なし jdk.tls.client.protocolsシステム・プロパティ。
クライアント上で特定のSunJSSEプロトコルを有効化するには、引用符で囲んでカンマ区切りリスト形式で指定します。それ以外のサポートされたプロトコルはクライアント上で無効にされます。たとえば、このプロパティの値が"TLSv1,TLSv1.1"の場合、TLSv1とTLSv1.1用のクライアント上のデフォルト・プロトコル設定がクライアント上で使用可能になり、SSLv3、TLSv1.2およびSSLv2Helloはクライアント上で無効になります。
エフェメラルDiffie-Hellman鍵のサイズ 1024ビット jdk.tls.ephemeralDHKeySizeシステム・プロパティ。

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


注: java.lang.Systemプロパティを設定してカスタマイズする項目と、java.security.Securityプロパティを設定してカスタマイズする項目があります。次のセクションでは、両方のプロパティ型の値を設定する方法を説明します。


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

JSSEの一部の側面は、システム・プロパティを設定してカスタマイズできます。次のいくつかの方法によって、これらのプロパティを設定できます。

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

JSSEの一部の側面は、セキュリティ・プロパティを設定してカスタマイズできます。セキュリティ・プロパティは静的または動的に設定します。

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

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

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

    cert.provider.x509v1=com.cryptox.MyX509CertificateImpl

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

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

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


注: 過去のJSSEリリースでは、JSSEのインストール中にjava.protocol.handler.pkgsシステム・プロパティを設定する必要がありました。このステップは、com.sun.net.ssl.HttpsURLConnectionのインスタンスを取得する場合以外は不要になりました。詳細については、「トラブルシューティング」セクションの「HttpsURLConnectionクラスを使用するコード」を参照してください。


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

JDK 1.4以降のリリースには、SunJSSEというJSSE暗号化サービス・プロバイダ(略称はプロバイダ)が標準で付属しています。基本的に、プロバイダは特定の暗号化アルゴリズムのエンジン・クラスを実装するパッケージです。JSSEのエンジン・クラスはSSLContextKeyManagerFactory、およびTrustManagerFactoryです。プロバイダとエンジン・クラスの詳細は、「Java暗号化アーキテクチャ・リファレンス・ガイド」を参照してください。


注: SunJSSEがCipher.getInstance()を呼び出すとき、使用される変換文字列は「RSA/ECB/PKCS1Padding」、「RC4」、「DES/CBC/NoPadding」、「DESede/CBC/NoPadding」になります。Cipherクラスと変換文字列の詳細は、「Java暗号化アーキテクチャ・リファレンス・ガイド」を参照してください。


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

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

プロバイダを静的に登録するには、セキュリティ・プロパティ・ファイルに次の行を追加します。

    security.provider.n=providerClassName

これはプロバイダを宣言し、その優先順位nを指定します。優先順位とは、特定のプロバイダが要求されていない場合に、要求されたアルゴリズムについて検索されるプロバイダの順序です。「1」が最優先で次に「2」、というように続きます。

providerClassNameは、プロバイダ・クラスの完全修飾名です。この名前は、プロバイダ・ベンダーから取得します。

標準のセキュリティ・プロバイダおよびJDK 6に付属するSunJSSEプロバイダが自動的に登録され、java.securityセキュリティ・プロパティ・ファイルに次の行が表示され、SunJCEセキュリティ・プロバイダの優先順位が5、SunJSSEプロバイダの優先順位が4として登録されます。

    security.provider.1=sun.security.pkcs11.SunPKCS11 \
    ${java.home}/lib/security/sunpkcs11-solaris.cfg
    security.provider.2=sun.security.provider.Sun
    security.provider.3=sun.security.rsa.SunRsaSign
    security.provider.4=com.sun.net.ssl.internal.ssl.Provider
    security.provider.5=com.sun.crypto.provider.SunJCE
    security.provider.6=sun.security.jgss.SunProvider
    security.provider.7=com.sun.security.sasl.Provider

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

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

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

プロバイダを静的に登録するかわりに、プログラム開始時にSecurity.addProvider()メソッドを呼び出して、実行時に動的にプロバイダを追加できます。たとえば、プロバイダのクラス名がMyProviderで、そのMyProviderクラスがcom.ABCパッケージにあるプロバイダを動的に追加するには、次を呼び出します。

    Security.addProvider(new com.ABC.MyProvider());

Security.addProviderメソッドは、次に利用できる優先順位に、指定したプロバイダを追加します。

この登録は恒久的ではなく、十分なアクセス権があるプログラムでしか実行できません。

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

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

デフォルトのSSLContextは、デフォルトのKeyManagerおよびデフォルトのTrustManagerで初期化されます。javax.net.ssl.keyStoreシステム・プロパティおよび適切なjavax.net.ssl.keyStorePassword システム・プロパティでキーストアを指定すると、デフォルトの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というトラストストアを検索し、それが存在していれば使用します。java-homeの詳細は、「インストール・ディレクトリ」を参照してください。トラスト・ストアが見つからない場合、TrustManagerは新しい空のトラスト・ストアを管理します。


注: JDKには、java-home/lib/security/cacertsファイル内に限定された数の信頼されたルート証明書が付属しています。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リファレンス実装と同じ方法でそれらを処理してください。


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

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

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

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


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


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


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


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


無効化済/制限付きアルゴリズム

暗号化ハッシュ・アルゴリズムMD2は、もはやセキュアだと考えられていません。Java SE 7には、特定の暗号化アルゴリズムの無効化をサポートする2つの新しいセキュリティ・プロパティと1つの新しいAPIが含まれています。

jdk.tls.disabledAlgorithmsプロパティはTLSハンドシェークに適用され、jdk.certpath.disabledAlgorithmsプロパティは証明書パスの処理に適用されます。

JDK 8u31以降では、SSLv3はデフォルトで無効であり、セキュリティ・プロパティjdk.tls.disabledAlgorithmsのデフォルト値は次のようになりました。

jdk.tls.disabledAlgorithms=SSLv3

SSLv3がどうしても必要な場合、「SSLv3の有効化」の説明に従って、プロトコルを再度アクティブにできます。

JDK 8以降では、jdk.certpath.disabledAlgorithmsのデフォルト値にRSA keySize < 1024が含まれています。これは、1024ビット未満のRSA鍵サイズによる証明書の使用が制限されていることを意味します。jdk.certpath.disabledAlgorithmsのデフォルト値は現在次のようになっています。

    jdk.certpath.disabledAlgorithms=MD2, RSA keySize < 1024

これは、MD2またはサイズが1024未満のRSAキーで署名されたすべての証明書が受け入れ不可であることを意味します。

各セキュリティ・プロパティには、証明書パスの処理中に使用されない暗号化アルゴリズムのリストが含まれています。プロパティの正確な構文はjava-home/lib/security/java.securityファイルに記載されていますが、ここでは簡単に要約します。アルゴリズム名はカンマで区切られています。また、使用できない特定の鍵サイズも指定できます。

たとえば、java.security内の次の行は、証明書パスの処理にMD2およびDSAアルゴリズムを使用できず、2048ビット未満の鍵サイズに対してRSAが無効になることを指定しています。

    jdk.certpath.disabledAlgorithms=MD2, DSA, RSA keySize < 2048

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

SunJSSEプロバイダは、その暗号化のすべてニーズに対してSunJCE実装を使用します。プロバイダは通常の位置に置くことが推奨されていますが、SunJCEプロバイダより前に登録することにより、他のJCAまたはJCEプロバイダからの実装を使用できます。セキュリティ・プロパティ・ファイルjava-home/lib/security/java.securityを介して静的に、またはjava.security.SecurityクラスのaddProvider()またはinsertProviderAt()メソッドを介して動的に、標準JCAメカニズムを使用して、プロバイダを構成できます。java-homeについては、「インストール・ディレクトリ」を参照してください。

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

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

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

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

jdk.tls.ephemeralDHKeySizeの値 未定義 legacy matched 整数値(固定)
エクスポート可能なDH鍵サイズ 512 512 512 512
エクスポート不可能な匿名暗号化方式群 1024 768 1024 固定鍵サイズは有効な整数プロパティ値で指定し、1024以上2048以下である必要があります。
認証証明書 1024 768 鍵サイズは認証証明書と同じですが、1024ビット以上2048ビット以下である必要があります。ただし、SunJCEプロバイダは1024ビットより大きい2048ビットDH鍵のみをサポートします。結果として、使用できる値は1024または2048のみになります。 固定鍵サイズは有効な整数プロパティ値で指定し、1024以上2048以下である必要があります。

Transport Layer Security (TLS)再ネゴシエーションの問題

2009年秋に、SSL/TLSプロトコルの問題が見つかりました。IETF TLS Working Groupによってプロトコルの修正が開発され、JDKの現行バージョンにはこの修正が含まれています。このセクションでは、このプロトコル修正を含まない以前の実装との通信時における相互運用性の問題を含め、状況をさらに詳しく説明します。

この脆弱性により、選択されたプレーン・テキストを接頭辞としてTLS接続に注入できるというMan-In-The-Middle (MITM)攻撃を許していました。この脆弱性は、クライアントとサーバーがセッションのネゴシエーションに成功した後、傍受されたネットワーク通信を攻撃者が復号化または変更することを許すものではありません。

詳細については、CVE-2009-3555 (MitreのCommon Vulnerabilities and Exposures List、2009年に投稿)およびTLS再ネゴシエーション攻撃について (Eric Rescorlaのブログ、Educated Guesswork、2009年10月5日)の記事を参照してください。

この問題を解決するためのフェーズ別アプローチ

この問題の修正は、2つのフェーズに分けて扱われています。


注: 再ネゴシエーションを必要としないアプリケーションはフェーズ2のデフォルト構成に影響を受けません。ただし、再ネゴシエーションを必要とするアプリケーション(最初は匿名のクライアント・ブラウズを許可するが、後でSSL/TLS認証済のクライアントを要求するWebサーバーなど)は:


フェーズ2修正の説明

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

フェーズ2修正により、SunJSSEは現在3つの再ネゴシエーション相互運用性モードを用意しています。どのモードもRFC 5746のセキュアな再ネゴシエーションを完全にサポートしていますが、アップグレードされていないピアと通信する場合、次のような意味合いが加わります。

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

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

表8: 相互運用性情報
クライアント サーバー モード
更新済み 更新済み

すべてのモードで再ネゴシエーションがセキュリティ保護されます。

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

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

脚注2 SunJSSEフェーズ1実装は、明示的に再有効化されないかぎり再ネゴシエーションを拒否します。再ネゴシエーションが再有効化された場合、それらは正しいRFC 5746メッセージを送信しないため、RFC 5746に準拠したピアによって「レガシー」として扱われます。

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

モードを設定するために、次のシステム・プロパティを使用します。

表9: 相互運用性モードを設定するためのシステム・プロパティの値
モード allowLegacyHelloMessages allowUnsafeRenegotiation
厳密 false false
相互運用(デフォルト) true false
セキュアでない true true

注意: セキュアでないSSL/TLS再ネゴシエーションは、脆弱性が再確立されるため、再有効化しないでください。


システム・プロパティを設定することによって特定のモードを構成する方法については、「java.lang.Systemプロパティの設定方法」を参照してください。

SSL/TLS再ネゴシエーションに対する回避方法と代替方法

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

実装の詳細

RFC 5746では2つの新しいデータ構造が定義されており、上級ユーザーのために、ここで説明します。

これらのいずれも、実装がRFC 5746に準拠していることと、セキュアな再ネゴシエーションが実行できることを通知するために使用できます。関連する技術的な議論については、2009年11月から2010年2月までのIETFの電子メールによる議論を参照してください。

RFC 5746により、クライアントは最初のClientHelloでSCSVまたはRIを送信できます。相互運用性を最大限に高めるため、SunJSSEはデフォルトでSCSVを使用しますが、これは、いくつかのTLSサーバー/SSLサーバーが不明な拡張機能を正しく処理できないためです。有効化された暗号化方式群(SSLSocket.setEnabledCipherSuites()またはSSLEngine.setEnabledCipherSuites())にSCSVが存在することによって、最初のClientHello内でSCSVを送信するか、あるいはRIをかわりに送信すべきかどうかが判別されます。

SSLv2はSSL/TLS拡張機能をサポートしません。SSLv2Helloプロトコルが有効化された場合、SCSVが最初のClientHello内で送信されます。

フェーズ1修正の説明

前述のとおり、フェーズ1修正は、RFC 5746に準拠した修正が開発されるまでの間、再ネゴシエーションをデフォルトで無効にするためのものでした。再ネゴシエーションは、sun.security.ssl.allowUnsafeRenegotiationシステム・プロパティを設定することによって再び有効化できました。フェーズ2修正では同じsun.security.ssl.allowUnsafeRenegotiationシステム・プロパティを使用するだけでなく、それにRFC 5746メッセージを使用させる必要もあります。

すべてのアプリケーションを、できるだけ早くフェーズ2 RFC 5746修正にアップグレードする必要があります。

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

次の場合、SSL/TLS再ネゴシエーションでサーバー証明書を変更することは、安全でないことがあります。

  1. エンドポイント識別がSSL/TLSハンドシェークで有効でない場合、および
  2. 以前のハンドシェークがセッション再開省略初期ハンドシェークである場合、および
  3. 両方の証明書によって表されるアイデンティティが同じと見なせる場合。

2つの証明書は、次の場合に、同じアイデンティティを表すと見なせます。

  1. IPアドレスのサブジェクトの代替名が両方の証明書に存在する場合、同一であるはずです。または、
  2. DNS名のサブジェクトの代替名が両方の証明書に存在する場合、同一であるはずです。または、
  3. サブジェクト・フィールドが両方の証明書に存在する場合、証明書のサブジェクトと発行人は同一であるはずです。

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

このシステム・プロパティのデフォルト値はfalseです。

注意: どうしても必要でないかぎり、このシステム・プロパティを"true"に設定しないでください。安全でないサーバー証明書変更の脆弱性が再び確立される可能性があります。

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

Java暗号化アーキテクチャ(JCA)は、暗号化、鍵生成と鍵合意およびメッセージ認証コード(MAC)アルゴリズム用のフレームワークと実装を提供するパッケージ・セットです。SunJSSEプロバイダは、すべての暗号化操作にJCAを排他的に使用するので、JCAの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アプリケーションの存続期間の間、異なるスマートカードをスマートカード・リーダーに入れることができ、それらのスマートカードは異なるパスワードを使用して保護できます。

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

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

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

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);

Kerberos暗号化方式群

SunJSSEプロバイダは、RFC 2712で規定されるKerberos暗号化方式群をサポートします。次の暗号化方式群がサポートされていますが、デフォルトでは有効になっていません。

これらの暗号化方式群を使用できるようにするには、明示的に指定する必要があります。詳細については、APIのドキュメントで、SSLEngine.setEnabledCipherSuites()およびSSLSocket.setEnabledCipherSuites()メソッドを参照してください。その他のすべてのSSL/TLS暗号化方式群と同様、暗号化方式群がピアによってサポートされていない場合は、暗号化のネゴシエーション時に選択されません。また、アプリケーションまたはサーバーが必要なKerberos資格を取得できない場合は、Kerberos符号化方式も選択されません。

次に、TLS_KRB5_WITH_DES_CBC_SHA暗号化方式群のみを使用するTLSクライアントの例を示します。

// Create socket
SSLSocketFactory sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslSocket = (SSLSocket) sslsf.createSocket(tlsServer, serverPort);

// Enable only one cipher suite
String enabledSuites[] = { "TLS_KRB5_WITH_DES_CBC_SHA" };
sslSocket.setEnabledCipherSuites(enabledSuites);

Kerberos要件

JSSEでKerberos符号化方式を使用する前に、配備されている環境でKerberosインフラストラクチャを設定しておく必要があります。特に、TLSクライアントとサーバーの両方に、Kerberos Key Distribution Center (KDC)によるアカウントが設定されていことが必要です。実行時にKerberos暗号化方式群の1つ以上が有効化されると、TLSクライアントとサーバーは、それぞれのアカウントに関連付けられている自身のKerberosクレデンシャルをKDCから取得します。たとえば、Kerberos領域IMC.ORGのマシンmach1.imc.orgで動作するTLSサーバーは、host/mach1.imc.org@IMC.ORGという名前のアカウントを持ち、IMC.ORG用のKDCを使用するように構成されている必要があります。Java SEでのKerberosの使用の詳細は、「Kerberos要件」のドキュメントを参照してください。

アプリケーションは、Java認証・承認サービス(JAAS)とKerberosログイン・モジュールを使用して、自身のKerberos資格を取得できます。JDKは、Kerberosログイン・モジュールに付属しています。JSSEでのKerberos暗号化方式群は、JAASプログラミングがある場合とない場合のJava Generic Security Services (Java GSS)を使用する方法と同様に、JAASプログラミングがある場合とない場合に使用できます。

JAASプログラミングなしでJSSEでKerberos暗号化方式群を使用するには、TLSサーバーJAAS構成エントリ用にcom.sun.net.ssl.serverまたはother、およびTLSクライアント用にcom.sun.net.ssl.clientまたはotherというインデックス名を使用し、javax.security.auth.useSubjectCredsOnlyシステム・プロパティをfalseに設定する必要があります。たとえば、JAASプログラミングを使用しないTLSサーバーには次のJAAS構成ファイルを使用できます。

    com.sun.net.ssl.server {
      com.sun.security.auth.module.Krb5LoginModule required
            principal="host/mach1.imc.org@IMC.ORG"
            useKeyTab=true
            keyTab=mach1.keytab
            storeKey=true;
    };

JAASプログラミングなしでJava GSSおよびKerberosを使用する方法の例は、「Java GSSチュートリアル」で説明されています。Java GSS呼出しをJSSE呼出しに置き換えることにより、JSSEの使用例に適応できます。

JAASプログラミングありでKerberos暗号化方式群を使用するには、任意のインデックス名を使用できます。これは、アプリケーションが、インデックス名を使用してJAAS LoginContextを作成し、JSSE呼出しをSubject.doAs()またはSubject.doAsPrivileged()呼出しの内部にラップする役割を持つためです。Java GSSおよびKerberosでJAASを使用する方法の例は、「Java GSSチュートリアル」で説明されています。Java GSS呼出しをJSSE呼出しに置き換えることにより、JSSEの使用例に適応できます。

Kerberosを使用するようにJSSEアプリケーションを使用し、構成する場合の問題については、Java GSSチュートリアルの「トラブルシューティング」セクションを参照してください。

ピアのアイデンティティ情報

SSL接続のピアの識別情報を判別するには、次のクラスで、getPeerPrincipal()メソッドを使用します。

同様に、(ローカル・エンティティを識別するために)ピアに送信された識別情報を取得するには、これらのクラスでgetLocalPrincipal()メソッドを使用します。X509ベースの暗号化方式群の場合、これらのメソッドはjavax.security.auth.x500.X500Principalのインスタンスを返します。Kerberos暗号化方式群の場合、これらのメソッドはjavax.security.auth.kerberos.KerberosPrincipalのインスタンスを返します。

JSSEアプリケーションは、getPeerCertificates()と、javax.net.ssl.SSLSessionjavax.net.ssl.HttpsURLConnectionjavax.net.HandshakeCompletedEventの類似のメソッドを使用して、ピアに関する情報を取得します。ピアに証明書がない場合、SSLPeerUnverifiedExceptionがスローされます。

アプリケーションでピアの識別情報またはピアに送信された識別情報のみを判別する必要がある場合は、それぞれgetPeerPrincipal()およびgetLocalPrincipal()メソッドを使用してください。getPeerCertificates()およびgetLocalCertificates()メソッドは、それらの証明書の内容を調べる必要がある場合にのみ使用してください。また、アプリケーションでは、認証されたピアに証明書がない場合の処理を準備しておく必要があります。

セキュリティ・マネージャ

セキュリティ・マネージャが有効な場合、ピアとの通信に必要なSocketPermissionに加えて、Kerberos暗号化方式群を使用するTLSクライアント・アプリケーションには次のアクセス権も必要です。

javax.security.auth.kerberos.ServicePermission(serverPrincipal, "initiate");

前のコードで、serverPrincipalは、TLSクライアントが通信するTLSサーバーのKerberosプリンシパル名です(host/mach1.imc.org@IMC.ORGなど)。TLSサーバー・アプリケーションには、次のアクセス権が必要です。

javax.security.auth.kerberos.ServicePermission(serverPrincipal, "accept");

前のコードで、serverPrincipalは、TLSサーバーのKerberosプリンシパル名です(host/mach1.imc.org@IMC.ORGなど)。サーバーまたはクライアントがKDCに接続する必要がある場合(そのクレデンシャルがローカルにキャッシュされていない場合など)、次のアクセス権も必要です。

javax.security.auth.kerberos.ServicePermission(tgtPrincipal, "initiate");
前のコードで、tgtPrincipalはKDCのプリンシパル名です(krbtgt/IMC.ORG@IMC.ORGなど)。

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

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

SunJSSEプロバイダでは、PKCS12のキーストア・タイプを使用して、KeyStore APIを介してPKCS12鍵にアクセスできます。さらにkeytoolコマンドと、pkcs12に設定された-storetypeオプションを使用して、インストールされた鍵および関連する証明書を表示できます。keytoolの詳細は、「セキュリティ・ツール」を参照してください。

Server Name Indication (SNI)拡張

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

クライアント・アプリケーションの開発者は、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);

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

例1

 
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);

例2

 
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の動作の例を示しています。

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

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

トラブルシューティング

このセクションにはJSSEのトラブルシューティングに関する情報が含まれます。まず、一般的な構成の問題とそれらの解決方法を説明し、次に、役に立つデバッグ・ユーティリティについて説明します。

構成上の問題点

このセクションでは、JSSEの使用時に発生する可能性のある一般的な構成の問題について説明します。

ハンドシェーク時のCertificateException

問題: SSL接続のネゴシエーション中に、クライアントまたはサーバーがCertificateExceptionをスローします。

原因1: 一般に、リモート側がローカル側に不明な証明書を送信することが原因です。

解決法1: このタイプの問題をデバッグするもっともよい方法は、デバッグをオンにして(「デバッグ・ユーティリティ」を参照)、証明書のロード時およびネットワーク接続経由での証明書の受信時に観察することです。多くの場合、間違ったトラスト・ファイルをロードしたため、受信した証明書がトラスト・メカニズムにとって不明です。詳細については、次のセクションを参照してください。

原因2: システム・クロックが正しく設定されていません。この場合、認識された時間が証明書の有効期間外になっている可能性があります。トラストストアからの有効な証明書と置き換えないかぎり、システムはこの証明書を無効とみなすため、例外をスローします。

解決法2: システム・クロックの時間を修正します。

java.security.KeyStoreException: TrustedCertEntry Not Supported

問題: 信頼できる証明書をPKCS12キーストアに格納しようとすると、java.security.KeyStoreException: TrustedCertEntry not supportedがスローされます。

原因: PKCS12キーストアへの信頼できる証明書の格納はサポートされていません。PKCS12は、主に関連付けられた証明書チェーンによる秘密鍵の配信に使用されます。「信頼できる」証明書の概念はありません。相互運用性の観点から、他のPKCS12ベンダーにも同じ制限があります。MozillaやInternet Explorerなどのブラウザは、信頼できる証明書のみを含むPKCS12ファイルを受け付けません。

解決法: 信頼できる証明書の格納には、JKSキーストアを使用します。

実行時例外: 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コマンド行ユーティリティを使用して、キーストアと関連の内容を調べることです。

実行時例外: "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 2246を参照してください。

正しいキーストアおよび証明書を渡す方法については、次のセクションを参照してください。

実行時例外: No Cipher Suites in Common

問題1: ハンドシェーク時にクライアントやサーバーがこの例外をスローします。

原因1: SSL接続の両側が共通の暗号化方式群について合意している必要があります。クライアントの暗号化方式群セットとサーバーの暗号化方式群セットの共通点がない場合、この例外がスローされます。

解決法1: 有効な暗号化方式群を構成し、共通の暗号化方式群を含めます。さらに、非対称の暗号化方式群に適切なkeyEntryが提供されるようにします。このセクションの「実行時例外: "No available certificate..."」を参照してください。)

問題2: DSAベースの証明書しかないサーバーのファイルにNetscape NavigatorやMicrosoft Internet Explorerでアクセスすると、共通の暗号化方式群がないことを示す実行時例外が発生します。

原因2: デフォルトでは、keytoolで作成されたkeyEntriesはDSA公開鍵を使用します。キーストア内にDSAのkeyEntriesのみが存在する場合、使用できるのはDSAベースの暗号化方式群のみです。デフォルトでは、NavigatorとInternet ExplorerはRSAベースの暗号化方式群のみを送信します。クライアントとサーバーの暗号化方式群セットの共通点がないため、この例外がスローされます。

解決法2: NavigatorやInternet Explorerと対話するには、RSAベースの鍵を使用する証明書を作成してください。そのためには、keytool使用時に-keyalg RSAオプションを指定します。たとえば、

    keytool -genkeypair -alias duke -keystore testkeys -keyalg rsa

JSSEの最初のアクセスが遅い

問題: 最初のアクセスでJSSEが停止したように見えます。

原因: JSSEには乱数のセキュアなソースが必要です。初期化には時間がかかります。

解決法: 別の乱数ジェネレータを使用するか、オーバーヘッドが通知されない場合は前もって初期化します。

SecureRandom sr = new SecureRandom();
sr.nextInt();
SSLContext.init(..., ..., sr);

java-home/lib/security/java.securityファイルは、SecureRandomのシードデータのソースを指定する方法も提供します。詳細は、ファイルの内容を参照してください。

HttpsURLConnectionクラスを使用するコードがJSSE 1.0.xでClassCastExceptionをスローする

問題: 次のコード(抜粋)は、JSSE 1.0.xのcom.sun.net.ssl.HttpsURLConnectionを使用して作成されたものです。

import com.sun.net.ssl.*;
...deleted...
HttpsURLConnection urlc = new URL("https://example.com/").openConnection();

JSSE 1.0.xで実行すると、このコードはjavax.net.ssl.HttpsURLConnectionオブジェクトを返し、ClassCastExceptionをスローします。

原因: デフォルトでは、HTTPS URLを開くとjavax.net.ssl.HttpsURLConnectionが作成されます。

解決法: 以前のリリースのJDK (リリース6以前)には、HTTPS URLの実装が付属していませんでした。JSSE 1.0.x実装は、そのようなHTTPS URLハンドラを提供し、インストールガイドに、URLハンドラの検索パスを設定してJSSE 1.0.xのcom.sun.net.ssl.HttpsURLConnection実装を取得する方法が記載されています。

JDKでは、URLハンドラのデフォルトの検索パスにHTTPSハンドラがあります。これは、javax.net.ssl.HttpsURLConnectionのインスタンスを返します。java.protocol.handler.pkgs変数を使用して、以前のJSSE 1.0.x実装パスをURL検索パスの先頭に追加することにより、com.sun.net.ssl.HttpsURLConnectionを取得することもでき、前述のコードがキャスト例外をスローすることはなくなります。

次の例を参照してください。
    % java -Djava.protocol.handler.pkgs=com.sun.net.ssl.internal.www.protocol YourClass
    System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");

ClientHelloメッセージの送信後ソケットが切断される

問題: ソケットが接続を試み、ClientHelloメッセージを送信すると、ただちに切断されます。

原因: SSL/TLSサーバーの中には、理解できない形式や、サポートしていないプロトコル・バージョン番号でClientHelloメッセージを受信した場合、接続を切断するものがあります。

解決法: クライアント側で、有効なプロトコルの調整を試みてください。これには、次のシステム・プロパティやメソッドの変更や呼び出しが含まれます。

後方互換性のため、一部のSSL/TLS実装(SunJSSEなど)はSSL/TLSのClientHelloメッセージをSSLv2のClientHello形式にカプセル化して送信できます。SunJSSEプロバイダはこの機能をサポートしています。この機能を使用する場合は、必要に応じて「SSLv2Hello」を有効なプロトコル・リストに追加します。(SunJSSEプロバイダでデフォルトで有効にされているプロトコルを一覧表示しているセクション「プロトコル」も参照してください。)

SSL/TLS RFC標準で、実装は、両側で使用されている最新バージョンとネゴシエーションする必要がありますが、認識できないバージョンを提示されると、一部の非準拠実装は単にハングアップします。たとえば、SSLv3のみを認識する一部の古いサーバー実装は、TLSv1.2を要求されると、シャットダウンします。この状況では、SSL/TLSバージョンのフォールバック・スキームを使用することを検討してください。

  1. サーバーでTLSv1.2を理解しない場合、TLSv1.2からTLSv1.1にフォールバックします。
  2. 前のステップが機能しない場合は、TLSv1.1からTLSv1.0にフォールバックします。

たとえば、クライアントの有効なプロトコル・リストがTLSv1、TLSv1.1およびTLSv1.2の場合、一般的なSSL/TLSバージョンのフォールバック・スキームが次のように見えます。

  1. サーバーに接続を試みます。サーバーがSSL/TLS接続要求をただちに拒否した場合は、ステップ2に進みます。
  2. 有効なプロトコル・リスト内の最高のプロトコル・バージョン(最初の失敗の場合TLSv1.2など)を削除して、バージョン・フォールバック・スキームを試行します。
  3. 再度サーバーに接続を試みます。サーバーが接続を拒否した場合、サーバーがフォールバックできるバージョンがない場合を除いて、ステップ2に進みます。
  4. 接続が失敗し、SSLv2Helloが有効なプロトコル・リストにない場合は、有効なプロトコル・リストを復元して、SSLv2Helloを有効にします。(たとえば、有効なプロトコル・リストはSSLv2Hello、SSLv3、TLSv1、TLSv1.1およびTLSv1.2にしてください。)ステップ1から再度開始します。

: 以前のバージョンにフォールバックすることは、通常、セキュリティ強度が弱いプロトコルにダウングレードすることを意味します。本当に必要で、サーバーが上位のプロトコル・バージョンをサポートしていないことが確実にわかっている場合でないかぎり、フォールバック・スキームを使用することはお薦めしません。

注意: 一部のサーバーはSSLv3無効化の過程でSSLv2Helloも無効にしていますが、それは、SSLv2Helloアクティブなクライアント(たとえばJDK 1.5/6)との通信が失敗することを意味します。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を使用します。デフォルトでは、Oracle JDKはStandard Extension ClassLoaderを使用して、java-home/lib/ext/sunjce_provider.jarにあるSunJCEプロバイダをロードします。ファイルが見つからないかロードできない場合、またはSunJCEプロバイダがProviderメカニズムから登録解除されており、JCEからの代替実装を利用できない場合、この例外がスローされます。

解決法: ファイルがロード可能であることをチェックしてSunJCEが使用可能であることを確認し、プロバイダがProviderインタフェースで登録されていることを確認します。SSL接続のコンテキストで次のコードを実行してみます。

import javax.crypto.*;

System.out.println("=====Where did you get AES=====");
Cipher c = Cipher.getInstance("AES/CBC/NoPadding");
System.out.println(c.getProvider());

WebサーバーからSSLでアプリケーション・リソースを取得しようとするとFailedDownloadExceptionがスローされる

問題: WebサーバーからSSLでアプリケーション・リソースを取得しようとしたときにcom.sun.deploy.net.FailedDownloadExceptionを受け取り、WebサーバーではServer Name Indication (SNI)拡張を持つ仮想ホストが使用されている場合(Apache HTTPサーバーなど)、Webサーバーが正しく構成されていないことがあります。

原因: Java SE 7では、JSSEクライアントのSNI拡張がサポートされているため、要求された仮想サーバーのホスト名は、SSLハンドシェーク中にクライアントからサーバーに送信された最初のメッセージに含まれています。要求されたホスト名(Server Name Indication)が、仮想ホストの構成に指定されているはずの期待されるサーバー名と一致しない場合、サーバーはクライアントの接続要求を拒否することがあります。これにより、SSLハンドシェークの認識されない名前の警告がトリガーされ、FailedDownloadExceptionがスローされます。

解決法: 問題を適切に診断するために、Javaコンソールを使用してトレースを有効にします。詳細は、「Javaコンソール、トレースおよびロギング」を参照してください。問題の原因がjavax.net.ssl.SSLProtocolException: handshake alert: unrecognized_nameの場合、SNIのための仮想ホスト構成が正しくない可能性があります。Apache HTTPサーバーを使用している場合、仮想ホストの構成については、「Name-based Virtual Host Support」を参照してください。特に、<VirtualHost>ブロック内でServerNameディレクティブが正しく構成されていることを確認してください。

詳細については、次の情報を参照してください。

デバッグ・ユーティリティ

JSSEには、動的デバッグのトレースをサポートする機能があります。これは、Java SE プラットフォームでデバッグのアクセス制御に失敗した場合に使用するサポート機能に似ています。一般的なJava動的デバッグ・トレースのサポートにはjava.security.debugシステム・プロパティを使用してアクセスしますが、JSSE固有の動的デバッグ・トレースのサポートにはjavax.net.debugシステム・プロパティを使用してアクセスします。


注: debugユーティリティは、公式にサポートされているJSSEの機能ではありません。


JSSE動的デバッグ・ユーティリティのオプションを表示するには、javaコマンドで次のコマンド行オプションを使用します。

-Djavax.net.debug=help

注: ユーティリティがデバッグするように設計されたクラスを使用しないプログラムの実行中に、いずれかの動的デバッグ・ユーティリティで値helpを指定しても、デバッグ・オプションは得られません。


次の完全な例では、いくつかのJSSEクラスを使用するMyAppというアプリケーションのデバッグ・オプションのリストを取得する方法を示しています。

java -Djavax.net.debug=help MyApp

MyAppアプリケーションは、デバッグのヘルプ情報が表示されると動作しなくなりますが、これはヘルプコードによりアプリケーションが終了するためです。

現在のオプション:

sslオプションとともに次のオプションを使用できます:

handshakeオプションによって生成されるメッセージは、次のオプションで拡張できます:

recordオプションによって生成されるメッセージは、次のオプションで拡張できます:

javax.net.debugプロパティ値は、allまたはsslを指定する必要があり、オプションでデバッグ指定子をその後に続けます。1つまたは複数のオプションが使用できます。オプションをセパレータで区切る必要はありませんが、コロン(:)やカンマ(,)などの区切り文字を使用すると読みやすくなります。どのセパレータでも使用でき、オプション・キーワードの順序も重要ではありません。

このデバッグ情報の見方の説明は、ガイド「SSL/TLS接続のデバッグ」を参照してください。

次に、javax.net.debugプロパティの使用例を示します。

コード例

このセクションには次のコード例が含まれています。

セキュアでないソケットからセキュアなソケットへの変換

このセクションでは、JSSEを使って、セキュアでないソケット接続をセキュアなソケット接続に変換するソース・コードの例を説明します。このセクションのコードは『Java SE 6 Network Security』(Marco Pistoiaほか著)から引用したものです。

まず、「SSLを使用しないソケットの例」で、セキュアでないソケットを使ってクライアントとサーバー間の通信を設定するサンプル・コードを示します。次に、「SSLを使用するソケットの例」ではこのコードを変更し、JSSEを使用してセキュアなソケット通信を設定します。

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) { }

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に設定して、プログラムを実行すると、フィードバックが表示されます。このデバッグ情報の見方の説明は、ガイド「SSL/TLS接続のデバッグ」を参照してください。


サンプル・コードの場所

ほとんどのサンプル・コードは、このドキュメントと同じディレクトリのsamplesサブディレクトリに格納されています。そのリンクから、すべてのサンプル・コード・ファイルとテキスト・ファイルのリストを表示できます。このページでは、すべてのサンプル・コード・ファイルを取得するためにダウンロードできるZIPファイルのリンクもあり、このドキュメントをWeb経由で表示している場合に便利です。

次のセクションでは、サンプルについて説明します。詳細については、README.txtを参照してください。

クライアントとサーバーのセキュアなソケット接続を表すサンプル・コード

samples/socketsディレクトリにあるサンプル・プログラムは、クライアントとサーバーとの間でセキュアなソケット接続を設定する方法を示しています。

サンプルのクライアント・プログラムを実行中に、商用Webサーバーなどの既存のサーバーと通信できます。また、サンプル・サーバー・プログラムClassFileServerと通信することもできます。サンプルのクライアント・プログラムとサーバー・プログラムは、同じネットワークに接続された別個のマシンで実行することも、1台のマシンで、ただし別のターミナル・ウィンドウから両方を実行することもできます。

amples/sockets/clientディレクトリのサンプルSSLSocketClient*プログラム(および「HTTPS接続を表すサンプル・コード」で説明するURLReader*プログラム)は、すべてClassFileServerサンプル・サーバー・プログラムで実行できます。これを実行する方法の例は、「ClassFileServerを使用したSSLSocketClientWithClientAuthの実行」に示しています。同様の変更をして、URLReaderSSLSocketClientまたはSSLSocketClientWithTunnelingClassFileServerで実行できます。

クライアントとサーバーとの間で通信中に認証エラーが発生する場合(WebサーバーとClassFileServerのどちらを使用しているか関係なく)、必要な鍵がトラストストア(トラスト鍵データベース)にないためである可能性があります。たとえば、ClassFileServerは、SSLハンドシェーク中に必要なlocalhostの公開鍵を格納するtestkeysというキーストアを使用します。testkeysキーストアはClassFileServerソースと同じsamples/sockets/serverディレクトリにあります。参照するトラストストアで、localhostの対応する公開鍵の証明書をクライアントが見つけられない場合、認証エラーが発生します。次のセクションで説明するように、samplecacertsトラストストア(localhostの公開鍵と証明書を格納する)を使用してください。

構成要件

クライアントとサーバーとの間のセキュアなソケット接続を作成するサンプル・プログラムを実行する場合は、適切な証明書ファイル(トラストストア)を利用できるようにしておく必要があります。クライアント・プログラムとサーバー・プログラムの両方で、samplesディレクトリのsamplecacerts証明書ファイルを使用してください。この証明書ファイルを使用すると、クライアントがサーバーを認証できるようになります。このファイルには、JDK (cacertsファイルにある)に付属する一般的な証明書発行局(CA)のすべての証明書と、サンプルサーバーClassFileServerとの通信時にクライアントがlocalhostを認証するために必要なlocalhostの証明書が含まれています。ClassFileServerは、samplecacertsの公開鍵に対応するlocalhostの公開鍵を含むキーストアを使用します。

クライアントとサーバーの両方でsamplecacertsファイルを使用できるようにするには、それをjava-home/lib/security/jssecacertsファイルにコピーして名前をcacertsに変更し、java-home/lib/security/cacertsファイルと置き換えるか、クライアントとサーバーの両方に対してjavaコマンドを実行する場合に、次のオプションをコマンド行に追加します。

-Djavax.net.ssl.trustStore=path_to_samplecacerts_file

java-homeの詳細は、「JREインストール・ディレクトリ」を参照してください。

samplecacertsトラスト・ストアのパスワードはchangeitです。keytoolユーティリティを使用して、サンプルの独自の証明書を置き換えることができます。

Netscape NavigatorやMicrosoftのInternet Explorerなどのブラウザを使用して、ClassFileServerの例で提供されているサンプルのSSLサーバーにアクセスすると、ダイアログ・ボックスが開いて、証明書が認識されないというメッセージが表示されます。これは、サンプル・プログラムで使用する証明書は自己署名付きのもので、テスト用にすぎないためです。現在のセッションで証明書に同意できます。SSLサーバーのテストが終了したあと、ブラウザを終了し、ブラウザの名前空間からテスト用証明書を削除します。

クライアント認証の場合、適切なディレクトリの別のduke証明書を使用できます。公開鍵および証明書も、samplecacertsファイルに格納されています。

SSLSocketClientの実行

SSLSocketClient.javaプログラムは、SSLSocketを使用するクライアントを作成し、HTTP要求を送信してHTTPSサーバーから応答を受け取る方法を示します。このプログラムの出力は、https://www.verisign.com/index.htmlのHTMLソースです。

ファイアウォールの内側から、このプログラムを提供されたとおりに実行しないでください。ファイアウォールの内側から実行すると、JSSEはファイアウォールを経由したwww.verisign.comへのパスを検出できないので、UnknownHostExceptionを受け取ります。ファイアウォールの外側から実行できる同等のクライアントを作成するには、サンプル・プログラムSSLSocketClientWithTunnelingで示すように、プロキシ・トンネリングを設定します。

SSLSocketClientWithTunnelingの実行

SSLSocketClientWithTunneling.javaプログラムは、ファイアウォールの外側からセキュアなWebサーバーにアクセスするプロキシ・トンネリングの方法を示します。このプログラムを実行するには、次のJavaシステム・プロパティに適切な値を設定する必要があります。

java -Dhttps.proxyHost=webproxy
-Dhttps.proxyPort=ProxyPortNumber
SSLSocketClientWithTunneling

注: -Dオプションによるプロキシ指定はオプションです。webproxyは使用するプロキシ・ホスト名に、ProxyPortNumberは適切なポート番号に置き換えてください。

プログラムはhttps://www.verisign.com/index.htmlのHTMLソース・ファイルを返します。

SSLSocketClientWithClientAuthの実行

SSLSocketClientWithClientAuth.javaプログラムは、サーバーから要求された場合にキー・マネージャを設定し、クライアント認証を行う方法を示します。このプログラムも、クライアントがファイアウォールの外側にはいないことを前提にしています。SSLSocketClientWithTunnelingの例に従ってプログラムを変更すれば、ファイアウォールの内側から接続することもできます。

このプログラムを実行するには、ホスト、ポート、および要求されたファイル・パスの3つのパラメータを指定する必要があります。前回の例を反映させるため、ホストにwww.verisign.com、ポート番号に443、要求されたファイル・パスにhttps://www.verisign.com/を設定して、このプログラムをクライアント認証なしで実行できます。これらのパラメータを使用したときの出力が、Webサイトhttps://www.verisign.com/のHTMLソースです。

SSLSocketClientWithClientAuthを実行してクライアント認証を行うには、クライアント認証を要求するサーバーにアクセスする必要があります。このサーバーには、サンプル・プログラムClassFileServerを使用できます。これについては、次のセクションで説明します。

ClassFileServerの実行

ここでClassFileServerと呼ばれているプログラムは、ClassFileServer.javaClassServer.javaの2つのファイルで構成されています。

これらを実行するには、ClassFileServer.classを実行します。その際は次のパラメータが必要です。


注: TLSおよびtrueパラメータはオプションです。それらを省略した場合は、TLSではない通常のファイルサーバーを認証なしで使用し、何も起こらないことを示します。これは、一方の側(クライアント)がTLSとネゴシエーションを行おうとしても、もう一方の側(サーバー)は行おうとしないため、通信ができないからです。


注: サーバーはGET /path_to_fileの形式でGET要求を期待します。


ClassFileServerによるSSLSocketClientWithClientAuthの実行

サンプル・プログラムSSLSocketClientWithClientAuthおよび ClassFileServerを使用して、認証済の通信を設定できます。この通信では、クライアントとサーバーが相互に認証します。サンプルのプログラムは、同じネットワークに接続された別個のマシンで実行することも、1台のマシンで、ただし別のターミナル・ウィンドウまたはコマンド・プロンプト・ウィンドウから両方を実行することもできます。クライアントとサーバーの両方を設定するには、次を実行します。

  1. ClassFileServerの実行」に示すように、ClassFileServerプログラムを1台のマシンやターミナル・ウィンドウから実行します。
  2. SSLSocketClientWithClientAuthプログラムを別のマシンやターミナル・ウィンドウで実行します。SSLSocketClientWithClientAuthには、次のパラメータが必要です。
    • hostは、ClassFileServerを実行するために使用するマシンのホスト名です。
    • portは、ClassFileServerに指定したものと同じポートです。
    • requestedfilepathは、サーバーから取得するファイルのパスを示します。このパラメータには、/filepathと指定する必要があります。GET文の一部として使用されるので、ファイル・パスにはフォワード・スラッシュが必要です。GET文には、稼動中のオペレーティング・システムの種類にかかわらず、フォワード・スラッシュが必要です。文の構成は次のようになります。

      "GET " + requestedfilepath + " HTTP/1.0"
      

注: ClassFileServerが動作しているローカル・マシンに接続するように、他のSSLClient*アプリケーションのGETコマンドを変更できます。

HTTPS接続を表すサンプル・コード

JSSEを介してセキュアな通信にアクセスするためのプライマリAPIは2つあります。1つの方法は、SSLSocketClientSSLSocketClientWithTunnelingおよびSSLSocketClientWithClientAuth (ClassFileServerを使用するか使用しないで)に示すように、任意のセキュアな通信に使用できるソケット・レベルのAPIを使用することです。

2つ目はもっと簡単な方法で、標準のJava URL APIを使用します。java.net.URLクラスを使用したHTTPS URLプロトコルまたはスキームを使用して、SSL対応のWebサーバーとセキュアに通信できます。

HTTPS URLスキームのサポートは一般的なブラウザの多くに実装されており、JSSEで提供されているソケット・レベルAPIを必要とせずにセキュアな通信にアクセスできます。

URLの例はhttps://www.verisign.comです。

HTTPS URL実装のトラストおよび鍵の管理は、環境に固有です。JSSE実装は、HTTPS URL実装を提供します。別のHTTPSプロトコル実装を使用するには、java.protocol.handler.pkgsシステム・プロパティにパッケージ名を設定します。詳細については、java.net.URLクラスのドキュメントを参照してください。

JSSEでダウンロードできるサンプルには、HTTPS接続の作成方法を示すサンプル・プログラムが2つ含まれています。これらのサンプル・プログラム(URLReader.javaURLReaderWithOptions.java)は、どちらもsamples/urlsディレクトリにあります。

URLReaderの実行

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の実行

URLReaderWithOptions.javaプログラムは基本的にはURLReader.javaプログラムと同じですが、実行時にプログラムの引数として次のシステム・プロパティのどれか、または全部をオプションで入力できる点が異なります。

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のサンプルに基づいており、基本的にはカスタムRMIソケット・ファクトリをインストールして使用するように変更された「Hello World」サンプルです。

Java RMIの詳細は、Java RMIドキュメントを参照してください。このWebページは、Java RMIのチュートリアルとJava RMIに関する他の情報を示します。

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の実行

SSLEngineSimpleDemoは単純なアプリケーションであり、I/Oおよびスレッドの発行を単純化してSSLEngineの操作に重点を置いています。このアプリケーションは、一般的なByteBufferオブジェクトによってSSL/TLSメッセージを交換する2つのSSLEngineオブジェクトを作成します。1つのループがすべてのエンジン操作を順番に実行して、セキュアな接続の確立(ハンドシェーク)、アプリケーション・データの転送、およびエンジンのクローズを示します。

SSLEngineResultは、SSLEngineの現在の状態に関して多くの情報を提供します。この例では、すべての状態を調べているわけではありません。I/Oおよびスレッドの発行を適度に単純化しているため本番稼動環境に適した例ではありませんが、SSLEngineの全体的な機能の説明に有用です。

NIOベースのサーバーの実行

SSLEngineによって提供される柔軟性を十分に利用するには、最初にI/Oやスレッド・モデルなどの相補的なAPIを理解する必要があります。

大規模なアプリケーションの開発者が有用と考えるI/Oモデルは、NIO SocketChannelです。NIOは、java.net.Socket APIに内在するスケーリングの問題のいくつかを解決するために部分的に導入されました。SocketChannelには、次のような様々な操作モードがあります。

新しい多くのNIO APIを示すだけでなく、セキュアなHTTPSサーバーを作成するためにSSLEngineを採用する方法も示す、基本的なHTTPサーバーのサンプル・コードが提供されています。サーバーは本番稼動の品質ではありませんが、これらの多くの新しいAPIの動作を示しています。

サンプル・ディレクトリにはREADME.txtファイルがあり、サーバーの紹介、サーバーの構築および構成方法の説明、コード・レイアウトの概要が含まれています。SSLEngineのユーザーにとってもっとも重要なファイルは、ChannelIO.javaおよびChannelIOSecure.javaです。


注: このセクションで説明するサーバーの例は、JDKに含まれています。コードは、jdk-home/samples/nio/serverディレクトリにバンドルされています。


JSSEで使用するキーストアの作成

このセクションでは、keytoolユーティリティを使用して、JSSEでの使用に適した単純なJKSキーストアを作成する方法を説明します。まずキーストア内に(公開鍵および秘密鍵を持つ) keyEntryを作成し、トラストストア内に対応するtrustedCertEntry (公開鍵のみ)を作成します。クライアント認証の場合、クライアントの証明書に対して同様の処理に従う必要があります。


注: PKCS12での信頼できるアンカーおよび秘密鍵の格納は、JDK 8以降でサポートされています。


注: ここでは、各ステップに関する詳しい解説は省略します。詳細は、Solaris、LinuxまたはMac OS XまたはMicrosoft Windowskeytoolのドキュメントを参照してください。

ユーザー入力は太字で示します。

  1. 対応する公開鍵および秘密鍵によって、新しいキーストアと自己署名付き証明書を作成します。

        % keytool -genkeypair -alias duke -keyalg RSA -validity 7 -keystore keystore 
        
        Enter keystore password:  password
        What is your first and last name?
        [Unknown]:  Duke
        What is the name of your organizational unit?
        [Unknown]:  Java Software
        What is the name of your organization?
        [Unknown]:  Oracle, Inc.
        What is the name of your City or Locality?
        [Unknown]:  Palo Alto
        What is the name of your State or Province?
        [Unknown]:  CA
        What is the two-letter country code for this unit?
        [Unknown]:  US
        Is CN=Duke, OU=Java Software, O="Oracle, Inc.",
        L=Palo Alto, ST=CA, C=US correct?
        [no]:  yes
        
        Enter key password for <duke>
        (RETURN if same as keystore password):  <CR>
        
    
  2. キーストアを調べます。エントリの型はkeyEntryで、これは、このエントリに秘密鍵が関連付けられていることを示します)。

        % keytool -list -v -keystore keystore
        
        Enter keystore password:  password
        
        Keystore type: jks
        Keystore provider: SUN
        
        Your keystore contains 1 entry
        
        Alias name: duke
        Creation date: Dec 20, 2001
        Entry type: keyEntry
        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: 3c22adc1
        Valid from: Thu Dec 20 19:34:25 PST 2001 until: Thu Dec 27 19:34:25 PST 2001
        Certificate fingerprints:
        MD5: F1:5B:9B:A1:F7:16:CF:25:CF:F4:FF:35:3F:4C:9C:F0
        SHA1: B2:00:50:DD:B6:CC:35:66:21:45:0F:96:AA:AF:6A:3D:E4:03:7C:74
        
    
  3. 自己署名付き証明書をエクスポートし、内容を調べます。

        % keytool -export -alias duke -keystore keystore -rfc -file duke.cer
        Enter keystore password:  password
        Certificate stored in file <duke.cer>
        % cat duke.cer
        -----BEGIN CERTIFICATE-----
        MIICXjCCAccCBDwircEwDQYJKoZIhvcNAQEEBQAwdjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNB
        MRIwEAYDVQQHEwlQYWxvIEFsdG8xHzAdBgNVBAoTFlN1biBNaWNyb3N5c3RlbXMsIEluYy4xFjAU
        BgNVBAsTDUphdmEgU29mdHdhcmUxDTALBgNVBAMTBER1a2UwHhcNMDExMjIxMDMzNDI1WhcNMDEx
        MjI4MDMzNDI1WjB2MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0
        bzEfMB0GA1UEChMWU3VuIE1pY3Jvc3lzdGVtcywgSW5jLjEWMBQGA1UECxMNSmF2YSBTb2Z0d2Fy
        ZTENMAsGA1UEAxMERHVrZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1loObJzNXsi5aSr8
        N4XzDksD6GjTHFeqG9DUFXKEOQetfYXvA8F9uWtz8WInrqskLTNzwXgmNeWkoM7mrPpK6Rf5M3G1
        NXtYzvxyi473Gh1h9k7tjJvqSVKO7E1oFkQYeUPYifxmjbSMVirWZgvo2UmA1c76oNK+NhoHJ4qj
        eCUCAwEAATANBgkqhkiG9w0BAQQFAAOBgQCRPoQYw9rWWvfLPQuPXowvFmuebsTc28qI7iFWm6BJ
        TT/qdmzti7B5MHOt9BeVEft3mMeBU0CS2guaBjDpGlf+zsK/UUi1w9C4mnwGDZzqY/NKKWtLxabZ
        5M+4MAKLZ92ePPKGpobM2CPLfM8ap4IgAzCbBKd8+CMp8yFmifze9Q==
        -----END CERTIFICATE-----
        
    

    また、-certreqによって証明書署名要求(CSR)を生成し、証明書発行局(CA)に送付して署名を求めることもできますが、それはこの例の範囲を超えています。

  4. 証明書を新しいトラスト・ストアにインポートします。

        % keytool -import -alias dukecert -file duke.cer -keystore truststore
        Enter keystore password:  trustword
        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: 3c22adc1
        Valid from: Thu Dec 20 19:34:25 PST 2001 until: Thu Dec 27 19:34:25 PST 2001
        Certificate fingerprints:
        MD5: F1:5B:9B:A1:F7:16:CF:25:CF:F4:FF:35:3F:4C:9C:F0
        SHA1: B2:00:50:DD:B6:CC:35:66:21:45:0F:96:AA:AF:6A:3D:E4:03:7C:74
        Trust this certificate? [no]:  yes
        Certificate was added to keystore
        
    
  5. トラストストアを調べます。エントリの型はtrustedCertEntryで、これは、秘密鍵をこのエントリで使用できないことを示します。これは、このファイルがKeyManagerのキーストアとして適切でないことも意味します。

        % keytool -list -v -keystore truststore
        Enter keystore password:  trustword
        
        Keystore type: jks
        Keystore provider: SUN
        
        Your keystore contains 1 entry
        
        Alias name: dukecert
        Creation date: Dec 20, 2001
        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: 3c22adc1
        Valid from: Thu Dec 20 19:34:25 PST 2001 until: Thu Dec 27 19:34:25 PST 2001
        Certificate fingerprints:
        MD5: F1:5B:9B:A1:F7:16:CF:25:CF:F4:FF:35:3F:4C:9C:F0
        SHA1: B2:00:50:DD:B6:CC:35:66:21:45:0F:96:AA:AF:6A:3D:E4:03:7C:74
        
    
  6. ここで、適切なキーストアを使用してアプリケーションを実行します。この例では、デフォルトのX509KeyManagerおよびX509TrustManagerを使用することを前提とするため、「JSSEのカスタマイズ」で説明したシステム・プロパティを使用してキーストアを選択します。

        % java -Djavax.net.ssl.keyStore=keystore -Djavax.net.ssl.keyStorePassword=password Server
        
        % java -Djavax.net.ssl.trustStore=truststore -Djavax.net.ssl.trustStorePassword=trustword Client
        
    

注: この例では、サーバーのみを認証しています。クライアントの認証の場合、クライアントの鍵に対して同様のキーストアを用意し、サーバーに対して適切なトラストストアを提供する必要があります。


Server Name Indication (SNI)拡張の使用

このセクションでは、クライアント側アプリケーションとサーバー側アプリケーションでServer Name Indication (SNI)拡張を使用する方法およびそれを仮想インフラストラクチャに適用する方法を示すコード例について説明します。

このセクションのすべての例で、パラメータの設定後にそれらを適用するには、対応するSSLSocketSSLEngineまたはSSLServerSocketオブジェクトで、setSSLParameters(SSLParameters)メソッドを呼び出します。

一般的なクライアント側使用例

次は、クライアント・アプリケーションを開発するためにSNI拡張の理解を必要とするユース・ケースのリストです。

一般的なサーバー側使用例

次は、サーバー・アプリケーションを開発するためにSNI拡張の理解を必要とするユース・ケースのリストです。

仮想インフラストラクチャの操作

このセクションでは、仮想インフラストラクチャ内からServer Name Indication (SNI)拡張を使用する方法について説明します。ここでは、ソケットからのClientHelloメッセージのパーサーの作成方法を示し、SSLSocketおよびSSLEngineを使用した仮想サーバー・ディスパッチャの例を示し、SNI拡張を使用できない場合に何が発生するかを説明して、フェールオーバーSSLContextを作成する方法を示します。

ClientHelloパーサーの準備

アプリケーションはソケットからのClientHelloメッセージを解析するAPIを実装する必要があります。次の例に、これらの機能を実行できるSSLCapabilitiesクラスとSSLExplorerクラスを示します。

SSLCapabilities.javaはハンドシェーク中にSSL/TLSセキュリティ機能をカプセル化します(つまり、SSL/TLSハンドシェークで受け付けられる暗号化方式群のリスト、レコード・バージョン、helloバージョン、サーバー名表示)。それは、SSLExplorer.explore()メソッドによって、SSL/TLS接続のネットワーク・データを調べることによって取得できます。

SSLExplorer.javaはTLSクライアントからの初期ClientHelloメッセージを調査しますが、ハンドシェークを開始したり、ネットワーク・データを消費したりしません。SSLExplorer.explore()メソッドはClientHelloメッセージを解析し、SSLCapabilitiesにセキュリティ・パラメータを取得します。このメソッドは、TLS接続でハンドシェークが行われる前に呼び出す必要があります。

SSLSocketに基づいた仮想サーバー・ディスパッチャ

このセクションでは、SSLSocketに基づいて、仮想サーバー・ディスパッチャを使用する手順を説明します。

  1. サーバー名ハンドラを登録します。

    このステップで、アプリケーションは様々なサーバー名表示の様々なSSLContextオブジェクトを作成したり、特定のサーバー名表示を指定した仮想マシンまたは分散システムにリンクしたりできます。

    たとえば、サーバー名がwww.example.orgの場合、登録されたサーバー名ハンドラはローカル仮想ホスティングWebサービス用などになります。ローカル仮想ホスティングWebサービスは指定したSSLContextを使用します。サーバー名がwww.example.comの場合、登録されたサーバー名ハンドラは10.0.0.36でホストしている仮想マシン用などになります。ハンドラはこの接続を仮想マシンにマッピングできます。

  2. ServerSocketを作成し、新しい接続を受け付けます。

        ServerSocket serverSocket = new ServerSocket(serverPort);
        Socket socket = serverSocket.accept();
            
    
  3. ソケット入力ストリームからバイトを読み取り、バッファして、バッファ済のバイトを調査します。

        InputStream ins = socket.getInputStream();
        
        byte[] buffer = new byte[0xFF];
        int position = 0;
        SSLCapabilities capabilities = null;
        
        // Read the header of TLS record
        while (position < SSLExplorer.RECORD_HEADER_SIZE) {
            int count = SSLExplorer.RECORD_HEADER_SIZE - position;
            int n = ins.read(buffer, position, count);
            if (n < 0) {
                throw new Exception("unexpected end of stream!");
            }
            position += n;
        }
        
        // Get the required size to explore the SSL capabilities
        int recordLength = SSLExplorer.getRequiredSize(buffer, 0, position);
        if (buffer.length < recordLength) {
            buffer = Arrays.copyOf(buffer, recordLength);
        }
        
        while (position < recordLength) {
            int count = recordLength - position;
            int n = ins.read(buffer, position, count);
            if (n < 0) {
                throw new Exception("unexpected end of stream!");
            }
            position += n;
        }
        
        // Explore
        capabilities = SSLExplorer.explore(buffer, 0, recordLength);
        if (capabilities != null) {
            System.out.println("Record version: " + capabilities.getRecordVersion());
            System.out.println("Hello version: " + capabilities.getHelloVersion());
        }
            
    
  4. 調査済の機能から要求されたサーバー名を取得します。

        List<SNIServerName> serverNames = capabilities.getServerNames();
            
    
  5. このサーバー名表示の登録済のサーバー名ハンドラを検索します。

    ホスト名のサービスが仮想マシンまたは他の分散システムに存在する場合、アプリケーションは接続を転送先に転送する必要があります。アプリケーションは、ソケット・ストリームからのSSLアプリケーションよりも、rawインターネット・データを読取りおよび書込みする必要があります。

        Socket destinationSocket = new Socket(serverName, 443);
        
        // Forward buffered bytes and network data from the current socket to the destinationSocket.
            
    

    ホスト名のサービスが同じプロセスに存在し、ホスト名サービスでSSLSocketを直接使用できる場合、アプリケーションはSSLSocketインスタンスをサーバーに設定する必要があります。

        // Get service context from registered handler
        // or create the context
        SSLContext serviceContext = ...
        
        SSLSocketFactory serviceSocketFac = serviceContext.getSSLSocketFactory();
        
        // wrap the buffered bytes
        ByteArrayInputStream bais = new ByteArrayInputStream(buffer, 0, position);
        SSLSocket serviceSocket = (SSLSocket)serviceSocketFac.createSocket(socket, bais, true);
        
        // Now the service can use serviceSocket as usual.
        
    

SSLEngineに基づいた仮想サーバー・ディスパッチャ

このセクションでは、SSLEngineに基づいて、仮想サーバー・ディスパッチャを使用する手順を説明します。

  1. サーバー名ハンドラを登録します。

    このステップで、アプリケーションは様々なサーバー名表示の様々なSSLContextオブジェクトを作成したり、特定のサーバー名表示を指定した仮想マシンまたは分散システムにリンクしたりできます。

    たとえば、サーバー名がwww.example.orgの場合、登録されたサーバー名ハンドラはローカル仮想ホスティングWebサービス用などになります。ローカル仮想ホスティングWebサービスは指定したSSLContextを使用します。サーバー名がwww.example.comの場合、登録されたサーバー名ハンドラは10.0.0.36でホストしている仮想マシン用などになります。ハンドラはこの接続を仮想マシンにマッピングできます。

  2. ServerSocketまたはServerSocketChannelを作成し、新しい接続を受け付けます。

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(...);
        ...
        SocketChannel socketChannel = serverSocketChannel.accept();
            
    
  3. ソケット入力ストリームからバイトを読み取り、バッファして、バッファ済のバイトを調査します。

        ByteBuffer buffer = ByteBuffer.allocate(0xFF);
        SSLCapabilities capabilities = null;
        while (true) {
            // ensure the capacity
            if (buffer.remaining() == 0) {
                ByteBuffer oldBuffer = buffer;
                buffer = ByteBuffer.allocate(buffer.capacity() + 0xFF);
                buffer.put(oldBuffer);
            }
    
            int n = sc.read(buffer);
            if (n < 0) {
                throw new Exception("unexpected end of stream!");
            }
    
            int position = buffer.position();
            buffer.flip();
            capabilities = explorer.explore(buffer);
            buffer.rewind();
            buffer.position(position);
            buffer.limit(buffer.capacity());
            if (capabilities != null) {
                System.out.println("Record version: " +
                        capabilities.getRecordVersion());
                System.out.println("Hello version: " +
                        capabilities.getHelloVersion());
                break;
            }
        }
        buffer.flip();  // reset the buffer position and limitation 
            
    
  4. 調査済の機能から要求されたサーバー名を取得します。

        List<SNIServerName> serverNames = capabilities.getServerNames();
            
    
  5. このサーバー名表示の登録済のサーバー名ハンドラを検索します。

    ホスト名のサービスが仮想マシンまたは他の分散システムに存在する場合、アプリケーションは接続を転送先に転送する必要があります。アプリケーションは、ソケット・ストリームからのSSLアプリケーションよりも、rawインターネット・データを読取りおよび書込みする必要があります。

        Socket destinationSocket = new Socket(serverName, 443);
        
        // Forward buffered bytes and network data from the current socket to the destinationSocket.
            
    

    ホスト名のサービスが同じプロセスに存在し、ホスト名サービスでSSLEngineを直接使用できる場合、アプリケーションは単にネット・データをSSLEngineインスタンスに提供します。

        // Get service context from registered handler
        // or create the context
        SSLContext serviceContext = ...
        
        SSLEngine serviceEngine = serviceContext.createSSLEngine();
    
        // Now the service can use the buffered bytes and other byte buffer as usual.
        
    

使用可能なSNI拡張がない

ClientHelloメッセージにサーバー名表示がない場合、SNIに従って正しいサービスを選択する方法がありません。そのような場合、アプリケーションは、サーバー名表示がない場合に、接続をそれに委譲できるように、デフォルトのサービスを指定する必要がある場合があります。

SSLContextのフェールオーバー

SSLExplorer.explore()メソッドはSSL/TLSの内容の妥当性をチェックしません。レコード形式がSSL/TLS仕様に準拠していない場合、またはハンドシェークの起動後に、explore()メソッドが呼び出された場合、メソッドはIOExceptionをスローすることがあり、ネットワーク・データを生成できません。そのような場合、SSL/TLS接続のネゴシエーションに使用されませんが、適切な警告メッセージで接続を閉じるフェールオーバー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();
    }

付録A: 標準名

JDK Security APIは、アルゴリズム、証明書およびキーストアのタイプの一連の標準名を必要とし、これらを使用します。以前にこの付録Aの仕様および他のセキュリティ仕様(JCA、CertPath)にあった仕様名は、標準名のドキュメントにまとめられました。特定のプロバイダの情報は、「Oracle Provider Documentation」にあります。

付録B: プロバイダのプラグイン可能性

JSSEは完全にプラガブルであり、サード・パーティのJSSEプロバイダの使用には何の制限もありません。


Copyright © 1993, 2018, Oracle and/or its affiliates. All rights reserved.