チュートリアルの紹介および目次 次のチュートリアル

Java GSS-APIを使用した、JAASプログラミングなしのセキュアなメッセージ交換

このチュートリアルでは、相互に通信するアプリケーション間でセキュアなメッセージ交換を実行するためのJava GSS-APIの使用方法を示す2つのサンプル・アプリケーションを紹介します。ここでは、クライアント・アプリケーションとサーバー・アプリケーションを例として使用しています。

Java GSS-APIは、「セキュリティ・メカニズム」と呼ばれるメカニズムを使用して、これらのサービスを提供します。 GSS-API実装には、Kerberos V5メカニズムのサポートと、他のベンダー固有の任意の選択肢が含まれます。 このチュートリアルでは、Kerberos V5メカニズムを使用しています。

クライアントとサーバー間の認証を実行し、セキュアな通信用の暗号化キーを確立するために、GSS-APIメカニズムは、接続の両側のローカル・エンティティ用の特定のクレデンシャルにアクセスする必要があります。 このチュートリアルでは、クライアント側で使用するクレデンシャルはKerberosチケットで構成され、サーバー側のクレデンシャルは長期Kerberos秘密キーで構成されます。 Kerberosチケットには、ホスト・アドレスをオプションで組み込むことができ、IPv4とIPv6の両方のホスト・アドレスがサポートされています。 Java GSS-APIは、メカニズムが、これらのクレデンシャルを、スレッドのアクセス制御コンテキストに関連付けられたサブジェクトから取得することを要求します。

通常、この種のクレデンシャルを持つサブジェクトを生成するため、クライアント・アプリケーションおよびサーバー・アプリケーションは、Kerberosモジュールを使用してJAAS認証を最初に実行します。 実行方法の詳細は、「JAAS認証」チュートリアルを参照してください。 認証されたサブジェクトをスレッドのアクセス制御コンテキストに関連付ける方法については、「JAAS承認」を参照してください。 これらの操作を自動的に実行するユーティリティも用意されています。 Loginユーティリティの使用方法については、「JAAS Loginユーティリティの使用」チュートリアルを参照してください。

このチュートリアルでは、クライアントおよびサーバーでJAAS認証を実行せず、Loginユーティリティを使用しません。 その代わり、システム・プロパティjavax.security.auth.useSubjectCredsOnlyfalseに設定する必要があります。この設定により、JAASによって設定された既存のサブジェクトから必要なクレデンシャルを取得する際、GSSメカニズムを必要とする制限が緩和されます。 「useSubjectCredsOnlyシステム・プロパティ」を参照してください。

ノート: これは簡略化された入門用のチュートリアルです。 たとえば、ポリシー・ファイルは含まれておらず、サンプル・コードの実行にセキュリティ・マネージャを使用していません。 実際には、Java GSS-APIを使用するコードは、セキュリティ・マネージャを使用して実行すべきであるため、必要なアクセス権が明示的に付与されていないかぎり、セキュリティ関連の操作は許可されません。

別のチュートリアル、「JAAS LoginユーティリティおよびJava GSS-APIを使用したセキュアなメッセージ交換」の内容は、このユーティリティと類似していますが、Loginユーティリティ、ポリシー・ファイル、およびより複雑なログイン構成ファイルを利用する点が異なります(ログイン構成ファイルは、JAAS認証の実行時には常に必要なファイルで、使用する認証モジュールを指定する)。

この一連のすべてのチュートリアルでは、認証およびアプリケーションのセキュアな通信をサポートする基盤の技術として、Kerberos V5を使用しています。 Kerberos要件を参照してください。

チュートリアルのコードを最初に実行してみる場合、「SampleClientおよびSampleServerプログラムの実行」を先に読んでから、その他のセクションに戻り、学習を続けてください。

クライアントおよびサーバー・アプリケーションの概要

このチュートリアルで使用するアプリケーションの名前は、SampleClientおよびSampleServerです。

次に、SampleClientおよびSampleServerアプリケーションの実行方法のサマリーを示します。

  1. SampleServerアプリケーションを実行します。 SampleServer
    1. 引数に注目し、クライアントからの接続を待機するポート番号を確認します。
    2. 指定されたポート上でクライアント接続を待機するServerSocketを作成します。
    3. 接続を待機します。
  2. SampleClientアプリケーションを実行します(通常別のマシンを使用)。 SampleClient
    1. その引数を参照してください。(1) SampleServerを表すKerberosプリンシパルの名前。 (「Kerberosユーザー名およびサービス・プリンシパル名」を参照)、(2) SampleServerを実行中のホスト(マシン)の名前、(3) SampleServerがクライアント接続を待機するポート番号。
    2. 引数として渡されたホストおよびポートを使用して、SampleServerへのソケット接続を試みます。
  3. ソケット接続がSampleServerにより受け入れられます。両方のアプリケーションが、ソケット入力および出力ストリームからのDataInputStreamおよびDataOutputStreamを初期化して、将来のデータ交換に使用します。
  4. SampleClientおよびSampleServerは、それぞれGSSContextをインスタンス化し、以後のセキュアなデータ交換を可能にする共有コンテキストをプロトコルに従って確立します。
  5. これで、SampleClientおよびSampleServerは、メッセージをセキュアに交換できます。
  6. SampleClientおよびSampleServerは、メッセージ交換の完了後に、クリーンアップ操作を実行します。

実際のコードおよび詳細は、後述のセクションで示します。

SampleClientおよびSampleServerコード

SampleClientおよびSampleServerプログラムの全体のコードは、mainメソッド内に配置されており、次の区分にさらに分割できます。

  1. コマンド行引数の取得
  2. SampleClientとSampleServer間の転送用ソケット接続の確立
  3. セキュリティ・コンテキストの確立
  4. メッセージのセキュアな交換
  5. クリーンアップ

ノート: これらのプログラムが使用するJava GSS-APIクラス(GSSManager、GSSContext、GSSName、GSSCredential、MessageProp、およびOid)は、org.ietf.jgssパッケージ内にあります。

コマンド行引数の取得

クライアントとサーバーのmainメソッドが最初に行うことは、コマンド行引数の読取りです。

SampleClientが読み取る引数

