在提供安全性方面,GSS-API 最重要的两个任务就是创建安全上下文和保护数据。应用程序获取必要的凭证之后,必须建立安全上下文。建立上下文时会涉及到两个应用程序,一个应用程序(通常是客户机)用于启动上下文,另一个应用程序(通常是服务器)用于接受上下文。允许在对等应用程序之间使用多个上下文。
相互通信的应用程序通过交换验证令牌来建立共同的安全上下文。安全上下文是一对 GSS-API 数据结构,其中包含要在两个应用程序之间共享的信息。这些信息从安全的角度描述了每个应用程序的状态。安全上下文是保护数据所必需的。
gss_init_sec_context() 函数用于启动应用程序和远程服务器之间的安全上下文。如果成功,该函数将返回一个上下文句柄以建立上下文,还将返回一个要发送到接受器的上下文级别的令牌。
调用 gss_init_sec_context() 之前,客户机应当执行以下任务:
使用 gss_acquire_cred() 获取凭证(如有必要)。通常,客户机会在登录时接收凭证。gss_acquire_cred() 只能从正在运行的操作系统中检索初始凭证。
使用 gss_import_name() 以 GSS-API 内部格式导入服务器的名称。有关名称和 gss_import_name 的更多信息,请参见Names in GSS-API()。
调用 gss_init_sec_context() 时,客户机通常会传递以下参数值:
GSS_C_NO_CREDENTIAL(用于 cred_handle 参数),用于表示缺省凭证。
GSS_C_NULL_OID(用于 mech_type 参数),用于表示缺省机制。
GSS_C_NO_CONTEXT(用于 context_handle 参数),用于表示初始的空上下文。由于 gss_init_sec_context() 通常会循环调用,因此后续的调用应传递以前的调用所返回的上下文句柄。
GSS_C_NO_BUFFER(用于 input_token 参数),用于表示最初为空的令牌。或者,应用程序可以传递一个指向 gss_buffer_desc 对象的指针,该对象的长度字段已经设置为零。
使用 gss_import_name() 以 GSS-API 内部格式导入的服务器名称。
应用程序不一定要使用这些缺省值。此外,客户机还可以使用 req_flags 参数指定其他安全参数的要求。下面描述了完整的 gss_init_sec_context() 参数集。
上下文接受器可能需要多次握手才能建立上下文。也就是说,接受器会要求启动器先发送多段上下文信息,然后再建立完整的上下文。因此,为了实现可移植性,应始终在检查上下文是否已完全建立的循环过程中启动上下文。
如果上下文不完整,则 gss_init_sec_context() 将返回一个主状态码 GSS_C_CONTINUE_NEEDED。因此,循环应当使用 gss_init_sec_context() 的返回值来测试是否继续执行启动循环操作。
客户机将上下文信息以输出令牌形式传递到服务器,输出令牌通过 gss_init_sec_context() 返回。客户机可接收从服务器返回的输入令牌形式的信息。以后调用 gss_init_sec_context() 时,输入令牌可以作为参数传递。但是,如果所收到的输入令牌的长度为零,则表明服务器不再需要其他输出令牌。
因此,除了检查 gss_init_sec_context() 的返回状态以外,该循环还应当检查输入令牌的长度。如果长度是非零值,则需要向服务器发送其他令牌。开始循环之前,应将输入令牌的长度初始化为零。请将输入令牌设置为 GSS_C_NO_BUFFER 或者将结构的长度字段设置为零值。
下面的伪代码举例说明如何从客户端建立上下文:
错误检查范围越大,实际循环将越完整。有关此类上下文启动循环的实际示例,请参见Establishing a Security Context With the Server。此外,gss_init_sec_context(3GSS) 手册页还提供了比较特殊的示例。
通常,在上下文未完全建立时返回的参数值即是那些会在上下文完整建立时返回的值。有关更多信息,请参见 gss_init_sec_context() 手册页。
如果 gss_init_sec_context() 成功完成,将返回 GSS_S_COMPLETE。如果对等应用程序需要上下文建立令牌,将返回 GSS_S_CONTINUE_NEEDED。如果出现错误,则将返回 gss_init_sec_context(3GSS) 手册页中所示的错误代码。
如果上下文启动失败,则客户机会断开与服务器的连接。
建立上下文的另一方面是接受上下文,这可通过 gss_accept_sec_context() 函数来完成。通常情况下,服务器接受客户机使用 gss_init_sec_context() 已启动的上下文。
gss_accept_sec_context() 的主要输入是来自启动器的输入令牌。启动器会返回一个上下文句柄,以及一个要返回到启动器的输出令牌。但是,服务器应当首先获取客户机所请求服务的凭证,然后才能调用 gss_accept_sec_context()。服务器使用 gss_acquire_cred() 函数获取这些凭证。或者,服务器可以指定在调用 gss_accept_sec_context() 时的缺省凭证(即 GSS_C_NO_CREDENTIAL),从而无需显式获取凭证。
调用 gss_accept_sec_context() 时,服务器可以按如下方式设置以下参数:
cred_handle-gss_acquire_cred() 返回的凭证句柄。或者,可以使用 GSS_C_NO_CREDENTIAL 来表示缺省凭证。
context_handle-GSS_C_NO_CONTEXT 表示最初为空的上下文。由于 gss_init_sec_context() 通常会循环调用,因此后续的调用应传递以前的调用所返回的上下文句柄。
input_token-从客户机接收的上下文令牌。
以下几段将介绍完整的 gss_accept_sec_context() 参数集。
建立安全上下文可能需要几次握手。启动器和接受器通常需要先发送多段上下文信息,然后才能建立完整的上下文。因此,为了实现可移植性,应始终在检查上下文是否已完全建立的循环过程中接受上下文。如果上下文尚未建立,则 gss_accept_sec_context() 将返回一个主状态码 GSS_C_CONTINUE_NEEDED。因此,循环应当使用 gss_accept_sec_context() 返回的值来测试是否继续执行接受循环操作。
上下文接受器将上下文信息以输出令牌形式返回到启动器,输出令牌通过 gss_accept_sec_context() 返回。以后,接受器可以从启动器接收输入令牌形式的其他信息。以后调用 gss_accept_sec_context() 时,输入令牌将作为参数传递。gss_accept_sec_context() 不再向启动器发送令牌时,将返回一个长度为零的输出令牌。除了检查返回状态 gss_accept_sec_context() 以外,循环还应当检查输出令牌的长度,查看是否必须发送其他令牌。开始循环之前,应将输出令牌的长度初始化为零。请将输出令牌设置为 GSS_C_NO_BUFFER 或者将结构的长度字段设置为零值。
下面的伪代码举例说明如何从服务器端建立上下文:
错误检查范围越大,实际循环将越完整。有关此类上下文接受循环的实际示例,请参见Establishing a Security Context With the Server。此外,gss_accept_sec_context() 手册页还提供了一个示例。
同样,GSS-API 不会收发令牌。令牌必须由应用程序进行处理。有关令牌传送函数的示例可以在Miscellaneous GSS-API Sample Functions中找到。
gss_accept_sec_context() 成功完成时将返回 GSS_S_COMPLETE。如果上下文不完整,该函数将返回 GSS_S_CONTINUE_NEEDED。如果出现错误,该函数将返回错误代码。有关更多信息,请参见 gss_accept_sec_context(3GSS) 手册页。
gss_init_sec_context() 函数允许应用程序请求超出所建立基本上下文范围的其他数据保护服务。可通过 gss_init_sec_context() 的 req_flags 参数来请求这些服务。
并非所有的机制都提供所有这些服务。gss_init_sec_context() 的 ret_flags 参数用于指示指定上下文中可用的服务。同样,上下文接受器也会通过检查 gss_accept_sec_context() 返回的 ret_flags 值来确定可用的服务。这些服务将在以下几节中进行介绍。
如果允许的话,上下文启动器可以请求由上下文接受器充当代理。在此类情况下,接受器可以代表启动器启动进一步的上下文。
假设计算机 A 上的某个用户希望通过 rlogin 登录到计算机 B,然后通过 rlogin 从计算机 B 登录到计算机 C。根据所使用的机制,所授予的凭证会将 B 标识为 A,或者将 B 标识为 A 的代理。
如果允许委托的话,可以将 ret_flags 设置为 GSS_C_DELEG_FLAG。接受器可接收委托凭证并将其作为 gss_accept_sec_context() 的 delegated_cred_handle 参数。委托凭证不同于导出上下文。请参见Exporting and Importing Contexts in GSS-API。二者的区别是应用程序可以将其凭证同时委托多次,而上下文一次只能由一个进程持有。
将文件传送到 ftp 站点的用户通常无需站点标识的证明。另一方面,需要向应用程序提供信用卡号的用户则需要接收者身份的确切证明。在此类情况下,需要进行相互验证。上下文启动器和上下文接受器都需要证明各自的身份。
上下文启动器可以通过将 gss_init_sec_context() req_flags 参数设置为值 GSS_C_MUTUAL_FLAG 来请求相互验证。如果已经对相互验证进行了授权,则函数会通过将 ret_flags 参数设置为该值来表示授权。如果所请求的相互验证不可用,则启动应用程序会负责进行相应的响应。在此情况下 GSS-API 不会自动终止上下文。另外,即使没有特定的请求,某些机制也会始终执行相互验证。
正常使用 GSS-API 时,启动器的身份会在建立上下文的过程中提供给接受器。但是,上下文启动器会请求不将其身份显示给上下文接受器。
例如,请考虑提供对医疗数据库进行无限制访问的应用程序。此类服务的客户机可能需要对该服务进行验证。此方法会信任从该数据库中检索的所有信息。例如,出于保密性方面的考虑,客户机可能不希望暴露其身份。
要请求进行匿名验证,请将 gss_init_sec_context() 的 req_flags 参数设置为 GSS_C_ANON_FLAG。要检验匿名验证是否可用,请检查 gss_init_sec_context() 或 gss_accept_sec_context() 的 ret_flags 参数,查看是否返回了 GSS_C_ANON_FLAG。
如果匿名验证有效,则针对 gss_accept_sec_context() 或 gss_inquire_context() 返回的客户机名称调用 gss_display_name() 时会生成通用的匿名名称。
对于许多应用程序来说,建立基本的上下文足以确保对上下文启动器进行正确的验证。如果需要实现额外的安全性,则可以使用 GSS-API 所提供的通道绑定功能。通道绑定是用于标识所使用的特定数据通道的标记。具体来说,通道绑定可标识起点和终点,即上下文的启动器和接受器。由于标记特定于始发者应用程序和接受者应用程序,因此此类标记可为有效身份提供更多证明。
gss_channel_bindings_t 数据类型是一个指针,它指向如下所示的作为通道绑定的 gss_channel_bindings_struct 结构。
typedef struct gss_channel_bindings_struct { OM_uint32 initiator_addrtype; gss_buffer_desc initiator_address; OM_uint32 acceptor_addrtype; gss_buffer_desc acceptor_address; gss_buffer_desc application_data; } *gss_channel_bindings_t;
第一个字段表示地址类型,用于标识所发送的启动器地址采用的格式。第二个字段表示启动器的地址。例如,initiator_addrtype 可能会发送到 GSS_C_AF_INET,表示 initiator_address 采用的是 Internet 地址(即 IP 地址)形式。同样,第三和第四个字段分别表示接受器的地址类型和接受器的地址。应用程序可以根据需要使用最后一个字段 application_data。如果不打算使用 application_data,请将 application_data 设置为 GSS_C_NO_BUFFER。如果没有为某个应用程序指定地址,则应当将该应用程序的地址类型字段设置为 GSS_C_AF_NULLADDR。Address Types for Channel Bindings一节提供了有效地址类型值的列表。
地址类型用于表示地址族,而不是表示特定的寻址格式。对于包含多种替换地址形式的地址族,initiator_address 和 acceptor_address 字段中必须包含足够的信息才能确定所使用的形式。除非另行指定,否则应当以网络字节顺序(即地址族的本机字节排序)指定地址。
要建立使用通道绑定的上下文,gss_init_sec_context() 的 input_chan_bindings 参数应当指向所分配的通道绑定结构。该结构的字段将连接到一个八位字节字符串,并将派生一个 MIC。该 MIC 随后会绑定到输出令牌。然后,应用程序会将该令牌发送到上下文接受器。接受器在收到该令牌之后将调用 gss_accept_sec_context()。有关更多信息,请参见Accepting a Context in GSS-API。gss_accept_sec_context() 会针对所收到的通道绑定计算 MIC。如果 MIC 不匹配,gss_accept_sec_context() 随后将返回 GSS_C_BAD_BINDINGS。
由于 gss_accept_sec_context() 会返回已传送的通道绑定,因此接受器可以使用这些值来执行安全检查。例如,接受器可以针对保留在安全数据库中的代码字检查 application_data 的值。
单个机制可以针对出现在通道绑定中的地址和地址类型施加额外的约束。例如,一个机制可能会检验通道绑定的 initiator_address 字段是否返回到 gss_init_sec_context()。因此,具有可移植性的应用程序应当为地址字段提供正确的信息。如果无法确定正确的信息,则应当将 GSS_C_AF_NULLADDR 指定为地址类型。
GSS-API 提供了用于导出和导入上下文的方法。此方法允许多进程应用程序(通常是上下文接受器)在进程之间传送上下文。例如,接受器可以有两个进程,一个进程侦听上下文启动器,另一个进程使用在上下文中发送的数据。Using the test_import_export_context() Function一节说明了如何使用这些函数来保存和恢复上下文。
gss_export_sec_context() 函数可创建进程间令牌,其中包含有关所导出上下文的信息。有关更多信息,请参见Interprocess Tokens in GSS-API。调用 gss_export_sec_context() 之前,应当将接收令牌的缓冲区设置为 GSS_C_NO_BUFFER。
应用程序随后会将令牌传递到另一个进程。新进程将接受该令牌并将其传递到 gss_import_sec_context()。用于在应用程序之间传递令牌的函数通常也可用于在进程之间传递令牌。
一次只能存在一个安全进程实例。gss_export_sec_context() 可取消激活所导出的上下文并将上下文句柄设置为 GSS_C_NO_CONTEXT。gss_export_sec_context() 还可取消分配与该上下文相关联的任何进程范围内的资源。如果无法完成对上下文的导出,gss_export_sec_context() 会保持现有的安全上下文不变,并且不返回进程间令牌。
并非所有机制都允许导出上下文。应用程序可以通过检查 gss_accept_sec_context() 或 gss_init_sec_context() 的 ret_flags 参数来确定上下文是否可以导出。如果此标志设置为 GSS_C_TRANS_FLAG,则可以导出上下文。(请参见Accepting a Context in GSS-API 和Initiating a Context in GSS-API。)
Figure 4–6 说明多进程接受器如何将上下文导出到多个任务。在这种情况下,进程 1 接收和处理令牌。此步骤会将上下文级别的令牌与数据令牌分开,并将令牌传递到进程 2。进程 2 按照应用程序特定的方式处理数据。在该图中,客户机已经从 gss_init_sec_context() 获取了导出令牌。客户机将令牌传递到用户定义的函数 send_a_token(),该函数指示要传送的令牌是上下文级别的令牌还是消息令牌。send_a_token() 将这些令牌传送到服务器。send_a_token() 可能会用于在线程之间传递令牌,但是该图中未显示这一点。
图 4-6 导出上下文:多线程接受器示例
GSS-API 提供了一个 gss_inquire_context(3GSS) 函数,该函数可用于获取有关指定的安全上下文的信息。请注意,上下文无需是完整的上下文。
如果提供了上下文句柄,则 gss_inquire_context() 将提供以下有关上下文的信息:
上下文启动器的名称。
上下文接受器的名称。
上下文保持有效的秒数。
要用于上下文的安全机制。
若干个上下文参数标志。这些标志与 gss_accept_sec_context(3GSS) 函数的 ret_flags 参数相同。这些标志涉及委托、相互验证等。请参见Accepting a Context in GSS-API。
指示查询应用程序是否为上下文启动器的标志。
指示上下文是否已完全建立的标志。