Solaris 开发者安全性指南

第 7 章 编写使用 SASL 的应用程序

SASL(Simple Authentication and Security Layer,简单验证和安全层)是一个安全框架。SASL(发音为 "sassel")为基于连接的协议提供验证服务以及完整性和保密性服务(可选)。本章包含以下主题:

简单验证安全层 (Simple Authentication Security Layer, SASL) 介绍

SASL 为应用程序和共享库的开发者提供了用于验证、数据完整性检查和加密的机制。开发者可通过 SASL 对通用 API 进行编码。此方法避免了对特定机制的依赖性。SASL 特别适用于使用 IMAP、SMTP、ACAP 和 LDAP 协议的应用程序,因为这些协议全都支持 SASL。RFC 2222 中说明了 SASL。

SASL 库基础

SASL 库称为 libsasllibsasl 是一个框架,允许正确编写的 SASL 消费方应用程序使用系统中可用的所有 SASL 插件。术语插件是指为 SASL 提供服务的对象。插件位于 libsasl 的外部。SASL 插件可用于验证和安全性、名称标准化以及辅助属性(如口令)的查找。加密算法存储在插件中,而不是 libsasl 中。

libsasl 为消费方应用程序和库提供应用编程接口 (application programming interface, API)。服务提供者接口 (service provider interface, SPI) 是为插件提供的,用于为 libsasl 提供服务。libsasl 不能识别网络或协议。相应地,应用程序必须负责在客户机与服务器之间发送和接收数据。

SASL 对用户使用两个重要的标识符。验证 ID (authid) 是用于验证用户的用户 ID。验证 ID 授予用户系统访问权限。授权 ID (userid) 用于检查是否允许用户使用特定选项。

SASL 客户机应用程序和 SASL 服务器应用程序将协商公用的 SASL 机制和安全级别。通常,SASL 服务器应用程序会将其可接受的验证机制的列表发送给客户机。随后 SASL 客户机应用程序便可决定哪种验证机制最能满足其要求。此后,客户机与服务器使用双方同意的验证机制,对它们之间交换的由 SASL 提供的验证数据进行验证。此交换将持续下去,直到验证成功完成、失败或被客户机或服务器中止。

在验证过程中,SASL 验证机制可以协商安全层。如果已选择安全层,则必须在 SASL 会话期间使用该层。

SASL 体系结构

下图显示了基本的 SASL 体系结构。

图 7–1 SASL 体系结构

图中显示了主要的 SASL 元素如何采用客户机/服务器关系一同工作。

客户机和服务器应用程序通过 SASL API 调用其 libsasl 的本地副本。libsasl 通过 SASL 服务提供者接口 (service provider interface, SPI) 与 SASL 机制进行通信。

安全机制

安全机制插件为 libsasl 提供安全服务。以下是安全机制提供的一些典型功能:

SASL 安全强度因子

SSF(即安全强度因子)指示 SASL 保护的强度。如果该机制支持安全层,则客户机与服务器会协商 SSF。SSF 的值基于执行 SASL 协商之前指定的安全属性。如果协商结果是非零 SSF,则验证完成后,客户机和服务器都需要使用该机制的安全层。SSF 由具有以下值之一的整数表示:

保密性和完整性操作是通过安全机制执行的。libsasl 可以协调这些请求。


注 –

在协商过程中,SASL 客户机会选择具有最大 SSF 的机制。但是,实际所选的 SASL 机制可能随后会协商较小的 SSF。


SASL 中的通信

应用程序通过 libsasl API 与 libsasl 进行通信。libsasl 可通过应用程序注册的回调方式请求其他信息。应用程序不会直接调用插件,而只是通过 libsasl 进行调用。一般情况下,插件会调用 libsasl 框架的插件,随后这些插件调用应用程序的回调。SASL 插件还可以直接调用应用程序,不过应用程序不知道调用来自插件还是来自 libsasl