SampleClientは、次の3つの引数を受け付けます。

  1. サービス・プリンシパル名 -- SampleServerを表すKerberosプリンシパルの名前。 (「Kerberosユーザー名およびサービス・プリンシパル名」を参照)。
  2. ホスト名 -- SampleServerを実行するマシン。
  3. ポート番号 -- SampleServerが接続を待機するポートのポート番号。

次に、コマンド行引数を読み取るコードを示します。

if (args.length < 3) {
    System.out.println("Usage: java <options> Login SampleClient "
       + " <servicePrincipal> <hostName> <port>");
    System.exit(-1);
}

String server = args[0];
String hostName = args[1];
int port = Integer.parseInt(args[2]);

SampleServerが読み取る引数

SampleServerが受け付ける引数は、次の1つだけです。

次に、コマンド行引数を読み取るコードを示します。

if (args.length != 1) {
    System.out.println(
        "Usage: java <options> Login SampleServer <localPort>");
    System.exit(-1);
}

int localPort = Integer.parseInt(args[0]);

メッセージ交換用ソケット接続の確立

Java GSS-APIは、トークン(不透明なバイト・データ)を作成および解釈するメソッドを提供します。 トークンには2つのピア間でセキュアに交換されるメッセージが含まれていますが、実際にトークンを転送するメソッドはピアによって異なります。 SampleClientおよびSampleServerアプリケーションの場合、クライアントとサーバー間のソケット接続を確立し、ソケット入力および出力ストリームを使ってデータを交換します。

ソケット接続用のSampleClientコード

SampleClientには、引数としてSampleServerを実行中のホスト・マシン名と、SampleServerが接続を待機するポート番号が渡されました。これで、SampleClientからSampleServerへのソケット接続確立に必要なすべてが整いました。 次のコードを使用して、接続を設定し、将来のデータ交換に備えてDataInputStreamおよびDataOutputStreamを初期化します。

Socket socket = new Socket(hostName, port);

DataInputStream inStream = 
  new DataInputStream(socket.getInputStream());
DataOutputStream outStream = 
  new DataOutputStream(socket.getOutputStream());

System.out.println("Connected to server " 
   + socket.getInetAddress());

ソケット接続用のSampleServerコード

SampleServerアプリケーションには、引数として、クライアントからの接続待機に使用するポート番号が渡されます。 このアプリケーションは、指定されたポート上で待機するServerSocketを作成します。

ServerSocket ss = new ServerSocket(localPort);

次に、ServerSocketはクライアントから接続を待機および受け付け、クライアントとの将来のデータ交換に備えてDataInputStreamおよびDataOutputStreamを初期化します。

Socket socket = ss.accept();

DataInputStream inStream =
    new DataInputStream(socket.getInputStream());
DataOutputStream outStream = 
    new DataOutputStream(socket.getOutputStream());

System.out.println("Got connection from client "
    + socket.getInetAddress());

acceptメソッドは、クライアント(ここではSampleClient)がSampleServerのホストおよびポート上で接続を要求するまで待機します。SampleClientはこの接続を介して操作を実行します。

Socket socket = new Socket(hostName, port);

接続の要求および確立が実行されると、acceptメソッドは新規ポートにバインドされた新規Socketオブジェクトを返します。 サーバーは、この新規ソケット経由でクライアントと通信しつつ、元のポートにバインドされたServerSocketに対するほかのクライアントからの接続要求を待機できます。 このため、一般に、サーバー・プログラムは複数の接続要求を処理可能なループを保持します。

SampleServerの基本的なループ構造を、次に示します。

while (true) {

    Socket socket = ss.accept();

    <Establish input and output streams for the connection>; 
    <Establish a context with the client>; 
    <Exchange messages with the client>;
    <Clean up>;
}

クライアント接続は、元のポートでキューに入れられます。このため、SampleServerの使用するプログラム構造では、接続を行う最初のクライアントとのやり取りが完了してからでないと、次の接続を受け付けることができません。 実際のところ、サーバーは次のように、クライアントごとに1つのスレッドを使用することで、複数のクライアントに同時に対応できます。

while (true) {
    <accept a connection>;
    <create a thread to handle the client>;
}

セキュリティ・コンテキストの確立

2つのアプリケーションがJava GSS-APIを利用してセキュアにメッセージを交換するためには、クレデンシャルを使用して、事前にジョイント・セキュリティ・コンテキストを確立しておく必要があります。 (ノート: SampleClientの場合、クレデンシャルは、SampleClientが自動的に実行され、Loginユーティリティによってユーザーが認証された時点で確立されます。SampleServerの場合も同様です。) セキュリティ・コンテキストは、共有状態の情報(暗号化キーなどを含む)をカプセル化します。 暗号化が要求された場合、こうしたキーを使用して、交換するメッセージを暗号化できます。

セキュリティ・コンテキスト確立の一部として、コンテキスト・イニシエータ(ここではSampleClient)がアクセプタ(SampleServer)に認証され、それに対し、アクセプタがイニシエータに認証されることが必要になる場合があります。これを、「相互認証」が行われると言います。

両方のアプリケーションが、GSSContextオブジェクトを作成および使用して、セキュリティ・コンテキストを構成する共有情報を確立および保守します。

コンテキスト・オブジェクトをインスタンス化する方法は、コンテキスト・イニシエータとコンテキスト・アクセプタで異なります。 イニシエータは、GSSContextをインスタンス化した後で、目的のセキュリティ・コンテキストの特性を決定する様々なコンテキスト・オプション(相互認証を行うかどうかの指定など)を設定できます。 目的の特性をすべて設定したら、イニシエータはinitSecContextメソッドを呼び出します。このメソッドは、アクセプタのacceptSecContextメソッドが必要とするトークンを生成します。

Java GSS-APIメソッドは、アプリケーション間で交換されるトークンを準備するために存在します。ただし、実際にアプリケーション間でトークンを転送するのは、アプリケーションの役割です。 このため、イニシエータは、initSecContextの呼出しによりトークンを受け取ると、それをアクセプタに送信します。 アクセプタは、acceptSecContextを呼び出してトークンを渡します。 それに対し、acceptSecContextメソッドがトークンを返すことができます。 その場合、アクセプタは、そのトークンをイニシエータに送信し、イニシエータはinitSecContextを再度呼び出してこのトークンを渡すようにすべきです。 initSecContextまたはacceptSecContextがトークンを返すたびに、メソッドを呼び出したアプリケーションはトークンをピアに送信し、ピアはトークンを適切なメソッド(acceptSecContextまたはinitSecContext)に渡すようにしてください。 この処理は、コンテキストが完全に確立される(コンテキストのisEstablishedメソッドがtrueを返す)まで継続して行われます。

