Java Standard Edition (J2SE) の次のリリースにおける Java セキュリティーアーキテクチャーの重要な拡張機能は、Kerberos Version 5 を使用したシングルサインオンを実現する機能です。シングルサインオンの解決策を利用すると、ユーザーは、1 回の認証で、複数のシステムのいずれかに存在する情報にアクセスできるようになります。これを実現するため、認証および承認には JAAS を、ピアアプリケーションとの通信用のセキュアなコンテキストの確立には Java GSS-API を、それぞれ使用します。ここでは、シングルサインオンの基盤となるセキュリティーメカニズムである Kerberos V5 に注目して解説します。ただし、将来、ほかのセキュリティーメカニズムも追加される可能性があります。
分散システムの使用が増加するにつれて、ユーザーがリモートのリソースにアクセスする必要性が高まっています。従来の方法では、ユーザーは複数のシステムにサインオンしなければならず、しかもシステムごとに使用するユーザー名や認証技術が異なる場合もあります。一方、シングルサインオンを利用すると、ユーザーが一度認証を行うだけで、認証された識別情報がセキュアにネットワーク内を通過し、ユーザーに代わってリソースへのアクセスが行われます。
このドキュメントでは、Kerberos V5 プロトコルに基づいたシングルサインオンを使用する方法を説明します。Kerberos に対するプリンシパルの認証、および識別情報を証明するクレデンシャルの取得には、Java 認証・承認サービス (JAAS) を使用します。Sun による Kerberos ログインモジュールを実装して、ネイティブの Kerberos をサポートするプラットフォーム上の既存のキャッシュからクレデンシャルを読み取る方法を示します。次に、Java Generic Security Service API (Java GSS-API) および取得済みの Kerberos クレデンシャルを使用して、リモートピアを認証します。また、多層環境でシングルサインオンの Kerberos クレデンシャルを委譲する方法も示します。
Kerberos V5 は、サードパーティーによる信頼性の高いネットワーク認証プロトコルで、秘密鍵暗号化を使用する強力な認証を提供します。Kerberos V5 を使用すると、Kerberos V5 の管理時を除き、ユーザーのパスワード (暗号化された状態も含む) がネットワーク内でやり取りされることがなくなります。Kerberos は、1980 年代半ばに MIT の Project Athena の一環として開発されました。Kerberos V5 プロトコルの完全な解説は、このドキュメントの目的ではありません。Kerberos V5 プロトコルの詳細は、[1] および [2] を参照してください。
Kerberos V5 は、成熟したプロトコルで、広範囲に実装されています。Solaris では SEAM として利用可能であり、また Windows 2000 やほかのプラットフォームでも利用可能です。C 言語による無料のリファレンス実装が、MIT により提供されています。J2SE におけるシングルサインオンの基盤となる技術として Kerberos V5 が選択されているのは、こうした理由によります。
最近まで、Java 2 セキュリティーアーキテクチャーが特権を決定する根拠は、コードの出所およびコード署名者に一致する公開鍵証明書だけでした。しかし、複数ユーザー環境では、コードを実行するユーザーの認証済み識別情報に基づいて、特権をさらに指定するのが望ましい方法です。
JAAS が提供するのは、この機能です。JAAS は、認証および認証済みの識別情報に基づくアクセス制御に特化した、プラグイン可能なフレームワークおよびプログラミングインタフェースです。
JAAS フレームワークは、認証コンポーネントと承認コンポーネントの 2 つに分けることができます。
JAAS 認証コンポーネントは、コードがアプリケーション、アプレット、Bean、サーブレットとして稼動しているかに関係なく、Java コードを実行しているユーザーを確実かつセキュアに判定する機能を提供します。
JAAS 承認コンポーネントは、コードソースおよびコードを実行するユーザーに基づき、Java コードが慎重を要するタスクの実行を制限する手段を提供して、既存の Java セキュリティーフレームワークを補完します。
JAAS 認証フレームワークは、Pluggable Authentication Module (PAM)[3]、[4] に基づいています。JAAS の認証はプラグイン可能な方法で行われるため、システム管理者は適切な認証モジュールを追加できます。これにより、Java アプリケーションは基盤となる認証技術に依存せずに済みます。また、アプリケーション自体を変更することなく、新規または更新された認証技術をシームレスに構成できます。
JAAS 認証フレームワークは、認証モジュールのスタックもサポートします。複数のモジュールを指定し、指定順にモジュールを JAAS フレームワークから呼び出すことができます。認証全体が成功するかどうかは、個別の認証モジュールの結果に依存しています。
JAAS では、「サブジェクト」という語は、リソースへのアクセス要求を発信するエンティティーを指して使用されます。サブジェクトは、ユーザーの場合もあれば、サービスの場合もあります。1 つのエンティティーは、多数の名前やプリンシパルを保持することが可能であるため、JAAS は、サブジェクトを、エンティティーごとの複数の名前を処理する特別な抽象レイヤーとして使用します。そのため、サブジェクトは一連のプリンシパルで構成されます。プリンシパル名には、制限が一切課されません。
サブジェクトは、認証済みのプリンシパルによってのみ生成されます。認証には、通常、アイデンティティーを証明するためにユーザーが提供する証拠 (パスワードなど) が含まれます。
さらに、サブジェクトは、「クレデンシャル」と呼ばれるセキュリティー関連の属性も保持します。クレデンシャルは、public あるいは private である場合もあります。非公開暗号化鍵などのサブジェクトの機密なクレデンシャルは、サブジェクトの private クレデンシャルセット内に格納されます。
Subject クラスは、プリンシパル、およびプリンシパルに関連付けられた public クレデンシャルや private クレデンシャルを取得するメソッドを保持します。
これらのクラスを操作するには、異なるアクセス権が必要な場合があります。たとえば、サブジェクトのプリンシパルセットを変更するには、AuthPermission("modifyPrincipals") が必要な場合があります。public クレデンシャルや private クレデンシャルの変更、および現行のサブジェクトの取得にも、同様のアクセス権が必要です。
Java 2 は、java.lang.SecurityManager を介して実行時アクセス制御を実行します。SecurityManager は、重要な操作が試行されるたびに参照されます。SecurityManager は、このタスクを java.security.AccessController に委譲します。AccessController は、AccessControlContext の現行のイメージを取得して、AccessControlContext が要求された操作を実行可能なアクセス権を保持していることを確認します。
JAAS は、2 つのメソッド doAs および doAsPrivileged を提供します。これらのメソッドを使用すると、認証済みサブジェクトを AccessControlContext に動的に関連付けることができます。
doAs メソッドは、サブジェクトを現行スレッドのアクセス制御コンテキストに関連付けます。そのあとのアクセス制御は、実行中のコードおよびそれを実行するサブジェクトに基づいて行われます。
public static Object doAs(final Subject subject, final PrivilegedAction action) public static Object doAs(final Subject subject, final PrivilegedExceptionAction action) throws PrivilegedActionException;
どちらの形式の doAs メソッドも、指定されたサブジェクトを現行スレッドの AccessControlContext に関連付けてから、アクションを実行します。これにより、サブジェクトと同じようにアクションを実行できます。最初のメソッドでは、ランタイム例外がスローされる可能性がありますが、通常の実行では、action 引数を指定して run() メソッドを実行して得られた Object が返されます。2 番目のメソッドの動作も同様です。ただし、run() メソッドにより、チェックされた PrivilegedActionException がスローされる場合がある点が異なります。doAs メソッドを呼び出すには、AuthPermission("doAs") を指定する必要があります。
次のメソッドでも、特定のサブジェクトとしてコードが実行されます。
public static Object doAsPrivileged(final Subject subject, final PrivilegedAction action, final AccessControlContext acc); public static Object doAsPrivileged(final Subject subject, final PrivilegedExceptionAction action, final AccessControlContext acc) throws PrivilegedActionException;
doAsPrivileged メソッドの動作は、呼び出し側がアクセス制御コンテキストを指定可能である点を除き、doAs と同様です。これにより、現行の AccessControlContext は事実上無視されるため、渡された AccessControlContext に基づいて承認が決定されます。
AccessControlContext はスレッドごとに設定されるため、JVM 内の個別のスレッドは、異なる識別情報を保持するものと見なされます。特定の AccessControlContext に関連付けられたサブジェクトは、次のメソッドを使用して取得できます。
public static Subject getSubject(final AccessControlContext acc);
LoginContext クラスは、サブジェクトの認証に使用する基本的なメソッドを提供します。このクラスを使用すると、アプリケーションを基盤となる認証技術から独立させることができます。ログインコンテキストは、構成を調べて、特定アプリケーション用に構成された認証サービスまたはログインモジュールを判別します。アプリケーションが特定のエントリを保持しない場合、「other」として識別されるデフォルトエントリが指定されます。
ログインモジュールのスタック特性をサポートするために、ログインコンテキストは認証を 2 段階で実行します。最初のログイン段階では、構成済みの各ログインモジュールを呼び出して、認証を試みます。必要なログインモジュールがすべて成功すると、ログインコンテキストは第 2 段階に入り、各ログインモジュールを再度呼び出して認証プロセスを正式にコミットします。この段階で、認証されたプリンシパルおよびそのクレデンシャルを使ってサブジェクトが生成されます。いずれかの段階が失敗すると、ログインコンテキストは構成済みの各モジュールを呼び出して、認証全体を中止します。次に、各ログインモジュールは、認証に関連する状態をすべてクリーンアップします。
ログインコンテキストは、インスタンス化に使用可能な 4 つのコンストラクタを保持します。どのコンストラクタでも、構成エントリ名の引き渡しが必要です。さらにサブジェクトや CallbackHandler をコンストラクタに渡すことも可能です。
JAAS により呼び出されるログインモジュールは、認証用の情報を呼び出し側から収集できなければなりません。たとえば、Kerberos ログインモジュールは、認証用の Kerberos パスワードの入力をユーザーに要求します。
ログインコンテキストを使用すると、基盤となるログインモジュールがユーザーとの対話に使用する callbackhandler をアプリケーションから指定できます。J2SE 1.4 では、コマンド行ベースと GUI ベースの 2 つのコールバックハンドラを使用できます。
Sun は、J2SE 1.4 で UnixLoginModule、NTLoginModule、JNDILoginModule、KeyStoreLoginModule、および Krb5LoginModule の実装を提供します。スマートカードベースの JAAS ログインモジュールは、GemPlus [5] 以降で使用可能です。
com.sun.security.auth.module.Krb5LoginModule クラスは、Sun による Kerberos バージョン 5 プロトコル用のログインモジュール実装です。認証が成功すると、Ticket Granting Ticket (TGT) がサブジェクトの private クレデンシャルセットに格納され、Kerberos のプリンシパルがサブジェクトのプリンシパルセットに格納されます。
構成可能な特定のオプションを指定することにより、Krb5LoginModule では、既存のクレデンシャルキャッシュ (オペレーティングシステム内のネイティブキャッシュなど) を使用した TGT の取得や、秘密鍵を含むキータブファイルを使用したプリンシパルの暗黙的な認証も実行可能です。Solaris および Windows 2000 プラットフォームの両方には、Krb5LoginModule が TGT の取得に使用可能なクレデンシャルキャッシュが含まれます。Solaris には、Krb5LoginModule が秘密鍵の取得に使用可能なシステム規模のキータブファイルも含まれます。Krb5LoginModule は、すべてのプラットフォームで、選択したチケットキャッシュまたはキータブファイルにファイルパスを設定するオプションをサポートします。サードパーティー製の Kerberos サポートがインストールされており、Java の統合が望まれる場合、これは有用な方法です。これらのオプションの詳細は、Krb5LoginModule のドキュメントを参照してください。ネイティブキャッシュまたはキータブが存在しない場合、ユーザーには KDC から取得したパスワードおよび TGT の入力が求められます。
図 1 は、クライアントアプリケーション用 JAAS ログイン構成エントリのサンプルを示します。この例では、Krb5LoginModule はネイティブチケットキャッシュを使用して、内部で使用可能な TGT を取得します。認証された識別情報は、TGT が所属する Kerberos プリンシパルの識別情報になります。
SampleClient { com.sun.security.auth.module.Krb5LoginModule required useTicketCache=true }; Figure 1. Sample client configuration entry
図 2 は、サーバーアプリケーション用ログイン構成エントリのサンプルを示します。この構成では、プリンシパル「nfs/bar.example.com」の認証に、キータブから取得した秘密鍵が使用されます。また、Kerberos KDC から取得した TGT と秘密鍵の両方が、サブジェクトの private クレデンシャルセットに格納されます。格納された鍵は、クライアントから送信されるサービスチケットの検証に使用されます (Java GSS-API セクションを参照)。
SampleServer { com.sun.security.auth.module.Krb5LoginModule required useKeyTab=true storeKey=true principal="nfs/bar.example.com" }; Figure 2. Sample server configuration entry
図 3 に示すクライアントコード例では、LoginContext により構成エントリ「SampleClient」が使用されます。Kerberos パスワードの入力をユーザーに求めるため、TextCallbackHandler クラスが使用されます。ユーザーがログインを行うと、Kerberos プリンシパル名および TGT を使ってサブジェクトが生成されます。これ以後、ユーザーは Subject.doAs を使ってコードを実行できます。その結果は、LoginContext から取得したサブジェクトに渡されます。
LoginContext lc = null; try { lc = new LoginContext("SampleClient", new TextCallbackHandler()); // attempt authentication lc.login(); } catch (LoginException le) { ... } // Now try to execute ClientAction as the authenticated Subject Subject mySubject = lc.getSubject(); PrivilegedAction action = new ClientAction(); Subject.doAs(mySubject, action); Figure 3. Sample client code
ClientAction アクションを、指定された値を保持する認証済みの Kerberos クライアント Principal にのみ許可できます。
図 4 に、サーバー側のサンプルコードを示します。これは、アプリケーションのエントリ名および PrivilegedAction を除けば、図 3 のクライアントコードに類似しています。
LoginContext lc = null; try { lc = new LoginContext("SampleServer", new TextCallbackHandler()); // attempt authentication lc.login(); } catch (LoginException le) { ... } // Now try to execute ServerAction as the authenticated Subject Subject mySubject = lc.getSubject(); PrivilegedAction action = new ServerAction(); Subject.doAs(mySubject, action); Figure 4. Sample server code
ほかのベンダーが Java GSS-API で使用可能な独自の Kerberos ログインモジュール実装を提供できるようにするため、javax.security.auth.kerberos パッケージに 3 つの標準 Kerberos クラスが導入されました。これらは、Kerberos プリンシパル用の KerberosPrincipal、長期の Kerberos 秘密鍵用の KerberosKey、および Kerberos チケット用の KerberosTicket です。Kerberos ログインモジュールのすべての実装で、これらのクラスを使用して、プリンシパル、鍵、およびチケットをサブジェクト内に格納する必要があります。
サブジェクトの認証が成功したら、認証されたサブジェクトに関連付けられたプリンシパルに基づいてアクセス制御を実行できます。JAAS プリンシパルベースのアクセス制御は、Java 2 の CodeSource アクセス制御を強化します。サブジェクトに付与されるアクセス権は、システム規模のアクセス制御ポリシーを表す抽象クラスである Policy 内で構成されます。Sun は、Policy クラスの実装をファイルベースで提供します。Policy クラスはプロバイダベースであるため、他者が独自のポリシー実装を提供することが可能です。
エンタープライズアプリケーションは、多くの場合、さまざまなセキュリティー要件を保持します。そして、これらの要件を満たすため、広範な基盤技術を配備します。こうした状況下では、ある技術から別の技術への移行が容易なクライアント/サーバーアプリケーションをどのように開発するかということが問題になります。この問題を解決するため、IETF の Common Authentication Technology 作業部会により、ピアツーピア認証およびセキュアな通信のための統一アプリケーションプログラミングインタフェースである GSS-API が設計されました。GSS-API を使用することにより、呼び出し側を、基盤技術の詳細から分離できます。
RFC 2743 [6] にあるように、この API は言語非依存の形式で記述されているため、認証、メッセージ機密性および整合性、保護されたメッセージの順序付け、再実行の検出、およびクレデンシャルの委譲などのセキュリティーサービスに対応します。基盤となるセキュリティー技術または「セキュリティーメカニズム」は、本質的な一方向認証に加え、これらの機能の 1 つ以上をサポートする選択権を持っています。脚注 1
IETF により定義された標準セキュリティーメカニズムには、Kerberos V5 [6] および Simple Public Key Mechanism (SPKM) [8] の 2 つが存在します。
こうした API の設計により、実装で複数のメカニズムを同時にサポートできるため、アプリケーションは実行時にいずれかのメカニズムを選択できます。メカニズムは、IANA に登録された一意のオブジェクト識別子 (OID) により識別されます。たとえば、Kerberos V5 メカニズムは、{iso(1) member-body(2) United States(840) mit(113554) infosys(1) gssapi(2) krb5(2)} という OID で識別されます。
この API の別の重要な特徴は、これがトークンベースである点です。このため、アプリケーションは、API の呼び出しにより生成された不透明なオクテットを、ピアに転送する必要があります。これにより、転送に依存しない形で API を利用できます。
図 5 複数メカニズムの GSS-API 実装
Generic Security Service 用の Java API も IETF で定義されており、RFC 2853 [10] でドキュメント化されています。Sun は、Java Community Process (JCP) [11] の下でこの API の標準化を目指しており、また J2SE 1.4 でのリファレンス実装の提供を計画しています。JCP は、この外部で定義された API を単に支持するだけです。IETF により割り当てられたパッケージ名前空間「org.ietf.jgss」は、引き続き J2SE 1.4 内に保持されます。
初期出荷段階では、Sun による Java GSS-API 実装は、Kerberos V5 メカニズムのみをサポートします。Kerberos V5 メカニズムのサポートは、Java SE のすべての Java GSS-API 実装で義務付けられています。ほかのメカニズムをサポートするかどうかは任意です。将来のリリースでは、Service Provider Interface (SPI) が追加され、新たなメカニズムを静的または実行時に構成できるようになります。現在でも、J2SE 1.4 のリファレンス実装はモジュール化されており、非公開のプロバイダ SPI をサポートします。非公開のプロバイダ SPI は、標準化の際、公開プロバイダ SPI に変換されます。
Java GSS-API フレームワーク自体は thin であるため、セキュリティー関連の機能はすべて、基盤となるメカニズムから取得したコンポーネントへ委譲されます。GSSManager クラスは、インストール済みのメカニズムプロバイダをすべて認識するため、プロバイダを呼び出してこれらのコンポーネントを取得できます。
Java SE に含まれるデフォルトの GSSManager 実装は、次の方法で取得できます。
GSSManager manager = GSSManager.getInstance();
GSSManager は新規プロバイダを構成したり、現存するすべてのメカニズムをリストしたりするために使用されます。GSSManager は、3 つの重要なインタフェース、GSSName、GSSCredential、および GSSContext のファクトリクラスとしても機能します。これらのインタフェースおよびその実装をインスタンス化するメソッドについては、次に説明します。API 仕様全体については、[9] および [11] を参照してください。
大半の Java GSS-API 呼び出しでは、GSS-API フレームワーク内およびメカニズムプロバイダ内の両方で発生する問題をカプセル化する GSSException がスローされます。
このインタフェースは、Java GSS-API におけるエンティティーを表します。このインタフェース実装は、次の方法でインスタンス化されます。
GSSName GSSManager.createName(String name, Oid nameType) throws GSSException
例:
GSSName clientName = manager.createName("duke", GSSName.NT_USER_NAME);
この呼び出しにより、GSSName が返されます。これは、メカニズム非依存レベルのユーザープリンシパル「duke」を表します。内部的には、サポートされる各メカニズムが、ユーザーのジェネリック表現の、よりメカニズム固有の形式へのマッピングをサポートすることが前提です。たとえば、Kerberos V5 メカニズムプロバイダは、この名前を duke@EXAMPLE.COM にマッピングできます (EXAMPLE.COM はローカルの Kerberos レルム)。同様に、公開鍵ベースのメカニズムプロバイダは、この名前を X.509 識別名にマッピングします。
このドキュメントで、ユーザー以外の何らかのサービスとしてのプリンシパルに言及する場合、メカニズムがそれを異なる方法で解釈できるように Java GSS-API 呼び出しを行うことを意味しています。
例:
GSSName serverName = manager.createName("nfs@bar.example.com", GSSName.NT_HOSTBASED_SERVICE);
Kerberos V5 メカニズムは、この名前を Kerberos 固有の形式 nfs/bar.example.com@EXAMPLE.COM にマッピングします (EXAMPLE.COM はプリンシパルのレルム)。このプリンシパルは、ホストマシン bar.example.com で稼動中の nfs サービスを表します。
Sun による GSSName インタフェースの実装は、コンテナクラスです。コンテナクラスは、メカニズムの使用時にマッピングを行い、マッピングされた各要素をプリンシパルセットに格納するよう、各プロバイダに適宜要求します。この面で、GSSName の実装はサブジェクトに格納されたプリンシパルセットに似ています。この実装には、サブジェクトのプリンシパルセット内の要素と同じものを含めることもできますが、用途は Java GSS-API のコンテキストに限定されています。
Sun Kerberos V5 プロバイダにより格納される名前要素は、javax.security.auth.kerberos.KerberosPrincipal のサブクラスのインスタンスです。
このインタフェースは、任意の 1 つのエンティティーが所有するクレデンシャルをカプセル化します。GSSName と同様、このインタフェースも複数メカニズムコンテナです。
この実装は、次の方法でインスタンス化されます。
GSSCredential createCredential(GSSName name, int lifetime, Oid[] desiredMechs, int usage) throws GSSException
クライアント側での呼び出し例を、次に示します。
GSSCredential clientCreds = manager.createCredential(clientName, 8*3600, desiredMechs, GSSCredential.INITIATE_ONLY);
GSSManager は desiredMechs にリストされたメカニズムのプロバイダを呼び出して、GSSName clientName に所属するクレデンシャルを要求します。さらに、8 時間のライフタイムを要求し、アウトバウンド要求を開始可能なクレデンシャル (クライアントクレデンシャル) でなければならないという制限を課します。返されるオブジェクトには、この条件を満たすために使用可能なクレデンシャルを保持する desiredMechs のサブセットの要素が含まれます。Kerberos V5 メカニズムにより格納される要素は、ユーザーに属する TGT を含む javax.security.auth.kerberos.KerberosTicket のサブクラスのインスタンスです。
サーバー側でのクレデンシャル取得は、次のように実行されます。
GSSCredential serverCreds = manager.createCredential(serverName, GSSCredential.INDEFINITE_LIFETIME, desiredMechs, GSSCredential.ACCEPT_ONLY);
クライアントの場合も、同様の動作を実行します。ただし、要求するクレデンシャルが、着信要求 (サーバークレデンシャル) を受け付けできる点が異なります。さらに、通常サーバーは長期にわたり稼動するため、ライフタイムの長いクレデンシャル (ここに示した INDEFINITE_LIFETIME など) を要求します。格納された Kerberos V5 メカニズムの要素は、サーバーの秘密鍵を含む javax.security.auth.kerberos.KerberosKey のサブクラスのインスタンスです。
このステップでは、高い負荷がかかることがあります。さらに、通常、アプリケーションは、ライフタイムの間に使用する可能性のあるすべてのクレデンシャルへの参照を初期化時に取得します。
GSSContext インタフェースの実装は、2 つのピアにセキュリティーサービスを提供します。
クライアント側では、次の API 呼び出しで GSSContext 実装が取得されます。
GSSContext GSSManager.createContext(GSSName peer, Oid mech, GSSCredential clientCreds, int lifetime) throws GSSException
これにより、通信先のピアおよび通信に使用するメカニズムを認識する、初期化済みのセキュリティーコンテキストが返されます。クライアントのクレデンシャルは、ピアの認証に必要です。
サーバー側では、次の方法で GSSContext を取得できます。
GSSContext GSSManager.createContext(GSSCredential serverCreds) throws GSSException
これにより、受け入れ側の初期化済みセキュリティーコンテキストが返されます。この時点では、コンテキスト確立要求を送信するピア (クライアント) の名前、および使用する基盤となるメカニズムでさえも不明です。ただし、着信要求がクレデンシャル serverCreds により表されるサービスプリンシパル用でないか、クライアント側が要求する基盤となるメカニズムが serverCreds 内にクレデンシャル要素を保持しない場合、要求は失敗します。
GSSContext をセキュリティーサービスで使用する前に、2 つのピア間でトークンを交換して GSSContext を確立する必要があります。コンテキスト確立メソッドへの各呼び出しにより、不透明なトークンが生成されます。アプリケーションは、このトークンを任意の通信チャンネルを使用してピアに送信する必要があります。
クライアントは、次の API 呼び出しを使用してコンテキストを確立します。
byte[] GSSContext.initSecContext(byte[] inToken, int offset, int len) throws GSSException
サーバーは次の呼び出しを使用します。
byte[] acceptSecContext(byte[] inToken, int offset, int len) throws GSSException
これら 2 つのメソッドは相補的な関係にあり、一方で生成された出力が、他方で入力として受け入れられます。クライアントがはじめて initSecContext を呼び出すと、最初のトークンが生成されます。呼び出し時に、このメソッドの引数は無視されます。最後に生成されるトークンは、使用するセキュリティーメカニズムの詳細、および確立されたコンテキストのプロパティーによって異なります。
ピアの認証に必要な GSS-API トークンの往復回数は、メカニズムごと、また特性ごと (相互認証か一方向の認証かなど) に異なります。このため、アプリケーションの各側は、コンテキスト確立メソッドの呼び出しを、プロセスが完了するまで、ループ内で継続して実行する必要があります。
Kerberos V5 メカニズムの場合、コンテキスト確立時にトークンは 1 往復するだけです。クライアントは、最初に initSecContext() により生成されたトークン (Kerberos AP-REQ メッセージ [2] を含む) を送信します。AP-REQ メッセージを生成するために、Kerberos プロバイダは、クライアントの TGT を使用してターゲットサーバー用のサービスチケットを取得します。サービスチケットは、サーバーの長期秘密鍵を使用して暗号化され、AP-REQ メッセージの一部としてカプセル化されます。サーバーはこのトークンを受け取ると、acceptSecContext() メソッドに送信します。このメソッドにより、サービスチケットの復号化およびクライアントの認証が行われます。相互認証が要求されなかった場合、コンテキストがクライアント側とサーバー側の両方で確立されます。サーバー側の acceptSecContext() は出力を生成しません。
ただし、相互認証が有効な場合、サーバーの acceptSecContext() は Kerberos AP-REP [2] メッセージを含む出力トークンを生成します。このトークンをクライアントに送り返し、initSecContext() を使って処理を行なってから、クライアント側コンテキストを確立する必要があります。
クライアント側で GSSContext を初期化する際、どの基盤となるメカニズムを使用するかは明白です。Java GSS-API フレームワークは、適切なメカニズムプロバイダからのコンテキスト実装を取得できます。このため、GSSContext オブジェクトに対する呼び出しはすべて、メカニズムのコンテキスト実装に委譲されます。サーバー側の場合、クライアント側からの最初のトークンが到着するまで、使用するメカニズムは決定されません。
次のクラスは、アプリケーションのクライアント側をコード化する方法を示します。これは、図 3 の doAs メソッドを使用して実行された ClientAction クラスです。
class ClientAction implements PrivilegedAction { public Object run() { ... ... try { GSSManager manager = GSSManager.getInstance(); GSSName clientName = manager.createName("duke", GSSName.NT_USER_NAME); GSSCredential clientCreds = manager.createCredential(clientName, 8*3600, desiredMechs, GSSCredential.INITIATE_ONLY); GSSName peerName = manager.createName("nfs@bar.example.com", GSSName.NT_HOSTBASED_SERVICE); GSSContext secContext = manager.createContext(peerName, krb5Oid, clientCreds, GSSContext.DEFAULT_LIFETIME); secContext.requestMutualAuth(true); // The first input token is ignored byte[] inToken = new byte[0]; byte[] outToken = null; boolean established = false; // Loop while the context is still not established while (!established) { outToken = secContext.initSecContext(inToken, 0, inToken.length); // Send a token to the peer if one was generated if (outToken != null) sendToken(outToken); if (!secContext.isEstablished()) { inToken = readToken(); else established = true; } } catch (GSSException e) { .... } ... ... } } Figure 6. Sample client using Java GSS-API
次に、図 4 の ServerAction クラスを実行するサーバー側に対応するコードセクションを示します。
class ServerAction implelemts PrivilegedAction { public Object run() { ... ... try { GSSManager manager = GSSManager.getInstance(); GSSName serverName = manager.createName("nfs@bar.example.com", GSSName.NT_HOSTBASED_SERVICE); GSSCredential serverCreds = manager.createCredential(serverName, GSSCredential.INDEFINITE_LIFETIME, desiredMechs, GSSCredential.ACCEPT_ONLY); GSSContext secContext = manager.createContext(serverCreds); byte[] inToken = null; byte[] outToken = null; // Loop while the context is still not established while (!secContext.isEstablished()) { inToken = readToken(); outToken = secContext.acceptSecContext(inToken, 0, inToken.length); // Send a token to the peer if one was generated if (outToken != null) sendToken(outToken); } } catch (GSSException e) { ... } ... ... } } Figure 7. Sample server using Java GSS-API
セキュリティーコンテキストを確立すると、それを使用してメッセージを保護できるようになります。Java GSS-API は、メッセージの整合性および機密性の両方を提供します。これを実現する 2 つの呼び出しを、次に示します。
byte[] GSSContext.wrap(byte[] clearText, int offset, int len, MessageProp properties) throws GSSExceptionおよび
byte[] unwrap(byte[] inToken, int offset, int len, MessageProp properties) throws GSSException
wrap メソッドを使用して、クリアテキストメッセージをトークン内にカプセル化し、整合性を保護します。properties オブジェクトを介してこれを要求することにより、メッセージを暗号化することもできます (オプション)。wrap メソッドは、不透明なトークンを返します。このトークンは、呼び出し側によりピアに送信されます。トークンがピアに渡されると、ピアの unwrap メソッドにより元のクリアテキストが返されます。unwrap 側の properties オブジェクトは、メッセージの整合性が保護されているだけなのか、暗号化も行われているかを示す情報を返します。このオブジェクトには順序付けも含まれます。また、トークンの警告の複製も行います。
Java GSS-API を使用すると、クライアントがクレデンシャルをサーバーにセキュアに委譲できるようになるため、サーバーはクライアントのためにほかのセキュリティーコンテキストを開始できます。この機能は、多層環境でシングルサインオンを実行する場合に有用です。
図 8 クレデンシャルの委譲
クライアントは、initSecContext() の呼び出しを最初に実行する前に、クレデンシャルの委譲を要求します。
void GSSContext.requestCredDeleg(boolean state) throws GSSExceptionその際、state を true に設定します。
サーバーは、コンテキストの確立後に、委譲されたクレデンシャルを受け取ります。
GSSCredential GSSContext.getDelegCred() throws GSSException
次に、サーバーはクライアントを装って、この GSSCredential を GSSManager.createContext() に渡すことができます。
Kerberos V5 メカニズムの場合、委譲されるクレデンシャルとは、クライアントからサーバーに送信される最初のトークンの一部としてカプセル化される転送された TGT です。この TGT を使用して、サーバーはクライアントに代わり、ほかのサービス用のサービスチケットを取得できます。
アプリケーションが、GSSManager.createCredential() メソッドを使用してメカニズム固有のクレデンシャルを保持する GSSCredential オブジェクトを生成する方法については、すでに説明しました。次の 2 つのサブセクションでは、Java GSS-API メカニズムを使用してこれらのクレデンシャルを取得する方法に焦点を当てて説明します。メカニズム自体が、ユーザーのログインを実行することはありません。Java GSS-API を使用する前にログインを実行すること、およびメカニズムプロバイダにとって既知のキャッシュ内にクレデンシャルが格納されていることが前提となっています。GSSManager.createCredential() メソッドは、これらのクレデンシャルへの参照を取得し、GSS を中心とするコンテナである GSSCredential に格納して返すに過ぎません。
Java 2 プラットフォームでは、Java GSS-API メカニズムプロバイダがこれらの要素の取得に使用するクレデンシャルキャッシュはすべて、現行のアクセス制御コンテキストのサブジェクト内の public および private クレデンシャルセットでなければなりません。
このモデルには、アプリケーションの観点から、クレデンシャル管理が単純で予測しやすいという利点があります。適切なアクセス権を付与されたアプリケーションは、サブジェクト内のクレデンシャルを取り除いたり、標準 Java API を使用してクレデンシャルを更新したりできます。クレデンシャルを取り除いた場合には Java GSS-API メカニズムは失敗し、時間ベースのクレデンシャルを更新した場合にはメカニズムは成功します。
図 3 および図 6 のクライアントアプリケーションが Kerberos V5 のメカニズムを使用する場合の、クレデンシャル取得関連のイベントの発生順序を、次に示します。
サーバー側では、ステップ 2 の Kerberos ログインが成功すると、Krb5LoginModule が、KerberosTicket に加え、サーバー用の KerberosKey をサブジェクト内に格納します。そのあと、ステップ 5 - 7 で取得される KerberosKey を使用して、クライアントから送信されるサービスチケットの暗号解読が行われます。
Java GSS-API 用のデフォルトクレデンシャル取得モデルでは、クレデンシャルが現行のサブジェクト内に存在する必要があります。一般に、JAAS ログイン後、クレデンシャルはアプリケーションによりこの場所に配置されます。
時には、アプリケーションで、サブジェクト外の Kerberos クレデンシャルを使用することが望まれる場合があります。この場合、Krb5LoginModule が読み取りを行うように構成するか、読み取りを行うカスタムログインモジュールを記述して、この種のクレデンシャルを初期 JAAS ログインの一部として読み取ることが推奨されています。ただし、アプリケーションの中には、Java GSS-API を呼び出す前に JAAS を使用できない、または現行のサブジェクトからクレデンシャルを取得しない Kerberos メカニズムプロバイダの使用を強制されるものがあります。
ほかのアプリケーション用の標準モデルを維持しつつ、このような場合に対処するため、システムプロパティー javax.security.auth.useSubjectCredsOnly が追加されました。これは boolean 型のシステムプロパティーで、値が true の場合は標準クレデンシャル取得モデルが使用され、false の場合はプロバイダに任意のキャッシュの使用が許可されます。このプロパティーのデフォルト値 (設定しない場合) は、true です。
現行のサブジェクト内に有効な Kerberos クレデンシャルが存在せず、このプロパティーが true の場合、Kerberos メカニズムは GSSException をスローします。このプロパティーを false に設定しても、プロバイダが現行のサブジェクト以外のキャッシュを使用しなければならないわけではなく、必要な場合には使用可能であることを意味するに過ぎません。
Kerberos V5 GSS-API メカニズム用の Sun プロバイダは、常にサブジェクトからクレデンシャルを取得します。現行のサブジェクトに有効なクレデンシャルが存在せず、このプロパティーが false に設定されている場合、プロバイダは JAAS ログイン自体を呼び出すことにより、一時的なサブジェクトから新規クレデンシャルの取得を試みます。ユーザーに対する入力/出力用のテキストコールバックハンドラが使用されます。また、使用するモジュールおよびオプションリストの JAAS 構成エントリ (「other」で識別される) が使用されます。脚注 2
Kerberos V5 GSS-API メカニズムの Sun プロバイダでは、これらのモジュールのいずれかが Kerberos ログインモジュールになることが前提です。「other」の下にリストされたモジュールを構成して既存のキャッシュの読み取りを行い、Java GSS-API の呼び出し中にユーザーが予期しない方法でパスワードの入力を求められることがないようにできます。このログインにより生成された新規サブジェクトは、要求されたクレデンシャルが取得されるとすぐに、Kerberos GSS-API メカニズムにより破棄されます。
Java シングルサインオンを利用する必要のあるアプリケーションの重要なクラスは、アプレットです。ここでは、ブラウザ JRE が必要なすべてのパッケージを保持するか、ユーザーのインストールした J2SE 1.4 の JRE とともに Java プラグインが使用されるものと仮定します。
アプレットを使用する上で 1 つの複雑な問題は、アプレットが、Java GSS-API の使用前に JAAS ログインを実行する必要があるという点にあります。このことに関する主要な問題は、(a) アプレット開発者に求められる労力の増加、および (b) 同一のユーザーがアプレットを起動するたびにログインが不必要に繰り返し実行されることです。
この問題を解決するのは、起動時にブラウザ (または Java プラグイン) が JAAS ログインを一度実行するようなモデルです。この場合、どのような Java コードの実行時にも、アクセス制御コンテキストへの関連付けが常に可能なサブジェクトが提供されます。結果として、アプレットコードは、Java GSS-API の使用前に JAAS ログインを実行する必要はなくなり、ユーザーログインは一度だけ行われます。
ブラウザ (または Java プラグイン) にこのログイン機能が存在しない場合でも、アプレットは JAAS ログインの実行を回避できます。これには、アプレットでシステムプロパティー javax.security.auth.useSubjectCredsOnly を false に設定し、現行のサブジェクト以外のソースからクレデンシャルを取得可能な GSS-API メカニズムプロバイダを使用する必要があります。Sun Kerberos GSS-API プロバイダと Sun JRE を併用する場合、メカニズムにより JAAS ログインが実行されて、前のセクションで説明した新規クレデンシャルが取得されることを期待できます。アプレットデプロイヤが行う必要があるのは、適切なモジュールおよびオプションが、JRE の使用する JAAS 構成の「other」エントリリストに記載されていることの確認だけです。これにより、アプレットの開発者は、JAAS API を直接呼び出す必要がなくなりますが、ユーザーが各アプレットを実行するたびに JAAS ログインが繰り返し発生するのを防ぐことはできません。ただし、ログインモジュールを構成して既存のネイティブキャッシュを読み取ることにより、デプロイヤはログインをユーザーから隠すと同時に、複数のログインによるオーバーヘッドを抑えることができます。(JAAS 構成エントリ「SampleClient」でこれを行う方法については、図 1 を参照)。
シングルサインオンによる利便性向上により、新たな危険も生まれます。悪意のあるユーザーが、だれも操作していないデスクトップにアクセスし、通常のユーザーと同様にアプレットを起動できるとしたらどうでしょうか。また、悪意のあるアプレットが、通常のユーザーと同様にサービスへのサインオンを実行する場合、しかも悪意のあるアプレットによるサービスへのサインオンが想定されていないとしたらどうでしょうか。
前者の場合、ユーザーに対し、ワークステーションをロックせずに席を立つことがないよう注意する以外に解決策はありません。後者の場合、多数の承認チェックが配備されています。
アクセス権モデルの詳細を理解するため、ブラウザが起動時に JAAS ログインを実行し、サブジェクトをその内部で実行するすべてのアプレットに関連付けた場合を考えましょう。
サブジェクトは、javax.security.auth.AuthPermission クラスにより、破壊行為を行うアプレットから保護されます。コードにより、いずれかのアクセス制御コンテキストに関連付けられたサブジェクトへの参照取得が試みられる場合、常にこのアクセス権のチェックが行われます。
アプレットがサブジェクトへのアクセス権を付与されている場合でも、その内部に格納された機密の private クレデンシャルを実際に読み取るには、javax.security.auth.PrivateCredentialPermission が必要になります。
クレデンシャルの所有者に代わって、クレデンシャルの読み取りおよびセキュリティーコンテキストの確立を行う場合、Java GSS-API メカニズムプロバイダにより、ほかの種類のチェックも行う必要があります。Kerberos V5 メカニズムをサポートするため、次の 2 つのアクセス権クラスが javax.security.auth.kerberos パッケージに新たに追加されました。
ServicePermission(String servicePrinicipal, String action) DelegationPermission(String principals)
新規 GSS-API メカニズムは Java SE 用に標準化されているため、このメカニズムのプロバイダに対応した関連するアクセス権クラスを含む、より多くのパッケージが今後追加される予定です。
Kerberos GSS-API メカニズムでは、プログラム実行時に、次のポイントでアクセス権チェックが行われます。
GSSManager.createCredential() メソッドは、現行のサブジェクトなどのキャッシュからメカニズム固有のクレデンシャル要素を取得して、GSSCredential コンテナに格納します。GSSCredential の自由な取得をアプレットに許可することは、アプレットが GSSCredential を使用して多くのことを行えないとしても、望ましいことではありません。そうすることは、ユーザーおよびサービスプリンシパルの存在に関する情報をリークしてしまうことになります。このため、アプリケーションが、内部のいずれかの Kerberos クレデンシャル要素を使って GSSCredential を取得する前に、ServicePermission のチェックが行われます。
クライアント側では、GSSCredential 取得成功は、キャッシュから TGT へのアクセスが行われたことを意味します。このため、次の ServicePermission がチェックされます。
ServicePermission("krbtgt/EXAMPLE.COM@EXAMPLE.COM", "initiate");
サービスプリンシパル krbtgt/EXAMPLE.COM@EXAMPLE.COM は、Kerberos レルム EXAMPLE.COM 内のチケット交付サービス (TGS) を表します。また、アクション "initiate" は、このサービスへのチケットがアクセス中であることを示します。クライアント側のクレデンシャル取得時に、TGS サービスプリンシパルは、このアクセス権チェックで常に使用されます。
サーバー側では、GSSCredential 取得成功は、キャッシュから秘密鍵へのアクセスが行われたことを意味します。このため、次の ServicePermission がチェックされます。
ServicePermission("nfs/bar.example.com@EXAMPLE.COM", "accept");
ここで、サービスプリンシパル nfs/bar.example.com は Kerberos サービスプリンシパルを、アクション "accept" はこのサービスの秘密鍵が要求されていることを、それぞれ表します。
特定サーバー (例、LDAP サーバー) への接続用アクセス権を保持するアプレットは、別のサーバー (例、FTP サーバー) に接続してはいけません。当然、SocketPermission の働きにより、アプレットがこの種の動作を行うことは制限されます。ただし、ネットワーク接続が許可された場合でも、ServicePermission を使用して、識別情報による認証を制限することは可能です。
Kerberos メカニズムプロバイダは、コンテキストの確立を開始する際に、ServicePermission をチェックします。
ServicePermission("ftp@EXAMPLE.COM", "initiate");
これにより、承認されていないコードによる、プリンシパル ftp@EXAMPLE.COM に対する Kerberos サービスチケットの取得および使用を防ぐことができます。
このアクセス権を使用して、特定のサービスプリンシパルへの制限されたアクセスを提供することは、やはり危険です。ダウンロードされたコードには、コードの出所であるホストとの通信が許可されます。悪意のあるアプレットが、ターゲットサービスプリンシパルの長期秘密鍵で暗号化された KerberosTicket を含む初期 GSS-API 出力トークンを送り返す可能性があります。このため、オフラインの辞書攻撃にさらされる危険性があります。この理由で、信頼できないサイトからダウンロードされたコードに、ServicePermission への「initiate」操作を許可することは、お勧めできません。
サーバー側では、着信するセキュリティーコンテキスト確立要求を秘密鍵を使用して受け入れるためのアクセス権は、クレデンシャル取得時にチェック済みです。このため、コンテキスト確立段階では、チェックは行われません。
ユーザーに代わってサーバーとのセキュリティーコンテキストを確立するアクセス権を保持するアプレットは、サーバーへのクレデンシャルの委譲を要求できます。ただし、すべてのサーバーが、クレデンシャルを委譲できるだけの信頼性を保持するわけではありません。このため、Kerberos プロバイダは、委譲されたクレデンシャルを取得してピアに送信する前に、次のアクセス権をチェックします。
DelegationPermission(" \"ftp@EXAMPLE.COM\" \"krbtgt/EXAMPLE.COM@EXAMPLE.COM\" ");
このアクセス権により、Kerberos サービスプリンシパル ftp@EXAMPLE.COM は、転送された TGT (チケット交付サービス krbtgt/EXAMPLE.COM@EXAMPLE.COM により表される) を受信できます。脚注 3
このドキュメントでは、Java でシングルサインオンを可能にするフレームワークについて解説しました。これには、初期認証を行なってクレデンシャルを取得する JAAS と、クレデンシャルを使用してセキュアな回線通信を行う Java GSS-API との間で、クレデンシャルの共有が必要です。ここでは、基盤となるセキュリティーシステムとして、Kerberos V5 に焦点を当てて説明しました。ただし、JAAS のスタックアーキテクチャーおよび Java GSS-API の複数メカニズム対応のために、任意の数の異なるメカニズムを同時に使用できます。
JAAS 用の Kerberos ログインモジュールは、ネイティブキャッシュの読み取りが可能であるため、Kerberos をサポートするプラットフォームのデスクトップにログインする場合以外、ユーザーは自らを認証する必要がありません。さらに、Java GSS-API 用の Kerberos V5 メカニズムを使用することにより、クレデンシャルを委譲して、多層環境へのシングルサインオンを可能にできます。
最後に、Kerberos の提供するシングルサインオン機能が承認なしで使用されることを防ぐ、多数のアクセス権チェックを紹介しました。
Kerberos シングルサインオンプロジェクトの各段階での貢献に対し、Gary Ellison、Charlie Lai、および Jeff Nisewanger に感謝します。JAAS 1.0 は、Charlie により、J2SE 1.3 のオプションパッケージとして実装されました。Kerberos Java GSS-API メカニズムのアクセス権モデルを設計する際、Gary が尽力してくれました。JAAS 1.0 の J2SE 1.4 への統合に関してフィードバックを送ってくれた Bob Scheifler に、また KeyStoreLoginModule および CallbackHandler 実装に関して尽力してくれた Tim Blackman に感謝します。また、コメントや提案をくださった、Bruce Rich、Tony Nadalin、Thomas Owusu、Yanni Zhang の諸氏にも感謝します。ドキュメントやチュートリアルの面で援助してくれた Mary Dageforde に感謝します。Sriramulu Lakkaraju、Stuart Ke、および Shital Shisode は、プロジェクトのテストを担当してくれました。Maxine Erlund は、プロジェクト管理をサポートしてくれました。
脚注 1 GSS-API Kerberos メカニズムは、最低限、クライアント認証を実行します。
脚注 2 最初に、クライアントに対して JAAS 構成エントリ「com.sun.security.jgss.initiate」の使用、サーバーに対して「com.sun.security.jgss.accept」の使用が試みられます。これらのエントリが存在しない場合、「other」のエントリが使用されます。これにより、システム管理者は、その動作をより細かく制御できるようになります。
脚注 3 アクセス権で 2 つのプリンシパル名を使用すると、よりきめの細かい委譲を行えます。たとえば、TGT に転送される無条件の許可とは異なる、特定のサービス用のプロキシチケットなどが可能になります。GSS-API がプロキシチケットを許可しない場合でも、別の API (JSSE など) が将来この機能をサポートする可能性があります。