GSS-API のプログラミング

第 2 章 GSS-API サンプルプログラムについての概略説明

サンプルプログラムの概要

付録 A 「C ベース の GSS-API サンプルプログラム」 には、GSS-API を使用する 2 つの C 言語アプリケーション (クライアント用とサーバー用) のソースコードが掲載されています。この章では、これらのアプリケーションを使用して GSS-API を段階的に説明します。この章は付録 A 「C ベース の GSS-API サンプルプログラム」 を参照しながら読んでいくことを想定しています。アプリケーションのすべての面を詳細に説明するわけではなく、GSS-API の使用に関連する面に焦点を置いて説明します。


注意 – 注意 –

GSS-API は自動的に自分自身をクリーンアップしないため、GSS-API を使用するアプリケーションや関数はこのクリーンアップを行う必要があります。つまり、たとえば、GSS-API バッファや GSS-API 名前空間を使用する関数は、終了時に、gss_release_buffer()gss_release_name() などの GSS-API 関数を呼び出す必要があります。

繰り返しを避けるため、以降のコードでは一般的にこのようなクリーンアップを省いてあります。実行する必要があることは必ず覚えておいてください。いつどこでクリーンアップ関数を使用するかが不明な場合は、付録 A 「C ベース の GSS-API サンプルプログラム」 のサンプルプログラムを参照してください。


クライアント側の GSS-API: gss-client

サンプルのクライアント側プログラム gss-client は、サーバーとのセキュリティコンテキストを作成し、セキュリティパラメータを確立し、文字列 (メッセージ) をサーバーに送信します。接続にはシンプルな TCP ベースのソケット接続を使用します。

次に、gss-client のコマンド行形式を示します。



gss-client [-port port] [-d] [-mech mech] host service [-f] msg

gss-client は主に次の作業を行います。

  1. コマンド行を解析します。

  2. 機構の OID (オブジェクト ID) を作成します (指定されている場合)。

  3. サーバーとの接続を設定します。

  4. コンテキストを確立します。

  5. メッセージをラップします。

  6. メッセージを送信します。

  7. サーバーが正しくメッセージに署名していることを検証します。

以降では、gss-client がどのように動作するかを段階的に説明します。機能性を示すために設計されたサンプルプログラムであるため、上記の手順にあまり関係のない部分は省略しています。コンテキストのインポートやエクスポート、ラップサイズの取得などの機能については、このマニュアルの他の場所を参照してください。

概要: main() (クライアント)

すべての C プログラムと同様に、プログラムの外部シェルはエントリポイント関数 main() に含まれます。main() は次の 4 つの機能を実行します。

  1. コマンド行引数を解析し、各引数を変数に割り当てます。

    • port が指定されている場合、porthost で指定されたリモートマシンへの接続を確立するポート番号です。

    • -d フラグが設定されている場合、セキュリティ資格はサーバーに委託されます。特に、deleg_flag 変数は GSS-API 値 GSS_C_DELEG_FLAG に設定されます。-d フラグが設定されていない場合、deleg_flag は 0 に設定されます。

    • (省略可能) mech は使用されるセキュリティ機構名 (Kerberos v5 や X.509 など) です。機構が指定されていない場合、GSS-API はデフォルトの機構を使用します。

    • クライアントから要求されたネットワークサービス名 (telnetftplogin などのサービス) は service_name に割り当てられます。

    • 最後に、msg は保護されたデータとしてサーバーに送信される文字列です。-f オプションが指定されている場合、msg は文字列を読み取るべきファイル名です。

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


    % gss-client -port 8080 -d -mech kerberos_v5 erebos.eng nfs "ls"
    

    次のコマンド行は機構もポートも指定せず、委託も使用しません。


    % gss-client erebos.eng nfs "ls"
    

  2. parse_oid() を呼び出して、(コマンド行に指定されている場合) セキュリティ機構名から、GSS-API OID (オブジェクト識別子) を作成します。


    if (mechanism)
             parse_oid(mechanism, &g_mechOid);

    mechanism は変換される文字列で、g_mechOid は機構の gss_OID オブジェクトへのポインタです。デフォルト以外の機構の指定については、付録 C 「OID の指定」 を参照してください。

  3. call_server() を呼び出します。この関数は、コンテキストの作成とデータの送信を実際に行います。


    if (call_server(hostname, port, g_mechOid, service_name,
                       deleg_flag, msg, use_file) < 0)
              exit(1);
  4. OID の記憶領域を解放します (まだ解放されていない場合)。


    if (g_mechOID != GSS_C_NULL_OID)
         (void) gss_release_oid(&min_stat, &g_mechoid);

    gss_release_oid() は、GSS-API の Sun の実装ではサポートされますが、すべての GSS-API 実装でサポートされるわけではなく、標準ではないと考えられます。アプリケーションは、gss_str_to_oid() で機構を割り当てるのではなく、可能な限り GSS-API が提供するデフォルトの機構を使用するべきです。したがって、gss_release_oid() コマンドは通常使用するべきではありません。

