機構を 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; |
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) |
s は、connect_to_server() で確立される接続を表すファイル記述子です。
service_name は要求するネットワークサービスです (nfs など)。
deleg_flag は、サーバーがクライアントのプロキシとして動作するかどうかを指定します。
oid は機構です。
context は作成されるコンテキストです。
ret_flags は GSS-API 関数 gss_init_sec_context() から戻されるフラグを指定します (型は int)。
コンテキストを起動するために、アプリケーションは 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; |
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_C_NO_CREDENTIAL を使用します。
(gss_context) 作成するコンテキストハンドル。
(target_name) GSS_API 内部名形式のサービス。
(oid) 機構。
要求フラグ。この場合、クライアントは、
a) サーバーが自分自身を認証すること
b) メッセージの複製をオンにすること
c) サーバーがプロキシとして動作すること (要求された場合)
を要求します。
コンテキストの時間制限はありません。
チャネルバインディングの要求はありません。
(token_ptr) サーバーから受け取るトークンへのポインタ (ある場合)。
サーバーが実際に使用する機構。アプリケーションがこの値に関与していないため、ここでは NULL に設定されています。
(&send_tok) サーバーに送信するために gss_init_sec_context() が作成するトークン。
戻りフラグ。ここでは無視するため、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() に戻ります。