Solaris 开发者安全性指南

第 5 章 GSS-API 客户机示例

本章将对典型的 GSS-API 客户机应用程序进行详细介绍,其中包含以下主题:

GSSAPI 客户机示例概述

样例客户端程序 gss-client可创建与服务器之间的安全上下文,建立安全参数,并将消息字符串发送到服务器。 此程序使用基于 TCP 的简单套接字连接机制来建立连接。

以下几节将提供有关 gss-client 工作方式的逐步说明。 由于 gss-client 是一个旨在说明 GSSAPI 功能的样例程序,因此仅详细讨论该程序的相关部分。 这两个应用程序的完整源代码可在附录中找到,也可以从以下网址下载:

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

GSSAPI 客户机示例结构

gss-client 应用程序执行以下步骤:

  1. 解析命令行。

  2. 如果指定了机制,则为机制创建一个对象 ID (object ID, OID); 否则,使用缺省机制,此为最常见的情况。

  3. 创建与服务器的连接。

  4. 建立安全上下文。

  5. 包装和发送消息。

  6. 检验服务器是否已对消息进行正确的“签名”。

  7. 删除安全上下文。

运行 GSSAPI 客户机示例

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 程序一样,该程序的外部 shell 也包含在入口点函数 main() 中。main() 可执行以下四种功能:

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;

s 是一个最初通过调用 socket() 返回的 int 类型的文件描述符。

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_namesend_tok 用于节省空间,而不是用于声明新的 gss_buffer_desc。 第三个参数类型为 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(服务器发回的令牌)。

在循环内部,需要检查两项:

以下伪代码对该循环进行了说明:

do

     gss_init_sec_context()

     if no context was created

         exit with error;

    if the status is neither "complete" nor "in process"

          release the service namespace and exit with error;

    if there is a token to send to the server, that is, the size is nonzero

          send the token;

         if sending the token  fails,

               release the token and service namespaces. Exit with error;

         release the namespace for the token that was just sent;

     if  the context is not completely set up

          receive a token from the server;

while the context is not complete

该循环从调用 gss_init_sec_context() 开始,该函数使用以下参数:


注 –

客户机在启动上下文之前无需获取凭证。 在客户端,凭证管理通过 GSS-API 以透明方式处理。即,GSS-API 知道如何获取此机制为该主体创建的凭证。因此,应用程序可以向 gss_init_sec_context() 传递缺省凭证。但是在服务器端,服务器应用程序在接受上下文之前必须明确获取服务的凭证。请参见获取凭证


检查了上下文或其一部分是否存在,以及 gss_init_sec_context() 是否返回有效状态之后,connect_to_server() 会检查 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 样例函数

客户端上的各种 GSSAPI 上下文操作

作为样例程序,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 中。如果这两个 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;