回调在以下几个方面非常有用。

应用程序可以注册两种回调:全局回调和会话回调。此外,libsasl 定义了大量用于为不同种类的回调注册的回调标识符。如果未注册给定类型的回调,则 libsasl 将执行缺省操作。

会话回调将覆盖全局回调。如果为给定 ID 指定了会话回调,则不会为该会话调用全局回调。某些回调必须是全局的,因为这些回调发生在会话之外。以下实例要求使用全局回调:

可以使用给定 SASL 回调 ID 的 NULL 回调函数来注册 SASL 回调。NULL 回调函数指示装配客户机的目的是为了提供所需的数据。所有的 SASL 回调 ID 都以前缀 SASL_CB_ 开头。

SASL 提供以下可供客户机或服务器使用的回调:

SASL_CB_GETOPT

获取 SASL 选项。选项用于修改 libsasl(3LIB) 和相关插件的行为。可由客户机或服务器使用。

SASL_CB_LOG

设置 libsasl 及其插件的日志记录函数。缺省行为是使用 syslog

SASL_CB_GETPATH

获取以冒号分隔的 SASL 插件搜索路径列表。缺省路径取决于以下体系结构:

  • 32 位 SPARC 体系结构: /usr/lib/sasl

  • 32 位 x86 体系结构: /usr/lib/sasl

  • 64 位 SPARC 体系结构: /usr/lib/sasl/sparcv9

  • x64 体系结构: /usr/lib/sasl/amd64

SASL_CB_GETCONF

获取 SASL 服务器的配置目录的路径。缺省设置为 /etc/sasl

SASL_CB_LANGUAGE

为客户机和服务器错误消息和客户机提示指定以逗号分隔的 RFC 1766 语言代码列表(按优先级顺序)。缺省设置为 i-default

SASL_CB_VERIFYFILE

验证配置文件和插件文件。

SASL 提供以下仅限客户机使用的回调:

SASL_CB_USER

获取客户机用户名。用户名与授权 ID 相同。LOGNAME 环境变量为缺省设置。

SASL_CB_AUTHNAME

获取客户机验证名称。

SASL_CB_PASS

获取基于客户机口令短语的机密。

SASL_CB_ECHOPROMPT

获取给定质询提示的结果。可以回显来自客户机的输入。

SASL_CB_NOECHOPROMPT

获取给定质询提示的结果。不应回显来自客户机的输入。

SASL_CB_GETREALM

设置用于验证的领域。

SASL 提供以下仅限服务器使用的回调:

SASL_CB_PROXY_POLICY

检查是否授权经过验证的用户代表指定用户执行操作。如果未注册此回调,则经过验证的用户与要授权的用户必须是同一用户。如果这些 ID 不同,则验证将失败。请使用服务器应用程序来维护非标准授权策略。

SASL_CB_SERVER_USERDB_CHECKPASS

针对调用方提供的用户数据库验证纯文本口令。

SASL_CB_SERVER_USERDB_SETPASS

在用户数据库中存储纯文本口令

SASL_CB_CANON_USER

调用应用程序提供的用户标准化函数。

首次初始化 SASL 库时,服务器和客户机会声明所有必要的全局回调。执行 SASL 会话之前或期间可以使用全局回调。初始化之前,回调将执行诸如装入插件、记录数据和读取配置文件之类的任务。SASL 会话开始时,可以声明其他回调。这类回调可以覆盖全局回调(如果必要)。

SASL 连接上下文

libsasl 使用 SASL 连接上下文维护 SASL 客户机和 SASL 服务器的每个 SASL 会话的状态。每个上下文一次只能用于一个验证和安全会话。维护的状态包括以下信息:

SASL 周期中的步骤

下图显示了 SASL 生命周期中的步骤。客户机操作显示在图的左侧,服务器操作显示在右侧。中间的箭头显示客户机与服务器之间通过外部连接执行的交互操作。

