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

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



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

Java GSS-API は、「セキュリティーメカニズム」と呼ばれるメカニズムを使用して、これらのサービスを提供します。Java 2 Standard Edition プラットフォームで利用可能な 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 Sring の場合です。このため、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 および Unix システムの両方で使用可能なすべてのコマンドを示します。

重要: このコマンドの、<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 行で入力してください。ただし、UNIX システムの場合、行の最後に行継続を示す文字「\」を付けることで (最後の行を除く)、複数行に分けて入力できます。ここでは、可読性のみを考慮して、複数行に分けて表示しています。このコマンドは非常に長いため、.bat ファイル (Windows) または .sh ファイル (UNIX) に記述して、そのファイルを実行することが必要な場合があります。

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

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

SampleClient の実行

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

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

次に、Windows と Unix の両方のシステムで使用可能な全コマンドを示します。

重要: このコマンドの、<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 ファイル (UNIX) に記述して、そのファイルを実行してください。

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

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



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

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