このガイドでは次のトピックについて説明します。
ネットワークを通じてやり取りされるデータには、意図された受信者以外の人も、簡単にアクセスできます。データにパスワードやクレジット・カード番号などの個人情報が含まれる場合、権限のない者がデータを理解できないよう、手段を講じる必要があります。また、意図的であるかどうかにかかわらず、通信中にデータが変更されていないことを確認することも重要です。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には次のような重要な特長があります。
SSLSocket
、SSLServerSocket
、およびSSLEngine
)暗号化アルゴリズム 脚注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は、javax.net
およびjavax.net.ssl
パッケージで利用でき、次を提供します。
SSLEngine
)。OracleのJava SEの実装には、SunJSSEという名前のJSSEプロバイダが含まれており、これはあらかじめインストールされ、JCAに登録されています。このプロバイダが提供する暗号化サービスは次のとおりです。
このプロバイダの詳細は、Oracleプロバイダ・ドキュメントの「SunJSSE」セクションで説明されています。
次のリストにはオンライン・ドキュメントのリンクと、関連サブジェクトの文書名を示しています。
このドキュメント全体で使われているjava-home変数プレースホルダは、Java Runtime Environment (JRE)がインストールされているディレクトリを表します。このディレクトリは、実行しているJSSEにJDKがインストールされているか、インストールされていないかに基づいて決定されます。JDKにはJREが含まれていますが、ファイル階層の異なるレベルにあります。
様々なインストールのjava-homeのデフォルトの場所を確認するには、表2を参照してください。
オペレーティング・システム | 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は秘密鍵を共有する2つの当事者間で、お互いが送信する情報を検証するために使用されます。
暗号化ハッシュ機能に基づくMACメカニズムは、HMACと呼ばれます。HMACは、共有する秘密鍵と組み合せて、Secure Hash Algorithm (SHA-256)などの暗号化ハッシュ関数とともに使用できます。HMACについては、RFC 2104で規定されています。
2つの鍵を生成する暗号化アルゴリズムを使用する鍵暗号化システムです。一方の鍵は公開されますが、他方は秘密のままです。公開鍵と非公開鍵では、逆の暗号化処理がなされ、一方の鍵で暗号化したものを他方の鍵で復号化します。公開鍵暗号化は、非対称暗号化とも呼ばれます。
すべてのデータ(アプリケーション・レベルであるかハンドシェーク・プロセスの一部であるかに関係なく)を独立したデータのレコードにパッケージ化するプロトコルで、TCPストリーム・ソケットがアプリケーション・バイト・ストリームをネットワーク・パケットに変換するのとよく似ています。個々のレコードは、現在の暗号化鍵と整合性保護鍵によって保護されます。
データの暗号化と復号化に同じ鍵を使用する暗号化アルゴリズムを使用する暗号化システム。秘密鍵暗号化は対称暗号化とも呼ばれます。
認証されたピア識別情報、暗号化方式群、鍵合意の秘密を含む名前付きの状態情報のコレクションで、セキュアなソケット・ハンドシェークを通じてネゴシエーションが行われ、複数のセキュアなソケット・インスタンス間で共有できます。
「キー・マネージャ/トラスト・マネージャ」を参照してください。
「キーストア/トラストストア」を参照してください。
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の標準は存在しません。
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ほど広くサポートされていません。
次の3つの理由から、機密情報をネットワークで送信する際に危険が伴う場合があります。
SSLはこれらの各問題に対処します。最初の問題には、認証と呼ばれるプロセスで、通信の当事者双方に相手側の識別情報をオプションで確認させることで対応しています。両者が認証されると、SSLはセキュアなメッセージ伝送のために両者間の暗号化接続を提供します。両者の通信を暗号化することで機密性が保持されるため、2番目の問題に対処します。SSLで使用する暗号化アルゴリズムには、セキュアなハッシュ関数が含まれており、これはチェックサムに似ています。これにより、送信中にデータが変更されていないことが保証されます。セキュアなハッシュ関数により、3番目のデータの整合性の問題に対処します。
注: 認証も暗号化もオプションであり、2つのエンティティ間でネゴシエーションされた暗号化方式群に依存します。
SSLを使用する場合の明確な例は電子商取引です。電子商取引では、通信するサーバーの識別情報は保証されていると考えるべきではありません。クレジット・カードの番号を入力するだけですばらしいサービスが受けられるという偽のWebサイトを作成するのは簡単なことです。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)によって提案されました。
Aliceは、BobへのメッセージをCharlieが確実に改ざんしないようにする場合、メッセージのHMACを計算して元のメッセージにHMACを追加できます。次に、Bobと共有している秘密鍵を使用してメッセージとHMACを暗号化できます。Bobは、メッセージを復号化してHMACを計算すれば、送信中にメッセージが変更されたかどうかを知ることができます。SSLでは、HMACを使ってセキュアなデータを送信します。
メッセージに暗号化ハッシュが作成されると、ハッシュは送信者の非公開鍵で暗号化されます。このような暗号化ハッシュをデジタル署名と呼びます。
SSLを使った通信は、クライアントとサーバー間の情報交換から始まります。この情報交換をSSLハンドシェークと呼びます。SSLハンドシェークには、次のステージがあります。
SSLセッションは、どの暗号群を使用するかについて、クライアントとサーバーがネゴシエーションを行うことから始まります。暗号化方式群とは、コンピュータがデータを暗号化するために使用する暗号化アルゴリズムと鍵のサイズのセットです。符号化方式には、公開鍵交換アルゴリズムまたは鍵合意アルゴリズム、および暗号化ハッシュ関数に関する情報が含まれます。クライアントは利用できる暗号群をサーバーに伝え、サーバーは、どちらにも適用できる暗号群を選択します。
SSLの認証ステップはオプションです。しかし、Web上の電子商取引の例では、一般にクライアントがサーバーを認証します。サーバーの認証により、サーバーが表すとクライアントが信じているエンティティを、そのサーバーが実際に表していることをクライアントが確認できます。
サーバーは、自らが表すと唱える組織に属していることを証明するため、クライアントに公開鍵証明書を提示します。この証明書が有効であれば、クライアントはサーバーの識別情報について確信できます。
クライアントとサーバーは、同じ秘密鍵について同意できる情報を交換します。たとえば、RSAを使う場合、クライアントは公開鍵証明書で取得したサーバーの公開鍵を使用して、秘密鍵情報を暗号化します。クライアントは暗号化された秘密鍵情報をサーバーに送信します。復号化にはサーバーの非公開鍵が必要なので、サーバーでだけ、このメッセージを復号化できます。
クライアントとサーバーは、同じ秘密鍵にアクセスします。それぞれのメッセージでは、ハンドシェークの最初のステップで選択した暗号化ハッシュ関数と、共有された秘密情報を使用して、メッセージに添付されるHMACを計算します。次に、秘密鍵と、ハンドシェークの最初のステップでネゴシエーションされた秘密鍵アルゴリズムを使用し、セキュアなデータとHMACを暗号化します。そのあと、クライアントとサーバーは、暗号化されハッシュ化されたデータを使ってセキュアに通信することができます。
前のセクションでは、SSLハンドシェークについて概要を説明しました。それは、暗号化されたメッセージを送信する前にクライアントとサーバーで行われる情報の交換です。このセクションでは、さらに詳しく説明します。
図1に、SSLハンドシェークで交換される一連のメッセージを示しています。特定の状況下でだけ送信されるメッセージには「optional」と記されています。各SSLメッセージについては図の下で説明しています。
SSLメッセージは、次の順序で送信されます。
注: クライアントに証明書を要求するのは、ごく一部のインターネット・サーバー・アプリケーションのみです。
close_notify
メッセージを送信し、接続が終了したことをピアに伝えます。SSLセッションで生成したパラメータを保存しておけば、将来のSSLセッションでこれらのパラメータを再利用できます。SSLセッションのパラメータを保存しておけば、暗号化通信をすばやく開始できます。
初期のハンドシェークが完了してアプリケーション・データが流れているとき、いずれの側からでも新しいハンドシェークをいつでも開始できます。特に重要な操作について、アプリケーションで強力な暗号化方式群を使用したり、サーバー・アプリケーションでクライアント認証が必要になる場合もあります。
理由は何であれ、新しいハンドシェークが既存の暗号化セッションに置き換わり、新しいセッションが確立されるまで、アプリケーション・データとハンドシェーク・メッセージが交互に配置されます。
アプリケーションで次のいずれかのメソッドを使用して、新しいハンドシェークを開始できます。
SSLSocket.startHandshake()
SSLEngine.beginHandshake()
再ネゴシエーションに関するプロトコルの問題が2009年に見つかりました。プロトコルおよびJava SE実装はいずれも修正されています。詳細は、「Transport Layer Security (TLS)再ネゴシエーションの問題」を参照してください。
raw SSLSocket
およびSSLEngine
クラスを使用する場合は、データの送信前に必ずピアのクレデンシャルをチェックしてください。SSLSocket
およびSSLEngine
クラスは、URL内のホスト名がピアのクレデンシャル内のホスト名と一致することを自動的に検証しません。ホスト名が検証されない場合、URL不正行為によってアプリケーションが悪用される可能性があります。
HTTPS (HTTP Over TLS)などのプロトコルでは、ホスト名検証が必要です。アプリケーションは、HostnameVerifierを使用してデフォルトのHTTPSホスト名規則をオーバーライドできます。詳細は「HttpsURLConnection」を参照してください。
セキュアな通信を行うには、接続の両側がSSL対応であることが必要です。JSSE APIの接続のエンドポイント・クラスは、SSLSocket
およびSSLEngine
です。図2では、SSLSocket
とSSLEngine
の作成に使用される主なクラスを論理的な順序で並べています。図の後のテキストで、図の内容を説明しています。
SSLSocket
はSSLSocketFactory
またはイン・バウンド接続を受け取るSSLServerSocket
によって作成されます。SSLServerSocket
はSSLServerSocketFactory
で作成されます。SSLSocketFactory
およびSSLServerSocketFactory
オブジェクトはどちらもSSLContext
で作成されます。SSLEngine
は、SSLContext
によって直接作成され、アプリケーションに依存してすべての入出力を処理します。
注: raw SSLSocket
またはSSLEngine
クラスを使用する場合は、データの送信前に必ずピアのクレデンシャルをチェックしてください。SSLSocket
およびSSLEngine
クラスは、たとえばURL内のホスト名がピアのクレデンシャル内のホスト名と一致することを自動的に検証しません。ホスト名が検証されない場合、URL不正行為によってアプリケーションが悪用される可能性があります。
SSLContext
を取得して初期化するには、次の2つの方法があります。
SSLSocketFactory
またはSSLServerSocketFactory
クラスでgetDefault
staticメソッドを呼び出すことです。このメソッドは、デフォルトのKeyManager
、TrustManager
およびSecureRandom
(セキュアな乱数ジェネレータ)を使用してデフォルトのSSLContext
を作成します。デフォルトのKeyManagerFactory
およびTrustManagerFactory
を使用すると、KeyManager
およびTrustManager
がそれぞれ作成されます。使用する鍵データは、「デフォルトのキーストアとトラストストア、ストア・タイプ、およびストア・パスワードのカスタマイズ」で説明するシステム・プロパティで指定されるデフォルトのキーストアおよびトラストストアにあります。SSLContext
クラスでstaticメソッドgetInstance()
を呼び出し、さらにそのインスタンスの適切なinit()
メソッドを呼び出してコンテキストを初期化することです。init()
メソッドの1つのバリアントは、KeyManager
オブジェクトの配列、TrustManager
オブジェクトの配列、およびSecureRandom
オブジェクトの3つの引数を取ります。適切なインタフェースを実装するか、実装を生成するKeyManagerFactory
クラスとTrustManagerFactory
クラスを使用して、KeyManager
オブジェクトとTrustManager
オブジェクトが作成されます。次に、KeyManagerFactory
およびTrustManagerFactory
を、TrustManagerFactory
またはKeyManagerFactory
クラスのinit()
メソッドへの引数として渡されたKeyStore
に含まれる鍵データでそれぞれ初期化できます。最後に、TrustManagerFactory
のgetTrustManagers()
メソッドとKeyManagerFactory
のgetKeyManagers()
メソッドを呼び出して、トラスト・データまたは鍵データの型ごとに1つずつトラスト・マネージャまたはキー・マネージャの配列を取得できます。SSL接続が確立されると、SSLSession
が作成され、これには設定した識別情報、使用する暗号化方式群などの様々な情報が含まれます。次にSSLSession
を使用して、2つのエンティティ間の進行中の関係と状態情報が記述されます。各SSL接続には、一度に1つのセッションが含まれますが、そのセッションがエンティティ間の接続に、同時に、または連続して何度も使用されることがあります。
コアJSSEクラスは、javax.net
およびjavax.net.ssl
パッケージの一部です。
抽象クラスjavax.net.SocketFactory
は、ソケットの作成に使われます。このクラスのサブクラスは、ソケットの特定のサブクラスを作成し、パブリック・ソケット・レベルの機能を追加するための汎用フレームワークを提供するファクトリです。たとえば、「SSLSocketFactoryおよびSSLServerSocketFactory」を参照してください。
javax.net.ServerSocketFactory
抽象クラスはSocketFactory
クラスに似ていますが、サーバー・ソケットの作成に特化して使われます。
ソケット・ファクトリは、構築されるソケットに関する様々なポリシーを取得するための簡単な方法であり、ソケットを要求する特別なコード構成を必要としない方法でそれらのソケットを生成します。
java.net.Socket
(またはjavax.net.ssl.SSLSocket
)のサブクラスにすることができます。そうすれば、圧縮、セキュリティ、レコード宣言、統計情報収集、ファイアウォール・トンネリングなどの機能の新しいAPIを直接公開できます。javax.net.ssl.SSLSocketFactory
クラスは、セキュアなソケットを作成するファクトリとして動作します。このクラスは、javax.net.SocketFactoryの抽象サブクラスです。
セキュアなソケット・ファクトリは、セキュアなソケットの作成と初期設定の詳細情報をカプセル化します。これには、認証鍵、ピア証明書の検証、使用できる暗号化方式群などが含まれます。
javax.net.ssl.SSLServerSocketFactory
クラスはSSLSocketFactory
クラスに似ていますが、サーバー・ソケットの作成に特化して使われます。
次の方法でSSLSocketFactory
を取得できます。
SSLSocketFactory.getDefault()
staticメソッドを呼び出してデフォルトのファクトリを取得します。SSLSocketFactory
を指定できるSSLSocketFactory
パラメータを持つメソッドを含めることができます(javax.net.ssl.HttpsURLConnection
など)。通常、デフォルトのファクトリはサーバー認証だけをサポートするように構成されています。このため、デフォルトのファクトリで作成されたソケットは、一般的なTCPソケット以上にクライアントの情報を漏らすことはありません。
ソケットを作成して使用するクラスの多くは、ソケットの作成方法を詳しく知る必要はありません。パラメータとして渡されたソケット・ファクトリを介してソケットを作成するのは、ソケット構成の詳細を分離し、ソケットを作成して使用するクラスの再利用性を高めるよい方法です。
新しいソケット・ファクトリ・インスタンスを作成するには、独自のソケット・ファクトリ・サブクラスを実装するか、ソケット・ファクトリのファクトリとして動作するクラスをべつに使用します。このようなクラスの1つの例がSSLContext
で、これはプロバイダ・ベースの構成クラスとしてJSSE実装に提供されます。
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
は、そのクラスの複数のcreateSocket()
メソッドのうちの1つを使用して、SSLSocketFactoryのインスタンスを作成できます。SSLSocket
はSSLServerSocket
クラスのaccept()
メソッドを使用して作成できます。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
オブジェクトは、バッファに格納されているデータまたはハンドシェーク・データを処理してSSL/TLSエンコード・データを生成し、アプリケーションによって提供されるネットワーク・バッファに格納します。次にアプリケーションは、右側に示されている適切なトランスポートを使用して、ネットワーク・バッファの内容をピアに送信する役割を実行します。トランスポートを介してピアからSSL/TLSエンコード・データを受け取ると、アプリケーションはそのデータをネットワーク・バッファに格納し、SSLEngine
に渡します。SSLEngine
オブジェクトは、ネットワーク・バッファの内容を処理し、ハンドシェーク・データまたはアプリケーション・データを生成します。
SSLEngine
クラスのインスタンスは次のいずれかの状態になります。
SSLEngine
オブジェクトを作成するには、SSLContext.createSSLEngine()
メソッドを使用します。次に、クライアントまたはサーバーとして動作するようにエンジンを構成し、使用する暗号化方式群やクライアント認証が必要かどうかなどの他の構成パラメータも設定します。
例1にSSLEngine
オブジェクトの作成方法を示します。
注: サーバー名とポート番号は、サーバーとの通信には使用されません(すべてのトランスポートはアプリケーションが担当します)。それらは、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);
2つの主要なSSLEngine
メソッドはwrap()
とunwrap()
です。それらは、それぞれネットワーク・データの生成と消費を担当します。SSLEngine
オブジェクトの状態に応じて、このデータはハンドシェーク・データかアプリケーション・データになります。
それぞれのSSLEngine
オブジェクトには、存続期間中に数種類の段階があります。アプリケーション・データを送信または受信できるようにするには、SSL/TLSプロトコルで、暗号化パラメータを確立するためのハンドシェークが必要です。このハンドシェークでは、SSLEngine
オブジェクトによる一連のやり取りのステップが必要です。「SSLハンドシェーク」セクションで、ハンドシェーク自体の詳細について説明しています。
初期ハンドシェーク時に、wrap()
およびunwrap()
メソッドはハンドシェーク・データを生成および消費し、アプリケーションはデータのトランスポートを担当します。wrap()
およびunwrap()
メソッド・シーケンスは、ハンドシェークが終了するまで繰り返されます。それぞれのSSLEngine
操作によりSSLEngineResult
クラスのインスタンスが生成され、その中のSSLEngineResult.HandshakeStatus
フィールドは、ハンドシェークを進めるために実行する必要のある次の動作を決定するために使用されます。
表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.wrap()
メソッドとSSLEngine.unwrap()
メソッドは、例2に示すようなSSLEngineResult
インスタンスを返します。このSSLEngineResult
オブジェクトには、エンジンの全体的なステータスとハンドシェークのステータスの2つのステータス情報が格納されます。
全体的なステータスは、SSLEngineResult.Status
enumによって表されます。次のステータスがあります。
OK
CLOSED
SSLEngine
が閉じられたか、操作はすでに閉じられていたために完了できませんでした。BUFFER_UNDERFLOW
BUFFER_OVERFLOW
例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の使用を示すサンプル・コード」を参照してください。
javax.net.ssl.SSLSession
インタフェースは、SSLSocket
またはSSLEngine
接続の2つのピアの間でネゴシエーションされたセキュリティ・コンテキストを表します。一度確立されたセッションは、同じ2つのピアの間で接続されるそのあとのSSLSocket
またはSSLEngine
オブジェクトによっても共有できます。
場合によっては、ハンドシェーク中に取り決めたパラメータが、後のハンドシェークでトラストについて判断を下す際に必要になることもあります。たとえば、有効な署名アルゴリズムのリストによって、認証に使用できる証明書タイプが制限されることがあります。SSLSession
はハンドシェーク時に、SSLSocket
またはSSLEngine
のgetHandshakeSession()
を呼び出すことによって取得できます。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
を参照してください。
HTTPSプロトコルはHTTPプロトコルに似ていますが、データを要求または受信する前に、まずSSL/TLSソケットを利用してセキュアなチャネルを確立してピアの識別情報を検証します。javax.net.ssl.HttpsURLConnection
クラスはjava.net.HttpsURLConnection
クラスを拡張し、HTTPSに固有の機能のサポートを追加します。HTTPS URLの構築および使用方法の詳細は、java.net.URL
、java.net.URLConnection
、java.net.HttpURLConnection
およびjavax.net.ssl.HttpURLConnection
クラスに関するAPIの仕様のセクションを参照してください。
HttpsURLConnection
インスタンスを取得する際、URLConnection.connect()
メソッドを使用して実際にネットワーク接続を開始する前に、複数のHTTPおよびHTTPSパラメータを構成できます。これらについては、次を参照してください。
状況によっては、HttpsURLConnection
のインスタンスによって使用されるSSLSocketFactory
を指定した方がよい場合があります。たとえば、デフォルト実装ではサポートされないプロキシ・タイプを使用してトンネリングを行いたいと考える場合があります。新しいSSLSocketFactory
は、すでに必要なトンネリングの完了したソケットを返すことができます。このため、HttpsURLConnection
は追加のプロキシを使用できます。
HttpsURLConnection
クラスには、ロード時に割り当てられたデフォルトのSSLSocketFactory
があります(これはSSLSocketFactory.getDefault()
メソッドによって返されるファクトリです)。以降、HttpsURLConnection
のインスタンスは、staticメソッドHttpsURLConnection.setDefaultSSLSocketFactory
によってクラスに新しいデフォルトのSSLSocketFactory
が割り当てられるまで、現在のデフォルトのSSLSocketFactory
を継承します。HttpsURLConnection
のインスタンスが作成された後、setSSLSocketFactory()
メソッドへの呼出しにより、このインスタンス上の継承されたSSLSocketFactory
をオーバーライドできます。
注: デフォルトのstatic SSLSocketFactory
の変更はHttpsURLConnection
の既存のインスタンスに影響しません。既存のインスタンスを変更するには、setSSLSocketFactory()
メソッドの呼出しが必要です。
getSSLSocketFactory()
メソッドまたはgetDefaultSSLSocketFactory()
メソッドを呼び出すことにより、インスタンスごと、またはクラスごとにSSLSocketFactory
を取得できます。
URLのホスト名がSSL/TLSハンドシェークの一部として受け取ったクレデンシャル内のホスト名と一致しない場合、URL不正行為が発生した可能性があります。実装で、十分な確信を持ってホスト名の一致を判断できない場合、SSL実装で、インスタンスの割当て済HostnameVerifier
のコールバックを実行して、より詳しいチェックを行います。ホスト名ベリファイアは、ホスト名パターン・マッチングを実行したり、対話型のダイアログ・ボックスを表示したりなど、判定を下すために必要なあらゆるステップを取ることができます。ホスト名ベリファイアによる検証に失敗した場合は、接続が切断されます。ホスト名の検証に関する詳細については、RFC 2818を参照してください。
setHostnameVerifier()
メソッドおよびsetDefaultHostnameVerifier()
メソッドの動作は、インスタンスごと、またはクラスごとにHostnameVerifier
オブジェクトが割り当てられ、現在の値がgetHostnameVerifier()
メソッドまたはgetDefaultHostnameVerifier()
メソッドの呼出しによって取得できる点で、setSSLSocketFactory()
メソッドおよびsetDefaultSSLSocketFactory()
メソッドと似ています。
このセクションのクラスとインタフェースは、SSLSocketFactory
、SSLServerSocketFactory
およびSSLEngine
オブジェクトを作成するために使用されるSSLContext
オブジェクトの作成と初期化をサポートするために提供されます。サポート・クラスとインタフェースはjavax.net.ssl
パッケージに含まれています。
このセクションで説明する3つのクラス(SSLContext
、KeyManagerFactory
およびTrustManagerFactory
)はエンジン・クラスです。エンジン・クラスとは、特定のアルゴリズムのAPIクラス(SSLContext
の場合はプロトコル)です。その実装では1つまたは複数の暗号化サービス・プロバイダ(プロバイダ)パッケージで提供されることがあります。プロバイダとエンジン・クラスの詳細は、「Java暗号化アーキテクチャ・リファレンス・ガイド」の「設計方針」と「概念」のセクションを参照してください。
JSSEに標準で付属するSunJSSEプロバイダは、SSLContext
、KeyManagerFactory
およびTrustManagerFactory
実装を提供し、標準のjava.security
APIではエンジン・クラスの実装も提供します。表5に、SunJSSEによって提供される実装を一覧表示します。
実装されるエンジン・クラス | アルゴリズムまたはプロトコル |
---|---|
KeyStore |
PKCS12 |
KeyManagerFactory |
PKIX、SunX509 |
TrustManagerFactory |
PKIX (X509またはSunPKIX)、SunX509 |
SSLContext |
SSLv3(1)、TLSv1、TLSv1.1、TLSv1.2 |
javax.net.ssl.SSLContext
クラスは、セキュアなソケット・プロトコルの実装のエンジン・クラスです。このクラスのインスタンスは、SSLソケット・ファクトリおよびSSLエンジンのファクトリとして動作します。SSLContext
オブジェクトは、そのコンテキストの下で作成されたすべてのオブジェクトで共有される状態情報をすべて保持します。たとえば、セッションの状態は、ソケット・ファクトリにより作成され、コンテキストにより提供されたソケットによってハンドシェーク・プロトコルが取り決められると、SSLContext
と関連付けられます。キャッシュに書き込まれたこれらのセッションは、同じコンテキストで作成された別のソケットで再利用したり共有することができます。
各インスタンスは、認証の実行に必要な鍵、証明書チェーン、および信頼されたルートCA証明書を使ってinit
メソッドで構成されます。この構成は、鍵とトラスト・マネージャの形で提供されます。これらのマネージャは認証をサポートし、コンテキストによってサポートされる暗号群の鍵合意を提供します。
現在は、X.509ベースのマネージャだけがサポートされています。
他のJCAプロバイダ・ベースのエンジン・クラスと同様に、SSLContext
オブジェクトは、SSLContext
クラスのgetInstance()
ファクトリ・メソッドを使用して作成されます。このようなstaticメソッドは、最低限要求されたセキュアなソケット・プロトコルを実装するインスタンスを返します。返されるインスタンスはその他のプロトコルも実装できます。たとえば、getInstance("TLSv1")
はTLSv1、TLSv1.1およびTLSv1.2を実装するインスタンスを返すことができます。getSupportedProtocols()
メソッドは、このコンテキストからSSLSocket
、SSLServerSocket
またはSSLEngine
が作成されたときに、サポート対象のプロトコルのリストを返します。実際のSSL接続でどのプロトコルを有効にするかは、setEnabledProtocols(String[] protocols)
メソッドを使用して制御できます。
注: SSLSocketFactory.getDefault()
メソッドを呼び出すと、SSLContext
オブジェクトが自動的に作成され、初期化され、SSLSocketFactory
クラスに静的に割り当てられます。したがって、デフォルト動作をオーバーライドする場合を除き、SSLContext
オブジェクトを直接作成したり初期化したりする必要はありません。
getInstance()
ファクトリ・メソッドを呼び出してSSLContext
オブジェクトを作成するには、プロトコル名を指定する必要があります。または、要求されたプロトコルの実装を提供するプロバイダを次のように指定することもできます。
public static SSLContext getInstance(String protocol);
public static SSLContext getInstance(String protocol, String provider);
public static SSLContext getInstance(String protocol, Provider provider);
プロトコル名のみを指定すると、システムは、要求されたプロトコルの実装がその環境で利用できるかどうかを判断します。複数の実装がある場合、より望ましいものがあるかどうかを判断します。
プロトコル名とプロバイダの両方を指定すると、システムは、要求されたプロトコルの実装が要求されたプロバイダにあるかどうかを判断します。実装がない場合は、例外がスローされます。
プロトコルは、希望するセキュアなソケット・プロトコルを記述する文字列(TLS
など)です。SSLContext
オブジェクトの一般的なプロトコル名は、付録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にできます。その場合、デフォルト実装が使用されます。
内部のデフォルト・コンテキストが使用される場合(SSLContext
はSSLSocketFactory.getDefault()
またはSSLServerSocketFactory.getDefault()
によって作成されるなど)、デフォルトのKeyManager
とTrustManager
が作成されます。また、デフォルトのSecureRandom
実装も選択されます。
TrustManager
は、提示された認証クレデンシャルの信頼性を判定します。信頼できないクレデンシャルの場合、接続は切断されます。セキュアなソケット・ピアのリモート識別情報を認証するには、1つまたは複数のTrustManager
オブジェクトでSSLContext
オブジェクトを初期化する必要があります。サポートされる認証メカニズムのそれぞれに対し、TrustManager
を1つずつ渡す必要があります。SSLContext
の初期化にnullが渡されると、自動的にトラスト・マネージャが作成されます。通常、単一のトラスト・マネージャは、X.509公開鍵証明書(X509TrustManager
など)に基づく認証をサポートしています。セキュアなソケット実装には、共有の秘密鍵、Kerberos、またはほかのメカニズムに基づく認証をサポートするものもあります。
TrustManager
オブジェクトはTrustManagerFactory
によって、またはインタフェースの具体的な実装を提供することによって作成されます。
javax.net.ssl.TrustManagerFactory
はプロバイダ・ベースのサービスのエンジン・クラスで、1つまたは複数の型のTrustManager
オブジェクトのファクトリとして動作します。これはプロバイダ・ベースなので、追加のファクトリを実装して構成し、より高度なサービスを提供したり、インストール固有の認証ポリシーを実装する追加または代替のトラスト・マネージャを提供できます。
SSLContext
と同様の方法で作成しますが、getInstance
メソッドにプロトコル名ではなくアルゴリズム名の文字列を渡す点が異なります。
TrustManagerFactory tmf = TrustManagerFactory.getInstance(String algorithm); TrustManagerFactory tmf = TrustManagerFactory.getInstance(String algorithm, String provider); TrustManagerFactory tmf = TrustManagerFactory.getInstance(String algorithm, Provider provider);
呼出しの例を次に示します。
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX", "SunJSSE");
前の呼出しは、SunJSSEプロバイダのPKIXトラスト・マネージャ・ファクトリのインスタンスを作成します。このファクトリを使用して、X.509 PKIXベースの証明書パス妥当性検査を実行するトラスト・マネージャを作成できます。
SSLContext
を初期化する場合は、トラスト・マネージャ・ファクトリから作成したトラスト・マネージャを使用するか、CertPath
APIなどを使用して独自のトラスト・マネージャを記述できます。詳細については、「Java PKIプログラマーズ・ガイド」を参照してください。X509TrustManager
インタフェースを使用してトラスト・マネージャを実装する場合は、トラスト・マネージャ・ファクトリを使用する必要はありません。
新しく作成されたファクトリは、init()
メソッドの1つを呼び出すことによって初期化してください。
public void init(KeyStore ks); public void init(ManagerFactoryParameters spec);
使用するTrustManagerFactory
に適したinit()
メソッドを呼び出します。不明な場合は、プロバイダのベンダーに問い合わせてください。
「SunX509」TrustManagerFactory
など、SunJSSEプロバイダが提供するファクトリは多数ありますが、TrustManagerFactory
を初期化するために必要な情報はKeyStore
のみであるため、最初にinit
メソッドを呼び出すのが適切です。TrustManagerFactory
はKeyStore
に、認証チェック中に信頼すべきリモート証明書の情報を問い合わせます。
プロバイダでは、KeyStore
以外の初期化パラメータを必要とすることがあります。そのプロバイダのユーザーは、プロバイダによる定義に従って、適切なManagerFactoryParameters
の実装を渡すことが期待されます。そのあと、プロバイダはManagerFactoryParameters
実装の特定のメソッドを呼び出し、必要な情報を取得できます。
たとえば、TrustManagerFactory
プロバイダが、そのプロバイダを使用するアプリケーションからの初期化パラメータB、RおよびSを必要としているとします。KeyStore
以外の初期化パラメータを必要とするすべてのプロバイダと同様に、プロバイダはアプリケーションがManagerFactoryParameters
の特定のサブインタフェースを実装するクラスのインスタンスを提供することを必要とします。たとえば、プロバイダは呼出し側のアプリケーションがMyTrustManagerFactoryParams
のインスタンスを実装して作成し、2つ目のinit()
メソッドに渡すことを必要としているとします。次の例に、MyTrustManagerFactoryParams
がどのように見えるかを示します。
public interface MyTrustManagerFactoryParams extends ManagerFactoryParameters { public boolean getBValue(); public float getRValue(); public String getSValue(): }
一部のトラスト・マネージャでは、KeyStore
オブジェクトやその他のパラメータで明示的に初期化されなくても、信頼性を判定できます。たとえば、LDAP経由でローカル・ディレクトリ・サービスのトラスト・データにアクセスしたり、オンラインの証明書ステータスチェックサーバーをリモートで使用したり、または標準のローカル位置からデフォルトのトラスト・データにアクセスすることもできます。
デフォルトのトラスト・マネージャのアルゴリズムは「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.enableCRLDP
をtrue
に設定する必要があります。
PKIXおよびCertPath APIの詳細は、「Java PKIプログラマーズ・ガイド」を参照してください。
javax.net.ssl.X509TrustManager
インタフェースは、汎用のTrustManager
インタフェースを拡張したものです。X. 509ベースの認証を使用する場合、トラスト・マネージャによって実装される必要があります。
JSSEを使用したリモート・ソケット・ピアのX.509認証をサポートするには、このインタフェースのインスタンスをSSLContext
オブジェクトのinit
メソッドに渡す必要があります。
このインタフェースは、ユーザーが直接実装することも、SunJSSEプロバイダによって提供されているものなど、プロバイダ・ベースのTrustManagerFactory
から取得することもできます。また、独自のインタフェースを実装して、ファクトリで生成されたトラスト・マネージャに委譲することもできます。たとえば、結果の信頼性の判定をフィルタし、グラフィカル・ユーザー・インタフェースからエンド・ユーザーに問い合わせる場合に、これを実行できます。
注: nullのKeyStoreパラメータがSunJSSEのPKIXまたはSunX509 TrustManagerFactory
に渡された場合、ファクトリは次のプロセスでトラスト・データを検索します。
javax.net.ssl.trustStore
プロパティが定義されている場合、TrustManagerFactory
は、このシステム・プロパティで指定されたファイル名を使用してファイルを検索し、KeyStoreパラメータにそのファイルを使用しようとします。javax.net.ssl.trustStorePassword
システム・プロパティも定義されている場合は、ファイルを開く前に、その値を使用してトラストストアのデータの整合性をチェックします。
javax.net.ssl.trustStore
プロパティが定義されているが、指定したファイルが存在しない場合、空のキーストアを使用するデフォルトのTrustManager
が作成されます。
javax.net.ssl.trustStore
システム・プロパティが指定されていない場合:
/lib/security/jssecacerts
ファイルが存在すれば、そのファイルが使用されます。/lib/security/cacerts
ファイルが存在すれば、そのファイルが使用されます。java-homeが参照する情報については、「インストール・ディレクトリ」を参照してください。
ファクトリは、cacerts
ファイルをチェックする前に、javax.net.ssl.trustStore
セキュリティ・プロパティによって指定されたファイルまたはjssecacertsファイルを検索します。そのため、コード署名の目的で、cacerts内に存在するものとは別に、JSSE固有の一連の信頼されたルート証明書を提供できます。
指定した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);
MyX509TrustManager
を拡張して、キーストアの動的更新処理を行うことができます。checkClientTrusted
またはcheckServerTrusted
のテストに失敗し、信頼できる証明書チェーンを確立できなかった場合、キーストアに対して、要求された信頼できる証明書を追加できます。更新されたキーストアを使用して初期化されたTrustManagerFactory
から新しいpkixTrustManager
を作成する必要があります。以前に初期化したSSLContext
を使って新しい接続を確立すると、信頼性の判定を行うときに、新しく追加された証明書が使用されます。
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
サブクラスを自分自身で作成するか(概略は次のセクションに記載)、プロバイダ・ベースのTrustManagerFactory
から取得できます(SunJSSEプロバイダによって提供されるものなど)。Java SE 7では、PKIXまたはSunX509 TrustManagerFactory
は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
は、最終的にリモート・ホストに送信される認証クレデンシャルを選択します。自分自身(ローカルのセキュアなソケット・ピア)をリモート・ピアに対して認証させるには、1つまたは複数のKeyManager
オブジェクトでSSLContext
オブジェクトを初期化する必要があります。サポートされる認証メカニズムごとに、KeyManager
を1つ渡す必要があります。SSLContext
の初期化中にnullが渡されると、空のKeyManager
が作成されます。内部のデフォルト・コンテキストが使用される場合(SSLSocketFactory.getDefault()
またはSSLServerSocketFactory.getDefault()
によって作成されるSSLContext
など)は、デフォルトのKeyManager
が作成されます。通常、単一のキー・マネージャは、X.509公開鍵証明書に基づく認証をサポートしています。セキュアなソケット実装には、共有の秘密鍵、Kerberos、またはほかのメカニズムに基づく認証をサポートするものもあります。
KeyManager
オブジェクトはKeyManagerFactory
によって、またはインタフェースの固定実装を提供することによって作成されます。
javax.net.ssl.KeyManagerFactory
クラスはプロバイダ・ベースのサービスのエンジン・クラスで、1つまたは複数の型のKeyManager
オブジェクトのファクトリとして動作します。SunJSSEプロバイダは、基本となるX.509キー・マネージャを返すことができるファクトリを実装します。これはプロバイダ・ベースであるため、追加のファクトリを実装し、構成することにより、追加の、または代替のキー・マネージャを提供できます。
このクラスのインスタンスはSSLContext
と同様の方法で作成しますが、getInstance
メソッドにプロトコル名ではなくアルゴリズム名の文字列を渡す点が異なります。
KeyManagerFactory kmf = getInstance(String algorithm); KeyManagerFactory kmf = getInstance(String algorithm, String provider); KeyManagerFactory kmf = getInstance(String algorithm, Provider provider);
呼出しの例を次に示します。
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509", "SunJSSE");
前の呼出しでSunJSSEプロバイダのデフォルトのキー・マネージャ・ファクトリのインスタンスが作成され、これは、基本となるX.509ベースの認証鍵を提供します。
新しく作成されたファクトリは、init
メソッドの1つを呼び出すことによって初期化してください。
public void init(KeyStore ks, char[] password); public void init(ManagerFactoryParameters spec);
使用するKeyManagerFactory
に適したinit
メソッドを呼び出します。不明な場合は、プロバイダのベンダーに問い合わせてください。
デフォルトのSunX509 KeyManagerFactory
など、SunJSSEプロバイダが提供するファクトリは多数ありますが、KeyManagerFactory
を初期化するために必要な情報はKeyStore
とパスワードのみであるため、最初にinit
メソッドを呼び出すのが適切です。KeyManagerFactory
はKeyStore
に、リモートのソケット・ピアを認証するために使用すべき秘密鍵、および対応する公開鍵証明書について問い合わせます。パスワード・パラメータは、KeyStore
の鍵にアクセスするメソッドで使用するパスワードを指定します。KeyStore
の鍵はすべて、同じパスワードで保護する必要があります。
プロバイダでは、KeyStore
とパスワード以外の初期化パラメータを必要とすることがあります。そのプロバイダのユーザーは、プロバイダによる定義に従って、適切なManagerFactoryParameters
の実装を渡すことが期待されます。そのあと、プロバイダはManagerFactoryParameters
実装の特定のメソッドを呼び出し、必要な情報を取得できます。
一部のファクトリでは、KeyStore
オブジェクトやその他のパラメータで初期化されなくても、認証データにアクセスできます。たとえば、Java認証・承認サービス(JAAS)などのログイン・メカニズムの一部として鍵データにアクセスできる場合があります。
前に述べたように、SunJSSEプロバイダは、KeyStore
パラメータで初期化されている必要があるSunX509ファクトリをサポートします。
javax.net.ssl.X509KeyManager
インタフェースは、汎用のKeyManager
インタフェースを拡張したものです。X.509ベースの認証を行うキー・マネージャで実装します。JSSEを使用したリモート・ソケット・ピアのX.509認証をサポートするには、このインタフェースのインスタンスをSSLContext
オブジェクトのinit()
メソッドに渡す必要があります。
このインタフェースは、ユーザーが直接実装することも、SunJSSEプロバイダによって提供されているものなど、プロバイダ・ベースのKeyManagerFactory
から取得することもできます。また、独自のインタフェースを実装して、ファクトリで生成されたキー・マネージャに委譲することもできます。たとえば、結果の鍵をフィルタし、グラフィカル・ユーザー・インタフェースを使用してエンド・ユーザーに問い合わせる場合に、これを実行できます。
X509KeyManager
のデフォルトの動作が状況に適していない場合は、「独自のX509TrustManagerの作成」に示す同様の方法で独自のX509KeyManager
を作成できます。
X509ExtendedKeyManager
抽象クラスは、接続に固有の鍵の選択が可能なX509KeyManager
インタフェースの実装です。これは、鍵のタイプ、許可される発行者および現在のSSLEngine
に基づいて、クライアントまたはサーバー用の鍵の別名を選択する2つのメソッドを追加します。
public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine)
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine)
キー・マネージャがX509ExtendedKeyManager
クラスのインスタンスでない場合、SSLEngine
クラスと連携して動作しません。
JSSEプロバイダおよびトラスト・マネージャの実装については、レガシーのX509KeyManager
インタフェースよりもX509ExtendedKeyManager
クラスの方が強く推奨されます。
TLS 1.2以降では、クライアントとサーバーの両方が、受け入れるハッシュ・アルゴリズムと署名アルゴリズムを指定できます。リモート側から要求される認証に合格するには、ローカルの鍵選択の決定が、X509証明書と、リモート側で受け入れられるハッシュ・アルゴリズムおよび署名アルゴリズムの両方に基づいていることが必要です。リモート側で受け入れられるハッシュ・アルゴリズムおよび署名アルゴリズムはExtendedSSLSession.getPeerSupportedSignatureAlgorithms()
メソッドを使用して取得できます。
「独自のX509ExtendedTrustManagerの作成」に示す同様の方法で、独自のX509ExtendedKeyManager
サブクラスを作成できます。
サーバー側でのServer Name Indication (SNI)拡張のサポートにより、キー・マネージャはサーバー名をチェックし、それに従って適切な鍵を選択できます。たとえば、キーストアに証明書付きの鍵エントリが3つあるとします。
cn=www.example.com
cn=www.example.org
cn=www.example.net
ClientHelloメッセージで、SNI拡張のwww.example.net
に接続するように要求する場合、サーバーはサブジェクト cn=www.example.net
で証明書を選択できるようにする必要があります。
これまでは、TrustManager
とKeyManager
の機能に関して混乱がありました。
TrustManager
はリモート認証クレデンシャル(すなわち接続)が信頼できるかどうかを判定します。
KeyManager
はリモート・ホストに送信される認証クレデンシャルを決定します。
二次サポート・クラスは、セキュアなソケットの作成、使用、および管理をサポートするJSSE APIの一部として提供されます。このクラスは、セキュアなソケット・アプリケーションでは、コア・クラスやサポート・クラスほどには使用されません。セカンダリ・サポート・クラスおよびインタフェースはjavax.net.ssl
およびjavax.security.cert
パッケージに含まれています。
SSLParameters
クラスはTLS接続に影響する次のパラメータをカプセル化します。
次のメソッドを使用して、SSLSocket
またはSSLEngine
の現在のSSLParameters
を取得できます。
SSLSocket
、SSLServerSocket
およびSSLEngine
内のgetSSLParameters()
SSLContext
内のgetDefaultSSLParameters()
およびgetSupportedSSLParamters()
SSLSocket
、SSLServerSocket
およびSSLEngine
のsetSSLParameters()
メソッドで、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)
メソッドを呼び出して、クライアントの優先順位ではなく、独自の優先順位に基づいて暗号化方式群を選択することがあります。
javax.net.ssl.SSLSessionContext
インタフェースは、1つのエンティティに関連付けられているSSLSession
オブジェクトのグループです。たとえば、多数のセッションに同時に参加するサーバーやクライアントに関連付けることができます。このインタフェースのメソッドを使用すると、コンテキストの全セッションを列挙したり、セッションIDで特定のセッションを検索したりできます。
SSLSessionContext
は、SSLSessionのgetSessionContext()
メソッドを呼び出してSSLSession
からオプションで取得することもできます。一部の環境では、コンテキストが使用できないことがあり、その場合、getSessionContext
メソッドはnullを返します。
javax.net.ssl.SSLSessionBindingListener
インタフェースは、SSLSession
からバインドまたはアンバインドされるときに通知を受けるオブジェクトによって実装されます。
javax.net.ssl.SSLSessionBindingEvent
クラスは、SSLSession
からバインドまたはアンバインドされるときに、SSLSessionBindingListener
に伝えられるイベントを定義します。
javax.net.ssl.HandShakeCompletedListener
インタフェースは、指定のSSLSocket
接続時にSSLプロトコル・ハンドシェークの完了通知を受け取る任意のクラスに実装されるインタフェースです。
javax.net.ssl.HandShakeCompletedEvent
クラスは、指定のSSLSocket
接続のSSLプロトコル・ハンドシェークの完了時にHandShakeCompletedListener
に伝えられるイベントを定義します。
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());
HostnameVerifier
をHttpsURLConnection
に割り当てる方法の詳細は、「HttpsURLConnectionクラス」を参照してください。
セキュアなソケット・プロトコルの多くは、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
クラスを使用してください。
java.security.AlgorithmConstraints
インタフェースは、許可される暗号化アルゴリズムを制御するために使用します。AlgorithmConstraints
は3つのpermits()
メソッドを定義します。これらのメソッドは、ある暗号化関数について許可されるアルゴリズム名または鍵を指定します。暗号化関数は一連のCryptoPrimitive
で表現され、これはSTREAM_CIPHER
、MESSAGE_DIGEST
、SIGNATURE
などのフィールドを含む列挙です。
したがって、AlgorithmConstraints
実装は、「この鍵とこのアルゴリズムを暗号化操作の目的で使用できるのか」といった質問に答えることができます。
新しいsetAlgorithmConstraints()
メソッドを使用して、AlgorithmConstraints
オブジェクトをSSLParameters
オブジェクトと関連付けることができます。SSLParameters
オブジェクトに対する現在のAlgorithmConstraints
オブジェクトは、getAlgorithmConstraints()
メソッドを使用して取得します。
StandardConstants
クラスは、JSSEでの標準の定数定義を表すために使用します。
StandardConstants.SNI_HOST_NAME
Server Name Indication (SNI)拡張でのドメイン・ネーム・サーバー(DNS)ホスト名を表し、SNIServerName
またはSNIMatcher
オブジェクトのインスタンス化時に使用できます。
抽象SNIServerName
クラスのインスタンスはServer Name Indication (SNI)拡張のサーバー名を表します。それは、指定したサーバー名のタイプとエンコード値を使用してインスタンス化されます。
getType()
およびgetEncoded()
メソッドを使用して、サーバー名のタイプとエンコードされたサーバー名の値のコピーをそれぞれ返します。equals()
メソッドは他のオブジェクトがこのサーバー名と「等しい」かどうかをチェックするために使用できます。hashCode()
メソッドはこのサーバー名のハッシュ・コード値を返します。サーバー名(サーバー名のタイプとエンコードされたサーバー名の値)の文字列表現を取得するには、toString()
メソッドを使用します。
抽象SNIServerName
クラスのインスタンスはSNIServerName
オブジェクトの一致操作を実行します。サーバーはServer Name Indication (SNI)拡張からの情報を使用して、特定のSSLSocket
またはSSLEngine
で接続を受け付けるべきであるかどうかを判定できます。たとえば、単一の基礎となるネットワーク・アドレスで複数の「仮想」または「名前ベース」のサーバーがホストされている場合、サーバー・アプリケーションは、SNI情報を使用して、このサーバーが、クライアントがアクセスしようとしている正しいサーバーであるかどうかを判断できます。このクラスのインスタンスは、サーバーによって、ホスト名などの特定のタイプの受け付け可能なサーバー名を確認するために使用できます。
SNIMatcher
クラスは、一致操作が実行される指定したサーバー名タイプを使用してインスタンス化されます。指定のSNIServerName
を照合するには、matches()
メソッドを使用します。指定のSNIMatcher
オブジェクトのサーバー名タイプを返すには、getType()
メソッドを使用します。
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には、様々な実装をプラグインしたり、デフォルトのキーストアを指定したりして、カスタマイズ可能な標準実装が含まれます。表6では、カスタマイズが可能な側面、デフォルトの内容、およびカスタマイズを提供するために使用するメカニズムを要約しています。表の最初の列には、指定した機能と、カスタマイズ方法の詳細が説明されているサイトへのリンクが設定してあります。
一部の機能は、システム・プロパティやセキュリティ・プロパティの値を設定してカスタマイズできます。表に続くセクションでは、プロパティ値の設定方法について説明します。
注: この表に示すプロパティの多くは、現在JSSE実装で使用されていますが、それらの名前や型(システムまたはセキュリティ)が引き続き同じで、それらが将来のリリースにも存在するという保証はありません。それらのすべてのプロパティは「*」でフラグ付けされています。ここでは、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、MD5、SHA1 jdkCA & usage TLSServer、RSA keySize < 1024、DSA keySize < 1024、EC keySize < 224 この無効化されたアルゴリズムのリストは、変更される可能性があります。最新の値については、JDKインストールの |
jdk.certpath.disabledAlgorithms セキュリティ・プロパティ |
無効化された制限付き暗号化アルゴリズム | SSLv3、RC4、MD5withRSA、DH keySize < 1024、EC keySize < 224、DES40_CBC、RC4_40、3DES_EDE_CBC この無効化されたアルゴリズムのリストは、変更される可能性があります。最新の値については、JDKインストールの |
jdk.tls.disabledAlgorithms セキュリティ・プロパティ。 |
レガシーの暗号化アルゴリズム | K_NULL、C_NULL、M_NULL、DH_anon、ECDH_anon、RC4_128、RC4_40、DES_CBC、DES40_CBC、3DES_EDE_CBC このレガシー・アルゴリズムのリストは、変更される可能性があります。最新の値については、JDKインストールの |
jdk.tls.legacyAlgorithms セキュリティ・プロパティ。
|
デフォルトのプロキシ・ホスト | なし | * 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
プロパティを設定してカスタマイズする項目があります。次のセクションでは、両方のプロパティ型の値を設定する方法を説明します。
JSSEの一部の側面は、システム・プロパティを設定してカスタマイズできます。次のいくつかの方法によって、これらのプロパティを設定できます。
システム・プロパティを静的に設定するには、java
コマンドの-D
オプションを使用します。たとえば、MyAppというアプリケーションを実行して、javax.net.ssl.trustStore
システム・プロパティを設定し、MyCacertsFileというトラストストアを指定するには、次のように入力します。
java -Djavax.net.ssl.trustStore=MyCacertsFile MyApp
システム・プロパティを動的に設定するには、次のコードでjava.lang.System.setProperty()
メソッドを呼び出します。
System.setProperty("propertyName", "propertyValue");
たとえば、システム・プロパティjavax.net.ssl.trustStore
を設定してMyCacertsFile
というトラストストアを指定する、前の例に対応したsetProperty
呼出しでは:
System.setProperty("javax.net.ssl.trustStore", "MyCacertsFile");
Java配備環境(プラグイン/Web Start)では、いくつかの方法でシステム・プロパティを設定できます。詳細は、Java Platform, Standard Edition配備ガイドを参照してください。
Javaコントロール・パネルを使用して、Runtime Environment PropertyをローカルまたはVM単位で設定します。これにより、ローカルでdeployment.properties
ファイルが作成されます。配備担当開発者はdeployment.config
メカニズムを使用して、企業全体のdeployment.properties
ファイルを配布することもできます。詳細については、「配備構成ファイルおよびプロパティ」を参照してください。
特定のアプレットにプロパティを設定するには、<APPLET>
タグ内のHTMLサブタグ<PARAM>
"java_arguments"を使用します。詳細は、Java Platform, Standard Edition配備ガイドのコマンドライン引数に関する項を参照してください。
特定のJava Web Startアプリケーションまたはアプレット内でPlugin2を使用してプロパティを設定するには、resources
要素のJNLP property
サブ要素を使用します。詳細については、Java Web Startガイドのresources要素に関するセクションを参照してください。
JSSEの一部の側面は、セキュリティ・プロパティを設定してカスタマイズできます。セキュリティ・プロパティは静的または動的に設定します。
セキュリティ・プロパティを静的に設定するには、セキュリティ・プロパティ・ファイルに1行追加します。セキュリティ・プロパティ・ファイルは、java-home/lib/security/java.securityにあります。
java-homeは、「インストール・ディレクトリ」に説明するように、JREがインストールされているディレクトリを表します。
セキュリティ・プロパティ・ファイルでセキュリティ・プロパティ値を指定するには、次の行を追加します。
propertyName=propertyValue
たとえば、デフォルトのSunX509以外のキー・マネージャ・ファクトリのアルゴリズム名を指定するとします。これを実行するには、ssl.KeyManagerFactory.algorithm
というセキュリティ・プロパティの値として、アルゴリズム名を指定します。たとえば、値をMyX509に設定するには、次の行をセキュリティ・プロパティ・ファイルに追加します。
ssl.KeyManagerFactory.algorithm=MyX509
セキュリティ・プロパティを動的に設定するには、次のコードでjava.security.Security.setProperty
メソッドを呼び出します。
Security.setProperty("propertyName," "propertyValue");
たとえば、キー・マネージャ・ファクトリのアルゴリズム名を指定する、前の例に対応したsetProperty()
メソッドの呼出しでは:
Security.setProperty("ssl.KeyManagerFactory.algorithm", "MyX509");
X509Certificate.getInstance()
メソッドで返されたX509証明書実装は、デフォルトでJSSE実装の実装です。
オプションで、別の実装を返すようにすることもできます。そうするには、他の実装のクラスの名前(およびパッケージ)を、cert.provider.x509v1
というセキュリティ・プロパティの値として指定します。たとえば、クラスがMyX509CertificateImpl
と呼ばれ、com.cryptox
パッケージにある場合、セキュリティ・プロパティ・ファイルに次の行を追加してください。
cert.provider.x509v1=com.cryptox.MyX509CertificateImpl
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のエンジン・クラスはSSLContext
、KeyManagerFactory
、および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.getDefault
やSSLServerSocketFactory.getDefault
を呼び出すことでデフォルトのSSLSocketFactory
やSSLServerSocketFactory
が作成され、このデフォルトの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リファレンス実装と同じ方法でそれらを処理してください。
「デフォルトのキーストアとトラストストア、ストア・タイプおよびストア・パスワードのカスタマイズ」で説明したように、デフォルトのSSLSocketFactory
やSSLServerSocketFactory
が作成され、このデフォルトのSSLSocketFactory
(またはSSLServerSocketFactory
)がJSSEリファレンス実装に由来する場合は、常にデフォルトのSSLContext
はソケット・ファクトリに関連付けられます。
デフォルトのSSLContext
は、KeyManager
およびTrustManager
で初期化されます。デフォルトのSSLContext
に提供されたKeyManager
とTrustManager
の両方またはどちらか一方は、前のセクションで説明したように、指定したキーストアまたはトラストストアを管理するための実装になります。
選択されるKeyManager
実装は、まずssl.KeyManagerFactory.algorithm
セキュリティプロパティを調査することによって決定されます。そのようなプロパティ値が指定されていると、指定したアルゴリズムのKeyManagerFactory
実装が検索されます。実装を提供する最初のプロバイダの実装が使用されます。そのgetKeyManagers()
メソッドが呼び出され、デフォルトのSSLContext
に提供するKeyManager
が決定されます。技術的には、getKeyManagers()
は、鍵データの型ごとに1つずつのKeyManager
で、KeyManager
オブジェクトの配列を返します。そのようなセキュリティ・プロパティ値が指定されていない場合、SunX509のデフォルト値を使用して検索を実行します。
注: SunX509アルゴリズムのKeyManagerFactory
実装はSunJSSEプロバイダによって提供されます。それが指定するKeyManager
はjavax.net.ssl.X509KeyManager
実装です。
同様に、選択されるTrustManager
実装は、まずssl.TrustManagerFactory.algorithm
セキュリティプロパティを調査することによって決定されます。そのようなプロパティ値が指定されていると、指定したアルゴリズムのTrustManagerFactory
実装が検索されます。実装を提供する最初のプロバイダの実装が使用されます。そのgetTrustManagers
メソッドが呼び出され、デフォルトのSSLContext
に提供するTrustManager
が決定されます。技術的には、getTrustManagers()
は、トラスト・データの型ごとに1つずつのTrustManager
で、TrustManager
オブジェクトの配列を返します。そのようなセキュリティ・プロパティ値が指定されていない場合、PKIXのデフォルト値を使用して検索を実行します。
注: PKIXアルゴリズムのTrustManagerFactory
実装はSunJSSEプロバイダによって提供されます。それが指定するTrustManager
はjavax.net.ssl.X509TrustManager
実装です。
注: このセクションでは、現在のJSSEリファレンス実装の動作を説明します。このセクションで説明するシステム・プロパティの名前と型(システムまたはセキュリティ)が引き続き同じで、将来のリリースにも存在するという保証はありせん。また、他のJSSE実装での検証や使用も保証されていません。実装で検証された場合、ここで説明するように、実装では、JSSEリファレンス実装と同じ方法でそれらを処理してください。
一部の環境では、TLS/DTLSの使用時は、特定のアルゴリズムまたは鍵の長さが不適切である場合があります。Oracle JDKでは、jdk.certpath.disabledAlgorithms
およびjdk.tls.disabledAlgorithm
セキュリティ・プロパティを使用して、バージョンのネゴシエーション、暗号化方式群の選択、ピア認証および鍵交換メカニズムなど、TLS/DTLSプロトコルのネゴシエーション中にアルゴリズムを無効にします。これらのセキュリティ・プロパティが他のJDK実装によって使用される保証はないことに注意してください。これらのセキュリティ・プロパティの構文とその現在アクティブな値については、<java-home>/lib/security/java.security
ファイルを参照してください。
jdk.certpath.disabledAlgorithms
プロパティ: CertPathコードは、このセキュリティ・プロパティを使用してCertPathチェックの際に使用しないアルゴリズムを決定します。たとえば、TLSサーバーによって識別証明書チェーンが送信される場合、受け取ったチェーンをCertPath実装を使用して検証するクライアントTrustManagerでは、示された条件は許可されません。たとえば、次の行は、cacaertsキーストアにプレインストールされている信頼できるアンカーに連鎖するSHA1 TLSServer証明書と同様に、MD2ベースの証明書をブロックします。さらに、この行は、1024ビット未満のRSA鍵をブロックします。
jdk.certpath.disabledAlgorithms=MD2, SHA1 jdkCA & usage TLSServer, RSA keySize < 1024
jdk.tls.disabledAlgorithms
プロパティ: SunJSSEコードは、このセキュリティ・プロパティを使用してTLS/DTLSプロトコル、暗号化スイート、鍵などを無効化します。この構文は、jdk.certpath.disabledAlgorithms
セキュリティ・プロパティに似ています。たとえば、次の行は、SSLv3アルゴリズムおよびすべてのTLS_*_RC4_*暗号化方式群を無効にします。
jdk.tls.disabledAlgorithms=SSLv3, RC4
特定の条件が必要な場合は、java.security
ファイル内のセキュリティ・プロパティで関連する値を削除するか、JSSEの初期化前に正しいセキュリティ・プロパティを動的に設定することで、それを再アクティブ化できます。
これらのセキュリティ・プロパティでは暗号化方式群の3番目のセットDisabledが事実上作成されることに注意してください。次のリストで、これら3つのセットを説明します。
jdk.tls.disabledAlgorithms
セキュリティ・プロパティで指定されている)、その暗号化方式群は無効になり、接続ハンドシェークに考慮されません。setEnabledCipherSuites
またはsetSSLParameters
メソッドを呼び出します。一部の環境では、特定のアルゴリズムが望ましくない可能性があっても、それがレガシー・アプリケーションで使用されている場合は無効化できません。レガシー・アルゴリズムは引き続きサポートされますが、通常、レガシー・アルゴリズムはセキュリティ強度が十分でないため、アプリケーションでは使用しないでください。TLS/DTLSセキュリティ・パラメータのネゴシエーション中に、他に候補がない場合を除き、レガシー・アルゴリズムはネゴシエートされません。セキュリティ・プロパティjdk.tls.legacyAlgorithms
では、Oracle JDKでレガシー・アルゴリズムとみなされるアルゴリズムを指定します。このセキュリティ・プロパティの構文については、<java-home>/lib/security/java.security
ファイルを参照してください。
注意:
jdk.tls.disabledAlgorithms
プロパティまたはjava.security.AlgorithmConstraints
API (javax.net.ssl.SSLParameters.setAlgorithmConstraints
メソッドを参照)でも制限されている場合、そのアルゴリズムは完全に無効化されてネゴシエートされません。jdk.tls.legacyAlgorithms
で指定されたアルゴリズムをアプリケーションで使用している場合は、できるだけ早く別のアルゴリズムを使用してください。将来のJDKリリースでは、レガシー・アルゴリズムが制限付きアルゴリズムに指定される可能性があります。SunJSSEプロバイダは、その暗号化のすべてニーズに対してSunJCE実装を使用します。プロバイダは通常の位置に置くことが推奨されていますが、SunJCEプロバイダより前に登録することにより、他のJCAまたはJCEプロバイダからの実装を使用できます。セキュリティ・プロパティ・ファイルjava-home/lib/security/java.securityを介して静的に、またはjava.security.Security
クラスのaddProvider()
またはinsertProviderAt()
メソッドを介して動的に、標準JCAメカニズムを使用して、プロバイダを構成できます。java-homeについては、「インストール・ディレクトリ」を参照してください。
1024ビット未満のサイズのDiffie-Hellman (DH)鍵は強度が不十分なため、非推奨になりました。JDK 8では、システム・プロパティjdk.tls.ephemeralDHKeySize
によって、ephemeral DH鍵のサイズをカスタマイズできます。このシステム・プロパティは、エクスポート可能な暗号化方式群のServerKeyExchangeメッセージ内のDH鍵のサイズには影響しません。これは、JSSE OracleプロバイダのDHE_RSA、DHE_DSSおよびDH_anonベースの暗号化方式群にのみ影響します。
このプロパティには次のいずれかの値を指定できます。
legacy
: JSSE Oracleプロバイダは、JDK 7以前のリリースのレガシーの動作を保持しています(512ビットおよび768ビットのサイズのephemeral DH鍵の使用など)。matched
: エクスポート不可能な匿名の暗号化方式群の場合、ServerKeyExchangeメッセージ内のDH鍵のサイズは1024ビットです。X.509証明書ベースの認証(エクスポート不可能な暗号化方式群の)の場合、サイズが1024ビットから2048ビットでなければならないことを除けば、対応する認証鍵に一致するDH鍵のサイズが使われます。たとえば、認証証明書の公開鍵サイズが2048ビットの場合、暗号化方式群がエクスポート可能でないかぎり、ephemeral DH鍵サイズは2048ビットにすべきです。この鍵サイズ設定スキームにより、認証鍵と鍵交換鍵間の暗号化強度の整合性を確保しています。次の表に、システム・プロパティ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以下である必要があります。 |
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つのフェーズに分けて扱われています。
フェーズ1: プロトコル修正が開発されるまで、SSL/TLSの再ネゴシエーションをデフォルトで無効化する中間修正が、「March 30, 2010 Java SE and Java for Business Critical Patch Update」から利用できるようになりました。
フェーズ2: IETFによって、再ネゴシエーション・プロトコルの問題を扱うRFC 5746が発表されました。次の表に、RFC 5746を実装してセキュアな再ネゴシエーションをサポートする修正を含むJDKおよびJREリリースを示します。
JDKファミリ | 脆弱性のあるリリース | フェーズ1の修正(再ネゴシエーションの無効化) | フェーズ2の修正(RFC 5746) |
---|---|---|---|
JDKおよびJRE 6 | Update 18以前 | 更新19-21 | Update 22 |
JDKおよびJRE 5.0 | Update 23以前 | 更新24-25 | Update 26 |
JDKおよびJRE 1.4.2 | Update 25以前 | 更新26-27 | Update 28 |
注: 再ネゴシエーションを必要としないアプリケーションはフェーズ2のデフォルト構成に影響を受けません。ただし、再ネゴシエーションを必要とするアプリケーション(最初は匿名のクライアント・ブラウズを許可するが、後でSSL/TLS認証済のクライアントを要求するWebサーバーなど)は:
SunJSSE実装は、RFC 5746に準拠したピアへの接続について、再ネゴシエーションをデフォルトで再び有効にします。つまり、セキュアに再ネゴシエーションを行うためには、クライアントとサーバーが両方ともRFC 5746をサポートする必要があります。まだアップグレードされていないピアとの接続について、SunJSSEではある程度の相互運用性モードが提供されていますが、ユーザーがクライアントとサーバーの両方の実装をできるだけ早く更新することを強く推奨します。
フェーズ2修正により、SunJSSEは現在3つの再ネゴシエーション相互運用性モードを用意しています。どのモードもRFC 5746のセキュアな再ネゴシエーションを完全にサポートしていますが、アップグレードされていないピアと通信する場合、次のような意味合いが加わります。
厳密モード: クライアントとサーバーの両方がRFC 5746にアップグレードされていること、および適切なRFC 5746メッセージを送信することが求められます。そうでない場合、初期の(または後続の)ハンドシェークが失敗して接続が切断されます。
相互運用モード(デフォルト): 正しいRFC 5746メッセージの使用はオプションですが、適切なメッセージが使用されない場合はレガシーの(元のSSL/TLS仕様の)再ネゴシエーションが無効になります。最初のレガシー接続は許可されますが、レガシーの再ネゴシエーションは無効化されます。これはセキュリティと相互運用性の最適な組み合わせであるため、デフォルト設定です。
セキュアでないモード: レガシーの再ネゴシエーションを完全に許可します。レガシーのピアとの相互運用性がもっとも高いですが、本来のMITM攻撃に対して脆弱です。
3つのモード区分は、アップグレードされていないピアとの接続にのみ影響します。すべてのクライアントおよびサーバーで厳密モード(完全なRFC 5746モード)を使用することが望ましいのですが、配備されているすべてのSSL/TLS実装がRFC 5746をサポートするようになるまである程度時間がかかるため、今のところは相互運用モードがデフォルトになっています。
表8に、クライアントとサーバーがRFC 5746をサポートするように更新されている場合と更新されていない場合の様々な例でのモードに関する相互運用性情報を示しています。
クライアント | サーバー | モード |
---|---|---|
更新済み | 更新済み |
すべてのモードで再ネゴシエーションがセキュリティ保護されます。 |
レガシー 脚注1 | 更新済み |
|
更新済み | レガシー 脚注1 | |
レガシー 脚注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
)。まだアップグレードされていないピアから再ネゴシエーション要求を受け取ったアプリケーションは、現在の接続のタイプに応じて応答します。
no_renegotiation(100)
タイプの警告メッセージがピアに送信され、接続は開いたままになります。古いバージョンのSunJSSEは、no_renegotiation
アラートを受け取ると接続を停止します。SSLHandshakeException
を受け取り、接続は閉じられます(handshake_failure
)。no_renegotiation
アラートはSSLv3仕様に定義されていません。モードを設定するために、次のシステム・プロパティを使用します。
sun.security.ssl.allowUnsafeRenegotiation
(フェーズ1で導入)は、レガシー(安全でない)再ネゴシエーションを許可するかどうかを制御します。sun.security.ssl.allowLegacyHelloMessages
(フェーズ2で導入)は、適切なRFC 5746メッセージを必要とすることなくピアがハンドシェーク・プロセスを実行することを許可します。モード | allowLegacyHelloMessages |
allowUnsafeRenegotiation |
---|---|---|
厳密 | false | false |
相互運用(デフォルト) | true | false |
セキュアでない | true | true |
注意: セキュアでないSSL/TLS再ネゴシエーションは、脆弱性が再確立されるため、再有効化しないでください。
システム・プロパティを設定することによって特定のモードを構成する方法については、「java.lang.Systemプロパティの設定方法」を参照してください。
すべてのピアは、できるだけ早くRFC 5746準拠の実装に更新する必要があります。このRFC 5746修正を適用しても、再ネゴシエーションが必要な場合は、アップグレードされていないピアとの通信に影響が生じます。推奨されるいくつかの方法を次に示します。
ピアを再構築して再ネゴシエーションを要求しないようにする。
再ネゴシエーションは通常、Webサーバーが最初は匿名のクライアント・ブラウズを許可するが、後でSSL/TLS認証済クライアントを要求する場合や、Webサーバーが最初は弱い暗号化方式群を許可するが、後で強い暗号化方式群を必要とする場合に、Webサーバーによって使用されます。代替策は、最初のネゴシエーション中にクライアント認証および強い暗号化方式群を必要とすることです。これを行うには、2つの選択肢があります。
アプリケーションに、あるポイントに到達して、再ネゴシエーションが必要になるまでの「ブラウズ・モード」がある場合、「ブラウズ・モード」を削除し、すべての初期接続を強化するようにサーバーを再構築できます。
サーバーを2つのエンティティに分割して、1つのエンティティ上でブラウズ・モードを実行し、2つ目のエンティティをよりセキュアなモードで使用します。ネゴシエーション・ポイントに到達したら、関連情報をサーバー間で転送します。
これらのどちらのオプションも、ある程度の作業が必要ですが、元のセキュリティ上の欠陥が再度発生することはありません。
システム・プロパティを使用して、再ネゴシエーション相互運用性モードを「セキュアでない」に設定します。
情報と警告については、「フェーズ2修正の説明」を参照してください。
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修正は、RFC 5746に準拠した修正が開発されるまでの間、再ネゴシエーションをデフォルトで無効にするためのものでした。再ネゴシエーションは、sun.security.ssl.allowUnsafeRenegotiation
システム・プロパティを設定することによって再び有効化できました。フェーズ2修正では同じsun.security.ssl.allowUnsafeRenegotiation
システム・プロパティを使用するだけでなく、それにRFC 5746メッセージを使用させる必要もあります。
すべてのアプリケーションを、できるだけ早くフェーズ2 RFC 5746修正にアップグレードする必要があります。
次の場合、SSL/TLS再ネゴシエーションでサーバー証明書を変更することは、安全でないことがあります。
2つの証明書は、次の場合に、同じアイデンティティを表すと見なせます。
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のガイドを参照してください。
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);
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);
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()
メソッドを使用します。
javax.net.ssl.SSLSession
javax.net.ssl.HttpsURLConnection
javax.net.HandshakeCompletedEvent
同様に、(ローカル・エンティティを識別するために)ピアに送信された識別情報を取得するには、これらのクラスでgetLocalPrincipal()
メソッドを使用します。X509ベースの暗号化方式群の場合、これらのメソッドはjavax.security.auth.x500.X500Principal
のインスタンスを返します。Kerberos暗号化方式群の場合、これらのメソッドはjavax.security.auth.kerberos.KerberosPrincipal
のインスタンスを返します。
JSSEアプリケーションは、getPeerCertificates()
と、javax.net.ssl.SSLSession
、javax.net.ssl.HttpsURLConnection
、javax.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
など)。
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
の詳細は、「セキュリティ・ツール」を参照してください。
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
の動作の例を示しています。
www\\.example\\.com
に構成されたマッチャ:
www.example.com
の場合、それは受け付けられ、ServerHelloメッセージで確認が送信されます。www.example.org
の場合、それはunrecognized_name
致命的エラーで拒否されます。www\\.invalid\\.com
に構成されたマッチャ:
www.example.com
の場合、それはunrecognized_name
致命的エラーで拒否されます。www.example.org
の場合、それは受け付けられ、ServerHelloメッセージで確認が送信されます。マッチャが構成されていない:
要求されたすべてのホスト名が受け付けられますが、ServerHelloメッセージで確認は送信されません。
SNI拡張を実装する新しいクラスの説明については、次を参照してください。
たとえば、「Server Name Indication (SNI)拡張の使用」を参照してください。
このセクションにはJSSEのトラブルシューティングに関する情報が含まれます。まず、一般的な構成の問題とそれらの解決方法を説明し、次に、役に立つデバッグ・ユーティリティについて説明します。
このセクションでは、JSSEの使用時に発生する可能性のある一般的な構成の問題について説明します。
問題: SSL接続のネゴシエーション中に、クライアントまたはサーバーがCertificateException
をスローします。
原因1: 一般に、リモート側がローカル側に不明な証明書を送信することが原因です。
解決法1: このタイプの問題をデバッグするもっともよい方法は、デバッグをオンにして(「デバッグ・ユーティリティ」を参照)、証明書のロード時およびネットワーク接続経由での証明書の受信時に観察することです。多くの場合、間違ったトラスト・ファイルをロードしたため、受信した証明書がトラスト・メカニズムにとって不明です。詳細については、次のセクションを参照してください。
原因2: システム・クロックが正しく設定されていません。この場合、認識された時間が証明書の有効期間外になっている可能性があります。トラストストアからの有効な証明書と置き換えないかぎり、システムはこの証明書を無効とみなすため、例外をスローします。
解決法2: システム・クロックの時間を修正します。
問題: 信頼できる証明書をPKCS12キーストアに格納しようとすると、java.security.KeyStoreException: TrustedCertEntry not supported
がスローされます。
原因: PKCS12キーストアへの信頼できる証明書の格納はサポートされていません。PKCS12は、主に関連付けられた証明書チェーンによる秘密鍵の配信に使用されます。「信頼できる」証明書の概念はありません。相互運用性の観点から、他のPKCS12ベンダーにも同じ制限があります。MozillaやInternet Explorerなどのブラウザは、信頼できる証明書のみを含むPKCS12ファイルを受け付けません。
解決法: 信頼できる証明書の格納には、JKSキーストアを使用します。
問題: JSSEを使用するプログラムを実行すると、SSLサービスが利用できないという例外が発生します。たとえば、次のような例外がスローされます。
Exception in thread "main" java.net.SocketException: no SSL Server Sockets Exception in thread "main": SSL implementation not available
原因: キーストア上の誤ったパスワードやキーストアの破損などのため、SSLContext
の初期化で問題が発生しました(かつてあるベンダーが不明な形式のキーストアを出荷し、それが原因でこのタイプのエラーが発生しました。)
解決法: 初期化パラメータを確認します。指定したキーストアが有効であり、指定したパスワードが正しいことを確認します。これをチェックできる1つの方法は、keytool
コマンド行ユーティリティを使用して、キーストアと関連の内容を調べることです。
問題: 単純な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を参照してください。
正しいキーストアおよび証明書を渡す方法については、次のセクションを参照してください。
問題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には乱数のセキュアなソースが必要です。初期化には時間がかかります。
解決法: 別の乱数ジェネレータを使用するか、オーバーヘッドが通知されない場合は前もって初期化します。
SecureRandom sr = new SecureRandom(); sr.nextInt(); SSLContext.init(..., ..., sr);
java-home/lib/security/java.securityファイルは、SecureRandom
のシードデータのソースを指定する方法も提供します。詳細は、ファイルの内容を参照してください。
問題: 次のコード(抜粋)は、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メッセージを送信すると、ただちに切断されます。
原因: SSL/TLSサーバーの中には、理解できない形式や、サポートしていないプロトコル・バージョン番号でClientHelloメッセージを受信した場合、接続を切断するものがあります。
解決法: クライアント側で、有効なプロトコルの調整を試みてください。これには、次のシステム・プロパティやメソッドの変更や呼び出しが含まれます。
HttpsURLConnection
クラスのシステム・プロパティhttps.protocols
jdk.tls.client.protocols
SSLContext.getInstance
メソッドSSLEngine.setEnabledProtocols
メソッドSSLSocket.setEnabledProtocols
メソッドSSLParameters.setProtocols
およびSSLEngine.setSSLParameters
メソッドSSLParameters.setProtocols
およびSSLSocket.setSSLParameters
メソッド後方互換性のため、一部のSSL/TLS実装(SunJSSEなど)はSSL/TLSのClientHelloメッセージをSSLv2のClientHello形式にカプセル化して送信できます。SunJSSEプロバイダはこの機能をサポートしています。この機能を使用する場合は、必要に応じて「SSLv2Hello」を有効なプロトコル・リストに追加します。(SunJSSEプロバイダでデフォルトで有効にされているプロトコルを一覧表示しているセクション「プロトコル」も参照してください。)
SSL/TLS RFC標準で、実装は、両側で使用されている最新バージョンとネゴシエーションする必要がありますが、認識できないバージョンを提示されると、一部の非準拠実装は単にハングアップします。たとえば、SSLv3のみを認識する一部の古いサーバー実装は、TLSv1.2を要求されると、シャットダウンします。この状況では、SSL/TLSバージョンのフォールバック・スキームを使用することを検討してください。
たとえば、クライアントの有効なプロトコル・リストがTLSv1、TLSv1.1およびTLSv1.2の場合、一般的なSSL/TLSバージョンのフォールバック・スキームが次のように見えます。
注: 以前のバージョンにフォールバックすることは、通常、セキュリティ強度が弱いプロトコルにダウングレードすることを意味します。本当に必要で、サーバーが上位のプロトコル・バージョンをサポートしていないことが確実にわかっている場合でないかぎり、フォールバック・スキームを使用することはお薦めしません。
注意: 一部のサーバーはSSLv3無効化の過程でSSLv2Helloも無効にしていますが、それは、SSLv2Helloアクティブなクライアント(たとえばJDK 1.5/6)との通信が失敗することを意味します。JDK 7以降では、デフォルトで、SSLv2Helloはクライアント上では無効、サーバー上では有効です。
問題: ハンドシェークが試行され、必要なアルゴリズムが見つからない場合は失敗します。例は次のとおりです。
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でアプリケーション・リソースを取得しようとしたときに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
アプリケーションは、デバッグのヘルプ情報が表示されると動作しなくなりますが、これはヘルプコードによりアプリケーションが終了するためです。
現在のオプション:
all
: すべてのデバッグを有効にしますssl
: SSLデバッグを有効にしますssl
オプションとともに次のオプションを使用できます:
record
: レコードごとのトレースを有効にしますhandshake
: 各ハンドシェーク・メッセージを出力しますkeygen
: 鍵生成データを出力しますsession
: セッション・アクティビティを出力しますdefaultctx
: デフォルトのSSL初期化を出力しますsslctx
: SSLContext
のトレースを出力しますsessioncache
: セッション・キャッシュのトレースを出力しますkeymanager
: キー・マネージャのトレースを出力しますtrustmanager
: トラスト・マネージャのトレースを出力しますhandshake
オプションによって生成されるメッセージは、次のオプションで拡張できます:
data
: 各ハンドシェイク・メッセージの16進ダンプverbose
: ハンドシェイク・メッセージの詳細出力record
オプションによって生成されるメッセージは、次のオプションで拡張できます:
plaintext
: レコードのプレーン・テキストの16進ダンプpacket
: raw SSL/TLSパケットを出力しますjavax.net.debug
プロパティ値は、all
またはssl
を指定する必要があり、オプションでデバッグ指定子をその後に続けます。1つまたは複数のオプションが使用できます。オプションをセパレータで区切る必要はありませんが、コロン(:)やカンマ(,)などの区切り文字を使用すると読みやすくなります。どのセパレータでも使用でき、オプション・キーワードの順序も重要ではありません。
このデバッグ情報の見方の説明は、ガイド「SSL/TLS接続のデバッグ」を参照してください。
次に、javax.net.debug
プロパティの使用例を示します。
デバッグ・メッセージをすべて表示する場合は、次のように入力します。
java -Djavax.net.debug=all MyApp
各ハンドシェーク・メッセージを16進ダンプで表示するには、次のように入力します(コロンはオプションです)。
java -Djavax.net.debug=ssl:handshake:data MyApp
各ハンドシェーク・メッセージを16進ダンプで表示し、さらにトラスト・マネージャのトレース状態を表示するには、次のように入力します(カンマはオプションです)。
java -Djavax.net.debug=SSL,handshake,data,trustmanager MyApp
このセクションには次のコード例が含まれています。
このセクションでは、JSSEを使って、セキュアでないソケット接続をセキュアなソケット接続に変換するソース・コードの例を説明します。このセクションのコードは『Java SE 6 Network Security』(Marco Pistoiaほか著)から引用したものです。
まず、「SSLを使用しないソケットの例」で、セキュアでないソケットを使ってクライアントとサーバー間の通信を設定するサンプル・コードを示します。次に、「SSLを使用するソケットの例」ではこのコードを変更し、JSSEを使用してセキュアなソケット通信を設定します。
次の例では、セキュアでないソケット接続を設定するサーバー側およびクライアント側のコードを示します。
サーバーとして動作し、ソケットを使用してクライアントと通信する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) { }
次の例では、セキュアなソケット接続を設定するサーバー側およびクライアント側のコードを示します。
サーバーとして動作し、セキュアなソケットを使用してクライアントと通信する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の使用法を示すために設計されていることに注意してください。堅牢なアプリケーションを開発するためのものではありません。
注: セキュアな通信を設定すると、アルゴリズムが複雑になります。サンプル・プログラムでは、設定プロセス中のフィードバックがありません。プログラムの実行時は、少し待ってください。しばらく出力が表示されない場合があります。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の実行」に示しています。同様の変更をして、URLReader
、SSLSocketClient
またはSSLSocketClientWithTunneling
をClassFileServer
で実行できます。
クライアントとサーバーとの間で通信中に認証エラーが発生する場合(WebサーバーとClassFileServer
のどちらを使用しているか関係なく)、必要な鍵がトラストストア(トラスト鍵データベース)にないためである可能性があります。たとえば、ClassFileServer
は、SSLハンドシェーク中に必要なlocalhost
の公開鍵を格納するtestkeys
というキーストアを使用します。testkeys
キーストアはClassFileServer
ソースと同じsamples/sockets/serverディレクトリにあります。参照するトラストストアで、localhost
の対応する公開鍵の証明書をクライアントが見つけられない場合、認証エラーが発生します。次のセクションで説明するように、samplecacerts
トラストストア(localhost
の公開鍵と証明書を格納する)を使用してください。
クライアントとサーバーとの間のセキュアなソケット接続を作成するサンプル・プログラムを実行する場合は、適切な証明書ファイル(トラストストア)を利用できるようにしておく必要があります。クライアント・プログラムとサーバー・プログラムの両方で、samples
ディレクトリのsamplecacerts
証明書ファイルを使用してください。この証明書ファイルを使用すると、クライアントがサーバーを認証できるようになります。このファイルには、JDK (cacertsファイルにある)に付属する一般的な証明書発行局(CA)のすべての証明書と、サンプルサーバーClassFileServer
との通信時にクライアントがlocalhost
を認証するために必要なlocalhost
の証明書が含まれています。ClassFileServer
は、samplecacerts
の公開鍵に対応するlocalhost
の公開鍵を含むキーストアを使用します。
クライアントとサーバーの両方でsamplecacerts
ファイルを使用できるようにするには、それをjava-home/lib/security/jssecacertsファイルにコピーして名前をcacertsに変更し、java-home/lib/security/cacertsファイルと置き換えるか、クライアントとサーバーの両方に対してjava
コマンドを実行する場合に、次のオプションをコマンド行に追加します。
-Djavax.net.ssl.trustStore=path_to_samplecacerts_file
java-homeの詳細は、「JREインストール・ディレクトリ」を参照してください。
samplecacerts
トラスト・ストアのパスワードはchangeit
です。keytool
ユーティリティを使用して、サンプルの独自の証明書を置き換えることができます。
Netscape NavigatorやMicrosoftのInternet Explorerなどのブラウザを使用して、ClassFileServer
の例で提供されているサンプルのSSLサーバーにアクセスすると、ダイアログ・ボックスが開いて、証明書が認識されないというメッセージが表示されます。これは、サンプル・プログラムで使用する証明書は自己署名付きのもので、テスト用にすぎないためです。現在のセッションで証明書に同意できます。SSLサーバーのテストが終了したあと、ブラウザを終了し、ブラウザの名前空間からテスト用証明書を削除します。
クライアント認証の場合、適切なディレクトリの別のduke
証明書を使用できます。公開鍵および証明書も、samplecacerts
ファイルに格納されています。
SSLSocketClient.javaプログラムは、SSLSocket
を使用するクライアントを作成し、HTTP要求を送信してHTTPSサーバーから応答を受け取る方法を示します。このプログラムの出力は、https://www.verisign.com/index.html
のHTMLソースです。
ファイアウォールの内側から、このプログラムを提供されたとおりに実行しないでください。ファイアウォールの内側から実行すると、JSSEはファイアウォールを経由したwww.verisign.com
へのパスを検出できないので、UnknownHostException
を受け取ります。ファイアウォールの外側から実行できる同等のクライアントを作成するには、サンプル・プログラムSSLSocketClientWithTunneling
で示すように、プロキシ・トンネリングを設定します。
SSLSocketClientWithTunneling.javaプログラムは、ファイアウォールの外側からセキュアなWebサーバーにアクセスするプロキシ・トンネリングの方法を示します。このプログラムを実行するには、次のJavaシステム・プロパティに適切な値を設定する必要があります。
java -Dhttps.proxyHost=webproxy -Dhttps.proxyPort=ProxyPortNumber SSLSocketClientWithTunneling
注: -D
オプションによるプロキシ指定はオプションです。webproxyは使用するプロキシ・ホスト名に、ProxyPortNumberは適切なポート番号に置き換えてください。
プログラムはhttps://www.verisign.com/index.html
のHTMLソース・ファイルを返します。
SSLSocketClientWithClientAuth.javaプログラムは、サーバーから要求された場合にキー・マネージャを設定し、クライアント認証を行う方法を示します。このプログラムも、クライアントがファイアウォールの外側にはいないことを前提にしています。SSLSocketClientWithTunneling
の例に従ってプログラムを変更すれば、ファイアウォールの内側から接続することもできます。
このプログラムを実行するには、ホスト、ポート、および要求されたファイル・パスの3つのパラメータを指定する必要があります。前回の例を反映させるため、ホストにwww.verisign.com
、ポート番号に443
、要求されたファイル・パスにhttps://www.verisign.com/
を設定して、このプログラムをクライアント認証なしで実行できます。これらのパラメータを使用したときの出力が、Webサイトhttps://www.verisign.com/
のHTMLソースです。
SSLSocketClientWithClientAuth
を実行してクライアント認証を行うには、クライアント認証を要求するサーバーにアクセスする必要があります。このサーバーには、サンプル・プログラムClassFileServer
を使用できます。これについては、次のセクションで説明します。
ここでClassFileServer
と呼ばれているプログラムは、ClassFileServer.javaとClassServer.javaの2つのファイルで構成されています。
これらを実行するには、ClassFileServer.class
を実行します。その際は次のパラメータが必要です。
port
は、利用できる未使用のポート番号です。たとえば、2001
のような数字を使用できます。docroot
は、取得するファイルを含むサーバーのディレクトリを表します。たとえば、Solarisでは/home/userid/ (useridは特定のUIDを表す)を使用でき、Microsoft Windowsシステムではc:\を使用できます。TLS
は、サーバーがSSLを使用するかTLSを使用するかを示すオプションのパラメータです。true
は、クライアント認証が必要であることを示すオプションのパラメータです。このパラメータは、TLSパラメータが設定されているかどうかだけを参照します。注: TLS
およびtrue
パラメータはオプションです。それらを省略した場合は、TLSではない通常のファイルサーバーを認証なしで使用し、何も起こらないことを示します。これは、一方の側(クライアント)がTLSとネゴシエーションを行おうとしても、もう一方の側(サーバー)は行おうとしないため、通信ができないからです。
注: サーバーはGET /path_to_file
の形式でGET要求を期待します。
サンプル・プログラムSSLSocketClientWithClientAuthおよび ClassFileServer
を使用して、認証済の通信を設定できます。この通信では、クライアントとサーバーが相互に認証します。サンプルのプログラムは、同じネットワークに接続された別個のマシンで実行することも、1台のマシンで、ただし別のターミナル・ウィンドウまたはコマンド・プロンプト・ウィンドウから両方を実行することもできます。クライアントとサーバーの両方を設定するには、次を実行します。
ClassFileServer
プログラムを1台のマシンやターミナル・ウィンドウから実行します。SSLSocketClientWithClientAuth
プログラムを別のマシンやターミナル・ウィンドウで実行します。SSLSocketClientWithClientAuth
には、次のパラメータが必要です。
host
は、ClassFileServer
を実行するために使用するマシンのホスト名です。port
は、ClassFileServer
に指定したものと同じポートです。requestedfilepath
は、サーバーから取得するファイルのパスを示します。このパラメータには、/filepath
と指定する必要があります。GET文の一部として使用されるので、ファイル・パスにはフォワード・スラッシュが必要です。GET文には、稼動中のオペレーティング・システムの種類にかかわらず、フォワード・スラッシュが必要です。文の構成は次のようになります。
"GET " + requestedfilepath + " HTTP/1.0"
注: ClassFileServer
が動作しているローカル・マシンに接続するように、他のSSLClient*
アプリケーションのGET
コマンドを変更できます。
JSSEを介してセキュアな通信にアクセスするためのプライマリAPIは2つあります。1つの方法は、SSLSocketClient
、SSLSocketClientWithTunneling
およびSSLSocketClientWithClientAuth
(ClassFileServer
を使用するか使用しないで)に示すように、任意のセキュアな通信に使用できるソケット・レベルのAPIを使用することです。
2つ目はもっと簡単な方法で、標準のJava URL APIを使用します。java.net.URL
クラスを使用したHTTPS URLプロトコルまたはスキームを使用して、SSL対応のWebサーバーとセキュアに通信できます。
HTTPS URLスキームのサポートは一般的なブラウザの多くに実装されており、JSSEで提供されているソケット・レベルAPIを必要とせずにセキュアな通信にアクセスできます。
URLの例はhttps://www.verisign.com
です。
HTTPS URL実装のトラストおよび鍵の管理は、環境に固有です。JSSE実装は、HTTPS URL実装を提供します。別のHTTPSプロトコル実装を使用するには、java.protocol.handler.pkgs
システム・プロパティにパッケージ名を設定します。詳細については、java.net.URL
クラスのドキュメントを参照してください。
JSSEでダウンロードできるサンプルには、HTTPS接続の作成方法を示すサンプル・プログラムが2つ含まれています。これらのサンプル・プログラム(URLReader.java
とURLReaderWithOptions.java
)は、どちらもsamples/urlsディレクトリにあります。
URLReader.javaプログラムは、セキュアなサイトにアクセスするURLクラスの使い方を示します。このプログラムの出力は、https://www.verisign.com/
のHTMLソースです。デフォルトで、JSSEに付属のHTTPSプロトコル実装が使用されます。別の実装を使用するには、システム・プロパティjava.protocol.handler.pkgs
の値を、実装を含むパッケージ名に設定します。
ファイアウォールの内側でサンプル・コードを実行している場合は、https.proxyHost
およびhttps.proxyPort
システム・プロパティを設定する必要があります。たとえば、ポート8080でプロキシ・ホスト「webproxy」を使用する場合は、java
コマンドで次のオプションを使用できます。
-Dhttps.proxyHost=webproxy -Dhttps.proxyPort=8080
または、java.lang.System
のメソッドsetProperty()
で、ソース・コード内のシステム・プロパティを設定することもできます。たとえば、コマンド行オプションを使用するかわりに、使用するプログラムに次の行を含めることができます。
System.setProperty("java.protocol.handler.pkgs", "com.ABC.myhttpsprotocol"); System.setProperty("https.proxyHost", "webproxy"); System.setProperty("https.proxyPort", "8080");
URLReaderWithOptions.javaプログラムは基本的にはURLReader.javaプログラムと同じですが、実行時にプログラムの引数として次のシステム・プロパティのどれか、または全部をオプションで入力できる点が異なります。
java.protocol.handler.pkgs
https.proxyHost
https.proxyPort
https.cipherSuites
URLReaderWithOptions
を実行するには、次のコマンドを入力します。
java URLReaderWithOptions [-h proxyhost -p proxyport] [-k protocolhandlerpkgs] [-c ciphersarray]
注: 複数のプロトコル・ハンドラを、縦線で区切った項目のリストでprotocolhandlerpkgs
引数に含めることができます。複数のSSL暗号化方式群名を、カンマで区切った項目のリストでciphersarray
引数に含めることができます。可能な暗号化方式群名はSSLSocket.getSupportedCipherSuites()
メソッドで返されるものと同じです。暗号群はSSLおよびTLSプロトコルの仕様から命名されています。
protocolhandlerpkgs
引数は、Oracleが提供するデフォルト以外のHTTPSプロトコル・ハンドラ実装を使用する場合にのみ必要です。
ファイアウォールの内側でサンプル・コード実行している場合は、プロキシ・ホストおよびプロキシ・ポートの引数を含める必要があります。また、使用できる暗号群のリストを含めることもできます。
次に、URLReaderWithOptions
の実行例と、ポート8080にプロキシ・ポート「webproxy」を指定する場合の例を示します。
java URLReaderWithOptions -h webproxy -p 8080
samples/rmiディレクトリのサンプル・コードは、セキュアなJava Remote Method Invocation (RMI)接続の作成方法を示しています。サンプル・コードは、RMIのサンプルに基づいており、基本的にはカスタムRMIソケット・ファクトリをインストールして使用するように変更された「Hello World」サンプルです。
Java RMIの詳細は、Java RMIドキュメントを参照してください。このWebページは、Java RMIのチュートリアルとJava RMIに関する他の情報を示します。
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は単純なアプリケーションであり、I/Oおよびスレッドの発行を単純化してSSLEngine
の操作に重点を置いています。このアプリケーションは、一般的なByteBuffer
オブジェクトによってSSL/TLSメッセージを交換する2つのSSLEngine
オブジェクトを作成します。1つのループがすべてのエンジン操作を順番に実行して、セキュアな接続の確立(ハンドシェーク)、アプリケーション・データの転送、およびエンジンのクローズを示します。
SSLEngineResult
は、SSLEngine
の現在の状態に関して多くの情報を提供します。この例では、すべての状態を調べているわけではありません。I/Oおよびスレッドの発行を適度に単純化しているため本番稼動環境に適した例ではありませんが、SSLEngine
の全体的な機能の説明に有用です。
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ディレクトリにバンドルされています。
このセクションでは、keytool
ユーティリティを使用して、JSSEでの使用に適した単純なJKSキーストアを作成する方法を説明します。まずキーストア内に(公開鍵および秘密鍵を持つ) keyEntry
を作成し、トラストストア内に対応するtrustedCertEntry
(公開鍵のみ)を作成します。クライアント認証の場合、クライアントの証明書に対して同様の処理に従う必要があります。
注: PKCS12での信頼できるアンカーおよび秘密鍵の格納は、JDK 8以降でサポートされています。
注: ここでは、各ステップに関する詳しい解説は省略します。詳細は、Solaris、LinuxまたはMac OS XまたはMicrosoft Windowsのkeytool
のドキュメントを参照してください。
ユーザー入力は太字で示します。
対応する公開鍵および秘密鍵によって、新しいキーストアと自己署名付き証明書を作成します。
% 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>
キーストアを調べます。エントリの型は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
自己署名付き証明書をエクスポートし、内容を調べます。
% 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)に送付して署名を求めることもできますが、それはこの例の範囲を超えています。
証明書を新しいトラスト・ストアにインポートします。
% 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
トラストストアを調べます。エントリの型は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
ここで、適切なキーストアを使用してアプリケーションを実行します。この例では、デフォルトの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)拡張を使用する方法およびそれを仮想インフラストラクチャに適用する方法を示すコード例について説明します。
このセクションのすべての例で、パラメータの設定後にそれらを適用するには、対応するSSLSocket
、SSLEngine
またはSSLServerSocket
オブジェクトで、setSSLParameters(SSLParameters)
メソッドを呼び出します。
次は、クライアント・アプリケーションを開発するためにSNI拡張の理解を必要とするユース・ケースのリストです。
ケース1. クライアントはwww.example.com
にアクセスする必要があります。
ホスト名を明示的に設定します。
SNIHostName serverName = new SNIHostName("www.example.com"); List<SNIServerName> serverNames = new ArrayList<>(1); serverNames.add(serverName); sslParameters.setServerNames(serverNames);
クライアントは常にホスト名を明示的に指定してください。
ケース2. サーバーでサポートしていないため、クライアントはSNIを使用する必要はありません。
空のサーバー名リストでSNIを無効にします。
List<SNIServerName> serverNames = new ArrayList<>(1); sslParameters.setServerNames(serverNames);
ケース3. クライアントはURL https://www.example.com
にアクセスする必要があります。
OracleプロバイダはデフォルトでSNI拡張にホスト名を設定しますが、サード・パーティ・プロバイダがデフォルトのサーバー名表示をサポートしていないことがあります。アプリケーションでプロバイダに依存しないようにするには、常にホスト名を明示的に設定します。
ケース4. クライアントはソケットをサーバー・モードからクライアント・モードに切り替える必要があります。
まず、メソッドsslSocket.setUseClientMode(true)
でモードを切り替えます。次に、ソケットでサーバー名表示パラメータをリセットします。
次は、サーバー・アプリケーションを開発するためにSNI拡張の理解を必要とするユース・ケースのリストです。
ケース1. サーバーはすべてのサーバー名表示タイプを受け付ける必要があります。
SNI拡張を処理するコードがない場合、サーバーはすべてのサーバー名表示型を無視します。
ケース2. サーバーは型 host_name
のすべてのサーバー名表示型を拒否する必要があります。
host_name
に無効なサーバー名パターンを設定します。
SNIMatcher matcher = SNIHostName.createSNIMatcher(""); Collection<SNIMatcher> matchers = new ArrayList<>(1); matchers.add(matcher); sslParameters.setSNIMatchers(matchers);
他の方法は、matches()
メソッドで、常にfalse
を返すSNIMatcher
サブクラスを作成することです。
class DenialSNIMatcher extends SNIMatcher { DenialSNIMatcher() { super(StandardConstants.SNI_HOST_NAME); } @Override public boolean matches(SNIServerName serverName) { return false; } } SNIMatcher matcher = new DenialSNIMatcher(); Collection<SNIMatcher> matchers = new ArrayList<>(1); matchers.add(matcher); sslParameters.setSNIMatchers(matchers);
ケース3. サーバーはexample.com
ドメイン内のすべてのホスト名への接続を受け付ける必要があります。
すべての*.example.com
アドレスを含むパターンとして、host_name
の認識可能なサーバー名を設定します。
SNIMatcher matcher = SNIHostName.createSNIMatcher("(.*\\.)*example\\.com"); Collection<SNIMatcher> matchers = new ArrayList<>(1); matchers.add(matcher); sslParameters.setSNIMatchers(matchers);
ケース4. サーバーはソケットをクライアント・モードからサーバー・モードに切り替える必要があります。
まず、メソッドsslSocket.setUseClientMode(false)
でモードを切り替えます。次に、ソケットでサーバー名表示パラメータをリセットします。
このセクションでは、仮想インフラストラクチャ内からServer Name Indication (SNI)拡張を使用する方法について説明します。ここでは、ソケットからのClientHelloメッセージのパーサーの作成方法を示し、SSLSocket
およびSSLEngine
を使用した仮想サーバー・ディスパッチャの例を示し、SNI拡張を使用できない場合に何が発生するかを説明して、フェールオーバーSSLContext
を作成する方法を示します。
アプリケーションはソケットからのClientHelloメッセージを解析するAPIを実装する必要があります。次の例に、これらの機能を実行できるSSLCapabilities
クラスとSSLExplorer
クラスを示します。
SSLCapabilities.javaはハンドシェーク中にSSL/TLSセキュリティ機能をカプセル化します(つまり、SSL/TLSハンドシェークで受け付けられる暗号化方式群のリスト、レコード・バージョン、helloバージョン、サーバー名表示)。それは、SSLExplorer.explore()
メソッドによって、SSL/TLS接続のネットワーク・データを調べることによって取得できます。
SSLExplorer.javaはTLSクライアントからの初期ClientHelloメッセージを調査しますが、ハンドシェークを開始したり、ネットワーク・データを消費したりしません。SSLExplorer.explore()
メソッドはClientHelloメッセージを解析し、SSLCapabilities
にセキュリティ・パラメータを取得します。このメソッドは、TLS接続でハンドシェークが行われる前に呼び出す必要があります。
このセクションでは、SSLSocket
に基づいて、仮想サーバー・ディスパッチャを使用する手順を説明します。
サーバー名ハンドラを登録します。
このステップで、アプリケーションは様々なサーバー名表示の様々なSSLContext
オブジェクトを作成したり、特定のサーバー名表示を指定した仮想マシンまたは分散システムにリンクしたりできます。
たとえば、サーバー名がwww.example.org
の場合、登録されたサーバー名ハンドラはローカル仮想ホスティングWebサービス用などになります。ローカル仮想ホスティングWebサービスは指定したSSLContext
を使用します。サーバー名がwww.example.com
の場合、登録されたサーバー名ハンドラは10.0.0.36
でホストしている仮想マシン用などになります。ハンドラはこの接続を仮想マシンにマッピングできます。
ServerSocket
を作成し、新しい接続を受け付けます。
ServerSocket serverSocket = new ServerSocket(serverPort); Socket socket = serverSocket.accept();
ソケット入力ストリームからバイトを読み取り、バッファして、バッファ済のバイトを調査します。
InputStream ins = socket.getInputStream(); byte[] buffer = new byte[0xFF]; int position = 0; SSLCapabilities capabilities = null; // Read the header of TLS record while (position < SSLExplorer.RECORD_HEADER_SIZE) { int count = SSLExplorer.RECORD_HEADER_SIZE - position; int n = ins.read(buffer, position, count); if (n < 0) { throw new Exception("unexpected end of stream!"); } position += n; } // Get the required size to explore the SSL capabilities int recordLength = SSLExplorer.getRequiredSize(buffer, 0, position); if (buffer.length < recordLength) { buffer = Arrays.copyOf(buffer, recordLength); } while (position < recordLength) { int count = recordLength - position; int n = ins.read(buffer, position, count); if (n < 0) { throw new Exception("unexpected end of stream!"); } position += n; } // Explore capabilities = SSLExplorer.explore(buffer, 0, recordLength); if (capabilities != null) { System.out.println("Record version: " + capabilities.getRecordVersion()); System.out.println("Hello version: " + capabilities.getHelloVersion()); }
調査済の機能から要求されたサーバー名を取得します。
List<SNIServerName> serverNames = capabilities.getServerNames();
このサーバー名表示の登録済のサーバー名ハンドラを検索します。
ホスト名のサービスが仮想マシンまたは他の分散システムに存在する場合、アプリケーションは接続を転送先に転送する必要があります。アプリケーションは、ソケット・ストリームからのSSLアプリケーションよりも、rawインターネット・データを読取りおよび書込みする必要があります。
Socket destinationSocket = new Socket(serverName, 443);
// Forward buffered bytes and network data from the current socket to the destinationSocket
.
ホスト名のサービスが同じプロセスに存在し、ホスト名サービスでSSLSocket
を直接使用できる場合、アプリケーションはSSLSocket
インスタンスをサーバーに設定する必要があります。
// Get service context from registered handler // or create the context SSLContext serviceContext = ... SSLSocketFactory serviceSocketFac = serviceContext.getSSLSocketFactory(); // wrap the buffered bytes ByteArrayInputStream bais = new ByteArrayInputStream(buffer, 0, position); SSLSocket serviceSocket = (SSLSocket)serviceSocketFac.createSocket(socket, bais, true); // Now the service can use serviceSocket as usual.
このセクションでは、SSLEngine
に基づいて、仮想サーバー・ディスパッチャを使用する手順を説明します。
サーバー名ハンドラを登録します。
このステップで、アプリケーションは様々なサーバー名表示の様々なSSLContext
オブジェクトを作成したり、特定のサーバー名表示を指定した仮想マシンまたは分散システムにリンクしたりできます。
たとえば、サーバー名がwww.example.org
の場合、登録されたサーバー名ハンドラはローカル仮想ホスティングWebサービス用などになります。ローカル仮想ホスティングWebサービスは指定したSSLContext
を使用します。サーバー名がwww.example.com
の場合、登録されたサーバー名ハンドラは10.0.0.36
でホストしている仮想マシン用などになります。ハンドラはこの接続を仮想マシンにマッピングできます。
ServerSocket
またはServerSocketChannel
を作成し、新しい接続を受け付けます。
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(...); ... SocketChannel socketChannel = serverSocketChannel.accept();
ソケット入力ストリームからバイトを読み取り、バッファして、バッファ済のバイトを調査します。
ByteBuffer buffer = ByteBuffer.allocate(0xFF); SSLCapabilities capabilities = null; while (true) { // ensure the capacity if (buffer.remaining() == 0) { ByteBuffer oldBuffer = buffer; buffer = ByteBuffer.allocate(buffer.capacity() + 0xFF); buffer.put(oldBuffer); } int n = sc.read(buffer); if (n < 0) { throw new Exception("unexpected end of stream!"); } int position = buffer.position(); buffer.flip(); capabilities = explorer.explore(buffer); buffer.rewind(); buffer.position(position); buffer.limit(buffer.capacity()); if (capabilities != null) { System.out.println("Record version: " + capabilities.getRecordVersion()); System.out.println("Hello version: " + capabilities.getHelloVersion()); break; } } buffer.flip(); // reset the buffer position and limitation
調査済の機能から要求されたサーバー名を取得します。
List<SNIServerName> serverNames = capabilities.getServerNames();
このサーバー名表示の登録済のサーバー名ハンドラを検索します。
ホスト名のサービスが仮想マシンまたは他の分散システムに存在する場合、アプリケーションは接続を転送先に転送する必要があります。アプリケーションは、ソケット・ストリームからのSSLアプリケーションよりも、rawインターネット・データを読取りおよび書込みする必要があります。
Socket destinationSocket = new Socket(serverName, 443);
// Forward buffered bytes and network data from the current socket to the destinationSocket
.
ホスト名のサービスが同じプロセスに存在し、ホスト名サービスでSSLEngine
を直接使用できる場合、アプリケーションは単にネット・データをSSLEngine
インスタンスに提供します。
// Get service context from registered handler // or create the context SSLContext serviceContext = ... SSLEngine serviceEngine = serviceContext.createSSLEngine(); // Now the service can use the buffered bytes and other byte buffer as usual.
ClientHelloメッセージにサーバー名表示がない場合、SNIに従って正しいサービスを選択する方法がありません。そのような場合、アプリケーションは、サーバー名表示がない場合に、接続をそれに委譲できるように、デフォルトのサービスを指定する必要がある場合があります。
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(); }
JDK Security APIは、アルゴリズム、証明書およびキーストアのタイプの一連の標準名を必要とし、これらを使用します。以前にこの付録Aの仕様および他のセキュリティ仕様(JCA、CertPath)にあった仕様名は、標準名のドキュメントにまとめられました。特定のプロバイダの情報は、「Oracle Provider Documentation」にあります。
JSSEは完全にプラガブルであり、サード・パーティのJSSEプロバイダの使用には何の制限もありません。