图 7–2 SASL 生命周期

图中显示了 SASL 生命周期中与客户机和服务器对应的各阶段。

以下各节说明了生命周期中的步骤。

libsasl 初始化

客户机调用 sasl_client_init() 来初始化 libsasl 以供客户机使用;而服务器调用 sasl_server_init() 来初始化 libsasl 以供服务器使用。

运行 sasl_client_init() 时,将装入 SASL 客户机、该客户机的机制以及该客户机的标准化插件。同样,调用 sasl_server_init() 时,将装入 SASL 服务器、该服务器的机制、该服务器的标准化插件以及该服务器的 auxprop 插件。调用 sasl_client_init() 后,可以使用 sasl_client_add_plugin()sasl_canonuser_add_plugin() 来添加其他客户机插件。在服务器端,调用 sasl_server_init() 后,可以通过 sasl_server_add_plugin()sasl_canonuser_add_plugin()sasl_auxprop_add_plugin() 来添加其他的服务器插件。依据体系结构,我们在 Solaris 软件的以下目录中提供了 SASL 机制:

可以使用 SASL_CB_GETPATH 回调覆盖缺省位置。

此时,可以设置所有必需的全局回调。SASL 客户机和服务器可能包括以下回调:

此外,SASL 服务器还可能包括 SASL_CB_GETCONF 回调。

SASL 会话初始化

服务器和客户机通过协议建立连接。要使用 SASL 执行验证,服务器和客户机可以分别使用 sasl_server_new()sasl_client_new() 来创建 SASL 连接上下文。SASL 客户机和服务器可以使用 sasl_setprop() 来设置对机制强制执行安全限制的属性。此方法使 SASL 消费方应用程序可以决定指定 SASL 连接上下文的最小 SSF、最大 SSF 和安全属性。

#define SASL_SEC_NOPLAINTEXT            0x0001

#define SASL_SEC_NOACTIVE               0x0002

#define SASL_SEC_NODICTIONARY           0x0004

#define SASL_SEC_FORWARD_SECRECY        0x0008

#define SASL_SEC_NOANONYMOUS            0x0010

#define SASL_SEC_PASS_CREDENTIALS       0x0020

#define SASL_SEC_MUTUAL_AUTH            0x0040

注 –

验证和安全层可由客户机/服务器协议提供,也可由 libsasl 外部的某些其他机制提供。在这类情况下,可以使用 sasl_setprop() 来设置外部验证 ID 或外部 SSF。例如,请考虑协议对服务器结合使用 SSL 和客户机验证的情况。在这种情况下,外部验证标识可以为客户机的主题名称。外部 SSF 可以为密钥大小。


对于服务器,libsasl 可以依据安全属性和外部 SSF 来确定可用的 SASL 机制。客户机可以通过协议从 SASL 服务器获取可用的 SASL 机制。

为使 SASL 服务器可以创建 SASL 连接上下文,服务器应该调用 sasl_server_new()。可以重复使用不再使用的现有 SASL 连接上下文。但是,可能需要重置以下参数:

#define SASL_DEFUSERREALM 3     /* default realm passed to server_new or set with setprop */

#define SASL_IPLOCALPORT 8      /* iplocalport string passed to server_new */

#define SASL_IPREMOTEPORT 9     /* ipremoteport string passed to server_new */

#define SASL_SERVICE    12      /* service passed to sasl_*_new */

#define SASL_SERVERFQDN 13      /* serverFQDN passed to sasl_*_new */

可以修改 sasl_client_new()sasl_server_new() 的任何参数,但回调和协议标志除外。

服务器和客户机还可以使用 sasl_setprop() 来指定以下属性,从而建立安全策略并设置连接特定参数:

#define SASL_SSF_EXTERNAL 100 /* external SSF active (sasl_ssf_t *) */

#define SASL_SEC_PROPS 101 /* sasl_security_properties_t */

