跳过导航链接 | |
退出打印视图 | |
Oracle Solaris 11 开发者安全性指南 Oracle Solaris 11.1 Information Library (简体中文) |
本节介绍了以下 GSS-API 的重要概念:主体、GSS-API 数据类型、状态码和令牌。
以下几节介绍了主要的 GSS-API 数据类型。有关所有 GSS-API 数据类型的信息,请参见GSS-API 数据类型和值。
由于 int 的长度因平台而异,因此 GSS-API 提供了以下整数数据类型:OM_uint32,此为 32 位无符号整数。
由于 GSS-API 处理所有内部格式的数据,因此在将字符串传递到 GSS-API 函数之前必须将其转换为 GSS-API 格式。GSS-API 可处理具有 gss_buffer_desc 结构的字符串:
typedef struct gss_buffer_desc_struct { size_t length; void *value; } gss_buffer_desc *gss_buffer_t;
gss_buffer_t 是指向此类结构的指针。在将字符串传递到使用它们的函数之前,必须先将其放到 gss_buffer_desc 结构中。在以下示例中,通用的 GSS-API 函数在发送消息之前会先对此消息应用保护机制。
示例 4-1 在 GSS-API 中使用字符串
char *message_string; gss_buffer_desc input_msg_buffer; input_msg_buffer.value = message_string; input_msg_buffer.length = strlen(input_msg_buffer.value) + 1; gss_generic_function(arg1, &input_msg_buffer, arg2...); gss_release_buffer(input_msg_buffer);
请注意,完成对 input_msg_buffer 的操作之后,必须通过 gss_release_buffer() 取消对 input_msg_buffer 的分配。
gss_buffer_desc 对象不只是用于字符串。例如,令牌可以作为 gss_buffer_desc 对象进行处理。有关更多信息,请参见GSS-API 令牌。
名称是指主体。在网络安全术语中,主体是指用户、程序或计算机。主体可以是客户机或服务器。
下面是主体的一些示例:
登录到另一台计算机的用户,如 user@machine
网络服务,如 nfs@machine
运行应用程序的计算机,如 myHost@eng.company.com
在 GSS-API 中,名称会存储为 gss_name_t 对象,该对象对于应用程序是不透明的。名称可以通过 gss_import_name() 函数从 gss_buffer_t 对象转换为 gss_name_t 形式。所导入的每个名称都有一个指示名称格式的相关名称类型。有关名称类型的更多信息,请参见GSS-API OID。有关有效名称类型的列表,请参见名称类型。
gss_import_name() 具有以下语法:
OM_uint32 gss_import_name ( OM_uint32 *minor-status, const gss_buffer_t input-name-buffer, const gss_OID input-name-type, gss_name_t *output-name)
基础机制返回的状态码。请参见GSS-API 状态码。
包含要导入的名称的 gss_buffer_desc 结构。应用程序必须显式分配此结构。请参见GSS-API 中的字符串和类似数据以及示例 4-2。应用程序不再使用为该参数分配的空间时,必须通过 gss_release_buffer() 取消分配该参数。
用于指定 input-name-buffer 的格式的 gss_OID。请参见GSS-API 中的名称类型。另外,名称类型中还包含一个有效名称类型表。
接收名称的 gss_name_t 结构。
示例 4-1 中所示的通用示例进行了小修改,说明如何使用 gss_import_name()。首先,将规则字符串插入到 gss_buffer_desc 结构中。然后,使用 gss_import_name() 将该字符串放到 gss_name_t 结构中。
示例 4-2 使用 gss_import_name()
char *name_string; gss_buffer_desc input_name_buffer; gss_name_t output_name_buffer; input_name_buffer.value = name_string; input_name_buffer.length = strlen(input_name_buffer.value) + 1; gss_import_name(&minor_status, input_name_buffer, GSS_C_NT_HOSTBASED_SERVICE, &output_name); gss_release_buffer(input_name_buffer);
使用 gss_display_name() 可以将所导入的名称放回到 gss_buffer_t 对象中,以便能够以可读格式显示。但是,鉴于基础机制存储名称的方式,gss_display_name() 不能保证所得到的字符串与原来的字符串相同。GSS-API 包括若干个用于处理名称的其他函数。请参见GSS-API 函数。
gss_name_t 结构可以包含一个名称的多个版本。可以为 GSS-API 所支持的每个机制都生成一个版本。也即是说,user@company 的 gss_name_t 结构可能会包含该名称的两个版本:一个版本由 Kerberos v5 提供,另一个版本由其他机制提供。gss_canonicalize_name() 函数将内部名称和机制用作输入。gss_canonicalize_name() 可生成另一个内部名称,其中包含一个特定于该机制的名称版本。
此类特定于机制的名称称为机制名称 (mechanism name, MN)。机制名称是指指定机制所生成的主体名称,而不是指机制的名称。下图对此流程进行了说明。
图 4-3 内部名称和机制名称
请考虑以下情况:服务器从客户机收到一个名称,并且需要在访问控制列表中查找该名称。访问控制列表(即 ACL)是指具有特定访问权限的主体的列表。
下面是一种用于进行查找的方法:
使用 gss_import_name() 以 GSS-API 内部格式导入客户机名称(如果该名称尚未导入)。
在某些情况下,服务器将以内部格式接收名称,因此可不必执行该步骤。例如,服务器可能会查找客户机本身的名称。在启动上下文的过程中,客户机本身的名称以内部格式进行传递。
使用 gss_import_name() 将每个名称导入到 ACL 中。
此过程如下图所示。在本示例中,假设步骤 1 是必需的。
图 4-4 比较名称(慢速)
如果只有少数名称,则可以接受使用上述单独比较名称的方法。如果有大量名称,则使用 gss_canonicalize_name() 函数会更高效。
此方法会执行以下步骤:
使用 gss_import_name() 导入客户机的名称(如果该名称尚未导入)。
和上述比较名称的方法一样,如果名称已经采用内部格式,则不必执行此步骤。
使用 gss_canonicalize_name() 生成机制名称版本的客户机名称。
此过程如下图所示。同样,再次假设服务器需要导入从客户机收到的名称。
图 4-5 比较名称(快速)
由于 gss_export_name() 需要使用机制名称 (mechanism name, MN),因此必须首先针对客户机名称运行 gss_canonicalize_name()。
有关更多信息,请参见 gss_export_name(3GSS)、gss_import_name(3GSS) 和 gss_canonicalize_name(3GSS)。
对象标识符 (Object identifier, OID) 用于存储以下几种数据:
OID 存储在 GSS-API gss_OID_desc 结构中。GSS-API 提供了指向 gss_OID 结构的指针,如以下示例中所示。
示例 4-3 OID 结构
typedef struct gss_OID_desc_struct { OM_uint32 length; void *elements; } gss_OID_desc, *gss_OID;
并且,gss_OID_set_desc 结构中可能会包含一个或多个 OID。
示例 4-4 OID 集合结构
typedef struct gss_OID_set_desc_struct { size_t count; gss_OID elements; } gss_OID_set_desc, *gss_OID_set;
尽管 GSS-API 允许应用程序选择基础安全机制,但是应用程序还是应当尽可能使用 GSS-API 已选择的缺省机制。同样,尽管 GSS-API 允许应用程序指定用于保护数据的保护质量级别,但还是应当尽可能使用缺省 QOP。可以通过向需要使用机制或 QOP 作为参数的函数传递值 GSS_C_NULL_OID 来表示接受缺省机制。
注意 - 显式指定安全机制或 QOP 会背离使用 GSS-API 的初衷。类似的特定选择会限制应用程序的可移植性。其他 GSS-API 实现可能无法以预期方式支持 QOP 或机制。尽管如此,附录 D 简要介绍了如何查找可用的机制和 QOP 以及如何从中选择一个机制或 QOP。 |
除了 QOP 和安全机制以外,OID 还用于指示名称类型,从而指明关联名称的格式。例如,gss_import_name() 函数(用于将主体的名称从字符串转换为 gss_name_t 类型)将需要转换的字符串的格式用作一个参数。例如,如果名称类型为 GSS_C_NT_HOSTBASED_SERVICE,则该函数便会知道所输入的名称是采用 service@host 形式。如果名称类型为 GSS_C_NT_EXPORT_NAME,则该函数需要使用 GSS-API 导出的名称。应用程序可以使用 gss_inquire_names_for_mech() 函数来确定指定机制可用的名称类型。名称类型中提供了 GSS-API 所使用的名称类型的列表。
所有 GSS-API 函数都会返回两种类型的代码,其中提供了有关该函数执行成败与否的信息。这两种类型的状态码都以 OM_uint32 值的形式返回。
这两种类型的返回码如下所示:
主状态码
主状态码指示以下错误:
常规的 GSS-API 例程错误,如为例程提供无效机制
特定于指定 GSS-API 语言绑定的调用错误,如函数参数无法读取、无法写入或格式有误
以上两种类型的错误
此外,主状态码还可以提供有关例程状态的补充信息。例如,代码可能会指示操作尚未完成或者令牌未按顺序发送。如果未出现任何错误,例程将返回主状态值 GSS_S_COMPLETE。
主状态码按如下方式返回:
OM_uint32 major_status ; /* status returned by GSS-API */ major_status = gss_generic_function(arg1, arg2 ...);
主状态返回码的处理方式与任何其他 OM_uint32 的处理方式相同。例如,请考虑以下代码:
OM_uint32 maj_stat; maj_sta = gss_generic_function(arg1, arg2 ...); if (maj_stat == GSS_CREDENTIALS_EXPIRED) <do something...>
主状态码可以通过 GSS_ROUTINE_ERROR()、GSS_CALLING_ERROR() 和 GSS_SUPPLEMENTARY_INFO() 宏进行处理。GSS-API 状态码介绍了如何读取主状态码并提供了 GSS-API 状态码的列表。
次状态码
基础机制返回次状态码。这些代码未在本手册中具体说明。
每个 GSS-API 函数都将次状态码的 OM_uint32 类型作为第一个参数。该函数返回到调用函数时,次状态码将存储到 OM_uint32 参数中。请考虑以下代码:
OM_uint32 *minor_status ; /* status returned by mech */ major_status = gss_generic_function(&minor_status, arg1, arg2 ...);
即使返回了致命的主状态码错误,minor_status 参数也始终由 GSS-API 例程设置。请注意,大多数其他输出参数均可以保持未设置状态。但是,需要将应当返回特定指针的输出参数设置为 NULL,这些指针指向该例程已分配的存储空间。NULL 表示并未实际分配存储空间。正如在 gss_buffer_desc 结构中一样,与此类指针相关联的任何长度字段均设置为零。在此类情况下,应用程序无需释放这些缓冲区。
GSS-API 中的基本“流通”(currency) 单位是令牌 (token)。使用 GSS-API 的应用程序可以借助令牌来相互通信。使用令牌可以交换数据并创建安全布局。令牌声明为 gss_buffer_t 数据类型。令牌对于应用程序是不透明的。
令牌的类型包括上下文级别的令牌和每消息令牌两种。上下文级别的令牌主要在建立(即启动和接受)上下文时使用。另外,还可以在以后通过传递上下文级别的令牌来管理上下文。
每消息令牌在建立了上下文之后使用。每消息令牌用于为数据提供保护服务。例如,请考虑一个希望将消息发送到另一个应用程序的应用程序。该应用程序可能会使用 GSS-API 来生成随该消息传递的加密标识符。该标识符可存储在令牌中。
可以考虑按如下方式针对消息使用每消息令牌。消息是应用程序发送到其对等应用程序的一段数据。例如,ls 命令可以是发送到 ftp 服务器的消息。每消息令牌是 GSS-API 为该消息生成的对象。每消息令牌可以是加密标记,也可以是消息的加密形式。请注意,最后一个示例不太准确。加密消息仍是消息,而不是令牌。令牌仅是指 GSS-API 生成的信息。但是,在非正式情况下,消息和每消息令牌通常可以互换使用。
应用程序负责进行以下活动:
收发令牌。开发者通常需要编写一般性的读写函数来执行这些操作。send_token() 和 recv_token() 函数在各种 GSS-API 样例函数中进行介绍。
由于令牌对于应用程序来说是不透明的,因此应用程序不会对不同的令牌加以区分。如果不知道令牌的内容,则应用程序必须能够区分令牌的类型才能将令牌传递到相应的 GSS-API 函数。
应用程序可以通过以下方法来区分令牌类型:
按状态。通过程序的控制流程。例如,等待接受上下文的应用程序可能会假设收到的任何令牌都与所建立的上下文有关。对等应用程序应当等到上下文完全建立之后才发送消息令牌(即数据)。建立上下文之后,正在等待接受上下文的应用程序会假设新令牌即是消息令牌。这是处理令牌的相当常见的方法。本书中的样例程序会使用此方法。
按标志。例如,如果某个应用程序包含用于向对等应用程序发送令牌的函数,则该应用程序可以引入一个标志,用于指示令牌类型。请考虑以下代码:
gss_buffer_t token; /* declare the token */ OM_uint32 token_flag /* flag for describing the type of token */ <get token from a GSS-API function> token_flag = MIC_TOKEN; /* specify what kind of token it is */ send_a_token(&token, token_flag);
接收应用程序可包含用于检查 token_flag 参数的接收函数,例如 get_a_token()。
通过显式使用标记。应用程序可以使用元令牌。元令牌是一种用户定义的结构,其中包含从 GSS-API 函数收到的令牌。元令牌包括用户定义的字段,这些字段指示如何使用 GSS-API 提供的令牌。
GSS-API 允许在多进程应用程序中的进程之间传递安全上下文。通常,应用程序已经接受了客户机的上下文。然后,应用程序将在其进程之间共享该上下文。有关多进程应用程序的信息,请参见在 GSS-API 中导出和导入上下文。
gss_export_context() 函数可用于创建进程间令牌。使用此令牌中包含的信息,另一个进程可以重建该上下文。应用程序负责在进程之间传递进程间令牌。这与应用程序负责将令牌传递到其他应用程序情况相似。
进程间令牌可能包含密钥或其他敏感信息。并非所有的 GSS-API 实现都以加密方式保护进程间令牌。因此,在进行交换之前,应用程序必须保护进程间令牌。这种保护可能涉及到使用 gss_wrap() 对令牌进行加密(如果加密机制可用)。
注 - 请勿假设进程间令牌可以在不同的 GSS-API 实现之间传送。