Java SEプラットフォームのセキュリティ・アーキテクチャ

このドキュメントでは、JDKに実装される主要なセキュリティ機能の目的の概要を示し、Javaセキュリティ・アーキテクチャの一部であるクラスについて説明し、この新しいアーキテクチャが既存のコードの及ぼす影響について説明し、セキュリティの影響を受けるコードの記述についての見解を示します。

警告:

セキュリティ・マネージャおよびそれに関連するAPIは非推奨であり、今後のリリースでは削除されます。セキュリティ・マネージャの代わりとなるものはありません。詳細および代替手段については、JEP 411を参照してください。

概要

Javaテクノロジの誕生当初から、Javaプラットフォームのセキュリティには強い関心が寄せられ、Javaテクノロジの展開により発生する新しいセキュリティ問題とともに、ますます関心が高まっています。

テクノロジの提供者側の視点から見ると、Javaのセキュリティには次の2つの側面があります。

  • Java対応のアプリケーションが安全に実行される、安全な既製プラットフォームとしてJavaプラットフォームを提供すること。
  • セキュリティを考慮した広範なアプリケーションを(たとえば企業内で)実行可能にするツールとサービスをJavaプログラミング言語に実装して提供すること。

このドキュメントでは、最初の側面に関する問題を扱います。この側面で対象となる顧客は、ブラウザやオペレーティング・システムなどのベンダーです。ベンダーは、製品にJavaテクノロジのバンドルや埋込みを行います。

オリジナルのsandboxモデル

Javaプラットフォームの提供するオリジナルのセキュリティ・モデルは、サンドボックス・モデルとして知られています。これは、オープン・ネットワークから取得した信頼できないコードを実行するための、厳しく制限された環境を提供するためのものです。サンドボックス・モデルの本質は、「ローカル・コードは信頼できるので、非常に重要なシステム・リソース(ファイル・システムなど)へのフル・アクセス権を持つ。一方、ダウンロードされたリモート・コード(アプレット)は信頼できないので、サンドボックスの中のかぎられたリソースにのみアクセスできる」というものです。このサンドボックス・モデルを図1-5に示します。

図1-5 オリジナルのJavaプラットフォーム・セキュリティ・モデル

図1-5の説明が続きます
「図1-5 オリジナルのJavaプラットフォーム・セキュリティ・モデル」の説明

sandboxモデルは、JDK (Java Development Kit)を通じて広まり、JDK 1.0を使って作成されたアプリケーションに一般的に採用されました。これらのアプリケーションには、Java対応のWebブラウザも含まれます。

多くのメカニズムによってセキュリティ全体が強化されます。まず、この言語は、型保証されるように設計されており、簡単に使用できます。CやC++などの他のプログラミング言語を使用する場合に比べ、無意識なミスを少なくし、プログラマの負担を軽くすることが期待されます。自動メモリー管理、ガベージ・コレクション、文字列や配列の範囲チェックなどの言語の機能は、プログラマが安全なコードを書くためにこの言語がどれだけ役立つかを示す例です。

次に、コンパイラとバイトコード・ベリファイアにより、正当なJavaバイト・コードだけが実行されます。バイトコード・ベリファイアとJava Virtual Machineにより、実行時の言語の安全性が保証されます。

さらに、クラス・ローダーはローカルな名前空間を定義します。これによって、信頼できないアプレットがほかのプログラムの実行を妨害しないようにすることができます。

最後に、非常に重要なシステム・リソースへのアクセスにはJava Virtual Machineが介在し、あらかじめSecurityManagerクラスによってチェックされます。このクラスは、信頼できないコードの動作を最小限に制限します。

JDK 1.1には、図1-6に示すような署名付きアプレットの概念が導入されています。そのリリースでは、正しくデジタル署名されたアプレットは、そのアプレットを受け取ったエンド・システムがその署名キーを信頼できると認めた場合には、信頼できるローカル・コードと同様に扱われます。署名付きアプレットは、署名鍵とともにJAR (Java Archive)形式で届けられます。JDK 1.1では、署名付きアプレットもsandbox内で実行します。

図1-6 JDK 1.1のセキュリティ・モデル

図1-6の説明が続きます
「図1-6 JDK 1.1のセキュリティ・モデル」の説明

sandboxモデルの改良

図1-7に示す新しいJava SEプラットフォームのセキュリティ・アーキテクチャは、主に次の目的で導入されています。

図1-7 Java SEのセキュリティ・アーキテクチャ

図1-7の説明が続きます。
「図1-7 Java SEのセキュリティ・アーキテクチャ」の説明
  • 詳細なアクセス制御。

    この機能は当初からJDKにありましたが、アプリケーションでこれを使用するには、負担の大きなプログラミングが必要でした(SecurityManagerクラスとClassLoaderクラスのサブクラス化およびそのカスタマイズなど)。HotJava Browser 1.0は、このようなアプリケーションのひとつで、ブラウザのユーザーには少数のセキュリティ・レベルからの選択だけが許されます。

    しかし、このようなプログラミングでは、セキュリティについて細かく考慮する必要があるため、高度な技能とコンピュータ・セキュリティに関する深い知識が要求されます。新しいアーキテクチャにより、この課題がより簡単で安全になります。

  • 簡単に構成できるセキュリティ・ポリシー

    この機能も以前からJDKにありましたが、使い方はやさしくありませんでした。また、セキュリティ・コードを記述するのも簡単な作業ではないので、アプリケーションの開発者とユーザーがプログラムを記述することなくセキュリティ・ポリシーを構成できることが望まれます。

  • 簡単に拡張できるアクセス制御構造

    JDK 1.1までは、新しいアクセス権を作成するにはSecurityManagerクラスに新しいcheckメソッドを追加する必要がありました。新しいアーキテクチャでは、型指定されたアクセス権(それぞれが1つのシステム・リソースへのアクセスを表す)が許され、正しい型の(現段階で未定義のアクセス権も含めて)すべてのアクセス権の自動処理が許されます。ほとんどの場合、SecurityManagerクラスに新しいメソッドを作成する必要はありません。事実、これまでのところ、新しいメソッドの作成は必要になっていません。

  • アプリケーションとアプレットを含む、すべてのJavaプログラムへのセキュリティ・チェックの拡張

    すべてのローカル・コードが信頼できるという組込みの概念は、なくなりました。ローカル・コード(システム・コード以外のコード、ローカル・ファイル・システムにインストールされたアプリケーション・パッケージなど)は、アプレットと同じセキュリティ制御の対象となりますが、必要に応じて、ローカル・コード(またはリモート・コード)のポリシーを最高の自由度として宣言できるため、事実上そのコードは完全に信頼できるものとして実行できます。同様の原則が署名付きアプレットおよびすべてのJavaアプリケーションに当てはまります。

    最後に、今後のプログラミングで隠れたセキュリティ・ホールを作り出す危険を減らすために、セキュリティ・クラス(SecurityManagerクラスとClassLoaderクラスを含む)の設計を内部的に調整するという暗黙の目標があります。

保護メカニズム - 基本概念の概要

ここからは、新しい保護アーキテクチャの概要を少し詳しく述べ、その機能について簡単に説明します。最初に、新しいアーキテクチャの背後にある基本概念の概要を説明します。次に、主要な新しいクラスを系統的に紹介します。アクセス権の指定、ポリシーと関連機能、アクセス制御とその使用法、セキュアなクラスのロードと解決の順に説明します。

システム・セキュリティの基本概念であり重要な構築ブロックであるのが、保護ドメイン[Saltzer and Schroeder 75]です。ドメインとは、あるプリンシパルが現在直接にアクセスできるオブジェクトのセットであると考えられます。ここでプリンシパルとは、アクセス権を与えられる(その結果としての責任がある)、コンピュータ・システム内のエンティティを意味します。JDK 1.0で使用されるサンドボックスは、固定の境界を持つ保護ドメインの一例です。

保護ドメインの概念は、保護の単位のグループ化と分離に便利なメカニズムとして働きます。たとえば、許可された相互作用が必ず、信頼できるシステム・コードを通じたものか、関係するドメインによって明示的に許可されたもののどちらかであるようにするために、保護ドメインを分離して相互作用ができないようにすることができます(ただしこの機能は、まだ組込み機能としては提供されていません)。オブジェクト・アクセスに関する既存の規則は、新しいセキュリティ・アーキテクチャの元でも有効なままであることに注意してください。

保護ドメインは、一般に2つの異なるカテゴリ、つまりシステム・ドメインとアプリケーション・ドメインに分類されます。重要な点は、「ファイル・システムやネットワーク設備、画面やキーボードなどの保護された外部リソースはすべて、システム・ドメインを通じてだけアクセスできる」ということです。図1-8は、Javaアプリケーション環境のドメイン構成を示しています。

図1-8 Javaアプリケーション環境のドメイン構成

図1-8の説明が続きます。
「図1-8 Javaアプリケーション環境のドメイン構成」の説明

ドメインには、概念上、各クラスのインスタンスに同一のアクセス権セットが与えられている1組のクラスが含まれます。保護ドメインは、現在有効なポリシーによって決定されます。Javaアプリケーション環境は、図1-9に示すように、コード(クラスとインスタンス)から保護ドメインへ、さらに保護ドメインのアクセス権へのマッピングを保持します。

図1-9 コードからドメインおよびアクセス権へのマッピング

図1-9の説明が続きます。
「図1-9 コードからドメインおよびアクセス権へのマッピング」の説明

実行のスレッド(これはたいてい1つのJavaスレッドに結びつくが、必ずしもその必要はなく、また背後のオペレーティング・システムのスレッドの概念に結びつく必要もない)は、単一の保護ドメインの範囲内で発生することもあり、またアプリケーション・ドメインとシステム・ドメインに関係することもあります。たとえば、出力ストリームへの唯一のアクセス・ポイントはシステム・ドメインなので、メッセージを出力するアプリケーションにはシステム・ドメインとの相互動作が必要です。この場合に重要なのは、アプリケーションがシステム・ドメインを呼び出すことによって、それ以外のアクセス権を獲得することが決してないことです。そうでない場合、セキュリティ上重要な問題が発生する可能性があります。

逆に、たとえばAWTシステム・ドメインがあるアプレットのペイント・メソッドを呼び出して、そのアプレットを表示する場合のように、システム・ドメインがアプリケーション・ドメインからメソッドを起動する場合は、有効なアクセス権が、そのアプリケーション・ドメインで現在有効になっているアクセス権と同一のものであることが重要です。

つまり、「弱い」ドメインは、強いドメインを呼び出した結果として、または強いドメインに呼び出された結果として追加のアクセス権を得ることはできません。

2つの保護ドメインに関係するスレッドについてのこの議論は、複数の保護ドメインにまたがるスレッドにも当然あてはめて考えることができます。アクセス権の計算には、次のような簡潔で賢明な経験則があります。

  • 実行スレッドのアクセス権のセットは、その実行スレッドが関係するすべての保護ドメインのアクセス権の共通部分であると考えられる。
  • コードの一部がdoPrivilegedメソッドを呼び出すと、実行スレッドのアクセス権セットにはそのアクセス権が含まれているとみなされます(そのアクセス権がそのコードの保護ドメインと、それ以降直接的または間接的に呼び出される(または入力される)すべての保護ドメインで許可されている場合)。

doPrivilegedメソッドは、ある信頼できるコードの一部が、その呼出し元のアプリケーションから直接利用できるリソースより多くのリソースに、一時的にアクセスできるようにします。これは、次のような場合に必要になります。たとえば、あるアプリケーションがフォントが入ったファイルへの直接のアクセスを許可されていないときに、ドキュメントを表示するシステム・ユーティリティがユーザーに代わってそのフォントを取得する必要がある場合などです。この状況に対処するために、システム・ドメインにdoPrivilegedメソッドが用意されました。実際には、このメソッドはすべてのドメインが利用できます。

実行中に重要なシステム・リソースへのアクセス(ファイル入出力やネットワーク入出力など)が要求されると、リソースを管理するコードは特別なAccessControllerクラス・メソッドを直接または間接的に呼び出し、その要求を評価して、要求を認めるかどうかを判断します。

この評価方法は、前述の経験則に従っており、それを一般化するものです。実際の評価の実施方法は、実装によって異なります。基本的には、呼出し履歴と、関係する保護ドメインに与えられているアクセス権を調べ、要求が許可される場合にはそのまま復帰し、要求が拒否される場合にはセキュリティ例外をスローします。

最後に、各ドメイン(システムまたはアプリケーション)は、その独自のドメイン境界内の内部リソースの保護も実装できます。たとえば、銀行取引のアプリケーションでは、当座預金、普通預金、預金引き出しなどの内部概念をサポートし、保護する必要がある場合があります。このような保護のセマンティックスは、JDKによる予測や強制はできない可能性が高いため、このレベルの保護システムは、システムやアプリケーションの開発者に任せることが最適です。それでも、私たちは、開発者のタスクを簡単にするために役立つ基本要素をできるかぎり提供していきます。このような基本要素の1つにSignedObjectクラスがあります。このクラスについては、後で詳しく説明します。

アクセス権とセキュリティ・ポリシー

Permissionクラス

Permissionクラスは、システム・リソースへのアクセス権を表します。java.security.Permissionクラスは抽象クラスで、このクラスから、特定のアクセス権を表すサブクラスが作成されます。

アクセス権の例としては、次のコードを使用すると/tmpディレクトリのabcというファイルの読取りアクセス権が得られます。

    perm = new java.io.FilePermission("/tmp/abc", "read");

新しいアクセス権は、Permissionクラス、またはそのサブクラスの1つ(java.security.BasicPermissionなど)からサブクラス化されます。サブクラス化された、BasicPermission以外のアクセス権は通常、それ自体のパッケージに属します。したがって、FilePermissionjava.ioパッケージにあります。

すべての新規Permissionクラスに実装する必要のある重要な抽象メソッドが、impliesメソッドです。基本的に、「a implies b」は、アクセス権「a」を与えられている者は、当然アクセス権「b」を与えられるということを意味します。この点は、アクセス制御の決定の際に重要です。

抽象クラスjava.security.Permissionには、java.security.PermissionCollectionという名前の抽象クラスとjava.security.Permissionsというfinalクラスが関連します。

java.security.PermissionCollectionクラスは、ファイルのアクセス権など1つのカテゴリのPermissionオブジェクトのコレクション(複製可能なセット)を表し、簡単にグループ化できるようになっています。ファイルのアクセス権などのように、アクセス権をPermissionCollectionオブジェクトに任意の順序で追加できる場合は、impliesメソッドが呼び出されたときに、PermissionCollectionオブジェクトが確実に正しいセマンティックスに従うことが重要です。

java.security.Permissionsクラスは、Permissionオブジェクトのコレクションを表します。つまり、異種のアクセス権のスーパー・コレクションです。

アプリケーションには、システムがサポートしているアクセス権の新規のカテゴリを自由に追加できます。アプリケーションに特定のアクセス権を追加する方法については、あとで説明します。

以降では、すべての組込みアクセス権の構文とセマンティックスについて説明します。

java.security.Permission

このabstractクラスは、すべてのアクセス権の上位クラスです。すべてのアクセス権に必要な必須機能を定義します。

アクセス権の各インスタンスは、通常、コンストラクタに1つ以上の文字列パラメータを渡すことによって生成されます。2つのパラメータを使う一般的なケースでは、通常、1つ目のパラメータは「ターゲットの名前」(アクセス権の目的のファイル名など)で、2つ目のパラメータは動作(ファイルに対する「読取り」動作など)です。通常、複数の動作のセットをカンマで区切った文字列の組み合わせで指定できます。

java.security.PermissionCollection

このクラスは、同一種のアクセス権のコレクションを持ちます。つまり、このクラスの各インスタンスは、同じタイプのアクセス権のみを持ちます。

java.security.Permissions

このクラスは、異種のアクセス権のコレクションを保持するように設計されています。基本的に、このクラスはjava.security.PermissionCollectionオブジェクトのコレクションです。

java.security.UnresolvedPermission

前述したように、セキュリティ・ポリシーの内部状態は通常、各コード・ソースに関連するアクセス権オブジェクトによって表されます。しかし、Javaテクノロジの動的な性質を考慮すると、ポリシーが初期化された時点で、特定のアクセス権クラスを実装した実際のコードがまだロードされておらず、Javaアプリケーション環境に定義されていない可能性があります。たとえば、参照されたアクセス権クラスは、あとにロードされるJARファイル内にある可能性があります。

UnresolvedPermissionクラスは、こうした「未解決」のアクセス権を保持するために使用されます。同様に、java.security.UnresolvedPermissionCollectionクラスにはUnresolvedPermissionアクセス権のコレクションが保存されます。