#define SASL_AUTH_EXTERNAL 102 /* external authentication ID (const char *)

 */

服务器可以调用 sasl_listmech() 来获取满足安全策略的可用 SASL 机制的列表。一般情况下,客户机可以采用与协议相关的方式从服务器获取可用机制的列表。

下图说明了 SASL 会话的初始化过程。在此图和后续的图中,为了简单起见,省略了通过协议进行传输后的数据检查。

图 7–3 SASL 会话初始化

图中显示了在 SASL 会话初始化期间客户机和服务器执行的步骤。

SASL 验证

验证根据使用的安全机制采用不同数量的客户机和服务器步骤。SASL 客户机将调用 sasl_client_start() 和要使用的安全机制列表。此列表通常来自服务器。libsasl 将依据可用机制和客户机的安全策略选择要用于此 SASL 会话的最佳机制。客户机的安全策略控制允许的机制。选定的机制由 sasl_client_start() 返回。有时,客户机的安全机制需要其他验证信息。对于已注册的回调,libsasl 将调用指定的回调,除非回调函数为 NULL。如果回调函数为 NULL,则 libsasl 将返回 SASL_INTERACT 和对所需信息的请求。如果返回 SASL_INTERACT,则应使用请求的信息调用 sasl_client_start()

如果 sasl_client_start() 返回 SASL_CONTINUE 或 SASL_OK,则客户机应向服务器发送包含生成的所有验证数据的选定机制。如果返回任何其他值,则表示出现了错误。例如,任何机制可能都不可用。

服务器将接收客户机选定的机制,以及所有的验证数据。随后,服务器将调用 sasl_server_start() 来初始化此会话的机制数据。sasl_server_start() 还将处理所有的验证数据。如果 sasl_server_start() 返回 SASL_CONTINUE 或 SASL_OK,则服务器将发送验证数据。如果 sasl_server_start() 返回任何其他值,则表示出现了错误,如不可接受的机制或验证失败。必须中止验证。应该释放或重复使用 SASL 上下文。

下图说明了此部分的验证过程。

图 7–4 SASL 验证:发送客户机数据

图中显示客户机向服务器发送验证数据时客户机和服务器执行的步骤。

如果服务器对 sasl_server_start() 的调用返回 SASL_CONTINUE,则服务器将继续与客户机进行通信,以获取所有必要的验证信息。后续步骤的数目取决于机制。如果需要,客户机可以调用 sasl_client_step() 来处理来自服务器的验证数据并生成回复。同样,服务器可以调用 sasl_server_step() 来处理来自客户机的验证并相应地生成回复。此交换将持续进行下去,直到验证完成或出现错误为止。如果返回 SASL_OK,则指示对客户机或服务器的验证已成功完成。SASL 机制可能仍然具有要发送给另一端的其他数据,因此另一端可以完成验证。在两端都完成验证后,服务器和客户机便可查询彼此的属性。

下图显示了服务器与客户机之间为传输其他验证数据所执行的交互。

图 7–5 SASL 验证:处理服务器数据

图中显示服务器向客户机返回数据时客户机和服务器执行的步骤。

SASL 保密性和完整性

要检查安全层,请使用 sasl_getprop(3SASL) 函数来查看安全强度因子 (security strength factor, SSF) 的值是否大于 0。如果已协商安全层,则成功验证后客户机和服务器必须使用生成的 SSF。在客户机与服务器之间交换数据的方式与验证的方式相同。通过协议向客户机或服务器发送数据之前,将 sasl_encode() 应用于数据。在接收端,使用 sasl_decode() 对数据进行解码。如果未协商安全层,则无需 SASL 连接上下文。随后,即可处理或重复使用该上下文。

释放 SASL 会话