デフォルト以外の機構の指定

一般的に、GSS-API を使用するアプリケーションは特定の機構を指定せず、GSS-API 実装が提供するデフォルトの機構を使用すべきです。デフォルトの機構を指定するには、機構の表す gss_OID を値 GSS_C_NULL_OID に設定します。

デフォルト以外の機構を設定することは推奨されないため、このプログラムでは取り上げません。クライアントアプリケーションがユーザー指定の (デフォルト以外の) 機構名を解析する方法については、parse_oid()のコードを参照してください。また、デフォルト以外の OID を使用する方法については、付録 C 「OID の指定」 を参照してください。

サーバーの呼び出し

機構を gss_OID 形式で格納した後、実際の作業を行うことができます。つまり、main() は、コマンド行引数とほとんど同じ引数で、call_server() 関数を呼び出します。


call_server(hostname, port, g_mechOid, service_name,
     deleg_flag, msg, use_file);

use_file は、送信されるメッセージがファイルに格納されているかどうかを示すフラグです。

サーバーへの接続

変数を宣言した後、call_server() はまずサーバーとの接続を設定します。


if ((s = connect_to_server(host, port)) < 0)
     return -1;
s はファイル記述子で (型は int)、最初は socket() の呼び出しから戻されます。

connect_to_server() は、ソケットを使用して接続を作成するシンプルな関数です。この関数は GSS-API を使用しないため、ここでは省略します。connect_to_server() についての詳細は、connect_to_server()を参照してください。

コンテキストの確立

接続が確立された後、call_server()client_establish_context() 関数を使用して、セキュリティコンテキストを確立します。


int client_establish_context(s, service_name, deleg_flag, oid, 
     &context, &ret_flags)

コンテキストを起動するために、アプリケーションは gss_init_sec_context() 関数を使用します。ほとんどの GSS-API 関数と同様に、この関数でも名前は GSS-API 内部形式である必要があります。したがって、アプリケーションはまず、サービス名を文字列から内部形式に変換する必要があります。このためには、gss_import_name() を使用します。