サンプル・アプリケーション用のコンテキスト確立コードについては、次で説明します。

SampleClientによるコンテキスト確立

このクライアント/サーバーのシナリオでは、SampleClientはコンテキスト・イニシエータです。 次に、セキュリティ・コンテキストを確立するための基本的なステップを示します。 次のとおりです。

  1. GSSContextをインスタンス化します。
  2. 使用するオプション機能をコンテキスト上に設定します。
  3. コンテキストが確立されるまでループを実行しますinitSecContextを呼び出すたびに、返されたトークンをすべてSampleServerに送信し、SampleServerからトークンを受け取ります(トークンが存在する場合)。

SampleClient GSSContextのインスタンス化

GSSContextは、GSSManagerのインスタンス化、およびいずれかのcreateContextメソッドの呼出しにより作成されます。 GSSManagerクラスは、ほかの重要なGSS APIクラスのファクトリとして機能します。 このクラスは、GSSContext、GSSCredential、およびGSSNameインタフェースを実装するクラスのインスタンスを作成します。

SampleClientは、GSSManagerのstaticメソッドgetInstanceを呼び出すことにより、デフォルトのGSSManagerサブクラスのインスタンスを取得します。

GSSManager manager = GSSManager.getInstance();

デフォルトGSSManagerサブクラスのcreate*メソッド(createContextなど)が返すクラスの実装は、基盤となるテクノロジとしてKerberosをサポートします。

イニシエータ側でコンテキストを作成するGSSManagerファクトリ・メソッドは、次のシグニチャを保持します。

GSSContext createContext(GSSName peer, Oid mech, 
            GSSCredential myCred, int lifetime);

次に、この引数について説明します。そのあとで、createContextへの完全な呼出しを示します。

GSSName peer引数

クライアント/サーバー・パラダイムにおけるピアは、サーバーです。 peer引数には、サーバーを表すサービス・プリンシパルのGSSNameを指定する必要があります。 (「Kerberosユーザー名およびサービス・プリンシパル名」を参照)。 サービス・プリンシパル名を表す文字列は、SampleClientの最初の引数として渡されます。SampleClientは引数をserverという名前のローカルのString変数に格納します。 GSSNameのインスタンス化には、GSSManager managerが(そのcreateNameメソッドのいずれかを呼び出すことにより)使用されます。 SampleClientは、次の署名を使用して、createNameメソッドを呼び出します。

GSSName createName(String nameStr, Oid nameType);

SampleClientは、nameStr引数としてserver Stringを渡します。

2番目の引数は、Oidです。 Oidは、汎用オブジェクト識別子(Universal Object Identifier)を表します。 Oidは、メカニズムと名前の型を識別するためにGSS-APIフレームワーク内で用いられる、グローバルに解釈できる階層的な識別子です。 Oidの構造およびエンコードは、ISOIEC-8824およびISOIEC-8825標準で定義されています。 createNameメソッドに渡されるOidは、厳密にはメカニズムOidではなく、名前型Oidです。

GSS-APIでは、文字列名は、しばしばメカニズムに依存しない形式からメカニズム固有の形式にマッピングされます。 通常、Oidでは、メカニズムがマッピング方法を認識できるよう、文字列の名前形式が指定されます。 nullのOidは、名前が、メカニズムの使用するネイティブの形式になっていることを示します。 これが当てはまるのは、適切なKerberos Version 5名の形式であるserver Stringの場合です。 このため、SampleClientは、Oidとしてnullを渡します。 次に、その呼出しを示します。

GSSName serverName = manager.createName(server, null);

Oid mech引数

GSSManager createContextメソッドの2番目の引数はOidです。これは、コンテキスト確立時のクライアントとサーバー間の認証、およびそのあとのクライアントとサーバー間のセキュアな通信に使用されるメカニズムを表します。

このチュートリアルでは、セキュリティ・メカニズムとしてKerberos V5を使用しています。 Kerberos V5メカニズムのOidは、RFC 1964で、「1.2.840.113554.1.2.2」と定義されているので、このOidを作成します。

Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");

SampleClientは、createContextの2番目の引数としてkrb5Oidを渡します。

GSSCredential myCred引数

GSSManager createContextメソッドの3番目の引数は、呼出し側のクレデンシャルを表すGSSCredentialです。 SampleClientの場合と同様、この引数にnullを渡す場合、デフォルトのクレデンシャルが使用されます。

int lifetime引数

GSSManager createContextメソッドの最後の引数はintで、作成するコンテキストのライフ・タイムを秒で指定します。 SampleClientは、GSSContext.DEFAULT_LIFETIMEを渡して、デフォルトのライフ・タイムを要求します。

createContextの完全な呼出し

ここまでで、必要な引数をすべて説明しました。ここでは、GSSContextを作成するSampleClientの呼出しを示します。

GSSContext context = 
    manager.createContext(serverName,
                          krb5Oid,
                          null,
                          GSSContext.DEFAULT_LIFETIME);

SampleClientのオプション設定

コンテキストのインスタンス化が完了したら、コンセプト・アクセプタを使用してコンテキストを実際に確立する前に、コンテキスト・イニシエータで、目的のセキュリティ・コンテキストの特性を決定する様々なオプションを設定できます。 それらの各オプションは、インスタンス化されたコンテキスト上でrequestメソッドを呼び出すことで設定されます。 大半のrequestメソッドは、その機能を要求するかどうかを示すboolean引数を取ります。 要求は、常に受け入れられるわけではないため、コンテキストの確立後に、いずれかのgetメソッドを呼び出して、それを確認できます。

SampleClientは、次のオプションを要求します。

  1. 相互認証 コンテキスト・イニシエータは、常にアクセプタに対して認証されます。 イニシエータが相互認証を要求すると、アクセプタもイニシエータに認証されます。
  2. 機密性 機密性を要求するとは、wrapという名前のコンテキスト・メソッドの暗号化を有効にすることを意味します。 暗号が実際に使用されるのは、wrapメソッドに渡されるMessagePropオブジェクトにより機密性が要求される場合だけです。
  3. 整合性 これは、wrapおよびgetMICメソッドの整合性を要求します。 整合性が要求されると、これらのメソッドの呼出し時に、メッセージ整合コード(MIC)として知られる暗号化タグが生成されます。 getMICの呼出しにより返されるトークン内に、生成されたMICが含まれます。 wrapが呼び出されると、MICがメッセージ(元のメッセージまたはメッセージ暗号化の結果、機密性が適用されたかどうかにより異なる)とともに1つのトークンの一部としてパッケージ化されます。 次に、メッセージに対するMICを検証して、メッセージが途中で変更されていないことを確認できます。