只有在不会重复使用会话时,才应释放 SASL 连接上下文。sasl_dispose() 将释放 SASL 连接上下文以及所有关联的资源和机制。调用 sasl_done() 之前,必须处理 SASL 连接上下文。sasl_done() 不负责释放 SASL 连接的上下文资源。请参见libsasl 清除

释放 SASL 会话时,将通知关联的机制所有状态均可释放。只有在不会重复使用 SASL 会话时,才应释放该会话。否则,其他会话可以重复使用 SASL 状态。客户机和服务器都使用 sasl_dispose() 释放 SASL 连接上下文。

libsasl 清除

此步骤可释放 SASL 库和插件中的所有资源。客户机和服务器可以调用 sasl_done() 来释放 libsasl() 资源并卸载所有的 SASL 插件。sasl_done() 不会释放 SASL 连接上下文。请注意,如果应用程序同时为 SASL 客户机和 SASL 服务器,则 sasl_done() 将同时释放 SASL 客户机和 SASL 服务器资源。您不能仅释放客户机或服务器的资源。


注意 – 注意 –

库不应调用 sasl_done()。应用程序在调用 sasl_done() 时务必要谨慎,避免与所有可能使用 libsasl 的库发生干扰。


SASL 示例

本节说明客户机应用程序与服务器应用程序之间的典型 SASL 会话。该示例包含以下步骤:

  1. 客户机应用程序可以初始化 libsasl 并设置以下全局回调:

    • SASL_CB_GETREALM

    • SASL_CB_USER

    • SASL_CB_AUTHNAME

    • SASL_CB_PASS

    • SASL_CB_GETPATH

    • SASL_CB_LIST_END

  2. 服务器应用程序可以初始化 libsasl 并设置以下全局回调:

    • SASL_CB_LOG

    • SASL_CB_LIST_END

  3. 客户机将创建 SASL 连接上下文,设置安全属性并从服务器请求可用机制的列表。

  4. 服务器将创建 SASL 连接上下文,设置安全属性,获取适当 SASL 机制的列表并向客户机发送该列表。

  5. 客户机将接收可用机制的列表,选择一种机制,并向服务器发送所选择的机制以及所有验证数据。

  6. 随后,客户机和服务器将交换 SASL 数据,直到验证和安全层协商完成为止。

  7. 验证完成后,客户机和服务器将确定是否已协商安全层。客户机将对测试消息进行编码。然后,会将该消息发送给服务器。服务器也会确定经过验证的用户的用户名和该用户的领域。

  8. 服务器将接收、解码和列显编码的消息。

  9. 客户机将调用 sasl_dispose() 以释放客户机的 SASL 连接上下文。随后,客户机将调用 sasl_done() 以释放 libsasl 资源。

  10. 服务器将调用 sasl_dispose() 以释放客户机连接上下文。

以下是客户机与服务器之间的对话。执行调用时,会显示对 libsasl 的每个调用。每次数据传输都由发送者和接收者指明。数据采用编码的形式显示,并在前面加上表示来源的字符: C: 表示来自于客户机,S: 表示来自于服务器。附录 D,SASL 示例的源代码 中同时提供了两种应用程序的源代码。

客户机

% doc-sample-client

*** Calling sasl_client_init() to initialize libsasl for client use ***

*** Calling sasl_client_new() to create client SASL connection context ***

*** Calling sasl_setprop() to set sasl context security properties ***

Waiting for mechanism list from server...
服务器

% doc-sample-server digest-md5

*** Calling sasl_server_init() to initialize libsasl for server use ***

*** Calling sasl_server_new() to create server SASL connection context ***

*** Calling sasl_setprop() to set sasl context security properties ***

Forcing use of mechanism digest-md5

Sending list of 1 mechanism(s)

S: ZGlnZXN0LW1kNQ==
客户机

S: ZGlnZXN0LW1kNQ==

received 10 byte message

got 'digest-md5'

Choosing best mechanism from: digest-md5

*** Calling sasl_client_start() ***

Using mechanism DIGEST-MD5

