この節では、クライアント側プログラム gss_client について説明します。
プログラムヘッダーはクライアントプログラムの宣言部分です。また、コマンド行が間違っていた場合に構文を説明する関数もあります。
/* * Copyright 1994 by OpenVision Technologies, Inc. * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appears in all copies and * that both that copyright notice and this permission notice appear in * supporting documentation, and that the name of OpenVision not be used * in advertising or publicity pertaining to distribution of the software * without specific, written prior permission. OpenVision makes no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied warranty. * * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <error.h> #include <sys/stat.h> #include <fcntl.h> #include <gssapi/gssapi.h> #include <gssapi/gssapi_ext.h> #include "gss-misc.h" /* ディスプレイ状態に必要な大域機構 oid、および資格の取得 */ gss_OID g_mechOid = GSS_C_NULL_OID; void usage() { fprintf(stderr, "Usage: gss-client [-port port] [-d]" " [-mech mechOid] host service msg\n"); exit(1); } |
main() はプログラムのエントリポイントです。次に、コマンド行構文を示します。
gss-client [-port port] [-d] [-mech mech] host service msg |
コマンド行を解析した後、main() は (指定されていれば) 該当するセキュリティ機構名を OID に変換し、安全な接続を確立し、そして、必要であれば、機構の OID を破棄します。
main() は標準ではない関数 gss_release_oid() を使用します。この関数は GSS-API のすべての実装でサポートされているわけではないため、可能な限り使用するべきではありません。アプリケーションは独自の機構を割り当てるのではなく、デフォルトの機構を使用するため (GSS_C_NULL_OID で指定)、gss_release_oid() 関数はあまり使用するべきではありません。ここで使用している理由は下位互換性のためと、この GSS-API 実装の全体を示すためです。
int main(argc, argv) int argc; char **argv; { /* char *service_name, *hostname, *msg; */ 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; /* 引数を解析する */ 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]; if (mechanism) parse_oid(mechanism, &g_mechOid); if (call_server(hostname, port, g_mechOid, service_name, deleg_flag, msg, use_file) < 0) exit(1); if (g_mechOid != GSS_C_NULL_OID) (void) gss_release_oid(&min_stat, &gmechOid); return 0; } |
GSS-API が処理できるように、(指定されていれば) コマンド行で指定されたセキュリティ機構名を OID に変換します。
このようなサンプルを示していますが、可能な限り、独自の機構を指定するのではなく、GSS-API 実装が提供するデフォルトの機構を使用することを強く推奨します。デフォルトの機構を取得するには、機構 OID 値を GSS_C_NULL_OID に設定します。また、gss_str_to_oid() 関数はすべての GSS-API 実装でサポートされているわけではありません。
static void parse_oid(char *mechanism, gss_OID *oid) { char *mechstr = 0, *cp; gss_buffer_desc tok; OM_uint32 maj_stat, min_stat; if (isdigit(mechanism[0])) { mechstr = malloc(strlen(mechanism)+5); if (!mechstr) { printf("Couldn't allocate mechanism scratch!\n"); return; } sprintf(mechstr, "{ %s }", mechanism); for (cp = mechstr; *cp; cp++) if (*cp == '.') *cp = ' '; tok.value = mechstr; } else tok.value = mechanism; tok.length = strlen(tok.value); maj_stat = gss_str_to_oid(&min_stat, &tok, oid); if (maj_stat != GSS_S_COMPLETE) { display_status("str_to_oid", maj_stat, min_stat); return; } if (mechstr) free(mechstr); } |
call_server() はプログラムの中心部分です。
/* * 関数: call_server * * 目的: 「署名」サービスを呼び出す * * 引数: * * host (r) サービスを提供するホスト * port (r) ホストに接続するためのポート * service_name (r) 認証する GSS-API サービス名 * msg (r) 「署名」されたメッセージ * * 戻り値: 成功した場合は 0、失敗した場合は -1 * * 効果: * * call_server は <host:port> への TCP 接続を開き、その接続上で * service_name との GSS-API コンテキストを確立する。次に、gss_wrap で * msg を GSS-API トークンにラップし、サーバーに送信し、サーバーから * 戻された msg の GSS-API の署名ブロックを読み取り、gss_verify で検証する。 * どこかで失敗した場合は -1 を戻し、そうでない場合は 0 を戻す。 */ int call_server(host, port, oid, service_name, deleg_flag, msg, use_file) char *host; u_short port; gss_OID oid; char *service_name; OM_uint32 deleg_flag; char *msg; int use_file; { gss_ctx_id_t context; gss_buffer_desc in_buf, out_buf, context_token; int s, state; OM_uint32 ret_flags; OM_uint32 maj_stat, min_stat; gss_name_t src_name, targ_name; gss_buffer_desc sname, tname; OM_uint32 lifetime; gss_OID mechanism, name_type; int is_local; OM_uint32 context_flags; int is_open; gss_qop_t qop_state; gss_OID_set mech_names; gss_buffer_desc oid_name; int i; int conf_req_flag = 0; int req_output_size = 1012; OM_uint32 max_input_size = 0; char *mechStr; /* 接続を開く */ if ((s = connect_to_server(host, port)) < 0) return -1; /* 接続を確立する */ if (client_establish_context(s, service_name, deleg_flag, oid, &context, &ret_flags) < 0) { (void) close(s); return -1; } /* 保存後、コンテキストを復元する */ 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_ctx_flags(ret_flags); /* コンテキスト情報を取得する */ 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_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); /* この機構によってサポートされる名前を取得する */ 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 { /* メッセージ内容を構造体へ設定完了 */ 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"); } /* サーバーに送信する */ 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); /* 署名ブロックを 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; } /* 署名ブロックを検証する */ 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; } (void) gss_release_buffer(&min_stat, &out_buf); if (use_file) free(in_buf.value); printf("Signature verified.\n"); /* コンテキストを削除する */ 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; } |
転送されるメッセージがファイルに格納されている場合、read_file() 関数は (call_server() から呼び出されて) ファイルを開いて読み取ります。
void read_file(file_name, in_buf) char *file_name; gss_buffer_t in_buf; { int fd, bytes_in, count; struct stat stat_buf; if ((fd = open(file_name, O_RDONLY, 0)) < 0) { perror("open"); fprintf(stderr, "Couldn't open file %s\n", file_name); exit(1); } if (fstat(fd, &stat_buf) < 0) { perror("fstat"); exit(1); } in_buf->length = stat_buf.st_size; in_buf->value = malloc(in_buf->length); if (in_buf->value == 0) { fprintf(stderr, "Couldn't allocate %ld byte buffer for reading file\n", in_buf->length); exit(1); } memset(in_buf->value, 0, in_buf->length); for (bytes_in = 0; bytes_in < in_buf->length; bytes_in += count) { count = read(fd, in_buf->value, (OM_uint32)in_buf->length); if (count < 0) { perror("read"); exit(1); } if (count == 0) break; } if (bytes_in != count) fprintf(stderr, "Warning, only read in %d bytes, expected %d\n", bytes_in, count); } |
client_establish_context() は gss_init_sec_context() を呼び出して、サーバーとのコンテキストを確立します。
/* * 関数: client_establish_context * * 目的: 指定されたサービスとの GSS-API コンテキストを確立し、 * コンテキストハンドルを戻す * * 引数: * * s (r) サーバーとの間で確立された TCP 接続 * service_name (r) サービスの ASCII 名 * context (w) 確立された GSS-API コンテキスト * ret_flags (w) init_sec_context から戻されたフラグ * * 戻り値: 成功した場合は 0、失敗した場合は -1 * * 効果: * * service_name が GSS-API 名としてインポートされ、 * 対応するサービスとの間で GSS-API コンテキストが確立される。 * サービスは TCP 接続 s 上で応答待ちする。デフォルトの * GSS-API 機構が使用され、相互認証とリプレイの検出が * 要求される。 * * 成功した場合、コンテキストハンドルが context に戻される。 * 失敗した場合、GSS-API エラーメッセージが stderr に表示され、 * -1 が戻される。 */ int client_establish_context(s, service_name, deleg_flag, oid, gss_context, ret_flags) int s; char *service_name; gss_OID oid; OM_uint32 deleg_flag; gss_ctx_id_t *gss_context; OM_uint32 *ret_flags; { gss_buffer_desc send_tok, recv_tok, *token_ptr; gss_name_t target_name; OM_uint32 maj_stat, min_stat; /* * 名前を target_name にインポートする。send_tok で * ローカル変数空間を保存する。 */ 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; } /* * コンテキスト確立ループを実行する。 * * ループを通過するごとに、token_ptr はサーバーに送信される * トークンに設定される (最初に通過するときは GSS_C_NO_BUFFER)。 * 生成された各トークンは send_tok に格納され、サーバーに送信される。 * 受信された各トークンは recv_tok に格納され、token_ptr は次の * gss_init_sec_context の呼び出しで処理されるトークンに * 設定される。 * * GSS-API は、以下の 2 つのことを保証する。 * 1.サーバーがクライアントからこれ以上トークンを期待していない場合のみ、 * send_tok の length の値が 0 になる。 * 2.サーバーにクライアントへ送るトークンが存在する場合のみ、 * gss_init_sec_context が GSS_S_CONTINUE_NEEDED を戻す。 */ token_ptr = GSS_C_NO_BUFFER; *gss_context = GSS_C_NO_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, /* チャネルバインディングなし */ token_ptr, NULL, /* 機構の型を無視する */ &send_tok, ret_flags, NULL); /* 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); (void) gss_release_name(&min_stat, &target_name); return 0; } |
connect_to_server() は TCP 接続を作成するだけの基本的な関数です。
/* * 関数: connect_to_server * * 目的: 指定されたホストとポートへの TCP 接続を開く * * 引数: * * host (r) ターゲットのホスト名 * port (r) ターゲットのポート。ホストのバイト順。 * * 戻り値: 成功した場合は、確立されたソケットのファイル記述子。 * 失敗した場合は -1。 * * 効果: * * ホスト名が gethostbyname() で解釈処理され、ソケットが開かれ、 * 接続される。エラーが発生した場合、エラーメッセージが表示され、 * -1 が戻される。 */ 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; } |