Oracle Solaris セキュリティーサービス開発ガイド

第 5 章 GSS-API クライアント例

この章では、一般的な GSS-API クライアントアプリケーションについて段階的に説明します。次の項目について説明します。

GSS-API クライアント例の概要

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

以降では、gss-client がどのように動作するかを段階的に説明します。gss-client は、GSS-API の機能説明用に設計されたプログラム例であるため、関連部分についてのみ詳しく説明します。2 つのアプリケーションの完全なソースコードは付録に含まれています。また、次の場所からダウンロードすることもできます。

http://developers.sun.com/prodtech/solaris/downloads/index.html

GSS-API クライアント例の構造

gss-client アプリケーションは次の手順を実行します。

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

  2. 機構が指定されている場合、その機構のオブジェクト ID (OID) を作成します。それ以外の場合、デフォルトの機構が使用されます。これがごく普通の場合です。

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

  4. セキュリティーコンテキストを確立します。

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

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

  7. セキュリティーコンテキストを削除します。

GSS-API クライアント例の実行

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


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

次に、クライアントアプリケーションプログラムの一般的なコマンド行の例を示します。


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

次の例では、機構、ポート、および委託が指定されていません。


% gss-client erebos.eng nfs "ls"

GSSAPI クライアント例: main() 関数

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

main() ルーチンのソースコードを、次に示します。


注 –

このソースコード例は、Sun ダウンロードセンターからダウンロードすることも可能です。http://www.sun.com/download/products.xml?id=41912db5 を参照してください。



例 5–1 gss-client 例: main()

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;

sint 型のファイル記述子であり、最初は socket() の呼び出しから戻されます。

connect_to_server() はソケット経由で接続を作成する単純な関数であり、GSS-API を使用していません。connect_to_server() のソースコードを、次に示します。


注 –

このソースコード例は、Sun ダウンロードセンターからダウンロードすることも可能です。http://www.sun.com/download/products.xml?id=41912db5 を参照してください。



例 5–2 connect_to_server() 関数

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;
     }

client_establish_context() は次の作業を実行します。

サービス名の GSS-API 形式への変換

client_establish_context() が実行する最初の作業は、gss_import_name() を使ってサービス名の文字列を GSS-API 内部形式に変換することです。


例 5–3 client_establish_context() – サービス名の変換

     /*
     * 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 セキュリティーコンテキストの確立

サービスの GSS-API 内部形式への変換が完了すると、コンテキストを確立できます。移植性を最大にするには、コンテキストの確立を常にループとして実行する必要があります。

ループに入る前に、client_establish_context() は、コンテキストと token_ptr パラメータを初期化します。token_ptr の使用には選択肢があります。token_ptr がポイントできるのは、サーバーに送信するトークンである send_tok、サーバーから返送されてきたトークンである recv_tok のいずれかです。

ループの内側では、次の 2 つの項目が検査されます。

次に、このループの擬似コードを示します。

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

ループの最初で、gss_init_sec_context() が呼び出されます。この関数の引数は次のとおりです。


注 –

クライアントは、コンテキストの起動前に資格を取得する必要はありません。クライアント側では、資格の管理は 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 を参照してください。



例 5–4 コンテキスト確立用のループ

/*
 * 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-API コンテキスト操作

プログラム例として、gss-client はいくつかの関数をデモ目的で実行しています。次のソースコードは基本作業に不可欠なものではありませんが、次の操作の使用法を示す目的で掲載します。

これらの操作を含むソースコードを、次に示します。


注 –

このソースコード例は、Sun ダウンロードセンターからダウンロードすることも可能です。http://www.sun.com/download/products.xml?id=41912db5 を参照してください。



例 5–5 gss-client: call_server() コンテキストの確立

/* 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 を参照してください。



例 5–6 gss-client 例: call_server() – メッセージのラップ

/* 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-API クライアントにおける署名ブロックの読み取りと検証

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 を参照してください。



例 5–7 gss-client 例 – 署名ブロックの読み取りと検証

/* 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 を参照してください。



例 5–8 gss-client 例: call_server() – コンテキストの削除

/* 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;