Sending initial response...

C: RElHRVNULU1ENQ==

Waiting for server reply...
服务器

C: RElHRVNULU1ENQ==

got 'DIGEST-MD5'

*** Calling sasl_server_start() ***

Sending response...

S: bm9uY2U9IklicGxhRHJZNE4Z1gyVm5lQzl5MTZOYWxUOVcvanUrcmp5YmRqaHM\

sbT0iam0xMTQxNDIiLHFvcD0iYXV0aCxhdXRoLWludCxhdXRoLWNvbmYiLGNpcGhlcj0ic\

QwLHJjNC01NixyYzQiLG1heGJ1Zj0yMDQ4LGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1k\

XNz

Waiting for client reply...
客户机

S: bm9uY2U9IklicGxhRHJZNE4Z1gyVm5lQzl5MTZOYWxUOVcvanUrcmp5YmRqaHM\

sbT0iam0xMTQxNDIiLHFvcD0iYXV0aCxhdXRoLWludCxhdXRoLWNvbmYiLGNpcGhlcj0ic\

QwLHJjNC01NixyYzQiLG1heGJ1Zj0yMDQ4LGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1k\

XNz

received 171 byte message

got 'nonce="IbplaDrY4N4szhgX2VneC9y16NalT9W/ju+rjybdjhs=",\

realm="jm114142",qop="auth,auth-int,auth-conf",cipher="rc4-40,rc4-56,\

rc4",maxbuf=2048,charset=utf-8,algorithm=md5-sess'

*** Calling sasl_client_step() ***

Please enter your authorization name : zzzz

Please enter your authentication name : zzzz

Please enter your password : zz

*** Calling sasl_client_step() ***

Sending response...

C: dXNlcm5hbWU9Inp6enoiLHJlYWxtPSJqbTExNDE0MiIsbm9uY2U9IklicGxhRHJZNE4\

yVm5lQzl5MTZOYWxUOVcvanUrcmp5YmRqaHM9Iixjbm9uY2U9InlqZ2hMVmhjRFJMa0Fob\

tDS0p2WVUxMUM4V1NycjJVWm5IR2Vkclk9IixuYz0wMDAwMDAwMSxxb3A9YXV0aC1jb25m\

Ghlcj0icmM0IixtYXhidWY9MjA0OCxkaWdlc3QtdXJpPSJyY21kLyIscmVzcG9uc2U9OTY\

ODI1MmRmNzY4YTJjYzkxYjJjZDMyYTk0ZWM=

Waiting for server reply...
服务器

C: dXNlcm5hbWU9Inp6enoiLHJlYWxtPSJqbTExNDE0MiIsbm9uY2U9IklicGxhRHJZNE4\

yVm5lQzl5MTZOYWxUOVcvanUrcmp5YmRqaHM9Iixjbm9uY2U9InlqZ2hMVmhjRFJMa0Fob\

tDS0p2WVUxMUM4V1NycjJVWm5IR2Vkclk9IixuYz0wMDAwMDAwMSxxb3A9YXV0aC1jb25m\

Ghlcj0icmM0IixtYXhidWY9MjA0OCxkaWdlc3QtdXJpPSJyY21kLyIscmVzcG9uc2U9OTY\

ODI1MmRmNzY4YTJjYzkxYjJjZDMyYTk0ZWM=

got 'username="zzzz",realm="jm114142",\

nonce="IbplaDrY4N4szhgX2VneC9y16NalT9W/ju+rjybdjhs=",\

cnonce="yjghLVhcDRLkAhoirwKCKJvYU11C8WSrr2UZnHGedrY=", \

nc=00000001,qop=auth-conf,cipher="rc4",maxbuf=2048,digest-uri="rcmd/",\

response=966e978252df768a2cc91b2cd32a94ec'

*** Calling sasl_server_step() ***

Sending response...