GSSException contextに対してこれらの要求を生成するSampleClientコードを、次に示します。

context.requestMutualAuth(true);  // Mutual authentication
context.requestConf(true);  // Will use encryption later
context.requestInteg(true); // Will use integrity later

ノート: デフォルトのGSSManager実装およびKerberosメカニズムを使用する場合、これらの要求は常に許可されます。

SampleClientのコンテキスト確立ループ

SampleClientは、GSSContextのインスタンス化および使用するコンテキスト・オプションの指定後に、SampleServerとのセキュリティ・コンテキストを実際に確立できます。 このために、SampleClientはループ構造を保持します。 各ループにより、次が反復実行されます。

  1. コンテキストのinitSecContextメソッドの呼出し。 最初の呼出しの場合、メソッドにはnullのトークンが渡されます。 それ以外の場合、SampleServerからSampleClientに直前に送信されたトークン(SampleServerによるacceptSecContextの呼出しで生成されるトークン)が渡されます。
  2. initSecContextにより返されたトークン(存在する場合)のSampleServerへの送信。 initSecContextへの最初の呼出しでは、常にトークンが生成されます。 最後の呼出しでは、トークンが返されない場合があります。
  3. コンテキストが確立されたかどうかの確認。 確立されていない場合、SampleClientはSampleServerから別のトークンを受け取って、次のループ処理を開始します。

initSecContextから返される、またはSampleServerから受け取るトークンは、バイト配列に格納されます。 トークンは、SampleClientおよびSampleServer間での送信時に不透明なデータとして処理され、Java GSS-APIメソッドにより解釈されます。

initSecContext引数は、トークン、トークン開始位置の配列内の開始オフセット、およびトークンの長さを含むバイト配列です。 最初の呼出しでは、SampleClientは、SampleServerからトークンをまだ受け取っていないため、nullのトークンを渡します。

SampleServerとトークンを交換する際、SampleClientは、SampleServerへのソケット接続用入力および出力ストリームを使用して設定した、DataInputStream inStreamおよびDataOutputStream outStreamを使用します。 トークンを記述する場合、常に、トークンのバイト数を最初に記述してから、トークン自体を記述してください。 その理由は、「SampleClientとSampleServerのメッセージ交換」セクションの導入部で説明します。

次に、SampleClientのコンテキスト確立ループ、およびクライアントおよびサーバーの実体、相互認証を実際に行うかどうかなどの情報を表示するコードを示します。

byte[] token = new byte[0];

while (!context.isEstablished()) {

    // token is ignored on the first call
    token = context.initSecContext(token, 0, token.length);

    // Send a token to the server if one was generated by
    // initSecContext
    if (token != null) {
        System.out.println("Will send token of size "
                   + token.length + " from initSecContext.");
        outStream.writeInt(token.length);
        outStream.write(token);
        outStream.flush();
    }

    // If the client is done with context establishment
    // then there will be no more tokens to read in this loop
    if (!context.isEstablished()) {
        token = new byte[inStream.readInt()];
        System.out.println("Will read input token of size "
                   + token.length
                   + " for processing by initSecContext");
        inStream.readFully(token);
    }
}

System.out.println("Context Established! ");
System.out.println("Client is " + context.getSrcName());
System.out.println("Server is " + context.getTargName());
if (context.getMutualAuthState())
    System.out.println("Mutual authentication took place!");

SampleServerによるコンテキスト確立

このクライアント/サーバーのシナリオでは、SampleServerはコンテキスト・アクセプタです。 次に、セキュリティ・コンテキストを確立するための基本的なステップを示します。 次のとおりです。

  1. GSSContextをインスタンス化します。
  2. コンテキストが確立されるまでループを実行します。各ループでは、SampleClientからトークンを受け取り、acceptSecContextを呼び出してトークンを渡し、返されたトークンをすべてSampleClientに送信します。

SampleServer GSSContextのインスタンス化

SampleClient GSSContextのインスタンス化で説明したように、GSSContextは、GSSManagerをインスタンス化して、createContextメソッドのいずれかを呼び出すことで作成されます。

SampleClientと同様、SampleServerは、GSSManagerのstaticメソッドgetInstanceを呼び出すことにより、デフォルトのGSSManagerサブクラスのインスタンスを取得します。

GSSManager manager = GSSManager.getInstance();

アクセプタ側でコンテキストを作成するGSSManagerファクトリ・メソッドは、次のシグニチャを保持します。

GSSContext createContext(GSSCredential myCred);

SampleServerの場合と同様、GSSCredential引数にnullを渡す場合、デフォルトのクレデンシャルが使用されます。 コンテキストは、次の方法でインスタンス化されます。

GSSContext context = manager.createContext((GSSCredential)null);

SampleServerのコンテキスト確立ループ

SampleServerは、GSSContextをインスタンス化したあとで、SampleClientとのセキュリティ・コンテキストを確立できます。 これを実行するため、SampleServerはコンテキストが確立されるまで反復するループ構造を保持します。 各ループにより、次の操作が反復実行されます。

  1. SampleClientからのトークンの受け取り。 このトークンは、SampleClient initSecContext呼出しの結果です。
  2. コンテキストのacceptSecContextメソッドの呼び出し、および直前に受け取ったトークンのメソッドへの引渡し。
  3. acceptSecContextがトークンを返す場合、SampleServerはこのトークンをSampleClientに送信し、コンテキストがまだ確立されていない場合、次のループ処理を開始します。

acceptSecContextから返される、またはSampleClientから受け取るトークンは、バイト配列に格納されます。

acceptSecContext引数は、トークン、トークン開始位置の配列内の開始オフセット、およびトークンの長さを含むバイト配列です。

SampleClientとトークンを交換する際、SampleServerは、SampleClientへのソケット接続用入力および出力ストリームを使用して設定した、DataInputStream inStreamおよびDataOutputStream outStreamを使用します。

