はじめに
Javaのセキュリティ実装に変更が加えられたことで、セキュリティ機能が強化され、開発者がデフォルトのJavaセキュリティ機構にプログラム的なフックを追加できるようになりました。さらに、Java Authentication and Authorization Services(JAAS)が導入され、JDK 1.4のコアAPIに組み込まれたにもかかわらず、Javaのセキュリティは引き続き「java.security」と「java.policy」というコア設定ファイルの構造化テキスト形式に基づいています。XMLファイルの解析にはある程度のオーバーヘッドが付随することは広く知られていますが、「java.policy」におけるセキュリティの「grant
ブロック」は、非常に特異なレイアウトを採用しているため、ビルド時に構文的な検証をするための標準的なツールがありません。
セキュリティ面でのニーズが低ければ、ポリシーファイルも単純で済み、担当チームも小規模なもので済むので、これが深刻な欠点として認識されることはないでしょう。しかし、複雑なセキュリティニーズを必要とする環境ならば、担当チームも大規模なものになり、「java.policy」ファイルに構文的なエラーが混入していても、実行時までそれに気づかない可能性があります。XMLファイルを利用するようPolicy
クラスを拡張するという方法もありますが、たいていはSunのデフォルト実装だけに頼っているので、実行時に「java.policy」ファイル関連の問題が生じる可能性が残されます。
こうした状況を受けて筆者が作成したのが、「java.policy」をチェックして、構文的なエラーがないかを報告するための簡単なユーティリティです。このユーティリティでは、各コードベースの指定パーミッションも出力できます。本稿では、このユーティリティのコードについて説明するほかに、ユーティリティの機能およびJava 2のセキュリティの基礎について簡単に紹介します。
続きを読む前に、各自の「java.policy」および「java.security」ファイルを随時参照できるようにしておくと便利です。これらの2つのJava設定ファイルは、デフォルトで「${JAVA_HOME}/jre/lib/security」ディレクトリに置かれています。「security.policy」ファイルがパーミッション関連で果たす役割は、アプリケーションの参照するポリシーファイルを指定するというものです。ファイルURLに指定されるパーミッションは累積的なものであり、実行時にはアプリケーションに割り当てられるパーミッションの全リストが作成されます。デフォルトで指定されるURLは次の2つです。ポリシーファイルの位置についてはデフォルトのままにしておくのが無難ですが、変更できないわけではありません。
policy.url.1=file:${java.home}/lib/security/java.policy policy.url.2=file:${user.home}/.java.policy
代わりの位置はVM引数で指定できます。
Djava.security.manager=C:\your_policy_name.policy
なお、policy.url
の機能を使うと複数のポリシーファイルを指定できますが、必要となるのは1つだけです。また、任意の名前を指定できます。
「security.policy」ファイルは、標準のJavaプロパティファイルのように構造化されているので管理が比較的簡単であり、変更される頻度も「java.policy」ほどではありません。また設定の多くは、暗号プロバイダ、キーストアなどに関係するものです。こうしたものを開発者が変更する機会はそう多くないでしょう。
「security.policy」の中で、「java.policy」へのプログラム的アクセスに関してもう1つ重要な意味を持つものとして、ポリシープロバイダの設定があります。これは、今回の検証ユーティリティが評価用のパーミッションを収集するときに最初にインスタンス化しなければならないクラスです。
policy.provider=sun.security.provider.PolicyFile
Policy
ファイルオブジェクトを取得するには、通常は次のようなコードを使用します。
pcoll = Policy.getPolicy().getPermissions(cs);
この呼び出しでは、Policy
ファイルオブジェクトを取得してから、関連するPermissionsCollection
を取得します。これについては、もう少し説明が必要です。
セキュリティマネージャ(Security Manager)のアクティブ化
2つ目の設定ファイル「java.policy」の解説に入る前に、話は少しずれますが、デフォルトではJavaセキュリティはオンにならない、という点を指摘しておきたいと思います。今日では世界中で膨大な量のJavaコードが使われていますが、多くのJavaプログラマがこの点を知らないか、意識すらしていません。Java 2におけるすべてのセキュリティ機能は、SecurityManager
クラスで管理されており、このクラスはデフォルトで非アクティブにされています。SecurityManager
を使用するには2つの方法がありますが、その1つは実行時にコマンドラインで次のように指定することです。
java -Djava.security.manager
2つ目はプログラム的にアクティブにする方法で、SecurityManager
をインスタンス化してから、これを明示的に呼び出し、目的とするアプリケーションのセキュリティチェックを実行させます。
アプリケーションのセキュリティを確立する重要性は言うまでもないことですが、そのための機能をデフォルトでは非アクティブにしておくとしたSunの選択は、控え目に言っても残念なことです。意識的にセキュリティマネージャをオンにしないかぎり「java.policy」の設定はまったく生かされず、アプリケーションがAccessException
をスローすることもありません。こうしたJavaのセキュリティモデルについて多くの開発者が適切な知識を有してないということは、実際は安全ではないのに、自分のアプリケーションは安全だと思いこんでいる人間が多くいるということです。
grantブロックの設定
ここで「java.policy」ファイルの説明に戻りますが、これはデフォルトのJava実行環境における2番目に重要なセキュリティ設定ファイルであり、パーミッションの設定もここで行います。Javaでは、特定のコードベース用に設定されるgrant
ブロックでパーミッションを設定します。たとえばSunから配布されるデフォルトの「java.policy」ファイルであれば、最初のgrant
エントリは次のようになっています。
// Standard extensions get all permissions by default grant codeBase "file:${java.home}/lib/ext/*" { permission java.security.AllPermission; };
このgrant
エントリは、「/ext」ディレクトリ中のすべてのコードに、文字どおりすべてのパーミッションを許可します。このような状況を考えると、クラスパスの問題を解決する際に、クラスローディングのデリゲーションの階層を上げて「/ext」ディレクトリに入れるという手段を取るのは危険だと言われる理由がわかります。このディレクトリに所属するコードは、システムプロパティを修正する権利を有し、ファイルシステムに対して読み書きできるので、システム上であらゆる行為ができることになります。
Sunの「コードベース」はURLとして指定されること、そしてアスタリスク記号(*)をワイルドカードとして使えることに注意してください。引用符、カッコ、末尾のセミコロン(;)を入れ忘れると、パーミッションファイルは解析できなくなり、実行時に正しく読み込まれなくなります(ビルド時にフォーマット検証を施しておこうというのは、こうした理由によります)。
「java.policy」で指定するパーミッションはすべて同じ基本フォーマットに従いますが、たとえば次のサンプルのように、修飾子を追加することもできます。
// com.tillman.util.security.permissionchecker package is in this directory grant codeBase "file:/C:/eclipse/workspace/Permissions/PermissionReader/bin/*" { permission java.security.SecurityPermission "getPolicy"; permission java.io.FilePermission "<<ALL FILES>>", "read,write,execute,delete"; permission java.net.URLPermission "http://www.nytimes.com", "listen, connect"; };
今回の検証ユーティリティはPolicy
オブジェクトへの参照を取得してパーミッションをチェックする必要があるので、上記のブロックにある最初のパーミッションは必須であり、ポリシーファイルで明示的に指定しておく必要があります。また、言うまでもありませんが、検証ユーティリティの所在位置を示すファイルURLについては、該当する情報に各自修正する必要があります。
パーミッションの検証コードは、基本的に単純な構造をしています。唯一複雑なのは、検証対象とするコードベースをプロパティファイル中で指定できるようにした部分です。これにより、デフォルトのgrant
ブロックを無視できるようになります(ただし結果の出力には、指定したパーミッションだけでなく、これらのデフォルトブロックについても出力されます)。検証用コードにあるgetFileLocations()
メソッドは、プロパティファイルで指定されたコードベースのリストを返すだけのものです(Windows上では、バックスラッシュ記号をエスケープする必要があるので、二重のバックスラッシュを使用します)。
while
ループでは、プロパティファイルに指定された個々のロケーションをコードベースのURLに変換しています。
codebase = new File((String)iter.next()).toURL();
その後、各々のコードベースは、CodeSource
コンストラクタに引数として渡されます。実質的にCodeSource
は、Policy
オブジェクトがPermissionCollection
を取得するための「キー」として機能します。
cs = new CodeSource(codebase, null); pcoll = Policy.getPolicy().getPermissions(cs);
PermissionCollection
型であるpcoll
変数のメンバは、elements()
メソッドを用いて列挙値として返されます。そして最後に、すべての列挙値を反復処理し、取得された各Permission
オブジェクトのtoString()
メソッドを用いて、該当する名前を出力します。
enum1 = pcoll.elements();
for (; enum1.hasMoreElements(); ) {
System.out.println((Object)enum1.nextElement());
}
得られた結果は、次のような形式で出力されます。
path: /C:/eclipse/workspace/Permissions/TestTwo/bin (java.util.PropertyPermission java.version read) (java.util.PropertyPermission java.vm.name read)
パーミッションのgrant
ブロックの構文、あるいはパーミッションそのものに何らかのエラーがあった場合、このユーティリティは解析エラーに関するメッセージを表示します。実際には、Policy
オブジェクトの取得要求は、コードの残りを実行することなく終了します。とは言うものの、このユーティリティで得られる、各々のコードベースのパーミッションを収集したリストは、自分が実装したセキュリティが想定どおりになっているかを確認する際に有用な情報となるはずです。
いったんSecurityManager
をアクティブにしてしまえば、Java 2の持つ柔軟かつ強力なセキュリティ機能を自由に使用することができます。「java.policy」と「java.security」ファイルは、もしかしたらXMLファイル形式として実装した方が便利なのかもしれませんが、現在の方法にも、可読性と管理性の点で優れているという長所があります。さらに、ここで見たように「java.policy」ファイルは、ビルド時にチェックを施すことで、実行時に想定外のセキュリティエラーが生じるのを避けることができます。JavaのPolicy
オブジェクトは、必要に応じて動作を拡張することもできますが、標準の設定オプションだけでもたいていのセキュリティ要件は満たせるはずです。