S: cnNwYXV0aD0yYjEzMzRjYzU4NTE4MTEwOWM3OTdhMjUwYjkwMzk3OQ==

Waiting for client reply...
客户机

S: cnNwYXV0aD0yYjEzMzRjYzU4NTE4MTEwOWM3OTdhMjUwYjkwMzk3OQ==

received 40 byte message

got 'rspauth=2b1334cc585181109c797a250b903979'

*** Calling sasl_client_step() ***

C:

Negotiation complete

*** Calling sasl_getprop() ***

Username: zzzz

SSF: 128

Waiting for encoded message...
服务器

Waiting for client reply... 

C: got '' *** Calling sasl_server_step() *** 

Negotiation complete 

*** Calling sasl_getprop() to get username, realm, ssf *** 

Username: zzzz 

Realm: 22c38 

SSF: 128 

*** Calling sasl_encode() *** sending encrypted message 'srv message 1'

S: AAAAHvArjnAvDFuMBqAAxkqdumzJB6VD1oajiwABAAAAAA==
客户机

S: AAAAHvArjnAvDFuMBqAAxkqdumzJB6VD1oajiwABAAAAAA==

received 34 byte message

got ''

*** Calling sasl_decode() ***

received decoded message 'srv message 1'

*** Calling sasl_encode() ***

sending encrypted message 'client message 1'

C: AAAAIRdkTEMYOn9X4NXkxPc3OTFvAZUnLbZANqzn6gABAAAAAA==

*** Calling sasl_dispose() to release client SASL connection context ***

*** Calling sasl_done() to release libsasl resources ***
服务器

Waiting for encrypted message...

C: AAAAIRdkTEMYOn9X4NXkxPc3OTFvAZUnLbZANqzn6gABAAAAAA==

got ''

*** Calling sasl_decode() ***

received decoded message 'client message 1'

*** Calling sasl_dispose() to release client SASL connection context ***

服务提供者的 SASL

本节介绍如何创建用于为 SASL 应用程序提供机制和其他服务的插件。


注 –

由于导出规程,对于非 Solaris 客户机/服务器机制插件,Solaris SASL SPI 不支持安全层。 因此,非 Solaris 客户机/服务器机制插件不能提供完整性和保密性服务。Solaris 客户机/服务器机制插件没有此限制。


SASL 插件概述

SASL 服务提供者接口 (service provider interface, SPI) 可实现插件与 libsasl 库之间的通信。 SASL 插件通常作为共享库来实现。单个共享库可以具有一个或多个不同类型的 SASL 插件。位于共享库中的插件是由 libsasl 通过 dlopen(3C) 函数动态打开的。

还可以将插件静态绑定至调用 libsasl 的应用程序。使用 sasl_client_add_plugin() 函数或 sasl_server_add_plugin() 函数装入这些种类的插件,具体视应用程序是客户机还是服务器而定。

Solaris 操作系统中的 SASL 插件具有以下要求:

SASL 插件分为四种类别:

sasl_client_init() 函数导致 SASL 客户机装入所有可用的客户机插件。sasl_server_init() 函数导致 SASL 服务器装入服务器插件、标准化插件和 auxprop 插件。调用 sasl_done() 时将卸载所有插件。

为查找插件,libsasl 使用 SASL_CB_GETPATH 回调函数或缺省路径。SASL_CB_GETPATH 返回以冒号分隔的目录列表,以供在其中查找插件。如果 SASL 消费方指定了 SASL_CB_GETPATH 回调,则 libsasl 将使用返回的搜索路径。否则,SASL 消费方可以使用与二进制类型对应的缺省路径:

在装入过程中,libsasl 将调用最新支持的插件版本。插件将返回该版本以及描述该插件的结构。如果该版本合格,则 libsasl 即可装入该插件。当前版本号 SASL_UTILS_VERSION 为 4。

