注意
本文档适用于 Ceph 的开发版本。
Cephx 协议的会话认证
Peter Reiher 7/30/12
原始的 Cephx 协议认证了客户端到认证器,并设置了一个会话密钥,用于认证客户端到它需要与之通信的服务器。然而,它没有认证客户端和服务器之间持续的消息。基于它们共享一个秘密密钥的事实,这些持续的会话消息可以通过使用该密钥对消息进行签名来轻松认证。
本文档描述了允许这种持续会话认证的代码更改。这些更改允许未来进行更改,以允许其他认证协议(以及现有的空 NONE 和 UNKNOWN 协议)来处理签名,但在撰写本文时,唯一实际进行签名的协议是 Cephx 协议。
简介
此代码在 Cephx 协议完成后发挥作用。此时,客户端和服务器共享一个秘密密钥。此密钥将用于认证。对于其他协议,可能存在或不存在这样的密钥,并且用于执行签名的实际过程可能不同,因此代码被编写为通用。
这里的“会话”由已建立的管道表示。对于此类管道,应在管道上附加一个 session\_security 结构体。每当要在管道上发送消息时,将调用处理此类会话安全的签名的代码。在管道的另一端,将调用检查此类会话安全的消息签名的代码。未能通过签名检查的消息将不会被进一步处理。这意味着发送方最好与接收方就使用的会话安全达成一致,否则它们之间的消息将被统一丢弃。
该代码还准备好处理会话消息的加密和解密,这将为签名提供的完整性增加保密性。但是,目前没有实现的协议对持续的会话消息进行加密。
为了使此功能起作用,需要执行几个步骤。首先,发送方和接收方必须成功运行 cephx 协议以建立共享密钥。他们必须将该密钥存储在管道以后可以访问的地方,以允许使用它对消息进行签名。发送的消息必须签名,接收的消息必须检查其签名。
签名可以通过各种方式计算,但目前其大小限制为 64 位。消息的签名放置在其页脚中,在一个名为 sig 的字段中。
Cephx 中的签名代码可以在运行时打开和关闭,使用一个名为 cephx\_sign\_messages 的 Ceph 布尔选项。目前,它默认设置为 false,因此不会对消息进行签名。必须将其更改为 true 才能计算和检查签名。
存储密钥
在发送端创建签名和在接收端检查签名都需要密钥。将来,如果非对称加密是一个选项,可能需要存储两个密钥(一个用于管道的这一端的私钥和一个用于另一端的公钥)。目前,双向消息将使用相同的密钥签名,因此只需要保存该密钥。
密钥在管道建立时保存。在客户端,这发生在 connect() 中,该函数位于 msg/Pipe.cc 中。密钥是从 Cephx 协议的运行中获得的,这会导致一个成功检查过的授权器结构体。如果存在这样的授权器,代码会调用 get\_auth\_session\_handler() 来创建一个新的认证会话处理程序,并将其存储在管道数据结构中。在服务器端,在验证了客户端提供的授权器之后,在 accept() 中也做了类似的事情。
一旦连接的两端完成了这些事情,会话认证就可以开始了。
这些例程(connect() 和 accept())也用于处理正在设置新会话的情况。在此阶段,尚未创建授权器,因此没有密钥。当使用 CEPH\_AUTH\_UNKNOWN 协议时,调用签名代码的代码中的特殊情况会跳过这些调用。此协议标签位于会话中的预授权器消息上,表明正在进行认证协议的协商,因此无法进行签名。在此会话中,在传递任何敏感内容之前,稍后会进行可靠的认证操作,因此这不是安全问题。
消息签名
消息在 msg/Pipe.cc 中的 write\_message 调用中签名。实际的签名过程是使用共享密钥加密消息的 CRC。因此,我们必须推迟签名,直到所有 CRC 都已计算。标头 CRC 最后计算,因此我们在计算完该 CRC 后立即调用 sign\_message()。
sign\_message() 是在 auth/AuthSessionHandler.h 中定义的虚函数。因此,必须为每个支持的认证协议编写一个特定版本。目前,仅支持 UNKNOWN、NONE 和 CEPHX。因此,在 auth/unknown/AuthUnknownSessionHandler.h、auth/none/AuthNoneSessionHandler.h 和 auth/cephx/CephxSessionHandler.cc 中有单独的 sign\_message() 版本。UNKNOWN 和 NONE 版本仅返回 0,表示成功。
CEPHX 版本更广泛。它位于 auth/cephx/CephxSessionHandler.cc 中。所做的第一件事是确定用于处理签名的运行时选项(见上文)是否打开。如果未打开,Cephx 版本的 sign\_message() 只返回成功,而不实际计算签名或将其插入消息中。
如果启用了运行时选项,sign\_message() 会将消息的所有 CRC(一个来自标头,三个来自页脚)复制到一个缓冲区中。它对缓冲区调用 encode\_encrypt(),使用从管道的 session\_security 结构体获得的密钥。加密结果的 64 位放入消息页脚的签名字段中,并设置一个页脚标志以指示消息已签名。(此标志是健全性检查。它不被视为消息已签名的确凿证据。接收端存在 session\_security 结构体要求签名,无论此标志的值如何。)如果一切顺利,sign\_message() 返回 0。如果在沿途的任何地方出现问题且未计算签名,则返回 SESSION\_SIGNATURE\_FAILURE。
检查签名
签名由一个名为 check\_message\_signature() 的例程检查。这也是一个虚函数,定义在 auth/AuthSessionHandler.h 中。因此,对于支持的认证协议(如 UNKNOWN、NONE 和 CEPHX),再次有特定版本。同样,UNKNOWN 和 NONE 版本分别存储在 auth/unknown/AuthUnknownSessionHandler.h 和 auth/none/AuthNoneSessionHandler.h 中,并且它们再次仅返回 0,表示成功。
CEPHX 版本的 check\_message\_signature() 执行真正的签名检查。此例程(存储在 auth/cephx/CephxSessionHandler.cc 中)如果运行时选项禁用了签名,则成功退出。否则,它会从标头和页脚中获取 CRC,加密结果,并将其与存储在页脚中的签名进行比较。由于前面的例程已检查 CRC 是否与消息内容实际匹配,因此无需在消息中的原始数据上重新计算 CRC。加密是使用与发送端相同的 encode\_encrypt() 例程执行的,使用存储在本地 session\_security 数据结构中的密钥。
如果一切检查通过,CEPHX 例程返回 0,表示成功。如果出现问题,例程返回 SESSION\_SIGNATURE\_FAILURE。
添加新的会话认证方法
仅用于会话认证(不是 Cephx 协议目前执行的客户端和服务器的基本认证),除了添加新协议之外,该协议还必须具有 sign\_message() 例程和 check\_message\_signature 例程。这些例程将把消息指针作为参数,成功时返回 0。用于签名和检查的过程将特定于新方法,但可能有一个附加到管道的 session\_security 结构体,其中包含一个加密密钥。此结构体将是 AuthSessionHandler(位于 auth/AuthSessionHandler.h 中)或派生自该类型的结构体。
为会话添加加密
现有代码已部分(但未完全)设置,以允许会话对其数据包进行加密。添加加密的一部分将类似于添加新的认证方法。但是,还需要在 write\_message() 和 read\_message() 中添加对加密和解密例程的调用。这些调用可能位于当前认证调用附近。您应该考虑是否要用更通用的东西替换现有的调用,该通用东西执行所选会话安全形式所需的任何操作,而不是明确说明 sign 或 encrypt。
会话安全统计信息
现有的 Cephx 认证代码保留了有关已签名消息数量、已检查消息签名数量以及成功和失败检查数量的统计信息。它已准备好保留有关加密和解密的类似统计信息。可以通过 auth/AuthSessionHandler.cc 中的 printAuthSessionHandlerStats 调用访问这些统计信息。
如果添加新的认证或加密方法,它们应包括保留这些统计信息的代码。