アクセス制御の際に、以前は未解決であったものの、現在そのクラスがロードされているアクセス権のタイプがチェックされ、未解決のアクセス権が「解決」されて、適切なアクセス制御が決定されます。つまり、可能であれば、UnresolvedPermission内の情報に基づいて適切なクラス・タイプの新規オブジェクトのインスタンスが生成されます。UnresolvedPermissionはこの新規オブジェクトに置き換えられ、削除されます。この時点でも解決されないアクセス権は、セキュリティ・ポリシーで付与されない無効なアクセス権とみなされます。

java.io.FilePermission

このクラスのターゲットは、次のように指定されます。ここで、ディレクトリとファイルの名前は文字列で、空白文字を含むことはできません。

file
directory (same as directory/)
directory/file
directory/* (all files in this directory)
* (all files in the current directory)
directory/- (all files in the file system under this directory)
- (all files in the file system under the current directory)
"<<ALL FILES>>" (all files in the file system)

<<ALL FILES>>は特別な文字列で、システム内のすべてのファイルを表します。LinuxまたはmacOSでは、rootディレクトリ下のすべてのファイルを含みます。Windowsでは、すべてのドライブのすべてのファイルを含みます。

動作は、readwritedelete、およびexecuteです。ファイルのアクセス権を作成する有効なコード例を次に示します。

import java.io.FilePermission;

FilePermission p = new FilePermission("myfile", "read,write");
FilePermission p = new FilePermission("/home/gong/", "read");
FilePermission p = new FilePermission("/tmp/mytmp", "read,delete");
FilePermission p = new FilePermission("/bin/*", "execute");
FilePermission p = new FilePermission("*", "read");
FilePermission p = new FilePermission("/-", "read,execute");
FilePermission p = new FilePermission("-", "read,execute");
FilePermission p = new FilePermission("<<ALL FILES>>", "read");

このクラスのimpliesメソッドは、ファイル・システムを正確に解釈します。たとえば、FilePermission("/-", "read,execute")は、FilePermission("/home/gong/public_html/index.html", "read")を表し、FilePermission("bin/*", "execute")FilePermission("bin/emacs19.31", "execute")を表します。

ノート:

多くの場合、これらの文字列の形式はプラットフォームにより異なります。たとえば、WindowsシステムのCドライブ上のtempディレクトリのfooというファイルへの読取りアクセスを表すには、次の表現を使用します

FilePermission p = new FilePermission("c:\\temp\\foo", "read");

文字列はトークナイザ(java.io.StreamTokenizer)によって処理されますが、そこでは\を使用したエスケープ文字列(改行を表す\nなど)が許されるので、単一のバックスラッシュを表すためには二重のバックスラッシュを使用する必要があります。トークナイザがFilePermissionのターゲット文字列の処理を終え、二重のバックスラッシュを単一のバックスラッシュに変換すると、結果は次の実際のパスになります:

"c:\temp\foo"

全プラットフォーム共通のファイル記述言語ができるまでは、文字列はプラットフォームごとに異なる形式で指定する必要があります。また、*-のようなメタ・シンボルを使用して特定のファイル名を表すことはできません。これは、さしあたり許容できる程度の小さな制限事項だと考えます。最後に、LinuxおよびmacOSでは-/<<ALL FILES>>は、どちらもファイル・システム全体を表す点で、同じターゲットです。(利用可能なファイル・システムであれば、複数のファイル・システムを指すこともできる。)別のオペレーティング・システム(WindowsやmacOSなど)ではこの2つは、別のターゲットを指す可能性があります。

次のように、ターゲットがディレクトリだけで動作が「read」の場合は、

FilePermission p = new FilePermission("/home/gong/", "read");

そのディレクトリ内のファイルを一覧表示するためだけのアクセス権が与えられ、ファイルの読取りは許可されません。ファイルの読取りを許可するには、次のように明示的なファイル名を指定するか、*または-を指定します

FilePermission p = new FilePermission("/home/gong/myfile", "read");
FilePermission p = new FilePermission("/home/gong/*", "read");
FilePermission p = new FilePermission("/home/gong/-", "read");

コードは必ず、そのコードと同じ(URLの)位置、およびそのサブディレクトリの位置からのファイルの読取りアクセス権を自動的に取得します。そのための明示的なアクセス権は必要ありません。

java.net.SocketPermission

このクラスは、ソケットを通じたネットワークへのアクセス権を表します。このクラスのターゲットはhostname:port_rangeの形式で指定します。hostnameは次の形式で指定します。

hostname (a single host)
IP address (a single host)
localhost (the local machine)
"" (equivalent to "localhost")
hostname.domain (a single host within the domain)
hostname.subdomain.domain
*.domain (all hosts in the domain)
*.subdomain.domain
* (all hosts)

つまり、ホストは、DNS名、数値のIPアドレス、"localhost" (ローカル・マシンの場合)、"" ("localhost"を指定するのと同じ)のどれかで表現されます。

DNS名によるホストの指定では、ワイルドカード*を1回のみ含めることができます。これを含める場合は、*.sun.comのように左端に置く必要があります。

port_rangeは次の形式で指定します。

N (a single port)
N- (all ports numbered N and above)
-N (all ports numbered N and below)
N1-N2 (all ports between N1 and N2, inclusive)

上の形式で、NN1およびN2は、0から65535 (216-1)の範囲内の負でない整数です。

ソケットでの動作は、acceptconnectlistenおよびresolve(これは基本的にDNSルックアップ)です。アクション「resolve」は、「accept」、「connect」および「listen」によって暗黙的に定義されます。つまり、ホストからの入力接続を待機または受け付けるか、ホストへの出力接続を開始できるユーザーは、リモート・ホストの名前をルックアップできるはずです。

次に、ソケット・アクセス権の例を示します。

import java.net.SocketPermission;

SocketPermission p = new SocketPermission("java.example.com","accept");
p = new SocketPermission("192.0.2.99","accept");
p = new SocketPermission("*.com","connect");
p = new SocketPermission("*.example.com:80","accept");
p = new SocketPermission("*.example.com:-1023","accept");
p = new SocketPermission("*.example.com:1024-","connect");
p = new SocketPermission("java.example.com:8000-9000",
         "connect,accept");
p = new SocketPermission("localhost:1024-",
          "accept,connect,listen");

ノート:

SocketPermission("java.example.com:80,8080","accept")SocketPermission("java.example.com,javasun.example.com","accept")は、有効なソケット・アクセス権ではありません。

listen動作はローカル・ホストのポートだけに適用され、accept動作はローカル・ホストとリモート・ホストの両方のポートに適用されます。両方の動作が必要です。

java.security.BasicPermission

BasicPermissionクラスはPermissionクラスを継承します。BasicPermissionと同じ命名規則に従うアクセス権の基底クラスとして使用できます。

BasicPermissionの名前は、「exitVM」、「setFactory」、「queuePrintJob」など、指定したアクセス権の名前です。命名規約は、階層的なプロパティ命名規約に従います。名前の末尾には「*」や「.*」を付けて、ワイルドカードを指定できます。たとえば、「java.*」や「*」は有効ですが、「*java」や「a*b」は無効です。

Permissionから継承したアクション文字列は使用しません。したがって、通常、BasicPermissionは名前付きアクセス権の基底クラスとして使用します(名前付きアクセス権は名前を持ちますが、アクション・リストは持ちません。ユーザーは名前付きアクセス権を持つ場合と、持たない場合があります。)サブクラスは、必要に応じてBasicPermissionの上位クラスでアクションを実装できます。

BasicPermissionサブクラスには、java.lang.RuntimePermissionjava.security.SecurityPermissionjava.util.PropertyPermissionjava.net.NetPermissionなどがあります。

java.util.PropertyPermission

このクラスのターゲットは、基本的には、さまざまなプロパティ・ファイルのセットとしてのJavaプロパティの名前です。たとえば、java.homeos.nameプロパティがあります。ターゲットは、「*」(任意のプロパティ)、「a.*」(「a.」接頭辞を持つ名前の任意のプロパティ)、「a.b.*」などの形式で指定できます。ワイルドカードは、右端に1回だけ使用できます。

このクラスは、BasicPermissionに基づいてアクションを実装するBasicPermissionサブクラスの1つです。アクションはreadとwriteです。それぞれの意味は、次のように定義されます。「read」アクセス権は、java.lang.System内のgetPropertyメソッドを呼び出してプロパティ値を取得することを許可します。「write」アクセス権は、setPropertyメソッドを呼び出してプロパティ値を設定することを許可します。

java.lang.RuntimePermission

RuntimePermissionのターゲットは任意の文字列で表現でき、このターゲットに関連する動作はありません。たとえば、RuntimePermission("exitVM")はJava仮想マシンを終了するアクセス権を表します。

次のターゲット名を使用できます。

createClassLoader
getClassLoader
setContextClassLoader
setSecurityManager
createSecurityManager
exitVM
setFactory
setIO
modifyThread
modifyThreadGroup
getProtectionDomain
readFileDescriptor
writeFileDescriptor
loadLibrary.{library name}
accessClassInPackage.{package name}
defineClassInPackage.{package name}
accessDeclaredMembers.{class name}
queuePrintJob
java.awt.AWTPermission

このクラスは、RuntimePermissionと同じ考え方に基づいており、アクセス権には動作がありません。このクラスのターゲットには、次のようなものがあります。

accessClipboard
accessEventQueue
listenToAllAWTEvents
showWindowWithoutWarningBanner
java.net.NetPermission

このクラスには次のターゲットがあり、動作はありません。

requestPasswordAuthentication
setDefaultAuthenticator
specifyStreamHandler
java.lang.reflect.ReflectPermission

リフレクション操作のためのアクセス権クラスです。ReflectPermissionは、名前付きのアクセス権(RuntimePermissionと同様)で、動作はありません。現在定義されている唯一の名前はsuppressAccessChecksです。これは、リフレクトされたオブジェクトが使用される位置で実行される、Javaプログラミング言語の標準のアクセス・チェック(public、default (package)アクセス、protected、privateメンバーに対するチェック)を無効にします。

java.io.SerializablePermission

このクラスには次のターゲットがあり、動作はありません。

enableSubclassImplementation
enableSubstitution
java.security.SecurityPermission

SecurityPermissionsは、セキュリティ関連のオブジェクト(SecurityPolicyProviderSignerIdentityオブジェクトなど)へのアクセスを管理します。このクラスには次のターゲットがあり、動作はありません。

getPolicy
setPolicy
getProperty.{key}
setProperty.{key}
insertProvider.{provider name}
removeProvider.{provider name}
setSystemScope
setIdentityPublicKey
setIdentityInfo
printIdentity
addIdentityCertificate
removeIdentityCertificate
clearProviderProperties.{provider name}
putProviderProperty.{provider name}
removeProviderProperty.{provider name}
getSignerPrivateKey
setSignerKeyPair
java.security.AllPermission

このアクセス権では、すべてのアクセスが許可されます。すべての(つまり多数の)アクセスの許可が必要なタスクを行うシステム管理者の仕事を簡単にするために用意されています。セキュリティ・ポリシーにすべてのアクセス権を繰返し定義するのは効率的ではありません。AllPermissionは、将来定義される新しいアクセス権も許可します。

このアクセス権を与える場合は、十分な注意が必要です。

javax.security.auth.AuthPermission

AuthPermissionでは、認証アクセス権と認証に関連するオブジェクト(SubjectSubjectDomainCombinerLoginContextおよびConfiguration)を扱います。このクラスには次のターゲットがあり、動作はありません。

doAs
doAsPrivileged
getSubject
getSubjectFromDomainCombiner
setReadOnly
modifyPrincipals
modifyPublicCredentials
modifyPrivateCredentials
refreshCredential
destroyCredential
createLoginContext.{name}
getLoginConfiguration
setLoginConfiguration
refreshLoginConfiguration
アクセス権の意味

前述したように、アクセス権は互いに比較されることがあります。このような比較を簡単に行えるように、各アクセス権クラスでimpliesメソッドを定義して、特定のアクセス権クラスと他のアクセス権クラスとの関係を表す必要があります。たとえば、java.io.FilePermission("/tmp/*", "read")java.io.FilePermission("/tmp/a.txt", "read")を暗示しますが、すべてのjava.net.NetPermissionを暗示しません。

一見しただけではわからない、暗黙の包含関係もあります。たとえば、あるアプレットにファイル・システム全体への書込みアクセス権を与えるとします。これにより、このアプレットには、JVM実行時環境を含むシステムのバイナリ・ファイルの書換えが許可されると考えられます。これは結果的に、そのアプレットにすべてのアクセス権を与えることを意味します。

別の例として、あるアプレットにクラス・ローダーを生成する実行時アクセス権を与える場合、クラス・ローダーはクリティカルな操作を実行できるので、結果的に、このアプレットにさらに多くのアクセス権を与えることになります。

「危険」を伴うアクセス権にはこれ以外に、システム・プロパティの設定を許可するもの、パッケージの定義やネイティブ・コード・ライブラリのローディングのための実行時アクセス権(Javaのセキュリティ構造はネイティブ・コード・レベルでの悪意のある動作を防ぐように設計されていないため)、およびAllPermissionがあります。

特定のアクセス権の割当てのリスクを列挙した表およびアクセス権を必要とするすべてのJDKの組込みメソッドの表など、アクセス権の詳細は、JDKでのアクセス権を参照してください。

新しいタイプのアクセス権を作成する方法

新機能の追加またはjava.lang.RuntimePermissionなどのクラスへの追加のターゲット・キーワードの導入により、JDKに組み込まれているアクセス権を拡張することは、Oracleだけに許されています。これは一貫性を維持するために必要なことです。

新しくアクセス権を作成するには、次の例のような方法をお薦めします。ABC社のアプリケーション開発者が「TVを見る」ための新しいカスタムのアクセス権を作成するとします。

最初に、抽象クラスjava.security.Permission (またはそのサブクラスの1つ)を継承する新しいクラスcom.abc.Permission、およびcom.abc.Permissionを継承する新しいクラスcom.abc.TVPermissionを作成します。ほかのクラスのimpliesメソッドが正しく実装されるように注意してください。ただし、com.abc.TVPermissionは中間のcom.abc.Permissionがなくても、直接java.security.Permissionを継承することはできます。

public class com.abc.Permission extends java.security.Permission

public class com.abc.TVPermission extends com.abc.Permission

図1-10に、サブクラスの関係を示します。

図1-10 com.abc.TV.Permissionのサブクラスの関係

図1-10の説明が続きます
「図1-10 com.abc.TV.Permissionのサブクラスの関係」の説明

次に、アプリケーション・パッケージにそれらの新規クラスをインクルードします。

特定のコードにこの新しいタイプのアクセス権を許可する場合、各ユーザーはポリシー・ファイルにエントリを追加します。(ポリシー・ファイルの構文の詳細についてはあとのセクションで説明。)次に、5チャネルを見る(watch)アクセス権をhttp://example.com/のコードに付与するポリシー・ファイル・エントリの例を示します。

grant codeBase  "http://example.com/" {
    permission com.abc.TVPermission "channel-5", "watch";
}

アプリケーションのリソース管理コードで、アクセス権を与えるかどうかのチェックを行うときは、com.abc.TVPermissionオブジェクトをパラメータとしてAccessControllercheckPermissionメソッドを呼び出します。

   com.abc.TVPermission tvperm = new
        com.abc.TVPermission("channel-5", "watch");
   AccessController.checkPermission(tvperm);

新しいアクセス権を追加するときは、新しい(アクセス権)クラスを作成し、セキュリティ・マネージャに新しいメソッドを追加しないでください。(以前は、新しいタイプのアクセスのチェックを有効にするためにはSecurityManagerクラスに新しいメソッドを追加する必要がありました。)

「channel-1:13」や「channel-*」など、より難解なTVPermissionsを許可するには、これらの擬似名のセマンティックスの扱いを知っているTVPermissionCollectionオブジェクトを実装する必要がある場合があります。

新しいコードがアクセス制御の組込みアルゴリズムを実行するためには、AccessControllerクラスのcheckPermissionメソッドを呼び出してアクセス権チェックを起動します。ClassLoaderSecurityManagerがあるかどうかについては、必ずしも調べる必要はありません。ただし、インストールされているセキュリティ・マネージャ・クラスにそのアルゴリズムを任せる場合は、SecurityManager.checkPermissionメソッドを呼び出します。

java.security.CodeSource

このクラスは、HTMLのコード・ベースの概念を拡張して、コードの位置(URL)だけでなくその位置からの署名付きコードの確認に使用する公開キーを持つ証明書が入ります。ただし、これはHTMLファイルのCodeBaseタグと同じではないので注意してください。各証明書はjava.security.cert.Certificateとして表され、各URLはjava.net.URLとして表されます。

java.security.Policy

どのソースからのコードにどのアクセス権が使用できるかを指定するJavaアプリケーション環境のシステム・セキュリティ・ポリシーは、Policyオブジェクトで表されます。正確には、Policyクラスのabstractメソッドを実装したPolicyサブクラスにより表現されます。

ファイルの読み書きなど、アプレット(またはSecurityManagerの下で実行中のアプリケーション)がセキュリティの対象であるアクションを実行するためには、そのアプレット(またはアプリケーション)にはその特定のアクションのためのアクセス権を与えられていることが必要です。唯一の例外として、コードは、常に、同じCodeSource、およびそのCodeSourceのサブディレクトリからのファイルの読取りアクセス権を自動的に持ちます。この場合は、明示的なアクセス権は必要ありません。

Policyオブジェクトのインスタンスは、複数存在できますが、「有効な」インスタンスは常に1つだけです。現在インストールされているPolicyオブジェクトはgetPolicyメソッドの呼出しにより取得でき、(Policyのリセット権を持つコードによる) setPolicyメソッドの呼出しによって変更できます。

Policyオブジェクトが使用するポリシー情報がどこに置かれるかは、Policyの実装によります。たとえば、ポリシーの構成は、単純なASCIIファイル、Policyクラスの直列化バイナリ・ファイル、データベースのどれかの形で保存できます。Policyのリファレンス実装の1つでは、情報を静的なポリシー構成ファイルから取得します。

Policyのファイル形式

Policyのリファレンス実装では、1つまたは複数のポリシー構成ファイルからポリシーを指定できます。構成ファイルは、特定のコード・ソースからのコードに許可されるアクセス権を示します。各構成ファイルは、UTF-8方式でエンコードする必要があります。

ポリシー構成ファイルは、エントリのリストで構成されます。ここには、1つのkeystoreエントリと、0個以上のgrantエントリを含めることができます。

キーストアは、秘密キーと、対応する公開キーを認証するX.509証明書チェーンなどのデジタル証明書が格納されたデータベースです。キーストアの作成と管理には、keytoolユーティリティを使用します。ポリシー構成ファイルで指定されているキーストアは、そのファイルのgrantエントリで指定されている署名者の公開キーを照合するために使用されます。署名者の別名を指定しているgrantエントリがある場合、またはプリンシパルの別名を指定しているgrantエントリがある場合は、ポリシー構成ファイルにkeystoreエントリを記述する必要があります。

現在、ポリシー・ファイルで指定できるkeystoreエントリは1つだけで、2つ目以降のkeystoreエントリは無視されるため、ファイルのgrantエントリの外であれば、どこに指定してもかまいません。構文は次のとおりです。

keystore "some_keystore_url", "keystore_type";

ここで、some_keystore_urlにはキーストアのURL位置を指定し、keystore_typeにはキーストアのタイプを指定します。この指定は任意で行います。指定しない場合、キーストアのタイプは、セキュリティ・プロパティ・ファイルのkeystore.typeプロパティで指定されたものと見なされます。

URLは、ポリシー・ファイルがある場所からの相対位置を表します。たとえば、セキュリティ・プロパティ・ファイルの中でポリシー・ファイルが次のように指定されているとします。

policy.url.1=http://foo.bar.example.com/blah/some.policy

また、このポリシー・ファイルには、次のエントリがあるとします。

keystore ".keystore";

この場合、キーストアは次の場所からロードされます。

http://foo.bar.example.com/blah/.keystore

URLに絶対位置を指定することもできます。

キーストアの型は、キーストア情報のストレージ形式とデータ形式を定義するとともに、キーストア内の秘密キーとキーストア自体の整合性を保護するために使われるアルゴリズムを定義します。Oracle JDKのデフォルトのキーストア・タイプはPKCS12です。

ポリシー・ファイルの各grantエントリは、本質的には1つのCodeSourceとそのアクセス権で構成されます。実際には、CodeSourceは1つのURLと証明書のセットで構成されますが、ポリシー・ファイルのエントリの内容はURLと署名者名のリストです。システムは、指定された署名者の証明書を決定するためにキーストアを調べた後で、対応するCodeSourceを生成します。

ポリシー・ファイルの各grantエントリは、次の形式のどれかです。ここで先頭のgrantは予約語で、新しいエントリの開始を示し、カッコの中にはオプション項目が示されます。各エントリ内の先頭のpermissionも予約語で、そのエントリ内で新しいアクセス権の記述が開始されることを示します。各grantエントリは、指定したコード・ソースとプリンシパルに一連のアクセス権を与えます。

grant [SignedBy "signer_names"] [, CodeBase "URL"]
      [, Principal [principal_class_name] "principal_name"]
      [, Principal [principal_class_name] "principal_name"] ... {
    permission permission_class_name [ "target_name" ] 
               [, "action"] [, SignedBy "signer_names"];
    permission ...
};

カンマの直前または直後には、空白文字を入れてもかまいません。アクセス権クラスの名前は、Javaの完全修飾形のクラス名(java.io.FilePermissionなど)でなければならず、省略(FilePermissionなど)はできません。

動作のフィールドは、省略可能なオプション・フィールドで、アクセス権クラスが動作を必要としない場合には省略できます。このフィールドを指定する場合は、必ずターゲット・フィールドの直後に置きます。

CodeBase URLの正確な意味は、最後の文字に依存します。末尾が「/」のCodeBaseは、指定されたディレクトリ内のすべてのクラス・ファイル(JARファイルを除く)を示します。末尾が「/*」のCodeBaseは、そのディレクトリ内にあるすべてのファイル(クラス・ファイルとJARファイルの両方)を示します。末尾が「/-」のCodeBaseは、指定されたディレクトリとその下の全サブディレクトリ内のすべてのファイル(クラス・ファイルとJARファイルの両方)を示します。

CodeBaseフィールド(URL)は、省略可能なオプション・フィールドで、省略した場合は「任意のコード・ベース」が指定されます。

最初の署名者名フィールドは、署名者に関連付けられている1組の公開キー(キーストアの証明書内にある)に、別のメカニズムを通じてマッピングされた文字列の別名です。これらのキーは、ある署名付きのクラスが本当にこれらの署名者によって署名されたかどうかを確認するために使用されます。

この署名者フィールドは、複数の署名者の名前をカンマ区切りの文字列で指定できます。たとえば、Adam,Eve,Charlesは、AdamとEveとCharlesによって署名されることを意味します(各者の関係はORではなくAND)。

このフィールドは省略可能なオプション・フィールドで、省略した場合は「任意の署名者」、つまり「コードが署名付きかどうかは不問」の指定になります。

permissionエントリ内にある2つ目の署名者フィールドは、keystoreエントリの別名を表します。このkeystoreエントリには、そのアクセス権クラスを実装しているバイト・コードへの署名に使用される秘密キーに対応する公開キーが含まれます。このアクセス権エントリが有効なのは(つまりこのエントリに基づいてアクセス制御権が与えられるのは)、バイト・コードの実装が別名によって正しく署名されていることが確認された場合のみです。

プリンシパルの値はclass_nameとprincipal_nameのペアを指定します。このペアは、実行中のスレッドのプリンシパル・セット内にある必要があります。プリンシパル・セットは、Subjectによって実行するコードに関連付けられます。プリンシパル・フィールドは省略可能です。省略した場合は、「任意のプリンシパル」という意味になります。

ノート:

キーストア別名の置換に関して: プリンシパルclass_name/principal_nameのペアが単一引用符で囲まれた文字列として指定される場合は、キーストアの別名として扱われます。キーストアは別名を経由してX509証明書を調査し、問い合わせます。キーストアがある場合は、principal_classは自動的にjavax.security.auth.x500.X500Principalとして扱われ、principal_nameは証明書で名前を識別されたサブジェクトとして自動的に扱われます。X509証明書のマッピングが見つからない場合は、grantエントリはすべて無視されます。

CodeBaseフィールド、SignedByフィールド、およびPrincipalフィールドの順序は任意です。

次に示すのはポリシー・ファイル形式の非公式のBNF構文で、ここで小文字で始まる語はターミナルです:

PolicyFile -> PolicyEntry | PolicyEntry; PolicyFile
PolicyEntry -> grant {PermissionEntry}; |
           grant SignerEntry {PermissionEntry} |
           grant CodebaseEntry {PermissionEntry} |
           grant PrincipalEntry {PermissionEntry} |
           grant SignerEntry, CodebaseEntry {PermissionEntry} |
           grant CodebaseEntry, SignerEntry {PermissionEntry} |
           grant SignerEntry, PrincipalEntry {PermissionEntry} |
           grant PrincipalEntry, SignerEntry {PermissionEntry} |
           grant CodebaseEntry, PrincipalEntry {PermissionEntry} |
           grant PrincipalEntry, CodebaseEntry {PermissionEntry} |
           grant SignerEntry, CodebaseEntry, PrincipalEntry {PermissionEntry} |
           grant CodebaseEntry, SignerEntry, PrincipalEntry {PermissionEntry} |
           grant SignerEntry, PrincipalEntry, CodebaseEntry {PermissionEntry} |
           grant CodebaseEntry, PrincipalEntry, SignerEntry {PermissionEntry} |
           grant PrincipalEntry, CodebaseEntry, SignerEntry {PermissionEntry} |
           grant PrincipalEntry, SignerEntry, CodebaseEntry {PermissionEntry} |
           keystore "url"
SignerEntry -> signedby (a comma-separated list of strings)
CodebaseEntry -> codebase (a string representation of a URL)
PrincipalEntry -> OnePrincipal | OnePrincipal, PrincipalEntry
OnePrincipal -> principal [ principal_class_name ] "principal_name" (a principal)
PermissionEntry -> OnePermission | OnePermission PermissionEntry
OnePermission -> permission permission_class_name
                 [ "target_name" ] [, "action_list"]
                 [, SignerEntry];

ここで、いくつか例を示します。次のポリシーでは、Rolandによって署名されたコードにアクセス権a.b.Fooを与えます。

grant signedBy "Roland" {
    permission a.b.Foo;
};

次の例では、すべてのコード(署名者やCodeBaseにかかわらず)にFilePermissionを与えます。

grant {
    permission java.io.FilePermission ".tmp", "read";
};

次の例では、LiRolandの両者によって署名されたコードに、2つのアクセス権を与えます。

grant signedBy "Roland,Li" {
    permission java.io.FilePermission "/tmp/*", "read";
    permission java.util.PropertyPermission "user.*";
};

次の例では、Liによって署名されhttp://example.comからロードされたコードに、2つのアクセス権を与えます。

grant codeBase "http://example.com/*", signedBy "Li" {
    permission java.io.FilePermission "/tmp/*", "read";
    permission java.io.SocketPermission "*", "connect";
};

次の例では、com.abc.TVPermissionを実装したバイト・コードが確実にLiによって署名されている場合にだけ、LiRolandの両者によって署名されたコードに2つのアクセス権を与えます。

grant signedBy "Roland,Li" {
    permission java.io.FilePermission "/tmp/*", "read";
    permission com.abc.TVPermission "channel-5", "watch", 
        signedBy "Li";
};

2つ目の署名者フィールドを使用する目的は、インストールされたJava Runtimeにそのアクセス権クラスが存在しない場合に、不正行為を防ぐことです。たとえば、com.abc.TVPermissionクラスのコピーは、リモートのJARアーカイブの一部としてダウンロードできますが、ユーザー・ポリシーにそれを参照するエントリが含まれることがあります。アーカイブは短命なので、com.abc.TVPermissionクラスが別のWebサイトから再びダウンロードされる可能性があります。このときのユーザー・ポリシー内のアクセス権エントリの存在は、クラスのバイト・コードの最初のコピーに対するユーザーの信頼を反映するものであるため、2つ目のコピーが認証済であることは重要です。

認証を確実なものにするために、バイト・コードの最初のコピー(のハッシュ値)を保存して2つ目のコピーと比べる方法ではなく、デジタル署名を使用する理由は、アクセス権クラスの作者が合法的にクラス・ファイルを更新して新しい設計や実装を反映することがあるからです。

ノート:

ファイル・パスの文字列は、プラットフォームごとに異なる形式で指定する必要があります。これは、汎用的なファイル記述言語ができるまでは必要です。前述の各例は、LinuxまたはmacOSで適切な文字列を示しています。Windowsでは、文字列中で直接ファイル・パスを指定する場合は、パス内の実際の単一のバックスラッシュごとに、次のように2つのバックスラッシュを含める必要があります

grant signedBy "Roland" {
    permission java.io.FilePermission "C:\\users\\Cathy\\*", "read";
};

これは、文字列はトークナイザ(java.io.StreamTokenizer)によって処理され、トークナイザは「\」をエスケープ文字列と解釈するため(たとえば、「\n」は改行を表す)、バックスラッシュそのものを表すには、バックスラッシュを2つ重ねる必要があるからです。トークナイザが前述のFilePermissionのターゲット文字列の処理を終え、二重のバックスラッシュを単一のバックスラッシュに変換すると、結果は次の実際のパスになります:

"C:\users\Cathy\*"

最後に、プリンシパルベースのgrantエントリの例を示します。

grant principal javax.security.auth.x500.X500Principal "cn=Alice" {
    permission java.io.FilePermission "/home/Alice", "read, write";
};

これにより、X500Principalcn=Aliceとして実行するコードには/home/Aliceの読取りと書込みのアクセス権が与えられます。

次の例は、codesource情報とprincipal情報を持つgrant文を表しています。

grant codebase "http://www.games.example.com",
      signedBy "Duke",
      principal javax.security.auth.x500.X500Principal "cn=Alice" {
    permission java.io.FilePermission "/tmp/games", "read, write";
};

これにより、Dukeによって署名され、www.games.example.comからダウンロードされたコードがcn=Aliceによって実行されると、/tmp/gamesディレクトリの読取りと書込みのアクセス権が与えられます。

次の例は、キーストアの別名を置き換えるgrant文を示しています。

keystore "http://foo.bar.example.com/blah/.keystore";

grant principal "alice" {
    permission java.io.FilePermission "/tmp/games", "read, write";
};

aliceは、javax.security.auth.x500.X500Principal cn=Aliceに置き換えられます。ただし、キーストアの別名aliceに関連付けられたX.509証明書がサブジェクトの識別名cn=Aliceを持っていることが前提となります。これにより、X500Principalcn=Aliceによって実行されるコードには、/tmp/gamesディレクトリへの読取りと書込みのアクセス権が与えられます。

ポリシー・ファイル内のプロパティの展開

ポリシー・ファイルとセキュリティ・プロパティ・ファイルでは、プロパティの展開が可能です。プロパティの展開は、シェルでの変数の展開に似ています。つまり、ポリシー・ファイルまたはセキュリティ・プロパティ・ファイルに${some.property}などの文字列が出現すると、それは指定されたシステム・プロパティの値まで展開されます。たとえば、

permission java.io.FilePermission "${user.home}", "read";

${user.home}は、システム・プロパティuser.homeの値に展開されます。そのプロパティの値が/home/cathyの場合、上の例は次のようになります

permission java.io.FilePermission "/home/cathy", "read";

プラットフォームにより異なるポリシー・ファイルを使いやすくするために、${/}という特殊な表記(${file.separator}の簡略形)も使用できます。これは「${file.separator}」へのショートカットで、次のようにしてアクセス権を定義できます。

permission java.io.FilePermission "${user.home}${/}*", "read";

user.home/home/cathyで、使用しているのがLinuxであれば、上の例は次のように変換されます:

permission java.io.FilePermission "/home/cathy/*", "read";

また、user.homeC:\users\cathyで、使用しているのがWindowsシステムであれば、上の例は次のように変換されます:

permission java.io.FilePermission "C:\users\cathy\*", "read";

特殊なケースとして、コード・ベースのプロパティを次のように記述すると、

grant codeBase "file:/${java.home}/lib/ext/"

file.separator文字は自動的にすべてスラッシュ(/)に置き換えられ、このことは、コード・ベースがURLであるため適切です。そのため、Windowsシステムでは、java.homeC:\j2sdk1.2に設定されていても、上の例は次のように変換されます

grant codeBase "file:/C:/j2sdk1.2/lib/ext/"

したがって、コード・ベース文字列では${/}を使用する必要はありません(使用しないでください)。

プロパティの展開は、ポリシー・ファイル内で、二重引用符で囲まれた文字列が使用できる場所であればどこでも行われます。これには、signedbycodebase、ターゲット名、動作の各フィールドがあります。

プロパティの展開が許可されるかどうかは、セキュリティ・プロパティ・ファイルの"policy.expandProperties"プロパティの値によって決まります。このセキュリティ・プロパティの値がtrue (デフォルト)の場合は、展開が許可されます。

ノート: 入れ子のプロパティは使用できません。たとえば、

"${user.${foo}}"

この例では、fooプロパティがhomeに設定されている場合であっても、エラーになります。これは、プロパティ構文解析プログラムは入れ子になったプロパティを認識しないためです。プロパティ構文解析プログラムは、最初の${を見つけたら、次に最初の}を探し、結果の${user.$foo}をプロパティと解釈しようと試みます。しかし、そのようなプロパティがない場合はエラーになります。

ノート: grantエントリ、アクセス権エントリ、またはkeystoreエントリで展開できないプロパティがある場合、そのエントリは無視されます。たとえば、次のようにシステム・プロパティfooが定義されていない場合、

grant codeBase "${foo}" {
    permission ...;
    permission ...;
};

このgrantエントリ内のpermissionはすべて無視されます。また、次のような場合、

grant {
    permission Foo "${foo}";
    permission Bar;
};

permission Foo "${foo}";エントリだけが無視されます。また、次のように指定されている場合、

keystore "${foo}";

この場合は、keystoreエントリが無視されます。

ノート: Windowsシステムでは、文字列中で直接ファイル・パスを指定する場合は、単一のバックスラッシュは次のように二重のバックスラッシュで表します。

"C:\\users\\cathy\\foo.bat"

これは、文字列はトークナイザ(java.io.StreamTokenizer)によって処理され、トークナイザはバックスラッシュ(\)をエスケープ文字列と解釈するため(たとえば、\nは改行を表す)、バックスラッシュそのものを表すには、バックスラッシュを2つ重ねる必要があるからです。トークナイザが上の文字列の処理を終え、二重のバックスラッシュが単一のバックスラッシュに変換されると、結果は次のようになります

"C:\users\cathy\foo.bat"

文字列中のプロパティの展開は、トークナイザがその文字列の処理を完了したあとに行われます。たとえば、次のような文字列があるとします。

"${user.home}\\foo.bat"

トークナイザは、この文字列中の二重のバックスラッシュを単一のバックスラッシュに変換し、結果は次のようになります。

"${user.home}\foo.bat"

次に、${user.home}プロパティが展開されて次のようになります。

"C:\users\cathy\foo.bat"

ここでは、user.homeの値をC:\users\cathyとしています。もちろん、プラットフォームに依存しないために、明示的にスラッシュを使うのではなく、${/}プロパティを使って次のように指定する方が望ましいと言えます。

"${user.home}${/}foo.bat"
ポリシー・ファイルにおける一般的な展開

ポリシー・ファイルでは一般化された形式の展開もサポートされています。たとえば、アクセス権名に${{protocol:protocol_data}}という形式の文字列が含まれているとします。このような文字列がアクセス権名に含まれている場合、protocolの値で実行される展開のタイプが決まり、protocol_dataを使用して展開が実行されます。protocol_dataは空にすることも可能で、その場合、上の文字列は次のような形式になります:

${{protocol}}

デフォルトのポリシー・ファイルの実装では、次の2つのプロトコルがサポートされます。

  1. ${{self}}

    プロトコルselfは、${{self}}文字列全体を1つ以上のプリンシパル・クラスとプリンシパル名のペアに置き換えることを示します。実際に実行される置換えは、permissionを含むgrant節の内容によって決まります。

    grant節にプリンシパルの情報が含まれていない場合は、permissionは無視されます。プリンシパルベースのgrant節のコンテキストでは、ターゲット名に${{self}}を含むpermissionのみが有効です。たとえば、次のgrant節内のBarPermissionは常に無視されます。

    grant codebase "www.foo.example.com", signedby "duke" {
        permission BarPermission "... ${{self}} ...";
    };
    grant句にプリンシパル情報が含まれている場合は、${{self}}がそのプリンシパル情報に置き換えられます。たとえば、次のgrant節のBarPermission内の${{self}}は、javax.security.auth.x500.X500Principal "cn=Duke"に置き換えられます。
    grant principal javax.security.auth.x500.X500Principal "cn=Duke" {
        permission BarPermission "... ${{self}} ...";
    };
    grant句内にカンマで区切られたプリンシパルのリストがある場合、${{self}}は、そのカンマで区切られたプリンシパルのリストに置き換えられます。grant句内のプリンシパル・クラスとプリンシパル名がどちらもワイルドカードになっている場合、${{self}}は、現在のAccessControlContext内のSubjectに関連付けられたすべてのプリンシパルに置き換えられます。

    次の例は、selfキーストアの別名の置き換えの両方を含む例を示しています。

        keystore "http://foo.bar.example.com/blah/.keystore";
    
        grant principal "duke" {
            permission BarPermission "... ${{self}} ...";
        };
    上の例では、初めにdukeが、javax.security.auth.x500.X500Principal "cn=Duke"に展開されますが、KeyStoreの別名"duke"に関連付けられたX.509証明書に、サブジェクトの識別名"cn=Duke"があることが前提となります。次に、${{self}}が、grant節内で展開されたその同じプリンシパル情報に置き換えられます。javax.security.auth.x500.X500Principal "cn=Duke"
  2. ${{alias:alias_name}}

    プロトコルaliasは、java.security.KeyStore別名の置換を示します。使用されるKeyStoreは、KeyStoreエントリに指定されたものです。Policyのファイル形式を参照してください。alias_nameは、KeyStoreの別名を表します。${{alias:alias_name}}は、javax.security.auth.x500.X500Principal "DN"に置き換えられます。このDNは、alias_nameに属する証明書のサブジェクト識別名を表します。たとえば:

                keystore "http://foo.bar.example.com/blah/.keystore";
    
                grant codebase "www.foo.example.com" {
                    permission BarPermission "... ${{alias:duke}} ...";
                };
    上の例の別名dukeに関連付けられているX.509証明書は、KeyStore (foo.bar.example.com/blah/.keystore)から取得されます。dukeの証明書がサブジェクト識別名として"o=dukeOrg, cn=duke"を指定し、${{alias:duke}}javax.security.auth.x500.X500Principal "o=dukeOrg, cn=duke"で置き換えられるものとします。

    次のエラー条件に該当する場合、アクセス権エントリは無視されます。

    • keystoreエントリが指定されていない
    • alias_nameが指定されていない
    • alias_nameの証明書を取得できない
    • 取得される証明書がX.509証明書ではない
アクセス権の割当て

プリンシパルが特定のCodeSourceからクラスを実行するとき、セキュリティ・メカニズムはポリシー・オブジェクトを調べてどのアクセス権を与えるかを決定します。この決定は、VMにインストールされたPolicyオブジェクトのgetPermissionsメソッドまたはimpliesメソッドを呼び出すことにより行われます。

ワイルドカード(*)の使用が認められているなどの理由により、ProtectionDomain内のコード・ソースがポリシー内の複数のエントリのコード・ソースに一致することがあります。

ポリシー内の適切なアクセス権セットを探すために、次のアルゴリズムが使用されます。

  1. コードが署名されている場合は、公開キーと照合します。

  2. ポリシーによって認識されないキーは無視します。

    すべてのキーが無視された場合は、コードを未署名として処理します。

  3. キーが一致している、または署名者が指定されていない場合は、キーのポリシー内のすべてのURLとの照合を試行します。

  4. キーが一致した(または署名者が指定されていない)場合、およびURLが一致した(またはコード・ベースが指定されていない)場合は、ポリシー内のすべてのプリンシパルを、現在実行中のスレッドに関連付けられているプリンシパルと照合しようとします。

  5. キー、URLまたはプリンシパルのいずれも一致しない場合は、デフォルトの組込みアクセス権(オリジナルのサンドボックス・アクセス権)を使用します。

ポリシー・エントリのcodeBase URLの正確な意味は、最後の文字に依存します。末尾が/のcodeBaseは、指定されたディレクトリ内のすべてのクラス・ファイル(JARファイルを除く)を示します。末尾が/*のcodeBaseは、そのディレクトリ内にあるすべてのファイル(クラス・ファイルとJARファイルの両方)を示します。末尾が/-のcodeBaseは、指定されたディレクトリとその下の全サブディレクトリ内のすべてのファイル(クラス・ファイルとJARファイルの両方)を示します。

たとえば、ポリシー内に"http://example.com/-"がある場合は、そのWebサイト上のすべてのコード・ベースはこのポリシー・エントリに一致します。一致するコード・ベースには、"http://example.com/j2se/sdk/"および"http://example.com/people/gong/appl.jar"があります。

複数のエントリが一致する場合は、それらのエントリ内のすべてのアクセス権が与えられます。つまり、アクセス権は追加割当てできます。たとえば、キーAで署名されたコードにアクセス権Xが与えられ、キーBで署名されたコードにはアクセス権Yが与えられており、特定のコード・ベースが指定されていない場合、AとBの両方で署名されたコードにはアクセス権XとYの両方が与えられます。同様に、codeBaseが"http://example.com/-"のコードにアクセス権Xが与えられ、"http://example.com/people/*"のコードにアクセス権Yが与えられており、特定の署名者が指定されていない場合は、"http://example.com/people/applet.jar"からのアプレットにはXとYの両方のアクセス権が与えられます。

ここでのURLのマッチングは、純粋に構文上のマッチングです。たとえば、あるポリシーにURL "ftp://ftp.example.com"を指定するエントリがあるとします。このようなエントリは、Javaコードを直接ftpからダウンロードして実行できる場合にだけ有用です。

ローカル・ファイル・システムのURLを指定する場合、ファイルのURLを使用できます。たとえばLinuxの/home/cathy/tempディレクトリ内のファイルを指定するには、次のようにします

"file:/home/cathy/temp/*"

WindowsのCドライブにあるtempディレクトリ内のファイルを指定するには、次のようにします

"file:/c:/temp/*"

ノート: codeBaseのURLには、プラットフォームに関係なく、常に(バックスラッシュではなく)スラッシュを使用します。

次のような、絶対パスを使用することもできます。

"/home/gong/bin/MyWonderfulJava"
デフォルトのシステム・ポリシー・ファイルとユーザー・ポリシー・ファイル

Policyのリファレンス実装では、1つまたは複数のポリシー構成ファイルからポリシーを指定できます。構成ファイルは、特定のコード・ソースからのコードに許可されるアクセス権を指定します。ポリシー・ファイルは、単純なテキスト・エディタを使用して作成できます。デフォルトでは、システム全体のポリシー・ファイルが1つあり、ユーザー・ポリシー・ファイルが1つあります。システム・ポリシー・ファイルは、デフォルトでは次の場所にあります。

  • {java.home}/conf/security/java.policy (Linux and macOS)
  • {java.home}\conf\security\java.policy (Windows)

ここで、java.homeはJDKがインストールされているディレクトリを示すシステム・プロパティです。ユーザー・ポリシー・ファイルは、デフォルトでは次の場所にあります。

  • {user.home}/.java.policy (LinuxおよびmacOS)
  • {user.home}\.java.policy (Windows)

ここでuser.homeは、ユーザーのホーム・ディレクトリを示すシステム・プロパティです。

Policyが初期化されると、まずシステム・ポリシーがロードされ、次に、ロードされたシステム・ポリシーにユーザー・ポリシーが追加されます。どちらのポリシーも存在しない場合は、組込みポリシーが使われます。組込みポリシーは、オリジナルのサンドボックス(sandbox)ポリシーと同じものです。ポリシー・ファイルの場所は、セキュリティ・プロパティ・ファイルの中で指定されています。セキュリティ・プロパティ・ファイルは、次の場所にあります。

  • {java.home}/conf/security/java.security (LinuxおよびmacOS)
  • {java.home}\conf\security\java.security (Windows)

ポリシー・ファイルの場所は、次のような形式の名前を持つプロパティの値として指定されています。

policy.url.n

ここで、nは数値です。次に示す形式の行で、それぞれのプロパティの値を指定します。

policy.url.n=URL

ここで、URLはURLの指定を表します。たとえば、デフォルトのシステムおよびユーザー・ポリシー・ファイルは、セキュリティ・プロパティ・ファイルに次のように指定されています。

policy.url.1=file:${java.home}/conf/security/java.policy
policy.url.2=file:${user.home}/.java.policy

「http://」の形式のものも含めて、実際には複数のURLを指定でき、指定したポリシー・ファイルのすべてがロードされます。また、上に示したポリシー・ファイルの指定のうち、2番目のポリシー・ファイルの指定をコメント・アウトするか、あるいは修正すれば、デフォルト・ユーザー・ポリシー・ファイルの読込みを無効にすることができます。

アルゴリズムは、policy.url.1から処理を開始して、番号を1つずつ増やしながら、URLが見つからなくなるまで処理を続けます。したがって、policy.url.1policy.url.3がある場合、policy.url.3は読み込まれません。

アプリケーションを実行するときに、追加のポリシー・ファイルや別のポリシー・ファイルを指定することもできます。この場合は、-Djava.security.policyコマンド行引数を使用してjava.security.policyプロパティの値を設定します。たとえば、次の例を考えてみます。

java -Djava.security.manager -Djava.security.policy=pURL SomeApp

ここでpURLは、ポリシー・ファイルの位置を示すURLです。指定されたポリシー・ファイルは、セキュリティ・プロパティ・ファイルで指定されたすべてのポリシー・ファイルに追加されてロードされます。-Djava.security.manager引数により、デフォルトのセキュリティ・マネージャが確実にインストールされるので、アプレットおよびアプリケーションの管理の説明にあるように、アプリケーションはポリシー・チェックを受けます。アプリケーションSomeAppがセキュリティ・マネージャをインストールする場合は、これは必要はありません。

次のように、二重の等号(==)の付いた形式を使用すると、指定されたポリシー・ファイルだけが使用され、その他のポリシー・ファイルはすべて無視されます。

java -Djava.security.manager -Djava.security.policy==pURL SomeApp

ノート:

  • 通常、java.securityファイル内のプロパティは1回のみ解析されます。このファイル内のプロパティを変更した場合は、アプリケーションを再起動して、変更が正しく反映されていることを確認します。
  • 組込みのJDKポリシー・ファイルは、JDKの構成がすぐに使用できるセキュアなものであるよう設計されたデフォルトの権限セットを付与しますが、java.security.policyプロパティで等号を2つ(==)使用すると、このファイルがオーバーライドされるため、使用する際は注意が必要です。このポリシーをオーバーライドすると予期しない動作が発生する(JDKコードに正しい権限が付与されない)可能性があるため、経験豊富なユーザーのみが実行するようにしてください。

  • セキュリティ・プロパティ・ファイルで policy.allowSystemPropertyプロパティにfalseが設定されている場合、-Djava.security.policyのポリシー・ファイルの値は(javaコマンドとappletviewerコマンドのどちらの場合も)無視されます。デフォルトは、trueです。

Policyの評価のカスタマイズ

Policyクラスの現在の設計は、すべての場合に適用できる包括的なものではありません。その点については考察が重ねられ、多くの場合に適したメソッドの呼出しが確実にできるよう部分的な改良が進められています。それまでの間、デフォルトのポリシー・クラスを別のポリシー・クラスに置き替えることができます。ただし、置き換えるポリシー・クラスが抽象Policyのサブクラスであり、getPermissionsメソッド(および必要に応じて他のメソッド)を実装していることが条件です。

Policyのリファレンス実装は、policy.providerセキュリティ・プロパティ(セキュリティ・プロパティ・ファイル、<java-home>/conf/security/java.security内)の値を、目的のPolicy実装クラスの完全修飾形の名前に設定しなおすことにより変更できます。

セキュリティ・プロパティpolicy.providerは、ポリシー・クラスの名前を指します。デフォルトは次のとおりです。

policy.provider=sun.security.provider.PolicyFile

カスタマイズするには、次のように、このプロパティに別のクラスを指定します。

policy.provider=com.mycom.MyPolicy

MyPolicyクラスは、java.security.Policyのサブクラスでなければなりません。このような、ポリシー・クラスのオーバーライドはあくまでも一時的な解決策であり、より包括的なポリシーAPIが設計されれば不要になります。

java.security.GeneralSecurityException

これは、例外クラスで、java.lang.Exceptionのサブクラスです。これは、セキュリティおよびセキュリティ・パッケージ関連の例外を2種類にすることを意図して追加されました。

  • java.lang.SecurityExceptionおよびそのサブクラスは、実行時例外(非チェック、未宣言)であり、Javaプログラムの実行を中止させます。

    このような例外は、ある種のセキュリティ違反が検出された場合にだけスローされます。たとえば、あるコードがアクセス権のないファイルにアクセスしようとする場合に、この種の例外がスローされます。アプリケーション開発者は、任意でそれらの例外をキャッチできます。

  • java.lang.Exception (宣言またはキャッチが必要)のサブクラスであるjava.security.GeneralSecurityExceptionは、他のすべての状況でセキュリティ・パッケージ内からスローされます。

    これらの例外はセキュリティに関連したものですが、必要不可欠というわけではありません。たとえば、無効なキーを渡すことはセキュリティ違反ではないと考えられますが、そのキーは開発者によりキャッチおよび処理される必要があります。

RuntimeExceptionのサブクラスであるjava.securityパッケージ内に、現時点で2つの例外があります。下位互換性により、現時点ではこれらの例外は変更できません。将来この問題を再検討します。

アクセス制御メカニズムとアルゴリズム

java.security.ProtectionDomain

ProtectionDomainクラスは、ドメインの特性をカプセル化します。ドメインは、指定されたプリンシパルのセットのアクセス権が実行されているとき、アクセス権を与えられているインスタンスのあるクラスのセットを囲みます。

ProtectionDomainは、CodeSourceClassLoader、プリンシパルの配列およびアクセス権のコレクションで構成されています。CodeSourceは、このドメインのすべてのクラスに対するコード・ベース(java.net.URL)、このドメインのすべてのコードに署名された秘密キーに対応する公開キーの証明書のセット(java.security.cert.Certificateタイプ)をカプセル化します。プリンシパルは、コードの実行対象のユーザーを表します。

ProtectionDomainの構築時に渡されるアクセス権は、適用されているポリシーにかかわらず、ドメインにバインドされているアクセス権の静的なセットを表します。その後、セキュリティ・チェック時にProtectionDomainによって、現在のポリシーへの問合せが行われ、ドメインに付与されている動的なアクセス権が取得されます。

異なるCodeSourcesからのクラス、または異なるプリンシパルに実行されるクラスは、別個のドメインに属しています。

現在JDKの一部として提供されているコードは、すべてシステム・コードと見なされ、独自のシステム・ドメインの内部で実行されます。アプレットまたはアプリケーションは、それぞれのポリシーによって決められるドメイン内で動作します。

システム・ドメイン以外のドメイン内のオブジェクトが、システム・ドメイン以外の別のドメイン内のオブジェクトを自動的に検出できないようにすることができます。この区分けは、ドメインごとに異なるクラス・ローダーを使用するなど、クラスを注意深く解決し、ロードすることにより実現できます。ただし、SecureClassLoader (またはそのサブクラス)は任意で様々なドメインからクラスをロードできるため、これらのクラスを同じ名前空間に共存させることができます(クラス・ローダーによる区分けと同様)。

java.security.AccessController

AccessControllerクラスは、次の3つの目的で使用されます:

  • 現在有効なセキュリティ・ポリシーに基づいて、重要なシステム・リソースへのアクセスが許可されるか、拒否されるかを決定する
  • コードを特権があるものとしてマークし、以降のアクセスの決定に効力を持たせる
  • 現在呼び出しているコンテキストのスナップショットを取得し、別のコンテキストからのアクセス制御の決定が、保存済のコンテキストを考慮して行われるようにする

システム・リソースへのアクセスを制御するコードは、特定のセキュリティ・モデルを使用する場合はAccessControllerメソッドおよびメソッドが利用するアクセス制御アルゴリズムを呼び出します。一方、アプリケーションがセキュリティ・モデルを、実行時にインストールされるSecurityManagerにゆだねる場合は、このメソッドではなくSecurityManagerクラスのメソッドを呼び出します。

たとえば、アクセス制御の呼出しには通常、次のようなコードを使用していました(JDKの旧バージョンより)。

ClassLoader loader = this.getClass().getClassLoader();
if (loader != null) {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkRead("path/file");
    }
}

現在のアーキテクチャでは、呼出し元のクラスに関連するクラス・ローダーの有無にかかわらず、チェックを呼び出すのが一般的な方法です。コードは、次のような簡潔なものになります。

FilePermission perm = new FilePermission("path/file", "read");
AccessController.checkPermission(perm);

AccessControllercheckPermissionメソッドは、現在の実行コンテキストを調べて、要求されたアクセスが許可されているかどうかを正しく判断します。アクセスが許可されている場合は、ただちに復帰します。アクセスが許可されていない場合は、AccessControlException (java.lang.SecurityExceptionのサブクラス)例外がスローされます。

ブラウザによっては、インストールされているSecurityManagerが様々なセキュリティ状態を示し、その結果、様々なアクションが実行される(レガシー)ケースがあるので注意してください。旧バージョンとの互換性のため、SecurityManagercheckPermissionメソッドを使用できます。

SecurityManager security = System.getSecurityManager();
if (security != null) {
    FilePermission perm = new FilePermission("path/file", "read");
    security.checkPermission(perm);
}

現状では、SecurityManagerのこの使用方法を変更しませんが、将来、JDKに適切なアクセス制御アルゴリズムが組み込まれたときには、以後のアプリケーションのプログラミングには新しい技法を使用することをお薦めします。

SecurityManagercheckPermissionメソッドは、デフォルトでは実際にAccessControllercheckPermissionメソッドを呼び出します。SecurityManagerの実装にはそれぞれ独自の管理手法が実装されており、アクセスが許可されるかどうかの判断にさらに制限が追加されている可能性もあります。

アクセス権チェックのアルゴリズム

図1-11に示すように、呼出し元が複数ある計算スレッドで、アクセス制御のチェックを行うとします(保護ドメインの境界を越える、複数のメソッド呼出しと考えてください)。

図1-11 保護ドメインの境界を超える複数のメソッド呼出し

図1-11の説明が続きます
「図1-11 保護ドメインの境界を超える複数のメソッド呼出し」の説明

AccessControllercheckPermissionメソッドが直前の呼出し元(Fileクラスのメソッドなど)によって呼び出されたとき、要求されたアクセスを許可するか拒否するかを判断する基本のアルゴリズムは次のとおりです。

呼出しチェーンの中に、要求されたアクセス権を持たない呼出し元がある場合は、AccessControlExceptionがスローされます。ただし、ある呼出し元のドメインにそのアクセス権が与えられており、その呼出し元に「特権付き(privileged)」のマークが付けられていて(次の項を参照)、それ以後その呼出し元から(直接または間接的に)呼び出されるすべての関係者がそのアクセス権を持つ場合は例外です。

2つの実装手法があります。

  • 「積極的評価」を行う実装では、スレッドが新しい保護ドメインに入るときと出るときには必ず、一組の有効なアクセス権が動的に更新される。

    この方法の長所は、アクセスが許可されているかどうかのチェックが容易になり、多くの場合、迅速になるという点です。短所は、ドメインの境界を越えた呼出しの頻度よりもアクセス権のチェックの頻度の方がかなり少なくなるために、アクセス権の更新のかなりの部分が無駄な行為になる可能性があるという点です。

  • 「消極的評価」を行う実装では、アクセス権のチェックが要求されたときにスレッドの状態(現在の状態を反映し、それには現在のスレッドのスタックまたはそれに相当するものが含まれる)が調べられ、要求された特定のアクセスを許可するかどうかが決定される。

    このアプローチの可能性のある短所の1つに、アクセス権のチェック時のパフォーマンスの低下があります。ただし、この低下は「積極的評価」アプローチでも発生する可能性があります(初期に、ドメイン間の各呼出しに拡散されますが)。私たちが行った実装では、良好なパフォーマンスが達成されました。全般的に、消極的評価の方法が経済的だと考えます。

したがって、アクセス権のチェックのアルゴリズムは、現在は「消極的評価」で実装されています。現在のスレッドが呼出し元1、呼出し元2、呼出し元mの順でm個の呼出し元をトラバースしたとします。呼出し元mはcheckPermissionメソッドを呼び出しました。基本アルゴリズムcheckPermissionは、アクセスが許可されているかどうかを判断するために次のコードを使用します(詳細については後のセクションを参照)。

    for (int i = m; i > 0; i--) {

        if (caller i's domain does not have the permission)
            throw AccessControlException

        else if (caller i is marked as privileged) {
            if (a context was specified in the call to doPrivileged)
                context.checkPermission(permission)
            if (limited permissions were specified in the call to doPrivileged) {
                for (each limited permission) {
                    if (the limited permission implies the requested permission)
                        return;
                }
            } else
                return;
        }
    }

    // Next, check the context inherited when the thread was created.
    // Whenever a new thread is created, the AccessControlContext at
    // that time is stored and associated with the new thread, as the
    // "inherited" context.

    inheritedContext.checkPermission(permission);
特権の扱い

AccessControllerクラスの新しいstaticメソッドにより、クラス・インスタンス内のコードはAccessControllerにそのコードの本体が「特権付きである」ことを通知できます。特権付きの場合、そのコードの本体は、アクセスの要求の発生元のコードが何であるかには関係なく、使用可能なリソースへのアクセスを要求する責任のみを負います。

つまり、ある呼出し元がdoPrivilegedメソッドを呼び出すと、その呼出し元には「特権付き」のマークが付けられます。アクセス制御を決定する場合、checkPermissionメソッドは、コンテキスト引数のないdoPrivilegedの呼出しによって「特権付き」のマークが付けられた呼出し側に届いたときに、チェックを中止します(コンテキスト引数については次のセクションを参照)。その呼出し元のドメインが指定されたアクセス権を持っている場合は、checkPermissionはそれ以上のチェックを行わずに正常復帰し、要求されたアクセスが許可されていることを示します。そのドメインが指定されたアクセス権を持たない場合は、通常は例外が発行されます。

「特権付き」機能の通常の使用法は、次のとおりです。

「特権付き」ブロックからの戻り値が必要ない場合は、次のようにします。

  somemethod() {
       ...normal code here...
       AccessController.doPrivileged(new PrivilegedAction() {
           public Object run() {
               // privileged code goes here, for example:
               System.loadLibrary("awt");
               return null; // nothing to return
           }
       });
       ...normal code here...
  }

PrivilegedActionは、Objectを返すrunという単一のメソッドを持つインタフェースです。これは、そのインタフェースを実装する匿名の内部クラスを作成する例で、runメソッドの実装を具体的に示しています。doPrivilegedへの呼出し時に、PrivilegedActionの実装のインスタンスが渡されます。doPrivilegedメソッドは、特権を有効にした後で、PrivilegedActionの実装からrunメソッドを呼び出し、runメソッドの戻り値をdoPrivilegedの戻り値として返します(これは、上の例では無視されています)。内部クラスの詳細は、Javaチュートリアルのネストされたクラスを参照してください。

次に、値を返す必要がある場合の例を示します。

  somemethod() {
       ...normal code here...
       String user = (String) AccessController.doPrivileged(
         new PrivilegedAction() {
           public Object run() {
               return System.getProperty("user.name");
           }
         }
       );
       ...normal code here...
  }

runメソッド内の動作で「チェック終了」例外(メソッドのthrows節の中に記述される)をスローする場合は、PrivilegedActionインタフェースではなくPrivilegedExceptionActionインタフェースを使用する必要があります。

  somemethod() throws FileNotFoundException {
       ...normal code here...
     try {
       FileInputStream fis = (FileInputStream)
        AccessController.doPrivileged(
         new PrivilegedExceptionAction() {
           public Object run() throws FileNotFoundException {
               return new FileInputStream("someFile");
           }
         }
       );
     } catch (PrivilegedActionException e) {
       // e.getException() should be an instance of
       // FileNotFoundException,
       // as only "checked" exceptions will be "wrapped" in a
       // <code>PrivilegedActionException</code>.
       throw (FileNotFoundException) e.getException();
     }
       ...normal code here...
 }

特権付きにはいくつか重要な点があります。まず、この概念は単一スレッドの内部でだけ存在します。特権付きのコードが終了すると、その特権は確実に消去または失効します。

次に、この例では、runメソッド内のコード本体は特権付きです。しかし、これが権限の少ない信頼性の低いコードを呼び出した場合、そのコードは結果として何の権限も得られません。アクセス権は、特権付きのコードがそのアクセス権を持つ場合にのみ与えられ、checkPermission呼出しに至るまでの呼出しチェーン内のこれ以降のすべての呼出し元も同様になります。

他のアクセス権をチェックするためのスタックの完全なトラバースを禁止せずにコード内でコードの特権のサブセットを表明できるAccessController.doPrivilegedのバリアントを使用できます。特権のサブセットのアサーションを参照してください。

コードを「特権付き」としてマーキングする方法の詳細は、付録A: 特権ブロックのためのAPIを参照してください。

アクセス制御コンテキストの継承

スレッドが新しいスレッドを生成するときは、新しいスタックが生成されます。新しいスレッドが生成されたときに現在のセキュリティ・コンテキストが書き換えられない場合、新しいスレッドの中でAccessController.checkPermissionが呼び出されたときのセキュリティの判断は、新しいスレッドのコンテキストだけに基づいて行われ、親スレッドのコンテキストは考慮されません。

この明確なスタックの考え方そのものはセキュリティ上の問題にはなりませんが、安全なコード(特にシステム・コード)を記述する上で、隠れた間違いを起こしやすくなります。経験の浅い開発者は、子スレッド(信頼できないコードを含まないものなど)は、親スレッド(信頼できないコードを含むものなど)から同じセキュリティ・コンテキストを継承すると仮定してしまうことが考えられます。これは、親のコンテキストが実際は保存されていない場合、制御対象のリソースに新しいスレッドからアクセスすると(そのリソースを信頼できないコードに渡すと)予期しないセキュリティ・ホールの原因となります。

このため、新しいスレッドが生成されるときは、子スレッドの生成時点における親スレッドのセキュリティ・コンテキストを子スレッドが確実に自動継承(スレッドの生成などのコードを通じて)するようにし、それ以降の子スレッドでのcheckPermissionの呼出しで、継承した親のコンテキストが考慮されるようにしました。

つまり、論理的スレッド・コンテキストは、親のコンテキスト(次の項で説明されているAccessControlContextの形式)と現在のコンテキストの両方を含むように拡張され、アクセス権のチェックのためのアルゴリズムは、次のように拡張されます。(前述したように、checkPermissionの呼出しに至るまでにはm個の呼出し元があります。AccessControlContextcheckPermissionメソッドの詳細は、次の項を参照してください。)

    for (int i = m; i > 0; i--) {

        if (caller i's domain does not have the permission)
            throw AccessControlException

        else if (caller i is marked as privileged) {
            if (a context was specified in the call to doPrivileged)
                context.checkPermission(permission)
            if (limited permissions were specified in the call to doPrivileged) {
                for (each limited permission) {
                    if (the limited permission implies the requested permission)
                        return;
                }
            } else
                return;
        }
    }

    // Next, check the context inherited when the thread was created.
    // Whenever a new thread is created, the AccessControlContext at
    // that time is stored and associated with the new thread, as the
    // "inherited" context.

    inheritedContext.checkPermission(permission);

この継承は、たとえば、孫が親とその親の両方の性質を継承するのと同じように過渡的なものです。また、継承されたコンテキストは、子コンテキストが最初に実行される時点ではなく、子コンテキストが生成された時点で保存されます。継承の機能についてはAPIの公の変更はありません。

java.security.AccessControlContext

前述したように、AccessControllercheckPermissionメソッドは、現在の実行スレッド内で(継承したコンテキストも含む)セキュリティ・チェックを実行します。このようなセキュリティ・チェックが別のコンテキスト内でだけ可能な場合には、問題が生じます。つまり、あるコンテキスト内で行うべきセキュリティ・チェックを、実際には別のコンテキストで行うことが必要な場合があります。たとえば、あるスレッドが別のスレッドにイベントを送ったとき、そのサービスにはコントローラ・リソースへのアクセスが必要であるのに、要求イベントを処理する後者のスレッドがアクセス制御を行うための適切なコンテキストを持っていないような場合です。

この問題に対処するために、AccessControllergetContextメソッドとAccessControlContextクラスが用意されました。getContextメソッドは、現在の呼出しコンテキストをAccessControlContextオブジェクトに格納して返します。呼出しの例を次に示します。

    AccessControlContext acc = AccessController.getContext();

このコンテキストは、適切な情報を取り込み、別のコンテキストからこのコンテキスト情報を調べることにより、アクセス制御の判断を行えるようにします。たとえば、あるスレッドは別のスレッドに要求イベントを送りながら、このコンテキスト情報を提供することができます。AccessControlContext自体がcheckPermissionメソッドを持っており、このメソッドを使用して、現在の実行スレッドのコンテキストではなく、カプセル化しているコンテキストに基づいてアクセスの判断を行います。したがって、2つ目のスレッドは必要な場合に次の呼出しを行うことにより、適切なセキュリティ・チェックができます。

    acc.checkPermission(permission);

このメソッド呼出しは2つ目のスレッドで行われますが、1つ目のスレッドのコンテキストでセキュリティ・チェックを行うのと同じです。

また、あるアクセス制御コンテキストに対して1つまたは複数のアクセス権のチェックが必要なときに、どのアクセス権をチェックするかの優先度が不明なことがあります。こうした場合には、コンテキストを値としてとるdoPrivilegedメソッドを使用できます。

    somemethod() {
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                // Code goes here. Any permission checks from 
                // this point forward require both the current 
                // context and the snapshot's context to have 
                // the desired permission.
             }
        });
        ...normal code here...

これで、AccessControllercheckPermissionメソッドによって利用される完全なアルゴリズムを提供できます。現在のスレッドが呼出し元1、呼出し元2、呼出し元mの順でm個の呼出し元をトラバースしたとします。次に、呼出し元mcheckPermissionメソッドを呼び出しました。アルゴリズムcheckPermissionは、アクセスが許可されているかどうかを判断するために次のコードを使用します。

    for (int i = m; i > 0; i--) {

        if (caller i's domain does not have the permission)
            throw AccessControlException

        else if (caller i is marked as privileged) {
            if (a context was specified in the call to doPrivileged)
                context.checkPermission(permission)
            if (limited permissions were specified in the call to doPrivileged) {
                for (each limited permission) {
                    if (the limited permission implies the requested permission)
                        return;
                }
            } else
                return;
        }
    }

    // Next, check the context inherited when the thread was created.
    // Whenever a new thread is created, the AccessControlContext at
    // that time is stored and associated with the new thread, as the
    // "inherited" context.

    inheritedContext.checkPermission(permission);

安全なクラス・ローディング

動的クラス・ローディングはJava仮想マシンの重要な機能で、Javaプラットフォームでソフトウェア・コンポーネントを実行時にインストールできるようにします。この機能には、独特の特長がいくつかあります。まず、遅延ローディングは、要求があってもできるかぎり直前までクラスがロードされないことを意味します。また、動的クラス・ローディングは、リンクタイム・チェックを追加することによって、Java仮想マシンの型の安全性を維持します。リンクタイム・チェックは、特定のランタイム・チェックと置き換わるもので、1回のみ実行されます。さらに、プログラマは、特定のクラスのロード元となるリモートの場所を指定したり、クラスに適切なセキュリティ属性を割り当てたりするような、独自のクラス・ローダーを定義できます。また、クラス・ローダーを使用して、様々なソフトウェア・コンポーネントに個別の名前空間を提供できます。たとえば、ブラウザは、異なるWebページのアプレットを別々のクラス・ローダーを使用してロードできるため、これらのアプレット・クラス間である程度の分離が維持されます。実際、これらのアプレットに同じ名前のクラスが含まれていてもかまいません。これらのクラスは、Java仮想マシンによって異なる種類として扱われます。

クラス・ローディングのメカニズムは、Javaプログラミング言語の動的性質の中心となっているだけではありません。クラス・ファイルの検索と取得、セキュリティ・ポリシーの参照、および適切なアクセス権を持たせたクラス・オブジェクトの定義は、クラス・ローダーが行うため、このメカニズムは、セキュリティの提供にも中心的な役割を果たしています。

クラス・ローダーのクラス階層

1つのJava仮想マシンに、クラス・ローダー・オブジェクトの複数のインスタンスが存在する可能性があるため、クラスをロードするときに、使用するクラス・ローダーをどのように決定するかは重要な問題です。JDKでは、異なる属性を持った複数のクラス・ローダーのクラスが導入されたため、どの種類のクラス・ローダーを使用するのかも大きな問題です。

クラス・ローダーのクラス階層のルートは、java.lang.ClassLoaderという名前の抽象クラスです。クラスjava.security.SecureClassLoaderは、抽象ClassLoaderクラスのサブクラスおよび固定実装です。java.net.URLClassLoaderクラスは、SecureClassLoaderのサブクラスです。

クラス・ローダーのカスタム・クラスを作成する場合は、そのカスタム・クラス・ローダー特有の必要に応じて、上の任意のクラス・ローダー・クラスのサブクラスにすることも可能です。

初期クラス・ローダー

各クラスは、そのクラス・ローダーによってロードされ、各クラス・ローダー自体もクラスで、別のクラス・ローダーによってロードされる必要があるため、最初のクラス・ローダーはどこからロードされるのかという明らかな「鶏と卵の問題」の問題が起こるように見えます。クラスのローディング・プロセスをブートストラップする「初期」クラス・ローダーがあります。初期クラス・ローダーは、一般的にはCなどのネイティブ言語で記述されており、Javaコンテキスト内には現れません。初期クラス・ローダーは、ローカルのファイル・システムからプラットフォームに依存した方法でクラスをロードする場合がよくあります。

クラスには、java.*パッケージ内で定義されたクラスのように、Java仮想マシンと実行システムが正しく機能するために不可欠なものがあります。そのようなクラスはよく、基底クラスと呼ばれます。歴史的な理由から、このようなすべてのクラスには、NULLのクラス・ローダーが存在します。おそらく、このNULLクラス・ローダーが、初期クラス・ローダーの存在を示す唯一のしるしです。実際、NULLクラス・ローダーを、単に初期クラス・ローダーとしてみなす方が簡単です。

1つのJavaアプリケーション環境にすべてのクラスがあれば、クラスのローディング関係を反映するクラス・ローディングのツリーを簡単に形成できます。クラス・ローダーでないクラスは、すべて葉ノードです。各クラスの親ノードはそのクラスのクラス・ローダーで、NULLクラス・ローダーはルート・クラスです。クラス・ローダーが自分の祖先のクラス・ローダーをロードするという循環はありえないため、この構造はツリー構造になります。

クラス・ローダーの委譲

クラス・ローダーにクラスのロードが要求されると、そのクラス・ローダー自体がクラスをロードする場合と、そのクラスが別のクラス・ローダーにロードを行うように要求する場合があります。つまり、1つ目のクラス・ローダーは、2つ目のクラス・ローダーに委譲することができます。委譲関係は、どのクラス・ローダーがほかのどのクラス・ローダーをロードするかには関係ないという点では仮想的です。しかしむしろ委譲関係は、クラス・ローダー・オブジェクトの作成時に、親子関係として形成されます。ただし、システム・クラス・ローダーは、すべてのクラス・ローダーの委譲ルートの原型です。委譲関係に循環がないように注意してください。循環があると、委譲プロセスが無限ループに入ることがあります。

クラスの解釈処理アルゴリズム

JDK ClassLoaderメソッドのデフォルト実装では、クラスをロードする際、次の順序でクラスが検索されます。

  1. クラスがすでにロードされていないかどうかをチェックする。
  2. 現在のクラス・ローダーに委譲関係の親が指定されている場合は、その親にクラスのロードを委譲する。親が指定されていない場合は、初期クラス・ローダーに委譲する。
  3. カスタマイズ可能なメソッドを呼び出して、別の場所にあるクラスを検索する。

最初のステップでは、クラス・ローダーのローカル・キャッシュ(または機能的にそれと同等な、グローバル・キャッシュなど)で、ロードされたクラスがターゲット・クラスと一致するかどうかを調べます。最後のステップでは、クラスの検索メカニズムをカスタマイズする方法が提供されています。このため、カスタム・クラス・ローダーで、このメソッドをオーバーライドして、クラスの検索方法を指定できます。たとえば、アプレットのクラス・ローダーでこのメソッドをオーバーライドして、アプレットのホストに戻ってクラス・ファイルを検索し、ネットワーク経由でそのクラス・ファイルをロードできます。

これらのステップのどこかでクラスが検索されると、それが返されます。上のステップでクラスが見つからない場合は、ClassNotFound例外がスローされます。

型の安全性のため、同じクラスが同じクラス・ローダーによって2回以上ロードされないように注意してください。クラスがすでにロードされたクラスでない場合、現在のクラス・ローダーは、タスクを親クラス・ローダーに委譲しようとします。このプロセスは、再帰的に実行されることもあります。このため、適切なクラス・ローダーが使われることが保証されます。たとえば、システム・クラスを検索する場合、システム・クラス・ローダーにたどり着くまで委譲プロセスが繰り返されます。

ここまで移譲アルゴリズムを見てきました。では、クラス名を指定されてそのクラスをロードするとき、どのクラス・ローダーから使い始めればよいでしょうか。クラス・ローダーを特定する規則は、次のとおりです。

  • アプリケーションの最初のクラスをロードする場合は、URLClassLoaderの新しいインスタンスが使用される。
  • アプレットの最初のクラスをロードする場合は、AppletClassLoaderの新しいインスタンスが使用される。
  • java.lang.Class.ForNameが直接呼び出された場合は、初期クラス・ローダーが使用される。
  • クラスのロード要求が既存のクラスからそのクラスへの参照によって発生した場合は、既存のクラスのクラス・ローダーがクラスをロードするように要求される。

URLClassLoaderのインスタンスおよびAppletClassLoaderのインスタンスの使用についての規則には例外があり、特定のシステム環境によっては、規則が変わる場合があります。たとえば、同じWebページから複数のアプレット・クラスをロードする場合、Webブラウザが既存のAppletClassLoaderを繰返し使用する場合もあります。

クラス・ローダーの能力のため、クラス・ローダー・インスタンスを作成できるプログラムを厳格に制限しています。一方、アプリケーションまたはアプレットに、URLの場所を指定し、それらからクラスをロードするための便利なメカニズムを与えることは望ましい方法です。どのプログラムでもURLClassLoaderクラスのインスタンスを作成できるようにするための静的メソッドを提供していますが、その他のタイプのクラス・ローダーについては提供していません。

セキュリティ管理

アプレットおよびアプリケーションの管理

現状では、すべてのJDKシステム・コードは、SecurityManagerメソッドを呼び出して、現在有効なポリシーをチェックし、アクセス制御チェックを実行します。通常、アプレットの実行中には、セキュリティ・マネージャ(SecurityManagerの実装)がインストールされています。appletviewerおよび大半のブラウザは、セキュリティ・マネージャをインストールします。

セキュリティ・マネージャは、アプリケーションの実行中に自動的にインストールされるわけではありません。ローカル・ファイル・システムのアプリケーションに対して、ダウンロードしたアプレットに対するのと同じセキュリティ・ポリシーを適用するには、アプリケーションを実行するユーザーが、次のように新しい-Djava.security.managerコマンド行引数(java.security.managerプロパティの値を設定する)を使用してJava仮想マシンを呼び出す必要があります

java -Djava.security.manager SomeApp

あるいは、アプリケーション自体が、java.lang.SystemクラスのsetSecurityManagerメソッドを呼び出して、セキュリティ・マネージャをインストールする必要があります。

特定のセキュリティ・マネージャをコマンド行で指定できます。その場合、次に示すように-Djava.security.managerの後に、等号、およびセキュリティ・マネージャとして使用するクラスの名前を指定します

java -Djava.security.manager=COM.abc.MySecMgr SomeApp

セキュリティ・マネージャが指定されない場合、デフォルトで組み込まれるセキュリティ・マネージャが使用されます(アプリケーションが別のセキュリティ・マネージャをインストールする場合を除く)。次のコードはすべて等価であり、デフォルトのセキュリティ・マネージャを使用します。

java -Djava.security.manager SomeApp
java -Djava.security.manager="" SomeApp
java -Djava.security.manager=default SomeApp

JDKには、java.class.pathという名前のプロパティが含まれています。ローカル・ファイル・システムに格納されるものの、基底クラスとしては扱われないクラス(SDKに組込みのクラスなど)は、このパスに置かれます。このパスのクラスは、安全なクラス・ローダーによってロードされ、実施中のセキュリティ・ポリシーに従います。

また、-Djava.security.policyコマンド行引数により、使用するポリシー・ファイルを指定できます。このコマンド行引数はデフォルトのPolicyの実装とポリシー・ファイルの構文に詳細に説明されています。基本的に、コマンド行に-Djava.security.policyを含めない場合、セキュリティ・プロパティ・ファイルで指定されたポリシー・ファイルが使用されます。

-Djava.security.policyコマンド行引数を使用して、アプリケーションの実行の呼出し時に、追加のポリシー・ファイルまたは別のポリシー・ファイルを指定できます。たとえば、次のように入力した場合(ここで、pURLはポリシー・ファイルの位置を指定するURL)、セキュリティ・プロパティ・ファイルに指定されたすべてのポリシー・ファイルに加えて、指定したポリシー・ファイルがロードされます。

java -Djava.security.manager -Djava.security.policy=pURL SomeApp

代わりに「==」を使って次のコマンドを記述すると、指定されたポリシー・ファイルだけが使用され、その他のポリシー・ファイルはすべて無視されます。

java -Djava.security.manager -Djava.security.policy==pURL SomeApp

SecurityManagerとAccessController

アクセス制御の新しいメカニズムは、完全に下位互換性があります。たとえば、SecurityManager内のcheckメソッドの大部分の実装は、SecurityManagerの新しいcheckPermissionメソッドを呼び出す(デフォルトの実装ではAccessControllercheckPermissionメソッドを呼び出す)ように変更されましたが、それらのメソッドはすべてサポートされています。特定の内部セキュリティ・チェックは、パラメータ化される場合を除いてSecurityManagerクラス内に引き続きあります。

現時点では、SecurityManagerを呼び出してクラス・ローダーの存在をチェックするかわりに、AccessControllerを呼び出すようにシステム・コードを変更していません。これは、SecurityManagerをサブクラス化し、checkメソッドをカスタマイズするサード・パーティのアプリケーションが存在する可能性があるからです。実際のところ、デフォルトで単にAccessController.checkPermissionを呼び出す新しいメソッドSecurityManager.checkPermissionを追加しました。

SecurityManagerAccessControllerの関係を理解するには、SecurityManagerがアクセス制御の中心的な概念を表しており、AccessControllerdoPrivilegedメソッドなど特殊な機能を使用した特定のアクセス制御アルゴリズムを実装していることに気付くだけで十分です。SecurityManagerを最新に保つことにより、下位互換性(以前のバージョンのJDKに基づく独自のセキュリティ・マネージャ・クラスを記述したアプリケーションとの互換性など)、および柔軟性(セキュリティ・モデルをカスタマイズして必須アクセス制御または複数レベルのセキュリティを実装するなど)を維持しています。AccessControllerを提供することで、最も制限が大きいと考え、ほとんどのシナリオで大規模なセキュリティ・コードを記述する必要がある重荷から一般的なプログラマを解放するアルゴリズムを組み込んでいます。

アプリケーション・コードでAccessControllerを使用することをお薦めします。一方、セキュリティ・マネージャの(サブクラス化による)カスタマイズは、最後の手段にし、細心の注意を払って実行してください。さらに、標準セキュリティ・チェックを呼び出す前に常に時間をチェックするようなカスタマイズされたセキュリティ・マネージャには、適切な場合にいつでもAccessControllerが提供するアルゴリズムを使用でき、使用する必要があります。

覚えておくことの1つは、独自のSecurityManagerを実装する場合、それを信頼されるソフトウェアとしてインストールし、それにjava.security.AllPermissionを付与すべきであるということです。これを行うには、AllPermissionSecurityManagerに付与するように、ポリシー・ファイルを調整します。詳細については、「デフォルトのPolicyの実装とポリシー・ファイルの構文」を参照してください。

補助ツール

この項では、セキュリティ機能の展開を支援する2つのツールの使用方法について簡単に説明します。

キーおよび証明書管理ツール

keytoolは、キーと証明書を管理するためのユーティリティです。それにより、自己認証(他のユーザーまたはサービスに対して、ユーザーが自分自身を認証する)やデータの整合性とデジタル署名を使用した認証サービスで使用するための独自の公開キーと秘密キーのペアおよび関連証明書を管理できます。認証情報には、X.509証明書のシーケンス(チェーン)と、関連の秘密キー(いわゆる「別名」によって参照可能)の両方が含まれます。このツールは、証明書(ユーザーにより「信頼されている」)も管理します。証明書は、認証情報と同じデータベースに格納され、「別名」によって参照できます。

keytoolは、キーと証明書をキーストアに格納します。デフォルトのキーストア実装は、キーストアをファイルとして実装します。キーストアは、秘密キーをパスワードで保護します。

X.509証明書のチェーンは、証明書発行局(CA)という組織によって提供されます。アイデンティティ(CAを含む)は、その秘密キーを使用して、オブジェクト(SSLを使用してセキュリティ設定したチャネルなど)との関係、署名したコードのアーカイブとの関係、および発行したX.509証明書(CA用)との関係を認証します。ブートストラップ・ツールとして、-gencertオプションを使用して生成した証明書は、証明書発行局が証明書チェーンを返すまで使用される可能性があります。

このデータベース内の秘密キーは、不適切に公開されないように常に暗号化されて保存されます。データベースにアクセスするか、データベースを変更する場合には、パスワードが要求されます。これらの秘密キーは、複数の単語で構成される「パスワード」を使用して暗号化されます。パスワードを忘れた場合、認証キーを復元することはできません。

実際、キーストア内の各秘密キーは、個々のパスワードによって保護することができます。このパスワードは、キーストアの全体的な整合性を保護するパスワードと同じである場合も、異なる場合もあります。

現在、このツールは、コマンド行でシェル・プロンプトに、単にkeytoolと入力して使用するようになっています。keytoolは、適切なJavaクラスを実行するスクリプトでSDKに組み込まれています。

各コマンドのコマンド行オプションは、任意の順序で指定できます。不正確なオプションを入力したり、keytool -helpと入力すると、ツールの使用方法の概略が出力デバイス(シェル・ウィンドウなど)に出力されます。

JAR署名および検証ツール
jarsignerツールを使用すると、Javaアーカイブ(JARファイル)にデジタル署名したり、その署名を検証したりすることができます。このツールは、keytoolによって管理されるキーストアに依存しています。

ノート:

jdk.security.jarsigner APIを使用してJARファイルに署名することもできます。

GuardedObjectおよびSignedObject

java.security.GuardedObjectとjava.security.Guard

AccessControlContextクラスは、異なるコンテキストでアクセス制御の決定を行う必要がある場合に有用であることを説明しました。このようなシナリオは他にも存在します。たとえば、リソースの供給者がそのリソースの消費者と同じスレッドになく、消費者スレッドが供給者スレッドにアクセス制御コンテキスト情報を提供できない(コンテキストのセキュリティが厳しい、またはコンテキストが大きすぎて渡せないなどの理由のため)場合です。このような場合に備え、図1-12のような、リソースへのアクセスを保護するGuardedObjectというクラスを用意しています。

図1-12 保護されているオブジェクトがリソースへのアクセスを保護する仕組み

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

基本的な考え方は、リソースの供給者がリソースを表すオブジェクトを作成し、リソース・オブジェクトを内部に組み込むGuardedObjectを作成し、それからGuardedObjectを消費者に提供するというものです。GuardedObjectの作成時に供給者もGuardオブジェクトを指定し、Guardオブジェクト内部のセキュリティ・チェックの条件が満たされた場合にだけ、消費者を含むどこからでもリソース・オブジェクトを取得できるように設定します。

Guardはインタフェースであるため、任意のオブジェクトをGuardにできます。このインタフェースの唯一のメソッドは、checkGuardです。これは、Object引数を取得し、特定のセキュリティ・チェックを実行します。java.securityPermissionクラスは、Guardインタフェースを実装します。

たとえば、システム・スレッドが/a/b/c.txtというファイルを読取りアクセス用に開くよう要求されたが、システム・スレッドは、だれが要求したのか、どんな状況で要求が行われたのかわからないとします。このため、サーバー側では、正確なアクセス制御の決定を行えません。システム・スレッドは、次に示すように、GuardedObjectを使用してアクセス制御チェックを遅延させることができます。

    FileInputStream f = new FileInputStream("/a/b/c.txt");
    FilePermission p = new FilePermission("/a/b/c.txt", "read");
    GuardedObject g = new GuardedObject(f, p);

これでシステム・スレッドは、gを消費者スレッドに渡すことができます。そのスレッドがファイル入力ストリームを取得するには、次のメソッドを呼び出す必要があります。

    FileInputStream fis = (FileInputStream) g.getObject();

次に、このメソッドは、GuardオブジェクトpcheckGuardメソッドを呼び出します。pPermissionなので、checkGuardメソッドは実際には次のようになります。

    SecurityManager sm = System.getSecurityManager();
    if (sm != null) sm.checkPermission(this);

これにより、適切なアクセス制御検査が消費者コンテキスト内で確実に実行されます。事実、頻繁に使用されるハッシュ表およびアクセス制御の一覧は、多くの場合置き換えられ、GuardedObjectsのハッシュ表が保存されます。

GuardedObjectおよびGuardのこの基本的なパターンは、非常に一般的です。GuardおよびGuardedObjectの基本クラスを拡張することにより、開発者が強力なアクセス制御ツールを容易に入手できることを期待します。たとえば、メソッドごとの呼出しは、各メソッドの適切なGuardにより実現でき、Guardは時間、署名者、またはその他の呼出し側の識別情報、またはその他の関連情報をチェックできます。

GuardedObjectオブジェクトを返すため、ある種の型情報が失われます。GuardedObjectは、連携するパーティ間で使用することを意図して設計されているため、受け取り側は受け取る(またはキャストする)オブジェクトの種類を知る必要があります。実際、GuardedObjectの一般的な使用方法として、それをサブクラス化する(GuardedFileInputStreamクラスの形成など)ことが想定されているため、入力情報のカプセル化およびキャストは、サブクラス内で適切に実行できます。

java.security.SignedObject

このクラスは、ほかのセキュリティ・プリミティブの重要な構築ブロックです。SignedObjectには、別の直列化可能オブジェクト、および署名されるオブジェクトとその署名が含まれています。署名がnull値でない場合、これには、署名付きオブジェクトの有効なデジタル署名が含まれます。このことを図1-13に示します。

図1-13 署名付きオブジェクトのコンテンツ

この図については、前の段落で説明しています。

基礎となる署名アルゴリズムは、signメソッド呼出しへのパラメータとしてのSignatureオブジェクトによって設定し、アルゴリズムは、特にDSAおよびSHA-256を使用するNIST標準DSAなどになります。「SHA/DSA」など、署名の同じ規則を使用してアルゴリズムを指定します。

署名付きオブジェクトは、元のオブジェクトの直列化された形式での精密なコピーです。ひとたびコピーが作成されると、元のオブジェクトをさらに操作してもコピーに影響が及ぶことはありません。署名付きのオブジェクトは、不変のオブジェクトです。

署名付きオブジェクト作成の一般的な例を次に示します。

    Signature signingEngine = Signature.getInstance(algorithm,provider);
    SignedObject so = new SignedObject(myobject, signingKey, signingEngine);

検査の一般的な例(SignedObjectであるsoを受け取る)は、次のとおりです。アルゴリズム名がわかっている場合は、最初の行は必要ありません。

    String algorithm = so.getAlgorithm();
    Signature verificationEngine = Signature.getInstance(algorithm, provider);
    so.verify(verificationEngine);

SignedObjectを使用するアプリケーションでは、次のことが行えます。

  • 偽造が不可能な認証トークンとして、任意のJavaアプリケーション環境で使用できる。このトークンを渡すときには、気付かないうちに悪意を持って変更されることを心配する必要がない。
  • Java実行時以外に格納する(重要なアクセス制御データをディスクに格納するなど)ために、データやオブジェクトに署名し、それらを直列化するために使用できる。
  • 入れ子のSignedObjectsを使用すると、承認と委譲の連鎖に類似した、署名の論理シーケンスを構築できる。

このクラスは同一の署名付きオブジェクトに複数の署名を行えるように、将来サブクラス化される可能性があります。その場合には、基底クラス内にある既存のメソッド呼出しは、セマンティックスが完全に互換性のあるものになります。特に、署名が1つだけある場合、どのgetメソッドも一意の値を返します。複数の署名がある場合には、一連の署名の中から任意の署名を1つ返します。

検討事項および将来の方針

リソース消費管理

リソース消費管理は、(アプリケーションが同時にポップアップさせるウィンドウの数を制限するなど)比較的簡単に実装できる場合もありますが、効果的に実装するのは非常に困難な場合(メモリーやファイル・システムの使用を制限するなど)もあります。将来、この問題を一貫性のある方法で解決する予定です。

アクセス権を任意にグループ化する

数多くのアクセス権をグループ化し、それぞれに略称を付けると便利な場合があります。たとえば、SuperPermissionという名前のアクセス権にFilePermission("-", "read,write")SocketPermission("*", "connect,accept")の両方を含めたい場合、技術的にはPermissionsクラスまたは同様のクラスのaddメソッドを使用して、目的のアクセス権を追加することにより、このSuperPermissionを実装できます。ただし、このようなグループ化は、複雑になる可能性があります。

次のようなさらに難しい問題もあります。まず、前述のSuperPermissionを与えた場合、実際に与えたアクセス権が何かを理解する必要があります。そのために固定アクセス権クラスまたは名前付きアクセス権クラスのどちらかを作成して、静的に指定されたアクセス権のグループを示すか、メンバーのアクセス権をポリシー・ファイルに正式な名前で記載する必要があります。次に、グループ化されたアクセス権を拡張する必要があるため、ポリシー(ファイル)の処理はさらに複雑になることがあります。グループ化されたアクセス権のネスト化は、処理をさらに複雑にします。

オブジェクト・レベルでの保護

Javaプログラミング言語はオブジェクト指向言語であるため、適切なオブジェクト・レベルの保護メカニズムは、開発者にとって有益であると考えられます。オブジェクト・レベルの保護メカニズムは、(1) Javaプログラミング言語が提供する保護機能を越え、(2)スレッド・ベースのアクセス制御メカニズムを追加します。

SignedObjectは、そのようなメカニズムの1つです。もう1つは、オブジェクトの内容を隠すために暗号化を使用するSealedObjectクラスです。

GuardedObjectは、クラスおよびオブジェクトごとに、またメソッド・レベルごとにアクセス制御を実施する一般的な方法です。ただし、この種の制御は上位レベルでの管理が困難なため、この方法は選択的に、また部分的に使用する必要があります。

保護ドメインの分割

現時点では実装されていませんが、有用と思われる概念に「サブドメイン」があります。サブドメインとは、別のドメインに含まれているドメインのことです。サブドメインは、それを含むドメインほどのアクセス権や特権は持っていません。たとえば、ドメインを作成して、プログラムの実行機能を選択的に制限することができます。

ドメインは、継承機能をサポートしているとみなされることがあります。サブドメインは、親ドメインのセキュリティ属性を自動的に継承します。ただし、親ドメインがさらにサブドメインを明示的に制限した場合は除きます。正当な継承によりサブドメインの制限を緩めることは、信頼性の高いコードの場合に有用な方法です。

便宜的に、システム・ドメインを、すべてのシステム・コードの単一の大きな集合体と考えることができます。ただし、保護を強化するためには、システム・コードを複数のシステム・ドメインで実行すべきであり、その場合、各ドメインは特定のタイプのリソースを保護し、一連の特別なアクセス権が与えられます。たとえば、ファイル・システム・コードおよびネットワーク・システム・コードが別々のドメインで実行されており、前者はネットワーク・リソースに対するアクセス権がなく、後者はファイル・システム・リソースに対するアクセス権を持たない場合、一方のシステム・ドメインのエラーやセキュリティ問題のリスクや結果は、その境界内に限定される可能性が高くなります。

署名付きコンテンツでのアプレットの実行

JARおよびマニフェスト仕様のコード署名では、非常に柔軟な署名の形式が可能です。同じアーカイブ内の複数のクラスを異なるキーで署名することも、1つのクラスに対して、未署名にしたり、1つのキーで署名したり、複数のキーで署名したりすることも可能です。オーディオ・クリップおよびグラフィック・イメージなどアーカイブ内のほかのリソースも、クラスと同様に署名したり未署名にすることができます。

この柔軟性のために、解釈が問題になります。特に複数のキーが別々に処理される場合に、次の事項を明確にしなければなりません。

  1. アーカイブ内のいずれかのクラスが署名されている場合は、イメージおよびオーディオを同じキーで署名する必要がありますか。

  2. イメージおよびオーディオが異なるキーで署名されている場合、それらを同じappletviewer (またはブラウザ・ページ)に配置できますか。または、別のビューアに送信して処理する必要がありますか。

これらの疑問に答えることは、容易ではなく、最大の効率性のためにプラットフォームおよびプロダクト間の一貫性が必要です。一時的な解決策は、署名付きかどうかに関係なくすべてのイメージおよびオーディオ・クリップを転送して、同じアプレット・クラス・ローダー内で処理するという簡単なものです。この一時的な解決策は、ひとたびコンセンサスが得られれば改善されていくと見込まれます。

さらに、クラス・ファイルのバイト・コードの内容がJARの署名付きハッシュ値に一致しないために、デジタル署名を検証できない場合、JAR作成者の本来の意図に反して、セキュリティ例外がスローされます。以前は、そのようなコードを信頼されないコードとして実行するという提案がなされていました。しかし、アプレットのクラス・ローダーは、複数の組織によって署名が付けられたコードのロードを許可するため、この提案は望ましいものではありません。つまり、部分的に修正されたJARファイルを受け入れると、信頼されないコードの一部の実行、および同じクラス・ローダーによるほかのコードへのアクセスを許可してしまいます。

付録A: 特権ブロックのためのAPI

この項では、特権コードの概要および使用目的について説明します。また、doPrivileged APIの使用方法も示します。

doPrivileged APIの使用

この項では、doPrivileged APIについて、および特権付き機能の使用方法について説明します。

戻り値がなく、例外がスローされない場合

特権ブロック内から値を返す必要がない場合は、doPrivilegedの呼出しは例1-1のようにできます。

ラムダ式を使ってdoPrivilegedを呼び出す場合は、ラムダ式をPrivilegedAction<Void>型として明示的にキャストします。doPrivilegedメソッドには、PrivilegedExceptionAction型のオブジェクトを取るバージョンも存在します。例外の処理を参照してください。

PrivilegedActionは、型パラメータで指定された型の値を返すrunという名前の単一抽象メソッドを持つ関数型インタフェースです。

この例では、runメソッドの戻り値は無視されています。特権コードを構成する実際の要素によっては、内部クラスの処理のために若干の変更が必要になる場合があります。たとえば、特権コードが例外をスローしたりローカル変数にアクセスしたりする場合は、後で説明する変更が必要になります。

特権構造の使用には細心の注意を払って、特権コード・セクションをできるだけ小さくしてください。つまり、runメソッドの中のコードは、特権付きで実行する必要のあるコードのみに制限し、より一般的な処理はrunメソッドの外側で行うようにします。また、doPrivilegedの呼出しは、特権を有効にする必要があるコードの中で行うようにします。それ自体がdoPrivilegedを呼び出すようなユーティリティ・クラスは、セキュリティ・ホールとなる危険があるため、作成しないでください。前の例で示したように、直接呼び出さなくても、PrivilegedActionクラスのためのユーティリティ・クラスを記述できます。Javaプログラミング言語のセキュア・コーディング・ガイドラインガイドライン9-3: java.security.AccessController.doPrivilegedの安全な呼出しを参照してください。

例1-1 特権ブロックのサンプル・コード

次のコードは、次の3つの方法で特権コードを指定します。
  • インタフェースPrivilegedActionを実装するクラスで。

  • 匿名クラスで。

  • ラムダ式で。

import java.security.*;

public class NoReturnNoException {
    
    class MyAction implements PrivilegedAction<Void> {
        public Void run() {
            // Privileged code goes here, for example:
            System.loadLibrary("awt");
            return null; // nothing to return
        }
    }
    
    public void somemethod() {
           
        MyAction mya = new MyAction();
        
        // Become privileged:
        AccessController.doPrivileged(mya);
       
        // Anonymous class
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                // Privileged code goes here, for example:
                System.loadLibrary("awt");
                return null; // nothing to return
            }
        });
        
        // Lambda expression
        AccessController.doPrivileged((PrivilegedAction<Void>)
            () -> {
                // Privileged code goes here, for example:
                System.loadLibrary("awt");
                return null; // nothing to return
            }
        );
    }
    
    public static void main(String... args) {
        NoReturnNoException myApplication = new NoReturnNoException();
        myApplication.somemethod();
    }
}
値を返す場合

次に、値を返す必要がある場合の例を示します。

System.out.println(
    AccessController.doPrivileged((PrivilegedAction<String>)
        () -> System.getProperty("user.name")
    )
);
ローカル変数にアクセスする場合

ラムダ式または匿名の内部クラスを使う場合は、アクセスするローカル変数がすべてfinalまたは事実上のfinalである必要があります。

たとえば:

String lib = "awt";
AccessController.doPrivileged((PrivilegedAction<Void>)
    () -> {
        System.loadLibrary(lib);
        return null; // nothing to return
    }
); 
   
AccessController.doPrivileged(new PrivilegedAction<Void>() {
    public Object run() {
        System.loadLibrary(lib);
        return null; // nothing to return
    }        
});

変数libは、その値が変更されていないため、事実上のfinalです。たとえば、変数libの宣言の後に次の代入文を追加するとします。

lib = "swing";

コンパイラは、ラムダ式と匿名クラスの両方にあるSystem.loadLibraryの呼出しを検出したときに、次のエラーを生成します。

  • error: local variables referenced from a lambda expression must be final or effectively final
  • error: local variables referenced from an inner class must be final or effectively final

詳細は、「ローカル・クラス」の「包含クラスのメンバーへのアクセス」を参照してください。

繰返し設定されるなどの理由で、既存の変数を事実上のfinalとして宣言できない場合は、doPrivilegedメソッドを呼び出す直前に新しいfinal変数を作成し、その変数を他方の変数と等しいものとして設定します。たとえば:

String lib;
    
// The lib variable gets set multiple times so you can't make it
// effectively final.
  
// Create a final String that you can use inside of the run method
final String fLib = lib;
    
AccessController.doPrivileged((PrivilegedAction<Void>)
    () -> {
        System.loadLibrary(fLib);
        return null; // nothing to return
    }
);
例外処理

runメソッド内で実行された処理によってチェック例外(メソッドのthrows節にリストする必要がある例外)がスローされた場合は、PrivilegedActionインタフェースではなく、PrivilegedExceptionActionインタフェースを使用する必要があります。

例1-2 例外処理の例

チェック例外がrunメソッドの実行中にスローされる場合は、この例で示したようにPrivilegedActionExceptionラッパー例外に置いてからスローし、記述したコードを使用してキャッチします。

public void processSomefile() throws IOException {

    try {
        Path path = FileSystems.getDefault().getPath("somefile");
        BufferedReader br = AccessController.doPrivileged(
            (PrivilegedExceptionAction<BufferedReader>)
                () -> Files.newBufferedReader(path)
        );
        // ... read from file and do something
    } catch (PrivilegedActionException e) {
    
        // e.getException() should be an instance of IOException
        // as only checked exceptions will be wrapped in a
        // PrivilegedActionException.
        throw (IOException) e.getException();
    }
}
特権のサブセットのアサーション

JDK 8では、他のアクセス権をチェックするためのスタックの完全なトラバースを禁止せずにコード内でコードの特権のサブセットを表明できる、doPrivilegedのバリアントを使用できます。このdoPrivilegedのバリアントには3つのパラメータがあり、そのうちの1つはこの特権のサブセットを指定するために使用します。たとえば、次のコード例はシステム・プロパティを取得するための特権を表明しています。

// Returns the value of the specified property. All code
// is allowed to read the app.version and app.vendor
// properties.

public String getProperty(final String prop) {
    return AccessController.doPrivileged(
        (PrivilegedAction<String>) () -> System.getProperty(prop),
        null,
        new java.util.PropertyPermission("app.version", "read"),
        new java.util.PropertyPermission("app.vendor", "read")
    );
    }

このバージョンのdoPrivilegedの1つ目のパラメータは、java.security.PrivilegedAction型です。この例では、1つ目のパラメータは関数型インタフェースPrivilegedActionを実装するラムダ式です。このインタフェースのrunメソッドは、パラメータpropで指定されたシステム・プロパティの値を返します。

このバージョンのdoPrivilegedの2つ目のパラメータは、AccessControlContext型です。場合によっては、異なるコンテキスト(ワーカー・スレッドなど)で追加のセキュリティ・チェックを実行する必要があります。AccessControlContext.getContextメソッドを使用して、特定の呼出しコンテキストからAccessControlContextインスタンスを取得できます。このパラメータに(この例のように)nullを指定すると、doPrivilegedの呼出し時に追加のセキュリティ・チェックが実行されません。

このバージョンのdoPrivilegedの3つ目のパラメータは、可変引数パラメータであるPermission...型です。つまり、1つ以上のPermissionパラメータを指定するか、またはPermission[]のようにしてPermissionオブジェクトの配列を指定できます。この例では、doPrivilegedを呼び出すことでapp.versionプロパティとapp.vendorプロパティを取得できます。

このdoPrivilegedの3つのパラメータ・バリアントは、最小特権のモードまたは追加特権のモードで使用できます。

最小特権

doPrivilegedメソッドの典型的な使用例は、現在のメソッドの呼出し側に必要なすべてのアクセス権がなくても、これを呼び出すことで、そのメソッドでアクセス権のチェックを必要とする1つ以上のアクションを実行できるようにする場合です。

たとえば、現在のメソッドが内部実装に固有の目的でファイルを開いたり、ネットワーク要求を実行したりする必要がある場合があります。

JDK 8より前のバージョンでは、doPrivilegedメソッドの呼出しには2つのパラメータしかありませんでした。これらは、呼出し側のメソッドに一時的な特権を付与し、アクセスをチェックするための通常の完全なスタックのトラバーサルを、必要なアクセス権がないクラスで定義されたメソッドに到達する可能性がある呼出しスタックまで継続せずに、そのクラスに到達したときに停止することで機能しました。通常、doPrivilegedを呼び出しているクラスは、そのコード・パスでは不要な追加のアクセス権を持っている可能性があり、それらは一部の呼出し側クラスにもない可能性があります。

通常、これらの追加権限は実行時に使用されません。doPrivilegedを使ってこれらを昇格させないことで、意図しないアクションを実行する可能性がある不正なコードの利用を防ぐことができます。これは特に、PrivilegedActionが通常より複雑な場合や、時間の経過とともに独立して発展する可能性があるクラスやパッケージの境界の外側にあるコードを呼び出す場合に当てはまります。

3つのパラメータを持つdoPrivilegedのバリアントは、必要とされる予定がないアクセス権を不必要に昇格しないため、一般により安全に使用できます。ただし、実行時の効率が低いため、単純なコード・パスやパフォーマンス重視のコード・パスでは使用されない場合があります。

追加特権

現在のメソッドをコーディングするときには、アクションを実行するために呼出し側のメソッドのアクセス権を一時的に拡張できます。

たとえば、フレームワークのI/O APIに、特定のデータ形式のファイルを開くための汎用メソッドが含まれる場合があります。このAPIは、通常のファイル・パス・パラメータを取り、それを使って基盤となるFileInputStreamを開くときに、呼出し側のコードのアクセス権を使用します。ただし、これによってすべての呼出し側が標準のデモ用サンプルを含む特別なディレクトリ内のデータ・ファイルを開くこともできるようになる可能性があります。

このAPIの呼出し側には、読取りアクセス用のFilePermissionを直接付与できます。ただし、呼出し側のコードのセキュリティ・ポリシーの更新が不便または不可能になる可能性があります。たとえば、呼出し側のコードがサンドボックス内で実行されるアプレットである可能性があります。

これを実装する1つの方法は、コード内で受け取ったパスをチェックし、それが特別なディレクトリ内のファイルを参照しているかどうかを判定することです。参照している場合は、doPrivilegedを呼び出してすべてのアクセス権を有効にしてから、PrivilegedActionの内部でファイルを開きます。ファイルが特別なディレクトリに含まれていない場合は、コード内でdoPrivilegedを使用せずにファイルを開きます。

この手法では、実装は要求されたファイル・パスを慎重に扱い、それが特別な共有ディレクトリを参照しているかどうかを判定する必要があります。doPrivilegedを呼び出す前にファイル・パスを正規化して、パスが特別なディレクトリ内のファイルを参照しているかどうかを判定する前に、すべての相対パスが処理されるように(およびuser.dirシステム・プロパティを読み取るためのアクセス権がチェックされるように)する必要があります。また、特別なディレクトリの外側に抜ける意図を持つ悪質な"../"パス要素を防止する必要もあります。

より簡単かつ適切な実装では、3つ目のパラメータを持つdoPrivilegedのバリアントを使用します。特別なディレクトリへの読取りアクセスを含むFilePermissionを3つ目のパラメータとして渡します。その後、PrivilegedActionの内部でファイルの操作を行います。この実装はより簡単で、セキュリティ上の欠陥を含む可能性がかなり低くなります。

特権コードを使う意味

コードを特権付きとしてマークすると、信頼できるコードは、それを呼び出しているコードが直接利用できるものより多くのリソースに、一時的にアクセスできるようになります。

JDKのインストール・ポリシーにより、特定のコード・ソースに基づくコードに対して許可されるアクセス権、つまりシステム・リソースに対するアクセスの種類が決まります。CodeSource型のコード・ソースは、基本的に、コードの場所(URL)と、コードへの署名に使われている秘密キーに対応する公開キーを含む証明書の参照(コードが署名されている場合)で、構成されています。

ポリシーは、Policyオブジェクトによって表されます。具体的には、Policyクラス(java.securityパッケージ内)内のabstractメソッドの実装を提供するPolicyサブクラスによって表されます。

Policyオブジェクトが使用するポリシー情報がどこに置かれるかは、Policyの実装によります。Policyのリファレンス実装では、ポリシー情報をポリシー構成ファイルから得ます。デフォルトのPolicyのリファレンス実装とそこで読み取られるポリシー・ファイルで使用する構文については、「Policyのデフォルト実装とポリシー・ファイルの構文」を参照してください。

そのとき有効になっているセキュリティ・ポリシーでの決定に従って、CodeSourceインスタンスと、そのCodeSourceに基づくコードに対して付与されているアクセス権は、保護ドメインに包含されます。したがって、同じキーで署名された同じURLにあるクラスは一般に同じドメインに置かれ、クラスは1つの保護ドメインのみに所属します。(ただし、同じキーで署名され、同じURLにあっても、別のクラス・ローダー・インスタンスでロードされたクラスは、一般に別のドメインに置かれます。)同じアクセス権を与えられていても、別のコード・ソースが基になっているクラスは、別のドメインに属します。

JDKランタイム・イメージに付属する、ブートストラップ・クラス・ローダーによってロードされたクラスには、AllPermissionが付与されます。ただし、JDKランタイム・イメージに付属する、プラットフォーム・クラス・ローダーによってロードされるクラスには、JDKのデフォルト・ポリシーで指定されたアクセス権が付与されます。各モジュールのクラスは、jrt URLスキームを使用して一意の保護ドメインを割り当てられ、それらが正しく機能するために必要なアクセス権のみが付与されます。必ずしもAllPermissionが付与されるわけではありません。

アプレットまたはアプリケーションは、それぞれのコード・ソースによって決められるドメイン内で動作します。アプレット(またはセキュリティ・マネージャの下で動作しているアプリケーション)がセキュリティ保護された操作(ファイルの読み書きなど)を行うためには、その操作を行うためのアクセス権がアプレットまたはアプリケーションに与えられている必要があります。

より具体的には、リソースにアクセスしようとする場合は常に、その地点に至るまでに実行スレッドがたどってきたすべてのコードが、そのリソース・アクセスのためのアクセス権を持っている必要があります。ただし、スレッドの中に特権付きのマークが付けられたコードがある場合は除きます。つまり、複数の呼出し側のチェーンが存在する実行のスレッドで、アクセス制御のチェックが行われる場合を考えます。(保護ドメインの境界を横断する可能性のある複数のメソッドの呼出しのような場合です。)AccessController.checkPermissionメソッドが直前の呼出し元によって呼び出されたとき、要求されたアクセスを許可するか拒否するかを決定する基本的なアルゴリズムは、次のようになります。呼出しチェーンのいずれかの呼出し元のコードに、要求されたアクセス権がない場合、AccessControlExceptionがスローされます。ただし、このアクセス権を与えられているコードの呼出し元に特権付きのマークが付けられていて、この呼出し元からその後(直接または間接的に)呼び出されるすべての部分がこのアクセス権を持っている場合、この例外はスローされません。

ノート:

AccessController.checkPermissionメソッドは、通常、checkという語で始まる特定のSecurityManagerメソッド(checkConnectなど)の呼出しによって、またはSecurityManager.checkPermissionメソッドによって間接的に呼び出されます。通常、これらのチェックはSecurityManagerがインストールされている場合にのみ行われます。AccessController.checkPermissionメソッドによってチェックされるコードは、最初にSystem.getSecurityManagerメソッドがnullを返すかどうかをチェックします。

コードを特権付きとしてマークすると、信頼できるコードは、それを呼び出しているコードが直接利用できるものより多くのリソースに、一時的にアクセスできるようになります。これは、次のような場合に必要になります。たとえば、あるアプリケーションがフォントが入ったファイルへの直接アクセスを許可されていないときに、ドキュメントを表示するシステム・ユーティリティがユーザーに代わってそのフォントを取得する必要がある場合などです。このシステム・ユーティリティは、フォントを取得するために特権付きになる必要があります。

リフレクション

doPrivilegedメソッドは、java.lang.reflect.Method.invokeメソッドを使用して、リフレクションとして呼び出すことができます。

このAPIとリフレクションの相互作用は、注意の必要な微妙な点です。doPrivilegedメソッドは、java.lang.reflect.Method.invokeメソッドを使用して、リフレクションとして呼び出すことができます。この場合、特権モードで与えられる特権は、Method.invokeの特権ではなく、それによって呼び出されるリフレクションでないコードの特権です。このようにしないと、システム特権が誤って(または悪意を持って)ユーザー・コードに与えられてしまう危険があります。既存のAPIでリフレクションを使う場合にも、同様の要件が適用されます。

付録B: 謝辞

Java 2 SDKの新しいセキュリティ機能の設計および実装は、Java Softwareセキュリティ・グループの主要メンバーの作業です。それ以外の(過去および現在の) Java Softwareコミュニティのメンバーも貴重な考察、詳細なレビューおよび必要な技術的支援を提供していただきました。次の方々に多大の貢献をしていただきました(アルファベット順、ただし、この方々だけにかぎりません)。Gigi Ankeny、Josh Bloch、Satya Dodda、Charlie Lai、Sheng Liang、Jan Luehe、Marianne Mueller、Jeff Nisewanger、Hemma Prafullchandra、Roger Riggs、Nakul Saraiya、Bill Shannon、Roland Schemers、ならびにVijay Srinivasan。

今回のプロジェクトは、Java Softwareの管理者(Dick Neiss、Jon Kannegaard、およびAlan Baratzに感謝します)、テスティング・グループ、およびドキュメント作成グループ(特にMary Dageforde)の強力なサポートなくしては実現できませんでした。James Gosling、Graham Hamilton、およびJim Mitchellからの技術的なアドバイスにも心から感謝します。

ここには名前を挙げることができませんが、弊社の企業パートナおよびライセンス保持者の皆様からも多くの提案をいただきました。

付録C: 参考資料

M.Gasser.Building a Secure Computer System. Van Nostrand Reinhold Co., New York, 1988.

L.Gong著『Java Security: Present and Near Future』IEEE Micro、17(3):14--19、1997年5/6月

L.Gong, T.M.A.Lomas, R.M. Needham, and J.H. Saltzer, "Protecting Poorly Chosen Secrets from Guessing Attacks". IEEE Journal on Selected Areas in Communications, 11(5):648--656, June, 1993.

J.Gosling, Bill Joy, and Guy Steele. The Java Language Specification. Addison-Wesley, Menlo Park, California, August 1996.

A.K. Jones.Protection in Programmed Systems. Ph.D. dissertation, Carnegie-Mellon University, Pittsburgh, PA 15213, June 1973.

B.W. Lampson.Protection.In Proceedings of the 5th Princeton Symposium on Information Sciences and Systems, Princeton University, March 1971. Reprinted in ACM Operating Systems Review, 8(1):18--24, January, 1974.

T.Lindholm and F.Yellin.The Java Virtual Machine Specification. Addison-Wesley, Menlo Park, California, 1997.

P.G. Neumann.Computer-Related Risks.Addison-Wesley, Menlo Park, California, 1995.

U.S. General Accounting Office. Information Security: Computer Attacks at Department of Defense Pose Increasing Risks. Technical Report GAO/AIMD-96-84, Washington, D.C. 20548, May 1996.

J.H. Saltzer.Protection and the Control of Information Sharing in Multics. Communications of the ACM, 17(7):388--402, July 1974.

J.H. Saltzer and M.D. Schroeder. The Protection of Information in Computer Systems}. Proceedings of the IEEE, 63(9):1278--1308, September 1975.

M.D. Schroeder.Cooperation of Mutually Suspicious Subsystems in a Computer Utility. Ph.D. dissertation, Massachusetts Institute of Technology, Cambridge, MA 02139, September 1972.

W.A. Wulf, R.Levin, and S.P. Harbison. HYDRA/C.mmp -- An Experimental Computer System. McGraw-Hill, 1981.