maj_stat = gss_import_name(&min_stat, &send_tok, 
     (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, &target_name);

この関数は引数としてサービス名を受け取り (参照できない (不透明な) GSS-API バッファ send_tok に格納される)、GSS-API 内部名 target_name に変換します。send_tok は新しい gss_buffer_desc を宣言せず、領域を節約するために使用されます。3 番目の引数は gss_OID 型で、send_tok に格納されている名前の形式を示します。この場合は GSS_C_NT_HOSTBASED_SERVICE で、サービスの形式が service@host であることを意味します。この引数に有効なその他の値については、名前型を参照してください。

サービスを GSS-API 内部形式に変換した後、コンテキストを確立する作業に進むことができます。移植性を最大限にするには、コンテキストの確立を常にループとして実行する必要があります。コンテキストの起動 (クライアント)を参照してください。

まず、アプリケーションはコンテキストを NULL に初期化します。


*gss_context = GSS_C_NO_CONTEXT;
次に、サーバーから受け取るトークンも NULL に初期化します。


token_ptr = GSS_C_NO_BUFFER;

次に、アプリケーションはループに入ります。ループは 2 つのことを検査しながら進みます。gss_init_sec_context() から戻される状態と、サーバーに送信されるトークンのサイズ (この値も gss_init_sec_context() で生成される) です。トークンのサイズが 0 の場合、サーバーがクライアントからこれ以上のトークンを期待していないことを意味します。次に、このループの疑似コードを示します。


do{
      gss_init_sec_context()
      if (コンテキストが全く確立されなかった場合)
                 エラーを出力して終了する
      if (状態が「完了」または「処理中」のどちらでもない場合
                 サービスの名前空間を解放し、エラーを出力して終了する
      if (サーバーに送信するトークンがある場合。つまり、サイズが 0 以外の場合)
                 トークンを送信する
                 if (トークンの送信が失敗した場合)
                 トークンとサービスの名前空間を解放し、エラーを出力して終了する
                 先ほど送信したトークンの名前空間を解放する
      if (コンテキストの確立が完了していない場合
                 サーバーからトークンを受け取る
} while (コンテキストが完全になるまで)

次に、gss_init_sec_context() への呼び出しを示します。


do {
     maj_stat = gss_init_sec_context(&min_stat,
                           GSS_C_NO_CREDENTIAL,
                           gss_context,
                           target_name
                           oid
                           GSS_C_MUTUAL_FLAG | 
                              GSS_C_REPLAY_FLAG |
                              deleg_flag,
                           0,
                           NULL,
                           &send_tok,
                           ret_flags,
                           NULL);

次に、各引数について説明します。

コンテキストを起動する前には、クライアントは資格を獲得する必要がないことに注意してください。クライアント側では、資格の管理は GSS-API によって透過的に処理されます。つまり、(通常はログイン時に) プリンシパルのために機構が作成した資格をどのように取得するかを、GSS-API は知っているということです。このため、アプリケーションは gss_init_sec_context() にデフォルトの資格を渡しています。しかし、サーバー側では、サーバーアプリケーションはコンテキストを受け入れる前に、サービスの資格を明示的に獲得する必要があります。資格の獲得を参照してください。

コンテキスト (完成されている必要はない) があることと、gss_init_sec_context() が有効な状態を戻したことを検査した後、アプリケーションは gss_init_sec_context() がサーバーに送信すべきトークンをコンテキストに渡しているかどうかを調べます。渡していない場合、これは、トークンが (これ以上) 必要でないことをサーバーが示しているためです。渡している場合、トークンをサーバーに送信します。トークンの送信が失敗した場合、トークンの名前空間とサービスを解放し、(エラーで) 終了します。トークンの存在をチェックするには、トークンの長さ (サイズ) を調べることに注意してください。


if (send_tok_length != 0) {
               if (send_token(s, &send_tok) < 0) {
                    (void) gss_release_buffer(&min_stat, &send_tok);
                    (void) gss_release_name(&min_stat, &target_name);
                    return -1;
               }
          }

send_token() は GSS-API 関数ではなく、ユーザーが作成した基本的なファイル書き込み関数です。詳細は、send_token()を参照してください。GSS-API 自身はトークンを送受信しないことに注意してください。GSS-API が作成したトークンを送受信するのは、呼び出し側アプリケーションの責任です。

サーバーが (これ以上) 送信するトークンを持っていない場合、gss_init_sec_context()GSS_S_COMPLETE を戻します。つまり、gss_init_sec_context() がこの値を戻さなかった場合、アプリケーションはまだ受け取るトークンがサーバーに残っていると判断できます。受け取りが失敗した場合、アプリケーションはサービスの名前空間を解放し、(エラーで) 終了します。


if (maj_stat == GSS_S_CONTINUE_NEEDED) {
               if (recv_token(s, &recv_tok) < 0) {
                    (void) gss_release_name(&min_stat, &target_name);
                    return -1;

最後に、プログラムはトークンのポインタをリセットします。そして、コンテキストが完全に確立されるまで、ループを繰り返します。したがって、do ループの終了は次のようになります。


} while (maj_stat == GSS_S_CONTINUE_NEEDED);

データの送信

セキュリティコンテキストが確立された後、gss-client はデータをラップし、データを送信し、そして、サーバーが戻した「署名」を検証する必要があります。他にも行うことはありますが (コンテキストについての情報の表示など)、gss-client はプログラム例であるため、ここでは省略して、データの送信と検証を行います。まず、送信すべきメッセージ (ls など) をバッファに格納します。


     if (use_file) {
         read_file(msg, &in_buf);
     } else {
         /* メッセージをラップする */
         in_buf.value = msg;
         in_buf.length = strlen(msg) + 1;
     }

ラップする前に、データを暗号化できるかどうかを検査します。


     if (ret_flag & GSS_C_CONF_FLAG) {
          state = 1;
     else
          state = 0;
     }

次に、データをラップします。


     maj_stat = gss_wrap(&min_stat, context, conf_req_flag, GSS_C_QOP_DEFAULT,
                         &in_buf, &state, &out_buf);
     if (maj_stat != GSS_S_COMPLETE) {
          display_status("wrapping message", maj_stat, min_stat);
          (void) close(s);
          (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER);
          return -1;
     } else if (! state) {
          fprintf(stderr, "Warning!  Message not encrypted.\n");
     }

このように、in_buf に格納されたメッセージは、context で参照されるサーバーに送信されます。このとき、機密性サービスとデフォルトの保護品質 (QOP) も要求されます。保護品質とは、データを変換するときに適用されるアルゴリズムのことです。移植性のためには、可能な限りデフォルトの保護品質を使用するようにします。gss_wrap() はメッセージをラップし、その結果を out_buf に格納し、機密性が実際にラップで適用されたかどうかをフラグ (state) に設定します。

クライアントは独自の send_token() 関数で、ラップされたメッセージをサーバーに送信します。send_token() 関数については、コンテキストの確立を参照してください。


send_token(s, &outbuf)

メッセージの検証

次に、送信したメッセージの有効性を検証します。送信したメッセージの MIC がサーバーから戻されることが判明しているため、プログラムは独自の recv_token() 関数で MIC (Message Integrity Code) を受け取ります。そして、gss_verify_mic() でサーバーの「署名」(MIC) を検証します。


   maj_stat = gss_verify_mic(&min_stat, context, &in_buf,
                               &out_buf, &qop_state);
   if (maj_stat != GSS_S_COMPLETE) {
       display_status("verifying signature", maj_stat, min_stat);
       (void) close(s);
       (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER);
       return -1;
   }

gss_verify_mic() はサーバーのトークン (out_buf に格納されている) とともに受け取った MIC を、オリジナルのラップ解除されたメッセージ (in_buf に格納されている) から作成した MIC と比較します。2 つの MIC が一致した場合、メッセージの有効性は検証されたことになります。次に、クライアントは受け取ったトークンのバッファ (out_buf) を解放します。

終了時、call_server() はコンテキストを削除し、main() に戻ります。

サーバー側の GSS-API: gss-server

通常クライアントは、セキュリティハンドシェークを実行するためにサーバーを必要とします。クライアントはセキュリティコンテキストを起動し、データを送信します。一方、サーバーはコンテキストを受け入れ、クライアントの ID を検証する必要があります。この中で、サーバーは自分自身をクライアントに対して認証したり (要求された場合)、データの「署名」をクライアントに提供したりする場合もあります。もちろん、これに加えて、サーバーはデータを処理する必要があります。

次に、gss-server のコマンド行形式を示します。


gss-server [-port port] [-verbose] [-inetd] [-once] [-logfile file] \
          [-mech mechanism] service_name

gss-server は次の作業を行います。

  1. コマンド行を解析します。

  2. コマンド行に指定された機構名を、内部形式に変換します (ある場合)。

  3. 呼び出し側の資格を獲得します。

  4. inetd デーモンを使用して接続するようにユーザーが指定しているかどうかをチェックします。

  5. 接続を確立します。

  6. データを取得します。

  7. データに署名し、署名を戻します。

  8. 名前空間を解放し、終了します。

以降では、gss-server がどのように動作するかを段階的に説明します。機能性を示すために設計されたサンプルプログラムであるため、上記の手順にあまり関係のない部分は省略しています。

概要: main() (サーバー)

gss-clientmain() 関数から始めます。main() は次の作業を実行します。

  1. コマンド行引数を解析し、各引数を変数に割り当てます。

    • port が指定されている場合、port は待機するポート番号です。port が指定されていない場合、プログラムはデフォルトでポート 4444 を使用します。

    • -verbose が指定されている場合、プログラムはデバッグに類似したモードで動作します。

    • -inetd オプションは、プログラムが inetd デーモンを使用してポートで待機することを指示します。inetdstdinstdout を使用してクライアントとの接続を処理します。

    • -once が指定されている場合、プログラムは 1 つのインスタンス接続だけを作成します。

    • (省略可能) mechanism は使用されるセキュリティ機構名 (Kerberos v5 など) です。機構が指定されていない場合、GSS-API はデフォルトの機構を使用します。

    • クライアントから要求されたネットワークサービス名 (telnetftplogin などのサービス) は service_name で指定されます。

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


    % gss-server -port 8080 -once -mech kerberos_v5 erebos.eng nfs "hello"
    

  2. 機構を GSS-API オブジェクト識別子 (OID) に変換します (指定されている場合)。これは、GSS-API 関数が名前を内部形式で処理するためです。

  3. 使用される機構 (Kerberos v5 など) のために、サービス (ftp など) の資格を獲得します。

  4. sign_server() 関数を呼び出します。この関数は、接続の確立、メッセージの受け取り、その署名など、ほとんどの作業を実際に行います。

    inetd を使用するようにユーザーが指定している場合、プログラムは標準出力と標準エラーを閉じ、標準入力で sign_server() を呼び出します。inetd はこの sign_server() を使用して接続を渡します。そうでない場合、プログラムはソケットを作成し、そのソケットの接続を TCP 関数 accept() で受け入れ、accept() から戻されたファイル記述子で sign_server() を呼び出します。

    inetd を使用しない場合、プログラムは終了されるまで接続とコンテキストを作成します。しかし、ユーザーが -once オプションを指定している場合、ループは最初の接続の後で終了します。

  5. 獲得した資格を解放します。

  6. 機構 OID の名前空間を解放します。

  7. 接続を閉じます (まだ開いている場合)。

機構の OID の作成

gss-client プログラムの例と同様に、サンプルのサーバープログラムでもユーザーは機構を指定できます。しかし、すべてのアプリケーションで、GSS-API 実装が提供するデフォルトの機構を使用することを強く推奨します。デフォルトの機構を指定するには、機構の表す gss_OID を値 GSS_C_NULL_OID に設定します。コードについては、createMechOid()を参照してください。また、デフォルト以外の機構を使用する方法については、付録 C 「OID の指定」 を参照してください。

資格の獲得

クライアントアプリケーションと同様に、サーバーアプリケーションと GSS-API はどちらも資格を作成しません。資格は実際の機構が作成します。クライアントプログラムとは異なり、サーバーは必要な資格を明示的に獲得する必要があります。クライアントアプリケーションの中にも、明示的に資格を獲得するものはあります。この場合も、ここで説明する方法を使用します。しかし、一般的には、クライアントはログイン時にすでに資格を獲得しており、GSS-API はこのような資格を自動的に獲得します。

gss-server プログラムは独自の関数 server_acquire_creds() を持っています。この関数は提供されるサービスの資格を取得します。この関数は、入力としてサービス名と使用されるセキュリティ機構を受け取り、そのサービスの資格を戻します。

server_acquire_creds() は GSS-API 関数 gss_acquire_cred() を使用して、サーバーが提供するサービスの資格を取得します。しかし、これを行う前には、次の 2 つのことを処理する必要があります。

1 つの資格を複数の機構で共有できる場合、gss_acquire_cred() はこのような機構すべての資格を戻します。したがって、gss_acquire_cred() は入力として 1 つの機構ではなく、機構の集合を受け取ります (資格を参照)。しかし、ほとんどの場合 (このプログラムも含む)、1 つの資格は複数の機構で機能しません。さらに、サーバーアプリケーションでは、1 つの機構をコマンド行に指定するか、デフォルトの機構を使用します。したがって、最初に行うことは、gss_acquire_cred() に渡される機構の集合に 1 つの機構 (デフォルトまたはそれ以外) だけが入っていることを確認することです。


if (mechOid != GSS_C_NULL_OID) {
     desiredMechs = &mechOidSet;
     mechOidSet.count = 1;
     mechOidSet.elements = mechOid;
} else
     desiredMechs = GSS_C_NULL_OID_SET;

GSS_C_NULL_OID_SET は、デフォルトの機構を使用することを示します。

gss_acquire_cred() はサービス名を gss_name_t 構造体の形式で受け取ります。したがって、2 番目に行うことは、サービス名を gss_name_t 構造体の形式にインポートすることです。このためには、gss_import_name() を使用します。ほとんどの GSS-API 関数と同様に、この関数でも引数は GSS-API 型である必要があるため、まず、サービス名を GSS-API バッファにコピーする必要があります。


     name_buf.value = service_name;
     name_buf.length = strlen(name_buf.value) + 1;
     maj_stat = gss_import_name(&min_stat, &name_buf,
                (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, &server_name);
     if (maj_stat != GSS_S_COMPLETE) {
          display_status("importing name", maj_stat, min_stat);
          if (mechOid != GSS_C_NO_OID)
                gss_release_oid(&min_stat, &mechOid);
          return -1;
     }

今度も標準でない関数 gss_release_oid() を使用していることに注意してください。概要: main() (クライアント)を参照してください。

入力のサービス名は name_buf に文字列として格納されます。出力は server_name という gss_name_t 構造体へのポインタです。3 番目の引数 GSS_C_NT_HOSTBASED_SERVICEname_buf に格納されている文字列の名前型です。この場合、文字列が service@host というサービスの形式で解釈されることを示します。

これで、サーバープログラムは gss_acquire_cred() を呼び出すことができます。


maj_stat = gss_acquire_cred(&min_stat, server_name, 0,
                                 desiredMechs, GSS_C_ACCEPT,
                                 server_creds, NULL, NULL);

次に、各引数について説明します。

コンテキストの受け入れと、データの取得と署名

サービスの資格を獲得した後、サーバープログラムは、ユーザーが inetd を使用するように指定しているかどうかを調べて (概要: main() (サーバー)を参照)、次に、sign_server() を呼び出します。sign_server() はプログラムの主な作業を行います。sign_server() が最初に行うことは、server_establish_context() を呼び出してコンテキストを確立することです。


注 –

ここでは、inetd については説明しません。基本的に、inetd が指定された場合、プログラムは標準入力で sign_server() を呼び出します。そうでない場合、プログラムはソケットを作成し、接続を受け入れ、次に、その接続で sign_server() を呼び出します。


sign_server() は次の作業を行います。

  1. コンテキストを受け入れます。

  2. データをラップ解除します。

  3. データに署名します。

  4. データを戻します。

コンテキストの受け入れ

コンテキストの確立では、クライアントとサーバー間で一連のトークンが交換されます。したがって、プログラムの移植性を保持するために、コンテキストの受け入れとコンテキストの起動はループで行われる必要があります。実際、コンテキストを受け入れるためのループとコンテキストを確立するためのループは (まったく逆ですが) 非常によく似ています。コンテキストの確立と比較してください。

  1. サーバーが最初に行うことは、クライアントがコンテキスト起動プロセスの一部として送信したトークンを探すことです。GSS-API 自身はトークンを送受信しないことを思い出してください。したがって、この作業を行うには、プログラムは独自のルーチンを持つ必要があります。サーバーがトークンの受信用に使用するルーチンを、この例では recv_token() としています。詳細は、recv_token()を参照してください。


         do {
              if (recv_token(s, &recv_tok) < 0)
                   return -1;
  2. 次に、プログラムは GSS-API 関数 gss_accept_sec_context() を呼び出します。


      maj_stat = gss_accept_sec_context(&min_stat,
                                       context,
                                       server_creds,
                                       &recv_tok,
                                       GSS_C_NO_CHANNEL_BINDINGS,
                                       &client,
                                       &doid,
                                       &send_tok,
                                       ret_flags,
                                       NULL,    /* time_rec を無視する */
                                       NULL);   /* del_cred_handle を無視する */
    次に、各引数について説明します。
    • min_stat は実際の機構から戻されるエラー状態です。

    • context は確立されているコンテキストです。

    • server_creds は提供されているサーバーの資格です (資格の獲得を参照)。

    • recv_tokrecv_token() でクライアントから受信したトークンです。

    • GSS_C_NO_CHANNEL_BINDINGS はチャネルバインディングを使用しないことを示すフラグです (チャネルバインディングを参照)。

    • client はクライアント名 (ASCII 文字) です。

    • oid は機構です (OID 形式)。

    • send_tok はクライアントに送信するトークンです。

    • ret_flags は、コンテキストがサポートするオプション (メッセージ順序検出など) を示すさまざまなフラグです。

    • NULL は、コンテキストが有効である期間の長さにも、サーバーがクライアントのプロキシとして動作できるかどうかにも、プログラムが関与していないことを示します。

    gss_accept_sec_context()maj_statGSS_S_CONTINUE_NEEDED を設定している限り、受け入れループは継続します (エラーの場合を除く)。maj_stat の値が GSS_S_CONTINUE_NEEDED でも GSS_S_COMPLETE でもない場合、問題が発生し、ループが終了したことを示します。

  3. クライアントに返送するトークンが存在する場合、gss_accept_sec_context()send_tok の長さ (正の数) を戻します。次に行うことは、送信するトークンがあるかどうかを調べて、もしあれば、そのトークンを送信することです。


         if (send_tok.length != 0) {
              . . .
              if (send_token(s, &send_tok) < 0) {
                   fprintf(log, "failure sending token\n");
                   return -1;
              }
    
              (void) gss_release_buffer(&min_stat, &send_tok);
              }

メッセージのラップ解除

コンテキストを受け入れた後、サーバーはクライアントから送信されたメッセージを受信します。GSS-API はこの作業を行う関数を提供しないため、プログラムは独自の関数 recv_token() を使用します。


if (recv_token(s, &xmit_buf) < 0)
     return(-1);

メッセージは暗号化されている可能性があるため、プログラムは GSS-API 関数 gss_unwrap() でメッセージをラップ解除します。


maj_stat = gss_unwrap(&min_stat, context, &xmit_buf, &msg_buf,
                           &conf_state, (gss_qop_t *) NULL);
     if (maj_stat != GSS_S_COMPLETE) {
        display_status("unwrapping message", maj_stat, min_stat);
        return(-1);
     } else if (! conf_state) {
        fprintf(stderr, "Warning!  Message not encrypted.\n");
     }

     (void) gss_release_buffer(&min_stat, &xmit_buf);

gss_unwrap() は、recv_token()xmit_buf に格納したメッセージを入力として受け取り、そのメッセージを変換し、その結果を msg_buf に格納します。gss_unwrap() への 2 つの引数に注目してください。conf_state は、このメッセージに機密性が適用されたかどうか (つまり、メッセージが暗号化されているかどうか) を示すフラグです。次に、最後の NULL は、メッセージを保護するときに使用された QOP にプログラムが関与していないことを示します。

メッセージへの署名とメッセージの返送

最後にサーバーがすることは、メッセージに署名することです。つまり、メッセージの MIC (メッセージ整合性コード。メッセージに関連付けられた一意なタグ) をクライアントに戻すことによって、メッセージの送信とラップ解除が正常に完了したことをクライアントに証明します。このためには、プログラムは gss_get_mic() 関数を使用します。


maj_stat = gss_get_mic(&min_stat, context, GSS_C_QOP_DEFAULT,
                            &msg_buf, &xmit_buf);
この関数は msg_buf 内のメッセージを調べて、そのメッセージから MIC を生成し、その結果を xmit_buf に格納します。次に、サーバーは send_token() で MIC をクライアントに返送します。すると、クライアントは gss_verify_mic() で MIC を検証します。メッセージの検証を参照してください。

最後に、sign_server() はいくつかのクリーンアップを実行します。つまり、gss_release_buffer() で GSS-API バッファの msg_bufxmit_buf を解放し、次に、gss_delete_sec_context() でコンテキストを無効にします。

コンテキストのインポートとエクスポート

コンテキストのエクスポートとインポートで説明したとおり、GSS-API を使用すると、コンテキストをエクスポートおよびインポートできます。通常、これを行う理由は、マルチプロセスプログラムにおける異なるプロセス間でコンテキストを共有することです。

sign_server() には検証用の関数 test_import_export_context() があります。この関数は、コンテキストのエクスポートとインポートがどのように機能するかを示します。この関数は実際にはコンテキストをプロセス間で渡しません。コンテキストをエクスポートするのにかかった時間を表示し、次に、インポートするのにかかった時間を表示するだけです。実際には機能しませんが、この関数は GSS-API のインポート関数とエクスポート関数をどのように使用するかを示すとともに、コンテキストの操作に関連するタイムスタンプをどのように使用するかの参考にもなります。test_import_export_context() の詳細は、test_import_export_context()を参照してください。

クリーンアップ

main() 関数に戻ると、アプリケーションは gss_delete_cred() でサービスの資格を削除します。機構の OID が指定されていた場合は、gss_delete_oid() で削除します。そして、終了します。

付属の関数

クライアントとサーバーのプログラムはいくつかのサポート関数を使用しています。たとえば、戻されたフラグの値を表示するなどです。このような関数は GSS-API に固有ではないため、あるいは、それほど重要ではないため、ここでは説明しません。このような関数については、補助的な関数で説明されているものもあります。しかし、このような関数の中でも send_token()recv_token() の 2 つは特別で、重要です。したがって、send_token()recv_token()で説明されています。