初始化插件后,插件与 libsasl 之间的后续通信通过必须建立的结构执行。插件使用 sasl_utils_t 结构来调用 libsasllibsasl 使用以下结构中的入口点与插件进行通信:

可以在 SASL 头文件中找到这些结构的源代码。这些结构将在下一节中进行介绍。

SASL 插件的重要结构

libsasl 与插件之间的通信是通过以下结构完成的:

客户机插件

客户机插件用于管理 SASL 协商的客户端。客户机插件通常随对应的服务器插件一同打包。客户机插件包含一种或多种客户端 SASL 机制。每种 SASL 客户机机制都支持验证、完整性和保密性(后两者是可选的)。每种机制都提供有关该机制的功能的信息:

客户机插件必须导出 sasl_client_plug_init()libsasl 将调用 sasl_client_plug_init() 来初始化客户机的插件。插件将返回 sasl_client_plug_t 结构。sasl_client_plug_t 将为 libsasl 提供以下用于调用机制的入口点:

服务器插件

服务器插件用于管理 SASL 协商的服务器端。服务器插件通常随对应的客户机插件一同打包。服务器插件包含一种或多种服务器端 SASL 机制。每种 SASL 服务器机制都支持验证、完整性和保密性(后两者是可选的)。每种机制都提供有关该机制的功能的信息:

服务器插件必须导出 sasl_server_plug_init()libsasl 将调用 sasl_server_plug_init() 来初始化服务器的插件。插件将返回 sasl_server_plug_t 结构。sasl_server_plug_t 将为 libsasl 提供以下用于调用机制的入口点:

用户标准化插件

标准化插件为客户端和服务器端的验证和授权名称的备用标准化提供支持。sasl_canonuser_plug_init() 用于装入标准化插件。标准化插件具有以下要求:

用户标准化插件必须导出 sasl_canonuser_init() 函数。sasl_canonuser_init() 函数必须返回 sasl_canonuser_plug_t 以建立必要的入口点。用户标准化插件必须至少实现 sasl_canonuser_plug_t 结构的一个 canon_user_client() 成员或 canon_user_server() 成员。

辅助属性 (auxprop) 插件

Auxprop 插件为查找 SASL 服务器的 authidauthzid 的辅助属提供支持。例如,应用程序可能需要查找内部验证的用户口令。sasl_auxprop_plug_init() 函数用于初始化 auxprop 插件并返回 sasl_auxpropr_plug_t 结构。

要成功实现 auxprop 插件,必须实现 sasl_auxprop_plug_t 结构的 auxprop_lookup 成员。标准化用户名后,应使用标准化的用户名来调用 auxprop_lookup() 函数。随后,插件即可执行请求的辅助属性所需的所有查询。


注 –

Sun Microsystems, Inc. 当前不支持 auxprop 插件。


SASL 插件开发指南

本节提供一些用于开发 SASL 插件的其他建议。

SASL 插件中的错误报告

适当的错误报告可以帮助跟踪验证问题和其他调试问题。建议插件开发者使用 sasl_utils_t 结构中的 sasl_seterror() 回调为给定连接提供详细的错误信息。

SASL 插件中的内存分配

在 SASL 中分配内存的一般规则是在不再需要已分配的任何内存时将其释放。遵循此规则可以提高性能和可移植性,而且可以避免内存泄漏。

设置 SASL 协商顺序

插件机制可以设置客户机和服务器通过以下标志执行 SASL 会话的顺序:

如果未设置任何标志,则机制插件将在内部设置顺序。在这种情况下,机制必须同时检查客户机和服务器中需要发送的数据。请注意,只有在协议允许初始响应时,才有可能出现客户机首先发送的情况。

服务器最后发送的情况要求在步骤函数返回 SASL_OK 时插件可设置 *serverout。永远不让服务器最后发送的那些机制必须将 *serverout 设置为 NULL。始终让服务器最后发送的那些机制需要将 *serverout 指向成功数据。