この章では、一般的な GSS-API クライアントアプリケーションについて段階的に説明します。次の項目について説明します。
クライアント側プログラム例 gss-client は、サーバーとのセキュリティーコンテキストを作成し、セキュリティーパラメータを確立し、文字列 (メッセージ) をサーバーに送信します。 このプログラムは接続時に、単純な TCP ベースのソケット接続を使用します。
以降では、gss-client がどのように動作するかを段階的に説明します。gss-client は、GSS-API の機能説明用に設計されたプログラム例であるため、関連部分についてのみ詳しく説明します。2 つのアプリケーションの完全なソースコードは付録に含まれています。また、次の場所からダウンロードすることもできます。
http://developers.sun.com/prodtech/solaris/downloads/index.html
gss-client アプリケーションは次の手順を実行します。
コマンド行を解析します。
機構が指定されている場合、その機構のオブジェクト ID (OID) を作成します。それ以外の場合、デフォルトの機構が使用されます。これがごく普通の場合です。
サーバーとの接続を設定します。
セキュリティーコンテキストを確立します。
メッセージをラップして送信します。
サーバーが正しくメッセージに署名していることを検証します。
セキュリティーコンテキストを削除します。
次に、gss-client 例のコマンド行の書式を示します。
gss-client [-port port] [-d] [-mech mech] host service-name [-f] msg |
port – host で指定されたリモートマシンへの接続を確立するためのポート番号です。
-d フラグ – サーバーへのセキュリティー資格の委託を可能にします。具体的には、deleg-flag 変数が GSS-API 値 GSS_C_DELEG_FLAG に設定されます。d フラグが設定されていない場合、deleg-flag は 0 に設定されます。
mech – Kerberos v5 など、使用するセキュリティー機構の名前です。機構が指定されていない場合、GSS-API はデフォルトの機構を使用します。
host – サーバーの名前です。
service-name – クライアントが要求するネットワークサービスの名前です。そのようなサービスの一般的な例として、telnet、ftp、login などが挙げられます。
msg – 保護されたデータとしてサーバーに送信される文字列です。-f オプションが指定されている場合、msg は文字列を読み取るべきファイル名です。
次に、クライアントアプリケーションプログラムの一般的なコマンド行の例を示します。
% gss-client -port 8080 -d -mech kerberos_v5 erebos.eng nfs "ls" |
次の例では、機構、ポート、および委託が指定されていません。
% gss-client erebos.eng nfs "ls" |
すべての C プログラムと同様に、プログラムの外部骨格はエントリポイント関数 main() に含まれます。main() は次の 4 つの機能を実行します。
コマンド行引数を解析し、それらを変数に代入します。
デフォルト以外の機構を使用する必要がある場合、parse_oid() を呼び出して GSS-API OID (オブジェクト識別子) を作成します。オブジェクト識別子はセキュリティー機構の名前から生成されます。ただし、それには機構名が指定される必要があります。
call_server() を呼び出します。この関数は、コンテキストの作成とデータの送信を実際に行います。
データの送信後、必要に応じて OID の記憶領域を解放します。
このソースコード例は、Sun ダウンロードセンターからダウンロードすることも可能です。http://www.sun.com/download/products.xml?id=41912db5 を参照してください。
int main(argc, argv) int argc; char **argv; { char *msg; char service_name[128]; char hostname[128]; char *mechanism = 0; u_short port = 4444; int use_file = 0; OM_uint32 deleg_flag = 0, min_stat; display_file = stdout; /* Parse command-line arguments. */ argc--; argv++; while (argc) { if (strcmp(*argv, "-port") == 0) { argc--; argv++; if (!argc) usage(); port = atoi(*argv); } else if (strcmp(*argv, "-mech") == 0) { argc--; argv++; if (!argc) usage(); mechanism = *argv; } else if (strcmp(*argv, "-d") == 0) { deleg_flag = GSS_C_DELEG_FLAG; } else if (strcmp(*argv, "-f") == 0) { use_file = 1; } else break; argc--; argv++; } if (argc != 3) usage(); if (argc > 1) { strcpy(hostname, argv[0]); } else if (gethostname(hostname, sizeof(hostname)) == -1) { perror("gethostname"); exit(1); } if (argc > 2) { strcpy(service_name, argv[1]); strcat(service_name, "@"); strcat(service_name, hostname); } msg = argv[2]; /* Create GSSAPI object ID. */ if (mechanism) parse_oid(mechanism, &g_mechOid); /* Call server to create context and send data. */ if (call_server(hostname, port, g_mechOid, service_name, deleg_flag, msg, use_file) < 0) exit(1); /* Release storage space for OID, if still allocated */ if (g_mechOid != GSS_C_NULL_OID) (void) gss_release_oid(&min_stat, &gmechOid); return 0; }
call_server() 関数は、次のコードを使ってサーバーとの接続を確立します。
if ((s = connect_to_server(host, port)) < 0) return -1;
s は int 型のファイル記述子であり、最初は socket() の呼び出しから戻されます。
connect_to_server() はソケット経由で接続を作成する単純な関数であり、GSS-API を使用していません。connect_to_server() のソースコードを、次に示します。
このソースコード例は、Sun ダウンロードセンターからダウンロードすることも可能です。http://www.sun.com/download/products.xml?id=41912db5 を参照してください。
int connect_to_server(host, port) char *host; u_short port; { struct sockaddr_in saddr; struct hostent *hp; int s; if ((hp = gethostbyname(host)) == NULL) { fprintf(stderr, "Unknown host: %s\n", host); return -1; } saddr.sin_family = hp->h_addrtype; memcpy((char *)&saddr.sin_addr, hp->h_addr, sizeof(saddr.sin_addr)); saddr.sin_port = htons(port); if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("creating socket"); return -1; } if (connect(s, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) { perror("connecting to server"); (void) close(s); return -1; } return s; }
接続が確立されたあと、call_server() は、次のように関数 client_establish_context() を使ってセキュリティーコンテキストを作成します。
if (client_establish_context(s, service-name, deleg-flag, oid, &context, &ret-flags) < 0) { (void) close(s); return -1; }
s は、connect_to_server() で確立された接続を表すファイル記述子です。
service-name は、要求されたネットワークサービスです。
deleg-flag は、サーバーがクライアントのプロキシとして動作できるかどうかを指定します。
oid は機構です。
context は作成されるコンテキストです。
ret-flags は、GSS-API 関数 gss_init_sec_context() から戻される任意のフラグを表す int です。
client_establish_context() は次の作業を実行します。
サービス名を GSS-API 内部形式に変換します
セキュリティーコンテキストが完了するまで、クライアントとサーバー間のトークン交換ループを実行します
client_establish_context() が実行する最初の作業は、gss_import_name() を使ってサービス名の文字列を GSS-API 内部形式に変換することです。
/* * Import the name into target_name. Use send_tok to save * local variable space. */ send_tok.value = service_name; send_tok.length = strlen(service_name) + 1; maj_stat = gss_import_name(&min_stat, &send_tok, (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, &target_name); if (maj_stat != GSS_S_COMPLETE) { display_status("parsing name", maj_stat, min_stat); return -1; }
gss_import_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 内部形式への変換が完了すると、コンテキストを確立できます。移植性を最大にするには、コンテキストの確立を常にループとして実行する必要があります。
ループに入る前に、client_establish_context() は、コンテキストと token_ptr パラメータを初期化します。token_ptr の使用には選択肢があります。token_ptr がポイントできるのは、サーバーに送信するトークンである send_tok、サーバーから返送されてきたトークンである recv_tok のいずれかです。
ループの内側では、次の 2 つの項目が検査されます。
gss_init_sec_context() から戻される状態
戻り状態は、ループの異常終了を要求する可能性のあるすべてのエラーを捕捉します。gss_init_sec_context() が GSS_S_CONTINUE_NEEDED を戻すのは、別の送信すべきトークンがサーバー側に存在する場合に限ります。
サーバーに送信すべきトークンのサイズ (gss_init_sec_context() によって生成される)
トークンサイズ 0 は、サーバーに送信できる情報がこれ以上存在しないことと、ループが終了可能であることを意味します。トークンサイズは token_ptr で決定されます。
次に、このループの擬似コードを示します。
do gss_init_sec_context() if (コンテキストが作成されなかった場合) エラーを出力して終了する if (状態が「完了」または「処理中」のどちらでもない場合) サービスの名前空間を解放し、エラーを出力して終了する if (サーバーに送信するトークンがある場合、つまりサイズが 0 以外の場合) トークンを送信する if (トークンの送信が失敗した場合) トークンとサービスの名前空間を解放し、エラーを出力して終了する 送信し終わったトークンの名前空間を解放する if (コンテキストの確立が完了していない場合) サーバーからトークンを受信する while (コンテキストが完了していない)
ループの最初で、gss_init_sec_context() が呼び出されます。この関数の引数は次のとおりです。
実際の機構が設定する状態コード。
資格ハンドル。例ではデフォルトの主体として動作させるために、GSS_C_NO_CREDENTIAL を使用します。
作成するコンテキストハンドル。
コンテキスト受け入れ側の名前。
必要な機構のオブジェクト ID。
要求フラグ。この場合にクライアントが要求することは、サーバーが自分自身を認証すること、メッセージの複製をオンにすること、要求された場合にサーバーがプロキシとして動作すること、のいずれかです。
コンテキストの時間制限はありません。
チャネルバインディングの要求はありません。
ピアアプリケーションから受信するトークン。
サーバーが実際に使用する機構。アプリケーションがこの値を使用しないため、ここでは NULL に設定されています。
ピアアプリケーションに送信するトークン。これは、gss_init_sec_context() が作成するトークンです。
戻りフラグ。この例では無視するため、NULL に設定されています。
クライアントは、コンテキストの起動前に資格を取得する必要はありません。クライアント側では、資格の管理は GSS-API によって透過的に処理されます。つまり、この主体のためにこの機構が作成した資格をどのように取得するかを、GSS-API は知っているということです。このため、アプリケーションは gss_init_sec_context() にデフォルトの資格を渡しています。しかし、サーバー側では、サーバーアプリケーションはコンテキストを受け入れる前に、サービスの資格を明示的に獲得する必要があります。「資格の獲得」を参照してください。
connect_to_server() は、コンテキストまたはその一部が存在しており、かつ gss_init_sec_context() が有効な状態を戻していることを確認したあと、gss_init_sec_context() がサーバーに送信すべきトークンを提供しているかどうかを検査します。トークンが存在しない場合、それはトークンがこれ以上必要ないことを、サーバーが示していると考えられます。トークンが提供された場合、そのトークンをサーバーに送信する必要があります。トークンの送信に失敗した場合、トークンとサービスの名前空間を決定できないため、connect_to_server() が終了します。次のアルゴリズムは、トークンの長さを調べることでトークンの存在の有無を検査しています。
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() 関数は、トークンをファイル記述子に書き込みます。send_token() は、正常終了時に 0 を、エラー時に –1 を戻します。GSS-API 自身はトークンの送受信を行いません。GSS-API によって作成されたトークンを送受信することは、呼び出し元のアプリケーションの責任です。
コンテキスト確立ループのソースコードを、次に示します。
このソースコード例は、Sun ダウンロードセンターからダウンロードすることも可能です。http://www.sun.com/download/products.xml?id=41912db5 を参照してください。
/* * Perform the context establishment loop. * * On each pass through the loop, token_ptr points to the token * to send to the server (or GSS_C_NO_BUFFER on the first pass). * Every generated token is stored in send_tok which is then * transmitted to the server; every received token is stored in * recv_tok, which token_ptr is then set to, to be processed by * the next call to gss_init_sec_context. * * GSS-API guarantees that send_tok's length will be non-zero * if and only if the server is expecting another token from us, * and that gss_init_sec_context returns GSS_S_CONTINUE_NEEDED if * and only if the server has another token to send us. */ token_ptr = GSS_C_NO_BUFFER; *gss_context = GSS_C_NO_CONTEXT; 1234567890123456789012345678901234567890123456789012345678901234567890123456 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, /* no channel bindings */ token_ptr, NULL, /* ignore mech type */ &send_tok, ret_flags, NULL); /* ignore time_rec */ if (gss_context == NULL){ printf("Cannot create context\n"); return GSS_S_NO_CONTEXT; } if (token_ptr != GSS_C_NO_BUFFER) (void) gss_release_buffer(&min_stat, &recv_tok); if (maj_stat!=GSS_S_COMPLETE && maj_stat!=GSS_S_CONTINUE_NEEDED) { display_status("initializing context", maj_stat, min_stat); (void) gss_release_name(&min_stat, &target_name); return -1; } if (send_tok.length != 0){ fprintf(stdout, "Sending init_sec_context token (size=%ld)...", send_tok.length); 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; } } (void) gss_release_buffer(&min_stat, &send_tok); if (maj_stat == GSS_S_CONTINUE_NEEDED) { fprintf(stdout, "continue needed..."); if (recv_token(s, &recv_tok) < 0) { (void) gss_release_name(&min_stat, &target_name); return -1; } token_ptr = &recv_tok; } printf("\n"); } while (maj_stat == GSS_S_CONTINUE_NEEDED);
send_token() と recv_token() の動作方法についての詳細は、「その他の GSS-API 関数例」を参照してください。
プログラム例として、gss-client はいくつかの関数をデモ目的で実行しています。次のソースコードは基本作業に不可欠なものではありませんが、次の操作の使用法を示す目的で掲載します。
コンテキストの保存と復元
コンテキストフラグの表示
コンテキストの状態の取得
これらの操作を含むソースコードを、次に示します。
このソースコード例は、Sun ダウンロードセンターからダウンロードすることも可能です。http://www.sun.com/download/products.xml?id=41912db5 を参照してください。
/* Save and then restore the context */ maj_stat = gss_export_sec_context(&min_stat, &context, &context_token); if (maj_stat != GSS_S_COMPLETE) { display_status("exporting context", maj_stat, min_stat); return -1; } maj_stat = gss_import_sec_context(&min_stat, &context_token, &context); if (maj_stat != GSS_S_COMPLETE) { display_status("importing context", maj_stat, min_stat); return -1; } (void) gss_release_buffer(&min_stat, &context_token); /* display the flags */ display_ctx_flags(ret_flags); /* Get context information */ maj_stat = gss_inquire_context(&min_stat, context, &src_name, &targ_name, &lifetime, &mechanism, &context_flags, &is_local, &is_open); if (maj_stat != GSS_S_COMPLETE) { display_status("inquiring context", maj_stat, min_stat); return -1; } if (maj_stat == GSS_S_CONTEXT_EXPIRED) { printf(" context expired\n"); display_status("Context is expired", maj_stat, min_stat); return -1; }
gss-client アプリケーションは、データを送信する前にデータをラップ (つまり暗号化) する必要があります。アプリケーションは、次の手順に従ってメッセージをラップします。
ラップサイズの制限値を決定します。この処理により、ラップ後のメッセージをプロトコルが確実に処理できることが保証されます。
ソース名とターゲット名を取得します。名前をオブジェクト識別子から文字列に変換します。
機構名の一覧を取得します。名前をオブジェクト識別子から文字列に変換します。
メッセージをバッファーに挿入し、メッセージをラップします。
サーバーにメッセージを送信します。
メッセージをラップするソースコードを、次に示します。
このソースコード例は、Sun ダウンロードセンターからダウンロードすることも可能です。http://www.sun.com/download/products.xml?id=41912db5 を参照してください。
/* Test gss_wrap_size_limit */ maj_stat = gss_wrap_size_limit(&min_stat, context, conf_req_flag, GSS_C_QOP_DEFAULT, req_output_size, &max_input_size); if (maj_stat != GSS_S_COMPLETE) { display_status("wrap_size_limit call", maj_stat, min_stat); } else fprintf (stderr, "gss_wrap_size_limit returned " "max input size = %d \n" "for req_output_size = %d with Integrity only\n", max_input_size , req_output_size , conf_req_flag); conf_req_flag = 1; maj_stat = gss_wrap_size_limit(&min_stat, context, conf_req_flag, GSS_C_QOP_DEFAULT, req_output_size, &max_input_size); if (maj_stat != GSS_S_COMPLETE) { display_status("wrap_size_limit call", maj_stat, min_stat); } else fprintf (stderr, "gss_wrap_size_limit returned " " max input size = %d \n" "for req_output_size = %d with " "Integrity & Privacy \n", max_input_size , req_output_size ); maj_stat = gss_display_name(&min_stat, src_name, &sname, &name_type); if (maj_stat != GSS_S_COMPLETE) { display_status("displaying source name", maj_stat, min_stat); return -1; } maj_stat = gss_display_name(&min_stat, targ_name, &tname, (gss_OID *) NULL); if (maj_stat != GSS_S_COMPLETE) { display_status("displaying target name", maj_stat, min_stat); return -1; } fprintf(stderr, "\"%.*s\" to \"%.*s\", lifetime %u, flags %x, %s, %s\n", (int) sname.length, (char *) sname.value, (int) tname.length, (char *) tname.value, lifetime, context_flags, (is_local) ? "locally initiated" : "remotely initiated", (is_open) ? "open" : "closed"); (void) gss_release_name(&min_stat, &src_name); (void) gss_release_name(&min_stat, &targ_name); (void) gss_release_buffer(&min_stat, &sname); (void) gss_release_buffer(&min_stat, &tname); maj_stat = gss_oid_to_str(&min_stat, name_type, &oid_name); if (maj_stat != GSS_S_COMPLETE) { display_status("converting oid->string", maj_stat, min_stat); return -1; } fprintf(stderr, "Name type of source name is %.*s.\n", (int) oid_name.length, (char *) oid_name.value); (void) gss_release_buffer(&min_stat, &oid_name); /* Now get the names supported by the mechanism */ maj_stat = gss_inquire_names_for_mech(&min_stat, mechanism, &mech_names); if (maj_stat != GSS_S_COMPLETE) { display_status("inquiring mech names", maj_stat, min_stat); return -1; } maj_stat = gss_oid_to_str(&min_stat, mechanism, &oid_name); if (maj_stat != GSS_S_COMPLETE) { display_status("converting oid->string", maj_stat, min_stat); return -1; } mechStr = (char *)__gss_oid_to_mech(mechanism); fprintf(stderr, "Mechanism %.*s (%s) supports %d names\n", (int) oid_name.length, (char *) oid_name.value, (mechStr == NULL ? "NULL" : mechStr), mech_names->count); (void) gss_release_buffer(&min_stat, &oid_name); for (i=0; i < mech_names->count; i++) { maj_stat = gss_oid_to_str(&min_stat, &mech_names->elements[i], &oid_name); if (maj_stat != GSS_S_COMPLETE) { display_status("converting oid->string", maj_stat, min_stat); return -1; } fprintf(stderr, " %d: %.*s\n", i, (int) oid_name.length, ( char *) oid_name.value); (void) gss_release_buffer(&min_stat, &oid_name); } (void) gss_release_oid_set(&min_stat, &mech_names); if (use_file) { read_file(msg, &in_buf); } else { /* Wrap the message */ 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, 1, 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"); } /* Send to server */ if (send_token(s, &out_buf) < 0) { (void) close(s); (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER); return -1; } (void) gss_release_buffer(&min_stat, &out_buf);
gss-client プログラムはこの段階で、送信したメッセージの有効性を検証できます。サーバーは、送信メッセージに対する MIC を戻します。そのメッセージは recv_token() を使って取得できます。
そして、gss_verify_mic() 関数を使ってメッセージの「署名」つまり MIC を検証します。gss_verify_mic() は、受け取った MIC を元のラップされていないメッセージと比較します。受け取った MIC は、out_buf に格納されたサーバーのトークンから取得します。ラップされていないメッセージの MIC は、in_buf 内に格納されています。2 つの MIC が一致した場合、メッセージの有効性は検証されたことになります。その後、クライアントは受け取ったトークンのバッファー (out_buf) を解放します。
次のソースコードは、署名ブロックの読み取りと検証を行う方法を示しています。
このソースコード例は、Sun ダウンロードセンターからダウンロードすることも可能です。http://www.sun.com/download/products.xml?id=41912db5 を参照してください。
/* Read signature block into out_buf */ if (recv_token(s, &out_buf) < 0) { (void) close(s); (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER); return -1; } /* Verify signature block */ maj_stat = gss_(&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; } (void) gss_release_buffer(&min_stat, &out_buf); if (use_file) free(in_buf.value); printf("Signature verified.\n");
call_server() 関数は最後に、コンテキストを削除したあと、main() 関数に戻ります。
このソースコード例は、Sun ダウンロードセンターからダウンロードすることも可能です。http://www.sun.com/download/products.xml?id=41912db5 を参照してください。
/* Delete context */ maj_stat = gss_delete_sec_context(&min_stat, &context, &out_buf); if (maj_stat != GSS_S_COMPLETE) { display_status("deleting context", maj_stat, min_stat); (void) close(s); (void) gss_delete_sec_context(&min_stat, &context, GSS_C_NO_BUFFER); return -1; } (void) gss_release_buffer(&min_stat, &out_buf); (void) close(s); return 0;