Kerberos を使った Java のシングルサインオン

Mayank Upadhyay
Ram Marti

Sun Microsystems, Inc.

{mayank.upadhyay, ram.marti} @sun.com

ABSTRACT

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 V5 の管理時を除き、ユーザのパスワード (暗号化された状態も含む) がネットワーク内でやり取りされることがなくなります。Kerberos は、1980 年代半ばに MIT の Project Athena の一環として開発されました。Kerberos V5 プロトコルの完全な解説は、このドキュメントの目的ではありません。Kerberos V5 プロトコルの詳細は、 [1] および [2] を参照してください。

Kerberos V5 は、成熟したプロトコルで、広範囲に実装されています。Solaris では SEAM として利用可能であり、また Windows 2000 や他のプラットフォームでも利用可能です。C 言語による無料のリファレンス実装が、MIT により提供されています。J2SE におけるシングルサインオンの基盤となる技術として Kerberos V5 が選択されているのは、こうした理由によります。

JAVA AUTHENTICATION AND AUTHORIZATION SERVICE (JAAS)

最近まで、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 資格の変更、および現行の被認証者の取得にも、同様のアクセス権が必要です。

doAs および doAsPrivileged

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

LoginContext クラスは、被認証者の認証に使用する基本的なメソッドを提供します。このクラスを使用すると、アプリケーションを基盤となる認証技術から独立させることができます。ログインコンテキストは、構成を調べて、特定アプリケーション用に構成された認証サービスまたはログインモジュールを判別します。アプリケーションが特定のエントリを保持しない場合、「other」として識別されるデフォルトエントリが指定されます。

ログインモジュールのスタック特性をサポートするために、ログインコンテキストは認証を 2 段階で実行します。最初のログイン段階では、構成済みの各ログインモジュールを呼び出して、認証を試みます。必要なログインモジュールがすべて成功すると、ログインコンテキストは第 2 段階に入り、各ログインモジュールを再度呼び出して認証プロセスを正式にコミットします。この段階で、認証された主体およびその資格を使って被認証者が生成されます。いずれかの段階が失敗すると、ログインコンテキストは構成済みの各モジュールを呼び出して、認証全体を中止します。次に、各ログインモジュールは、認証に関連する状態をすべてクリーンアップします。

ログインコンテキストは、インスタンス化に使用可能な 4 つのコンストラクタを保持します。どのコンストラクタでも、構成エントリ名の引き渡しが必要です。さらに被認証者や CallbackHandler をコンストラクタに渡すことも可能です。

コールバック

JAAS により呼び出されるログインモジュールは、認証用の情報を呼び出し側から収集できなければなりません。たとえば、Kerberos ログインモジュールは、認証用の Kerberos パスワードの入力をユーザに要求します。

ログインコンテキストを使用すると、基盤となるログインモジュールがユーザとの対話に使用する callbackhandler をアプリケーションから指定できます。 J2SE 1.4 では、コマンド行ベースと GUI ベースの 2 つのコールバックハンドラを使用できます。

LoginModule

Sun は、J2SE 1.4 で UnixLoginModule、NTLoginModule、JNDILoginModule、KeyStoreLoginModule、および Krb5LoginModule の実装を提供します。スマートカードベースの JAAS ログインモジュールは、GemPlus [5] 以降で使用可能です。

Kerberos ログインモジュール

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 から取得した被認証者に渡されます。

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 クライアント 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);

図 4 サーバコードの例

Kerberos クラス

他のベンダーが 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 クラスはプロバイダベースであるため、他者が独自のポリシー実装を提供することが可能です。

JAVA GENERIC SECURITY SERVICE APPLICATION PROGRAM INTERFACE (Java GSS-API)

Generic Security Service API (GSS-API)

エンタープライズアプリケーションは、多くの場合、さまざまなセキュリティ要件を保持します。そして、これらの要件を満たすため、広範な基盤技術を配備します。こうした状況下では、ある技術から別の技術への移行が容易なクライアント/サーバアプリケーションをどのように開発するかということが問題になります。この問題を解決するため、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 を利用できます。

多機構 GSS-API

図 5 多機構の GSS-API 実装

Java 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 がスローされます。

GSSName インタフェース

このインタフェースは、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 のサブクラスのインスタンスです。

