Sun Microsystems, Inc.
JavaTM 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 }; 図 1 クライアントの構成エントリの例
図 2 は、サーバアプリケーション用ログイン構成エントリのサンプルを示します。 この構成では、プリンシパル "nfs/bar.foo.com" の認証に、キータブから取得した秘密鍵が使用されます。また、Kerberos KDC から取得した TGT と秘密鍵の両方が、サブジェクトの private クレデンシャルセットに格納されます。 格納された鍵は、クライアントから送信されるサービスチケットの検証に使用されます (Java GSS-API セクションを参照)。
SampleServer { com.sun.security.auth.module.Krb5LoginModule required useKeyTab=true storeKey=true principal="nfs/bar.foo.com" }; 図 2 サーバの構成エントリの例
図 3 に示すクライアントコード例では、LoginContext により構成エントリ "SampleClient" が使用されます。 Kerberos パスワードの入力をユーザに求めるため、TextCallbackHandler クラスが使用されます。 ユーザがログインを行うと、Kerberos プリンシパル名および TGT を使ってサブジェクトが生成されます。 これ以後、ユーザは Subject.doAs を使ってコードを実行できます。その結果は、ログインコンテキストから取得したサブジェクトに渡されます。
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); 図 3 クライアントコードの例
ClientAction 操作を、指定された値を保持する認証済みの Kerberos クライアントプリンシパルにのみ許可することができます。
図 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); 図 4 サーバコードの例
他のベンダーが 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 機構のサポートは、J2SE のすべての Java GSS-API 実装で義務付けられています。他の機構をサポートするかどうかは任意です。 将来のリリースでは、Service Provider Interface (SPI) が追加され、新たな機構を静的または実行時に構成できるようになります。 現在でも、J2SE 1.4 のリファレンス実装はモジュール化されており、非公開プロバイダ SPI をサポートします。非公開プロバイダ SPI は、標準化の際、公開プロバイダ SPI に変換されます。
Java GSS-API フレームワーク自体は thin であるため、セキュリティ関連の機能はすべて、基盤となる機構から取得したコンポーネントへ委譲されます。 GSSManager クラスは、インストール済みの機構プロバイダをすべて認識するため、プロバイダを呼び出してこれらのコンポーネントを取得できます。
J2SE に含まれるデフォルトの 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@FOO.COM にマッピングできます (FOO.COM はローカルの Kerberos 領域)。 同様に、公開鍵ベースの機構プロバイダは、この名前を X.509 識別名にマッピングします。
このドキュメントで、ユーザ以外の何らかのサービスとしてのプリンシパルに言及する場合、機構がそれを異なる方法で解釈できるように Java GSS-API 呼び出しを行うことを意味しています。
例:
GSSName serverName = manager.createName("nfs@bar.foo.com", GSSName.NT_HOSTBASED_SERVICE);
Kerberos V5 機構は、この名前を Kerberos 固有の形式 nfs/bar.foo.com@FOO.COM にマッピングします (FOO.COM はプリンシパルの領域)。 このプリンシパルは、ホストマシン bar.foo.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.foo.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) { .... } ... ... } } 図 6 Java GSS-API を使用するクライアントの例
以下に、図 4 の ServerAction クラスを実行するサーバ側に対応するコードセクションを示します。
class ServerAction implelemts PrivilegedAction { public Object run() { ... ... try { GSSManager manager = GSSManager.getInstance(); GSSName serverName = manager.createName("nfs@bar.foo.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) { ... } ... ... } } 図 7 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() の呼び出しを最初に実行する前に、次のように state を true に設定してクレデンシャルの委譲を要求します。
void GSSContext.requestCredDeleg(boolean state) throws GSSException
サーバは、コンテキストの確立後に、委譲されたクレデンシャルを受け取ります。
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 ログインモジュールになることが前提です。 "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 機構は J2SE 用に標準化されているため、この機構のプロバイダに対応した関連するアクセス権クラスを含む、より多くのパッケージが今後追加される予定です。
Kerberos GSS-API 機構では、プログラム実行時に、次のポイントでアクセス権チェックが行われます。
GSSManager.createCredential() メソッドは、現行のサブジェクトなどのキャッシュから機構固有のクレデンシャル要素を取得して、GSSCredential コンテナに格納します。 GSSCredential の自由な取得をアプレットに許可することは、アプレットが GSSCredential を使用して多くのことを行えないとしても、望ましいことではありません。 そうすることは、ユーザおよびサービスプリンシパルの存在に関する情報をリークしてしまうことになります。 このため、アプリケーションが、内部のいずれかの Kerberos クレデンシャル要素を使って GSSCredential を取得する前に、ServicePermission のチェックが行われます。
クライアント側では、GSSCredential 取得成功は、キャッシュから TGT へのアクセスが行われたことを意味します。 このため、次の ServicePermission がチェックされます。
ServicePermission("krbtgt/FOO.COM@FOO.COM", "initiate");
サービスプリンシパル krbtgt/FOO.COM@FOO.COM は、Kerberos 領域 FOO.COM 内のチケット交付サービス (TGS) を表します。また、操作 "initiate" は、このサービスへのチケットがアクセス中であることを示します。 クライアント側のクレデンシャル取得時に、TGS サービスプリンシパルは、このアクセス権チェックで常に使用されます。
サーバ側での GSSCredential の取得成功は、 キャッシュから秘密鍵へのアクセスが行われたことを意味します。 このため、次の ServicePermission がチェックされます。
ServicePermission("nfs/bar.foo.com@FOO.COM", "accept");
ここで、サービスプリンシパル nfs/bar.foo.com は Kerberos サービスプリンシパルを、操作 "accept" はこのサービスの秘密鍵が要求されていることを、それぞれ表します。
特定サーバ (例、LDAP サーバ) への接続用アクセス権を保持するアプレットは、別のサーバ (例、FTP サーバ) に接続してはなりません。 当然、SocketPermission の働きにより、アプレットがこの種の動作を行うことは制限されます。 ただし、ネットワーク接続が許可された場合でも、ServicePermission を使用して、識別情報の認証を制限することは可能です。
Kerberos 機構プロバイダは、コンテキストの確立を開始する際に、ServicePermission をチェックします。
ServicePermission("ftp@FOO.COM", "initiate");
これにより、承認されていないコードによる、プリンシパル ftp@FOO.COM に対する Kerberos サービスチケットの取得および使用を防ぐことができます。
このアクセス権を使用して、特定のサービスプリンシパルへの制限されたアクセスを提供するのは、やはり危険です。 ダウンロードされたコードには、コードの出所であるホストとの通信が許可されます。 悪意のあるアプレットが、ターゲットサービスプリンシパルの長期秘密鍵で暗号化された KerberosTicket を含む初期 GSS-API 出力トークンを送り返す可能性があります。このため、オフラインの辞書攻撃にさらされる危険性があります。 この理由で、信頼できないサイトからダウンロードされたコードに、ServicePermission への "initiate" 操作を許可することは、お勧めできません。
サーバ側では、着信するセキュリティコンテキスト確立要求を秘密鍵を使用して受け入れるためのアクセス権は、クレデンシャル取得時にチェック済みです。 このため、コンテキスト確立段階では、チェックは行われません。
ユーザに代わってサーバとのセキュリティコンテキストを確立するアクセス権を保持するアプレットは、サーバへのクレデンシャルの委譲を要求できます。 ただし、すべてのサーバが、クレデンシャルを委譲できるだけの信頼性を保持するわけではありません。 このため、Kerberos プロバイダは、委譲されたクレデンシャルを取得してピアに送信する前に、次のアクセス権をチェックします。
DelegationPermission(" ¥"ftp@FOO.COM¥" ¥"krbtgt/FOO.COM@FOO.COM¥" ");
このアクセス権により、Kerberos サービスプリンシパル ftp@FOO.COM は、転送された TGT (チケット交付サービス krbtgt/FOO.COM@FOO.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 など) が将来この機能をサポートする可能性があります。