次に、SampleServerのコンテキスト確立ループを示します。

byte[] token = null;

while (!context.isEstablished()) {

    token = new byte[inStream.readInt()];
    System.out.println("Will read input token of size "
       + token.length
       + " for processing by acceptSecContext");
    inStream.readFully(token);
    
    token = context.acceptSecContext(token, 0, token.length);
    
    // Send a token to the peer if one was generated by
    // acceptSecContext
    if (token != null) {
        System.out.println("Will send token of size "
           + token.length
           + " from acceptSecContext.");
        outStream.writeInt(token.length);
        outStream.write(token);
        outStream.flush();
    }
}

System.out.print("Context Established! ");
System.out.println("Client is " + context.getSrcName());
System.out.println("Server is " + context.getTargName());
if (context.getMutualAuthState())
    System.out.println("Mutual authentication took place!");

メッセージのセキュアな交換

SampleClientとSampleServer間のセキュリティ・コンテキストが確立されると、そのコンテキストを使用してメッセージをセキュアに交換できます。

メッセージ交換用のGSSContextメソッド

メッセージのセキュアな交換のためにメッセージを準備するメソッドには、wrapgetMICの2種類があります。 実際には、2つのwrapメソッド(および2つのgetMICメソッド)が存在します。2つのメソッドの相違点は、入力メッセージの位置(バイト配列または入力ストリーム)および出力先(バイト配列の戻り値または出力ストリーム)です。

メッセージ交換用のこれらのメソッド、および対応するメソッド(生成されるトークンのピアが解釈を行うための)について、次で説明します。

wrap

wrapメソッドは、メッセージ交換用のプライマリ・メソッドです。

SampleClientにより呼び出されるwrapメソッドのシグニチャは、次のようになります。

byte[] wrap (byte[] inBuf, int offset, interface len, 
                MessageProp msgProp)

wrapに、メッセージ(inBuf内)、inBuf内でのメッセージ開始位置のオフセット(offset)、およびメッセージの長さ(len)を渡します。 使用するQOP (Quality-of-Protection)、および機密性(暗号化)が必要かどうかを指定する際に使用するMessagePropも渡します。 QOP値により、使用する暗号化整合および暗号化(必要な場合)アルゴリズムが決定されます。 基盤となるメカニズムのプロバイダにより、さまざまなQOP値に対応するアルゴリズムが指定されます。 たとえば、Kerberos V5に対応する値は、RFC 1964のセクション4.2で定義されています。 デフォルトのQOPを要求する場合は、通常QOP値に0を指定します。

wrapメソッドは、メッセージを含むトークンおよびその暗号化メッセージ整合コード(MIC)を返します。 MessagePropにより機密性が指定されている場合、トークンに格納されたメッセージは暗号化されます。 返されるトークンは、不透明なデータとして扱われるため、その形式について知る必要はありません。 返されるトークンをピア・アプリケーションに送信すると、unwrapメソッドが呼び出されてトークンの「ラップが解除」され、元のメッセージの取得および整合性の検証が行われます。

getMIC

指定されたメッセージの暗号化メッセージ整合コード(MIC)を含むトークンの取得だけを行う場合、getMICを呼び出します。 たとえば、ピアが同じデータを保持していることを確認する場合、データ自体を相互に転送しなくても、データのMICを転送するだけで目的を達成できます。

SampleServerにより呼び出されるgetMICメソッドのシグニチャは、次のようになります。

byte[] getMIC (byte[] inMsg, int offset, int len,
            MessageProp msgProp)

getMICに、メッセージ(inMsg内)、inMsg内でのメッセージ開始位置のオフセット(offset)、およびメッセージの長さ(len)を渡します。 使用するQOP (Quality-of-Protection)の指定に使うMessagePropも渡します。 デフォルトのQOPを要求する場合は、通常QOP値に0を指定します。

getMICにより作成されたトークンおよびMICの計算に使用するメッセージ(またはMICを計算する予定のメッセージ)を保持する場合、verifyMICメソッドを呼び出して、メッセージのMICを検証できます。 検証が成功する(GSSExceptionがスローされない)場合、メッセージが、MIC計算時のメッセージと正確に一致することが保証されます。 通常、アプリケーションからメッセージを受け取るピアは、MICを検証して、途中でメッセージが変更または破損していないかどうかを確認できるように、MICも期待します。 ノート: メッセージに加え、MICも必要であることが前もってわかっている場合は、wrapおよびunwrapメソッドを使用する方が便利です。 ただし、メッセージとMICを別個に受け取る状況も考えられます。

前述のgetMICに対応するverifyMICのシグニチャを、次に示します。

void verifyMIC (byte[] inToken, int tokOffset, int tokLen,
        byte[] inMsg, int msgOffset, int msgLen,
        MessageProp msgProp);

これにより、inMsg (長さがmsgLenで、オフセットmsgOffsetから始まる)内のメッセージに対応する、inToken (長さがtokLenで、オフセットtokOffsetから始まる)内のMICの検証が実行されます。 基盤となるメカニズムは、MessagePropを使用して呼出し側に情報(メッセージに適用された保護の強さを示すQOPなど)を返します。

SampleClientとSampleServerのメッセージ交換

SampleClientとSampleServerとの間のメッセージ交換の概要を次に示します。そのあと、コードの詳細を示します。