GSSCredential インタフェース

このインタフェースは、任意の 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 インタフェース

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 の機構を使用する場合の、資格取得関連のイベントの発生順序を、次に示します。

  1. アプリケーションが JAAS ログインを呼び出し、JAAS ログインが構成済みの Krb5LoginModule を呼び出します。

  2. Krb5LoginModule は、KDC または既存のチケットキャッシュからユーザ用の TGT (KerberosTicket) を取得して、TGT を被認証者の private 資格セットに格納します。

  3. アプリケーションは、生成された被認証者を取得してから、Subject.doAs/doAsPrivileged を呼び出して、ClientAction を実行するスレッドのアクセス制御コンテキストにこの被認証者を配置します。

  4. ClientAction は GSSManager.createCredential メソッドを呼び出して、desiredMechs 内の Kerberos V5 OID を渡します。

  5. GSSManager.createCredential は Kerberos V5 GSS-API プロバイダを呼び出して、セキュリティコンテキストを開始するための Kerberos 資格を要求します。

  6. Kerberos プロバイダは、現行のアクセス制御コンテキストから被認証者を取得し、ユーザの TGT を表す有効な KerberosTicket のその private 資格セットを検索します。

  7. KerberosTicket が GSSManager に返されます。GSSManager は KerberosTicket を GSSCredential コンテナインスタンス内に格納して、呼び出し側に返します。

    サーバ側では、ステップ 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 機構により破棄されます。

    Web ブラウザの統合

    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. 『Kerberos: An Authentication Service for Computer Networks』第 39 巻 33 〜 38 ページ、Clifford Neuman、Theodore Tso 著、IEEE Communications 発行、1994 年

    2. 『The Kerberos Network Authentication Service (V5)』、J.Kohl、C.Neuman 著、Internet Engineering Task Force 発行、1993 年 9 月。RFC 1510

    3. 『Making Login Services Independent from Authentication Technologies』、V. Samar、C. Lai 著、SunSoft Developer's Conference 議事録、1996 年 3 月

    4. 『X/Open Single Sign-On Service (XSSO) - Pluggable Authentication』仮仕様 702 ページ、The Open Group、1997 年 6 月。http://www.opengroup.org

    5. 『A Smart Card Login Module for Java Authentication and Authorization Service』http://www.gemplus.com/techno/smartjaas/index.html

    6. 『Generic Security Service Application Program Interface,Version 2』、J. Linn 著、Internet Engineering Task Force 発行、2000 年 1 月。RFC 2743

    7. 『The Kerberos Version 5 GSS-API Mechanism』、J. Linn 著、Internet Engineering Task Force 発行、1996 年 6 月。RFC 1964

    8. 『The Simple Public-Key GSS-API Mechanism (SPKM)』、C.Adams 著、Internet Engineering Task Force 発行、1996 年 10 月。RFC 2025

    9. 『Generic Security Service API Version 2: Java Bindings』、J. Kabat、M.Upadhyay 著、Internet Engineering Task Force 発行、1997 年 1 月。RFC 2853

    10. 『JSR 000072 Generic Security Services API』http://java.sun.com/aboutJava/communityprocess/final-draft/jsr072/index.html

    11. 『JavaTM 2 Platform, Standard Edition, v 1.4 API 仕様』http://java.sun.com/j2se/1.4.2/docs/api/overview-summary.html -------------------------

      1 GSS-API Kerberos 機構は、最低限、クライアント認証を実行します。

      2最初に、クライアントに対して JAAS 構成エントリ「com.sun.security.jgss.initiate」の使用、サーバに対して「com.sun.security.jgss.accept」の使用が試みられます。これらのエントリが存在しない場合、「other」のエントリが使用されます。これにより、システム管理者は、その動作をより細かく制御できるようになります。

      3 アクセス権で 2 つの主体名を使用すると、よりきめの細かい委譲を行えます。たとえば、TGT に転送される無条件の許可とは異なる、特定のサービス用のプロキシチケットなどが可能になります。GSS-API がプロキシチケットを許可しない場合でも、別の API (JSSE など) が将来この機能をサポートする可能性があります。


      フィードバック

      最終更新日: 2001 年 5 月 2 日 (水) 12 時 54 分 31 秒