これらのステップは、GSS-APIクライアントおよびサーバーの検証に使用される「標準的な」ステップです。 GSS-APIライブラリの異なる実装間の相互運用性をチェックする、広く普及したテスト・プログラムとなったGSS-APIクライアントおよびGSS-APIサーバーは、MITのグループが記述しました。 これらのGSS-APIサンプル・アプリケーションは、MITからダウンロード可能なKerberosディストリビューションの一部です(http://web.mit.edu/kerberos)。 MITから入手可能なこのクライアントおよびサーバーが準拠するプロトコルでは、コンテキストの確立後にクライアントが送信したメッセージに、MICが追加されて返されます。 GSS-APIライブラリを実装する場合、そのライブラリ実装を使用するクライアントまたはサーバーを、別のGSS-APIライブラリ実装を使用する対応するピア・サーバーまたはクライアントに対して実行して、テストを行うのが一般的な方法です。 両方のライブラリ実装が標準に準拠する場合、2つのピアの通信は成功します。

使用するクライアントまたはサーバーを、Cで記述されたクライアントまたはサーバー(MIT提供のクライアントやサーバーなど)に対してテストする場合、トークンの交換方法を考慮する必要があります。 CによるGSS-APIの実装には、ストリーム・ベースのメソッドが含まれません。 ピアにストリーム・ベースのメソッドが存在しない状況でトークンを記述する場合、最初にバイト数を記述してから、トークンを記述する必要があります。 同様に、トークンを読み取る際、バイト数を読み取ってからトークンを読み取る必要があります。 SampleClientおよびSampleServerは、実際にこの処理を実行します。

SampleClientおよびSampleServerが行うメッセージ交換のサマリーを、次に示します。

  1. SampleClientがwrapを呼び出して、メッセージのMICを暗号化および計算します。
  2. SampleClientが、wrapから返されたトークンをSampleServerに送信します。
  3. SampleServerが、unwrapを呼び出して元のメッセージを取得し、整合性を検証します。
  4. SampleServerがgetMICを呼び出して、復号化したメッセージに対するMICを計算します。
  5. SampleServerが、getMICにより返されたトークン(MICを含む)をSampleClientに送信します。
  6. SampleClientがverifyMICを呼び出して、SampleServerにより送信されたMICが元のメッセージの有効なMICかどうかを検証します。

メッセージの暗号化および送信を行うSampleClientコード

メッセージの暗号化、MICの計算、および結果のSampleServerへの送信を実行するSampleClientコードを、次に示します。

byte[] messageBytes = "Hello There!\0".getBytes();

/*
 * The first MessageProp argument is 0 to request
 * the default Quality-of-Protection.
 * The second argument is true to request
 * privacy (encryption of the message).
 */
MessageProp prop =  new MessageProp(0, true);

/*
 * Encrypt the data and send it across. Integrity protection
 * is always applied, irrespective of encryption.
 */
token = context.wrap(messageBytes, 0, messageBytes.length, 
    prop);
System.out.println("Will send wrap token of size " 
    + token.length);
outStream.writeInt(token.length);
outStream.write(token);
outStream.flush();

トークンのラップ解除、MICの計算および送信を行うSampleServerコード

次のSampleServerコードは、SampleClientにより送信されたラップ済みのトークンを読み取り、「ラップを解除」して元のメッセージを取得し、整合性を検証します。 この場合、メッセージが暗号化されているため、ラップの解除には復号化も含まれます。

ノート: ここでは、整合性チェックは成功するものと考えることができます。 ただし、一般に、整合性チェックの失敗は、メッセージが途中で変更されたことが原因です。 unwrapメソッドは、整合性チェックの失敗に遭遇すると、GSSExceptionをメジャー・エラー・コードGSSException.BAD_MICとともにスローします。

/*
 * Create a MessageProp which unwrap will use to return 
 * information such as the Quality-of-Protection that was 
 * applied to the wrapped token, whether or not it was 
 * encrypted, etc. Since the initial MessageProp values
 * are ignored, it doesn't matter what they are set to.
 */
MessageProp prop = new MessageProp(0, false);

/* 
 * Read the token. This uses the same token byte array 
 * as that used during context establishment.
 */
token = new byte[inStream.readInt()];
System.out.println("Will read token of size " 
    + token.length);
inStream.readFully(token);

byte[] bytes = context.unwrap(token, 0, token.length, prop);
String str = new String(bytes);
System.out.println("Received data \""
    + str + "\" of length " + str.length());
System.out.println("Encryption applied: "
    + prop.getPrivacy());

次にSampleServerは、復号化されたメッセージのMICを生成して、SampleClientに送信します。 これは、必須というわけではなく、復号化されたメッセージのMICの生成を示しているだけです。復号化されたメッセージは、SampleClientがラップしてSampleServerに送信した元のメッセージと正確に同じになるはずです。 SampleServerがこれを生成してSampleClientに送信し、その後SampleClientによる検証が行われると、SampleServerが保持する復号化済のメッセージは、SampleClientから送信された元のメッセージと正確に一致することが保証されます。

/*
 * First reset the QOP of the MessageProp to 0
 * to ensure the default Quality-of-Protection
 * is applied.
 */
prop.setQOP(0);

token = context.getMIC(bytes, 0, bytes.length, prop);

System.out.println("Will send MIC token of size " 
                   + token.length);
outStream.writeInt(token.length);
outStream.write(token);
outStream.flush();

MICを検証するSampleClientコード

次のSampleClientコードは、SampleServerにより計算された(復号化済みメッセージに対する) MICを読み取って、このMICが元のメッセージのMICなのかどうかを検証します。これにより、SampleServerが保持する復号化されたメッセージが、元のメッセージと同じであることが保証されます。

token = new byte[inStream.readInt()];
System.out.println("Will read token of size " + token.length);
inStream.readFully(token);

/* 
 * Recall messageBytes is the byte array containing
 * the original message and prop is the MessageProp 
 * already instantiated by SampleClient.
 */
context.verifyMIC(token, 0, token.length, 
          messageBytes, 0, messageBytes.length,
          prop);

System.out.println("Verified received MIC for message.");

クリーンアップ

SampleClientおよびSampleServerは、メッセージ交換の完了後に、クリーンアップ操作を実行する必要があります。 どちらにも、次の操作を実行するコードが含まれます。
socket.close();
context.dispose();

Kerberosユーザー名およびサービス・プリンシパル名

このチュートリアルでは、ベースとなる認証およびセキュアな通信技術としてKerberos V5が使用されているため、ユーザーまたはサービスが要求される場合、常にKerberosスタイルのプリンシパル名が使用されます。

たとえば、SampleClientを実行する場合、ユーザー名の指定が求められます。 Kerberosスタイルのユーザー名は、Kerberos認証用だけに割り当てられたユーザー名です。 このユーザー名は、ベース・ユーザー名(例、「mjones」)、「@」、およびレルムの順序で構成されます(例、「mjones@KRBNT-OPERATIONS.EXAMPLE.COM」)。

通常、SampleServerなどのサーバー・プログラムは、「サービス」を提供し、特定の「サービス・プリンシパル」に代わって実行されるプログラムと見なされます。 SampleServerのサービス・プリンシパル名が必要とされるのは、次の場合です。

このドキュメントおよび関連するログイン構成ファイルを通じて、

service_principal@your_realm
という形式で記述された部分は、自分の環境で使用する実際の名前で置き換えてください。 サービス・プリンシパル名として、任意の Kerberosプリンシパルを実際に使用できます。 このため、このチュートリアルを実行してみる場合、クライアント・ユーザー名とサービス・プリンシパル名の両方に自分のユーザー名を使用できます。

通常、本番稼動環境では、システム管理者は、サーバーを特定のプリンシパルのみで実行し、特定の名前を割り当てて使用します。 たいてい、割り当てるKerberos形式のサービス・プリンシパル名は、次のようになります。

service_name/machine_name@realm;

たとえば、「KRBNT-OPERATIONS.EXAMPLE.COM」というレルム内の「raven」という名前のマシンでnfsサービスを実行する場合、サービス・プリンシパル名は次のようになります

nfs/raven@KRBNT-OPERATIONS.EXAMPLE.COM

ただし、このようなマルチコンポーネント名は必須ではありません。 ユーザー・プリンシパル名のような、シングルコンポーネント名も使用できます。 たとえば、インストールによって、レルム内のすべてのftpサーバーで同じftpサービス・プリンシパルftp@realmを使用する場合と、ftpサーバーごとに異なるftpプリンシパルを使用する(たとえば、マシンhost1host2のftpプリンシパルがそれぞれftp/host1@realmftp/host2@realmとなる)場合があります。

プリンシパル名にレルムを指定する必要がある場合

ユーザーまたはサービス・プリンシパル名のレルムがデフォルト・レルムの場合は(「Kerberos要件」を参照)、Kerberosにログインする際、ユーザー名を求めるプロンプトが表示された時点で、必ずしもレルムを指定する必要はありません。 このため、たとえばユーザー名が「mjones@KRBNT-OPERATIONS.EXAMPLE.COM」で、SampleClientを実行する場合、ユーザー名が要求されたら、レルムを省略して「mjones」とだけ入力できます。 名前はKerberosプリンシパル名のコンテキストで解釈され、必要に応じてデフォルトのレルムが付けられます。

GSSManagerのcreateNameメソッドにより、プリンシパル名がGSSNameに変換される場合にも、レルムの指定を省略できます。 たとえば、SampleClientの実行時に、引数の1つにサービス・プリンシパル名を指定します。 この場合、SampleClientが名前をcreateNameメソッドに渡し、このメソッドが必要に応じてデフォルトのレルムを追加するため、名前を指定する際にレルムを省略できます。

プリンシパル名をログイン構成ファイルおよびポリシー・ファイルで使用する場合は、常にレルムを含めて名前を指定することをお薦めします。理由は、これらのファイルのパーサーの動作が実装に依存しないため、プリンシパル名の使用前にデフォルトのレルムが追加される場合と、追加されない場合があるためです。名前にレルムが指定されていない場合、以降のアクションは失敗します。

ログイン構成ファイル

このチュートリアルでは、JAASメソッドを直接的(「JAAS認証」および「JAAS承認」チュートリアルの場合のように)または間接的(たとえば、「JAAS Loginユーティリティの使用」チュートリアルや「JAAS LoginユーティリティおよびJava GSS-APIを使用したセキュアなメッセージ交換」チュートリアルで解説されているLoginユーティリティを介して)に呼び出すのではなく、基盤となるKerberosメカニズムがSampleClientおよびSampleServerを実行するユーザーのクレデンシャルを取得するようにしました。

Sun Microsystemsの提供するKerberosメカニズムのデフォルト実装では、Kerberos名およびパスワードの入力が求められ、Kerberos KDCに対して指定されたユーザーまたはサービスの認証が行われます。 メカニズムでは、この認証処理にJAASが使用されています。

JAASはプラガブル認証フレームワークをサポートします。これは、呼出し側アプリケーションに任意の型の認証モジュールをプラグインできるということです。 特定のアプリケーションで使用されるログイン・モジュールは、ログイン構成によって指定されます。 Sun Microsystems提供のデフォルトのJAAS実装では、ファイル内にログイン構成情報を指定する必要があります。 (ノート: ベンダーによってはファイルベースの実装が提供されない場合もあります。) ログイン構成ファイルとその内容、および使用するログイン構成ファイルの指定方法については、「JAASログイン構成ファイル」を参照してください。

このチュートリアルでは、構成ファイル内にKerberosログイン・モジュールcom.sun.security.auth.module.Krb5LoginModuleが指定されます。 このログイン・モジュールは、Kerberos名とパスワードの入力を求めるプロンプトを表示し、Kerberos KDCに対する認証処理を実行しようとします。

ログイン構成ファイルに、クライアント側が使用するエントリとサーバー側が使用するエントリの2つが含まれる場合、SampleClientとSampleServerの両方で同じログイン構成ファイルを使用できます。

このチュートリアルで使用するログイン構成ファイルbcsLogin.confを、次に示します。

com.sun.security.jgss.initiate  {
  com.sun.security.auth.module.Krb5LoginModule required;
};

com.sun.security.jgss.accept  {
  com.sun.security.auth.module.Krb5LoginModule required storeKey=true 
};

SunのGSS-APIメカニズムの実装では、新しいクレデンシャルが必要な場合、com.sun.security.jgss.initiateという名前のエントリとcom.sun.security.jgss.acceptという名前のエントリが使用されます。 このチュートリアルではKerberos V5メカニズムを使用するため、これらのクレデンシャルの取得にKerberosログイン・モジュールの呼出しが必要です。 このため、エントリ内にKrb5LoginModuleを必須モジュールとして記載してあります。 com.sun.security.jgss.initiateエントリはクライアント側の構成を指定し、com.sun.security.jgss.acceptエントリはサーバー側の構成を指定します。

Krb5LoginModuleが成功するのは、指定されたエンティティでのKerberos KDCへのログインが成功した場合だけです。 SampleClientまたはSampleServerの実行時、ユーザーは名前とパスワードの入力を求められます。

SampleServerのエントリstoreKey=trueは、ログイン時に指定されたパスワードから秘密キーを計算すること、およびログインにより作成されたサブジェクトのprivateクレデンシャルに秘密キーを格納することを意味します。 このキーは、SampleClientとSampleServerとの間でセキュリティ・コンテキストを確立する際、相互認証に利用されます。

Krb5LoginModuleに引渡し可能なすべてのオプションの詳細は、Krb5LoginModuleドキュメントを参照してください。

useSubjectCredsOnlyシステム・プロパティ

このチュートリアルでは、システム・プロパティjavax.security.auth.useSubjectCredsOnlyfalseに設定します。この設定により、JAASによって設定された既存のサブジェクトから必要なクレデンシャルを取得する際、GSSメカニズムを必要とする通常の制限が緩和されます。 この制限緩和により、メカニズムが、ベンダー固有の位置からクレデンシャルを取得できるようになります。 たとえば、オペレーティング・システムのキャッシュ(存在する場合)の使用を選択するベンダーや、ディスク上の保護されたファイルから読取りを選択するベンダーがあります。

この制限が緩和された場合でも、Sun MicrosystemのKerberosメカニズムにより、スレッドのアクセス制御コンテキストに関連付けられたサブジェクト内のクレデンシャルを検索します。ただし、クレデンシャルが見つからなかった場合、Kerberosモジュールを使用してJAAS認証を実行し、新規クレデンシャルを取得します。 Kerberosモジュールは、Kerberosプリンシパル名とパスワードの入力を求めます。 他のベンダーが提供するKerberosメカニズムの実装では、このプロパティをfalseに設定した場合の動作が異なる場合があることに注意してください。 各実装の動作を判断するには、それらのドキュメントを参照してください。

SampleClientおよびSampleServerプログラムの実行

SampleClientおよびSampleServerプログラムを実行するには、次の操作を行います。

SampleServerの実行準備

SampleServerの実行準備では、次の操作を行います。

  1. 次のファイルを、SampleServerを実行するマシンからアクセス可能なディレクトリにコピーします。
  2. SampleServer.javaをコンパイルします。
    javac SampleServer.java

SampleClientの実行準備

SampleClientの実行準備では、次の操作を行います。

  1. 次のファイルを、SampleClientを実行するマシンからアクセス可能なディレクトリにコピーします。
  2. SampleClient.javaをコンパイルします。
    javac SampleClient.java

SampleServerの実行

SampleClientを実行する前に、必ずSampleServerを実行してください。SampleClientはSampleServerへのソケット接続を試みるため、SampleServerが稼動していないとソケット接続が受け付けられず、失敗します。

SampleServerを実行する場合、SampleServerを稼動する予定のマシンで実行してください。 このマシン名(ホスト名)は、SampleClientの引数として指定します。 サービス・プリンシパル名は、ログイン構成ファイルやポリシー・ファイルなど、いくつかの場所に表示されます。

SampleServerの実行用に準備したディレクトリに移動します。 次を指定して、SampleServerを実行します。

SampleServerに必要な引数は、クライアント接続の待機に使用するポート番号だけです。 通常は使用しない大きなポート番号であれば、どの番号でも選択できます。 (例、4444)。

次に、Microsoft WindowsおよびSolaris、LinuxおよびMac OS Xシステムで使用するすべてのコマンドを示します。

重要: このコマンドの、<port_number>を適切なポート番号に、<your_realm>を使用するKerberosレルムに、<your_kdc>を使用するKerberos KDCにそれぞれ置き換えてください。

次に、コマンドを示します。

java -Djava.security.krb5.realm=<your_realm> 
 -Djava.security.krb5.kdc=<your_kdc> 
 -Djavax.security.auth.useSubjectCredsOnly=false
 -Djava.security.auth.login.config=bcsLogin.conf 
 SampleServer <port_number>

コマンド全体は、1行で(または、Solaris、LinuxまたはMac OS Xシステムでは、最後の行を除く各行が、続いていることを示す「\」で終わる複数行で)表示されるようにしてください。 ここでは、読みやすくするために複数行に分けて表示してあります。 このコマンドは非常に長いため、.batファイル(Windowsの場合)または.shファイル(Solaris、LinuxまたはMac OS Xの場合)に記述して、そのファイルを実行して、コマンドを実行する必要がある場合があります。

SampleServerコードは、指定されたポート上でソケット接続を待機します。 入力を求められたら、サービス・プリンシパルのKerberos名およびパスワードを入力してください。 ログイン構成ファイルで指定された基盤となるKerberos認証メカニズムにより、サービス・プリンシパルのKerberosへのログインが行われます。

ログイン時のトラブルシューティングについては、「トラブルシューティング」を参照してください。

SampleClientの実行

SampleClientを実行するため、最初にSampleClientの実行準備を行なったディレクトリに移動します。 次を指定して、SampleClientを実行します。

SampleClientの引数は、(1) SampleServerを表すサービス・プリンシパルのKerberos名、(2) SampleServerを実行するホスト(マシン)の名前、(3) SampleServerがクライアント接続を待機するポート番号です。

次に、WindowsおよびSolaris、LinuxおよびMac OS Xシステムで使用するすべてのコマンドを示します。

重要: このコマンドの、<service_principal><host><port_number><your_realm>、および<your_kdc>を、適切な値に置き換えてください (ポート番号は、SampleServerの引数として渡したポート番号と同じにする必要がある)。 値を引用符で囲む必要はありません。

次に、コマンドを示します。

java -Djava.security.krb5.realm=<your_realm> 
 -Djava.security.krb5.kdc=<your_kdc> 
 -Djavax.security.auth.useSubjectCredsOnly=false
 -Djava.security.auth.login.config=bcsLogin.conf 
 SampleClient <service_principal> <host> <port_number>

コマンド全体を1行で入力してください。 ここでは、読みやすくするために複数行に分けて表示してあります。 SampleServerを実行するコマンドと同様、コマンド・ウィンドウに直接入力するにはコマンドが長すぎる場合、.batファイル(Windowsの場合)または.shファイル(Solaris、LinuxおよびMac OS Xの場合)に記述して、そのファイルを実行してください。

入力が求められたら、Kerberosユーザー名およびパスワードを入力します。 ログイン構成ファイルで指定された基盤となるKerberos認証メカニズムにより、Kerberosへのログインが行われます。 SampleClientコードは、SampleServerへのソケット接続を要求します。 SampleServerが接続を受け付けると、SampleClientおよびSampleServerにより、このチュートリアルで解説した方法で、共有コンテキストの確立およびメッセージの交換が行われます。

ログイン時のトラブルシューティングについては、「トラブルシューティング」を参照してください。


チュートリアルの紹介および目次 次のチュートリアル

Copyright © 1993, 2025, Oracle and/or its affiliates. All rights reserved.