TLS 支持
目录
RabbitMQ 内置了对 TLS 的支持。这包括客户端连接以及适用的流行插件,例如 Federation 链接。还可以使用 TLS 来 加密集群中的节点间连接。
本指南涵盖了与 RabbitMQ 中 TLS 相关的各种主题,重点是客户端连接。
- 两种 用于客户端连接的 TLS 使用方式:直接连接或通过 TLS 终止代理。
- TLS 支持的 Erlang/OTP 要求。
- 启用 RabbitMQ 中的 TLS。
- 如何 使用 tls-gen 或 手动生成用于开发和 QA 环境的自签名证书。
- TLS 在 Java 和 .NET 客户端中的配置。
- 客户端连接的对等方(证书链)验证或相互(“mTLS”)验证。
- 与 RabbitMQ 相关的公共 密钥用法扩展。
- 如何控制启用的 TLS 版本和 密码套件。
- TLSv1.3 支持。
- 可用于 评估 TLS 设置 的工具。
- 证书和密钥轮换
- 对于信任的证书频繁更改的环境,可以使用 信任存储插件。
- 已知的 TLS 攻击及其缓解措施。
- 如何使用 私钥密码。
等等。
何处学习 TLS 基础知识
本指南试图 解释 TLS 的基础知识,但它并不是 TLS、加密、公钥基础设施和相关主题的入门教程,因此这些概念仅做简要介绍。
网上有许多面向初学者的 TLS 入门教程。
TLS 和消息传递协议
TLS 可以为 RabbitMQ 支持的所有协议启用。但是,本指南主要关注 AMQP 1.0 和 AMQP 0-9-1 监听器。有关这些协议的 TLS 配置示例,请参阅 MQTT、STOMP 及其 各自的 WebSocket 传输指南。
HTTP API、节点间流量和 CLI 工具流量也可以配置为使用 TLS (HTTPS)。
要在 Kubernetes 中使用 RabbitMQ Cluster Operator 配置 TLS,请参阅 配置 TLS 的指南。
有关常见 TLS 故障排除技术的概述,请参阅 TLS 相关问题故障排除 和 网络故障排除。
RabbitMQ 客户端连接的常见 TLS 方法
对于客户端连接,有两种常见方法:
这两种方法都是有效的,各有优缺点。本指南将重点介绍第一种方法。本指南的某些部分对于选择第二种方法的环境仍然是相关的。
TLS 支持的 Erlang/OTP 要求
为了支持 TLS 连接,RabbitMQ 需要 TLS 和加密相关的模块在 Erlang/OTP 安装中可用。与 TLS 一起使用的推荐 Erlang/OTP 版本是最新 支持的 Erlang 版本。早期版本,即使得到支持,对于大多数证书也能正常工作,但存在已知限制(见下文)。
必须安装并运行 Erlang 的 asn1、crypto、public_key 和 ssl 库(应用程序)。在 Debian 和 Ubuntu 上,它们分别由 erlang-asn1、erlang-crypto、erlang-public-key 和 erlang-ssl 包提供。RabbitMQ 的 零依赖 Erlang RPM 包含上述模块。
如果从源代码编译 Erlang/OTP,则必须确保 configure 能够找到 OpenSSL 并构建上述库。
在排查 TLS 连接问题时,请牢记,绝大多数情况下它们都是环境特定的(例如,信任的证书存储中缺少证书),并且不表示 Erlang/OTP 的 TLS 实现存在错误或限制。请先按照 TLS 故障排除指南中的步骤进行操作以收集更多信息。
已知不兼容性和限制
如果预计使用椭圆曲线密码学 (ECC) 密码套件,强烈建议使用最新的 支持的 Erlang 版本。早期版本在 ECC 支持方面存在已知限制。
如果您遇到上述限制或任何其他不兼容性,请使用 TLS 终止选项(见上文)。
TLS 基础:证书颁发机构、证书、密钥
TLS 是一个庞大且相当复杂的主题。在解释 如何在 RabbitMQ 中启用 TLS 之前,值得简要介绍一下本指南中使用的一些概念。本节内容故意简洁,并对某些内容进行了简化。其目的是帮助读者开始为 RabbitMQ 和应用程序启用 TLS。
网上有许多面向初学者的 TLS 入门教程。
为了全面了解 TLS 以及如何充分利用它,我们建议使用其他资源,例如 Network Security with OpenSSL。
TLS 的两个主要目标
TLS 的两个主要目的是:加密连接流量和提供一种验证(验证)对等方身份的方法,以防御 中间人攻击。这两种目的都通过一套称为 公钥基础设施 (PKI) 的角色、策略和程序来实现。
PKI 基于可以被密码学(数学上)验证的数字身份的概念。这些身份被称为证书,更准确地说,是证书/密钥对。每个启用 TLS 的服务器通常都有自己的证书/密钥对,用于计算用于加密连接上发送的流量的连接特定密钥。
此外,如果被要求,它可以向连接方展示其证书(公钥)。客户端可能拥有也可能不拥有自己的证书。在消息传递和 RabbitMQ 等工具的上下文中,客户端通常也使用证书/密钥对,以便服务器可以验证其身份。
证书、私钥和证书颁发机构
证书/密钥对由 OpenSSL 等工具生成,并由称为证书颁发机构 (CA) 的实体签名。CA 会颁发用户(应用程序或其他 CA)使用的证书。当证书由 CA 签名时,它们就构成了一个信任链。这样的链可以包含多个 CA,但最终会签署由应用程序使用的证书/密钥对(叶子或最终用户证书)。CA 证书链通常捆绑在一个文件中。这样的文件称为CA 捆绑包。
以下是一个最简单的链的示例,包含一个根 CA 和一个叶子(服务器或客户端)证书:

包含中间证书的链可能看起来像这样:

有一些组织负责签名和颁发证书/密钥对。其中大多数是广受信赖的 CA,并收取服务费。
启用 TLS 的 RabbitMQ 节点必须有一个包含其信任的证书颁发机构证书的文件(CA 捆绑包)、一个证书(公钥)文件和一个私钥文件。文件将从本地文件系统读取。它们必须可以被 RabbitMQ 节点进程的有效用户读取。
启用 TLS 的连接的双方都可以选择验证对方。在进行此操作时,它们会尝试在对等方提供的证书列表中找到受信任的证书颁发机构。当双方都执行此验证过程时,这称为相互 TLS 身份验证或mTLS。更多信息请参阅 对等方验证 部分。
本指南假设用户可以访问证书颁发机构以及各种格式的两个证书/密钥对,以供不同的客户端库使用。这最好通过 现有工具来完成,但对于那些希望更熟悉该主题和 OpenSSL 命令行工具的人,有一个 单独的部分。
在生产环境中,证书由商业证书颁发机构或内部安全团队颁发的证书颁发机构生成。在这些情况下,证书颁发机构捆绑包文件很可能包含多个证书。只要满足相同的基本 文件和路径要求,这就不会改变捆绑包文件在配置 RabbitMQ 时的使用方式。换句话说,无论是自签名证书还是由 受信任 CA 颁发的证书,它们的配置方式都相同。关于 对等方验证 的部分对此进行了详细介绍。
生成 CA、证书和密钥的捷径
本指南假设用户拥有 CA 证书捆绑包文件和两个 证书/密钥对。证书/密钥对由 RabbitMQ 和连接到服务器的客户端在启用 TLS 的端口上使用。生成证书颁发机构和两个密钥对的过程相当繁琐且容易出错。在 MacOS 或 Linux 上生成所有这些内容的更简单方法是使用 tls-gen:它需要 Python 3.5+、make 和 openssl 在 PATH 中。
请注意,tls-gen 及其生成的证书/密钥对是自签名的,仅适用于开发和测试环境。绝大多数生产环境应使用由广受信赖的商业 CA 颁发的证书和密钥。
tls-gen 支持 RSA 和 椭圆曲线密码学 算法进行密钥生成。
使用 tls-gen 的基本配置文件
下面是一个生成 CA 并使用它生成两个证书/密钥对的示例,一个用于服务器,另一个用于客户端。这是本指南其余部分期望的设置。
git clone https://github.com/rabbitmq/tls-gen tls-gen
cd tls-gen/basic
# private key password
make PASSWORD=bunnies
make verify
make info
ls -l ./result
此基本 tls-gen 配置文件的证书链如下所示:

在 RabbitMQ 中启用 TLS 支持
要启用 RabbitMQ 的 TLS 支持,节点必须配置为知道 证书颁发机构捆绑包(包含一个或多个 CA 证书的文件)、服务器证书文件和服务器密钥的位置。还应启用 TLS 监听器,以了解 TLS 客户端连接的监听端口。可以配置更多与 TLS 相关的设置。这些将在本指南的其余部分介绍。
以下是与 TLS 相关的重要配置设置:
| 配置键 | 描述 |
|---|---|
listeners.ssl | 用于 TLS 连接的监听端口列表。RabbitMQ 可以监听 单个接口或多个接口。 |
ssl_options.cacertfile | 证书颁发机构 (CA) 捆绑包文件路径。 |
ssl_options.certfile | 服务器证书文件路径。 |
ssl_options.keyfile | 服务器私钥文件路径。 |
ssl_options.password | 私钥文件的密码。 |
ssl_options.verify | 是否启用 对等方验证? |
ssl_options.fail_if_no_peer_cert | 如果设置为 |
这些选项在 配置文件 中提供。下面的示例配置将在所有接口的 5671 端口上启动一个 TLS 监听器,监听此主机名:
listeners.ssl.default = 5671
ssl_options.cacertfile = /path/to/ca_certificate.pem
ssl_options.certfile = /path/to/server_certificate.pem
ssl_options.keyfile = /path/to/server_key.pem
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = true
# If the private key file is password protected, set this value:
# ssl_options.password = PASSWORD
此配置还将执行 对等方证书链验证,因此没有证书的客户端将被拒绝。
可以完全禁用常规(非 TLS)监听器。只有启用 TLS 的客户端才能连接到此类节点,并且仅当它们使用正确的端口时。
# disables non-TLS listeners, only TLS-enabled clients will be able to connect
listeners.tcp = none
listeners.ssl.default = 5671
ssl_options.cacertfile = /path/to/ca_certificate.pem
ssl_options.certfile = /path/to/server_certificate.pem
ssl_options.keyfile = /path/to/server_key.pem
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = true
TLS 设置也可以使用 经典配置格式 进行配置。
[
{rabbit, [
{ssl_listeners, [5671]},
{ssl_options, [{cacertfile, "/path/to/ca_certificate.pem"},
{certfile, "/path/to/server_certificate.pem"},
{keyfile, "/path/to/server_key.pem"},
{verify, verify_peer},
{fail_if_no_peer_cert, true}]}
]}
].
证书和私钥文件路径
RabbitMQ 必须能够读取其配置的 CA 证书捆绑包、服务器证书和私钥。文件必须存在并具有适当的权限。如果不是这种情况,节点将无法启动或无法接受 TLS 连接。
Windows 用户注意事项:在 Windows 上,配置文件中的反斜杠(“\”)被解释为转义序列。例如,要在 Windows 上为 CA 证书指定路径 c:\ca_certificate.pem,需要使用 "c:\\ca_certificate.pem" 或 "c:/ca_certificate.pem"。
如何验证 TLS 是否已启用
要验证节点上是否已启用 TLS,请重新启动它并检查其 日志文件。它应该包含有关已启用 TLS 监听器的条目,如下所示:
2020-07-13 21:13:01.015 [info] <0.573.0> started TCP listener on [::]:5672
2020-07-13 21:13:01.055 [info] <0.589.0> started TLS (SSL) listener on [::]:5671
另一种方法是使用 rabbitmq-diagnostics listeners,它应该包含 TLS 监听器的行。
rabbitmq-diagnostics listeners
#
# ... (some output omitted for brevity)
# => Interface: [::], port: 5671, protocol: amqp/ssl, purpose: AMQP 0-9-1 and AMQP 1.0 over TLS
# ...
提供私钥密码
私钥可以根据需要通过密码进行保护。要提供密码,请使用 password 选项。
listeners.ssl.1 = 5671
ssl_options.cacertfile = /path/to/ca_certificate.pem
ssl_options.certfile = /path/to/server_certificate.pem
ssl_options.keyfile = /path/to/server_key.pem
ssl_options.password = t0p$3kRe7
使用 经典配置格式 的相同示例:
[
{rabbit, [
{ssl_listeners, [5671]},
{ssl_options, [{cacertfile,"/path/to/ca_certificate.pem"},
{certfile, "/path/to/server_certificate.pem"},
{keyfile, "/path/to/server_key.pem"},
{password, "t0p$3kRe7"}
]}
]}
].
经典配置文件格式支持 配置值加密,建议用于密码。
TLS 对等方验证:您如何称呼自己?
如 证书和密钥 部分所述,TLS 有两个主要目的:加密连接流量和提供一种验证对等方是否可信(例如,由受信任的证书颁发机构签名)的方法,以防御 中间人攻击,这类攻击中攻击者会冒充合法的受信任对等方(通常是服务器)。本节将重点介绍后者。
对等方验证如何工作
当建立 TLS 连接时,客户端和服务器会执行连接协商,其中涉及几个步骤。第一步是对等方选择性地交换其 证书。交换证书后,对等方可以选择性地尝试在其 CA 证书和提供的证书之间建立信任链。这用于验证对等方是否是它声称的身份(前提是私钥未被盗)。
这个过程称为对等方验证或对等方验证,并遵循一个称为 证书路径验证算法 的算法。为了使用对等方验证,并不需要理解整个算法,因此本节对关键部分进行了过度简化。
每个对等方提供一个证书链,该链以一个“叶子”(客户端或服务器)证书开始,并继续包含至少一个证书颁发机构 (CA) 证书。该 CA 颁发(签名)了叶子 CA。如果存在多个 CA 证书,它们通常形成一个签名链,这意味着每个 CA 证书都由下一个 CA 证书签名。例如,如果证书 B 由 A 签名,C 由 B 签名,则链为 A, B, C(此处使用逗号是为了清晰)。“最高”(第一个或唯一)CA 通常被称为该链的根 CA。根 CA 可以由知名的证书颁发机构(商业供应商)或任何其他方(自签名)颁发。
以下是一个最简单的链的示例,包含一个根 CA 和一个叶子(服务器或客户端)证书:

包含中间证书的链可能看起来像这样:

在对等方验证期间,TLS 连接的客户端(或服务器)会遍历对等方提供的证书链,如果找到受信任的证书,则认为该对等方是受信任的。
相互对等方验证(相互 TLS 身份验证或 mTLS)
当双方都执行此对等方验证过程时,这称为相互 TLS 身份验证或mTLS。
启用相互对等方验证涉及两项内容:
换句话说,相互对等方验证(“mTLS”)是 RabbitMQ 节点和客户端连接的共同责任。仅在一端启用对等方验证是不够的。
对等方验证失败时
如果没有找到受信任且否则有效的证书,对等方验证将失败,并且客户端的 TLS(TCP)连接将以致命错误(OpenSSL 术语中的“警报”)关闭,显示“未知 CA”或类似信息。警报将由服务器记录,消息类似于:
2018-09-10 18:10:46.502 [info] <0.902.0< TLS server generated SERVER ALERT: Fatal - Unknown CA
证书的有效性也会在每一步进行检查。已过期或尚未生效的证书将被拒绝。在这种情况下,TLS 警报将如下所示:
2018-09-10 18:11:05.168 [info] <0.923.0< TLS server generated SERVER ALERT: Fatal - Certificate Expired
以上示例演示了 RabbitMQ 节点记录的 TLS 警报消息。执行对等方验证的客户端也将发出警报,但可能使用不同的错误消息。 RFC 8446 第 6.2 节 提供了各种警报及其含义的概述。
受信任的证书
每个启用 TLS 的工具和 TLS 实现,包括 Erlang/OTP 和 RabbitMQ,都有一个标记一组证书为受信任的方法。
这有三种常见方法:
- 所有受信任的 CA 证书都必须添加到单个文件中,称为CA 证书捆绑包。
- 目录中的所有 CA 证书都将被视为受信任。
- 使用专用工具管理受信任的 CA 证书。
不同的 TLS 实现和工具使用不同的选项。在 RabbitMQ 的上下文中,这意味着不同客户端库、工具和 RabbitMQ 服务器本身管理受信任证书的方法可能不同。
例如,OpenSSL 和 OpenSSL 命令行工具(如 Linux 和其他类 UNIX 系统上的 s_client)将使用由超级用户管理的目录。该目录中的 CA 证书将被视为受信任,而由它们颁发的证书(例如客户端提供的证书)也是如此。受信任证书目录的位置会 因发行版、操作系统和版本而 有所不同。
在 Windows 上,受信任的证书使用 certmgr 等工具进行管理。
服务器的 CA 证书捆绑包中的证书可能被视为受信任。我们说“可能”,因为对于所有客户端库来说,它的工作方式并非完全相同,这取决于 TLS 实现。例如,在 Python 中,CA 证书捆绑包中的证书不会被视为受信任,除非显式添加到信任存储中。
RabbitMQ 依赖于 Erlang 的 TLS 实现。它假定所有受信任的 CA 证书都已添加到服务器证书捆绑包中。
在执行对等方验证时,RabbitMQ 只会将根证书(列表中的第一个证书)视为受信任。任何中间证书都将被忽略。如果希望中间证书也被视为受信任,则必须将它们添加到受信任证书列表:证书捆绑包中。
虽然可以将最终(“叶子”)证书(例如服务器和客户端使用的证书)放在受信任证书目录中,但更常见的做法是将 CA 证书添加到受信任证书列表中。
将多个证书连接在一起并使用单个证书颁发机构捆绑包文件的最常见方法是简单地将它们串联起来:
cat rootca/ca_certificate.pem otherca/ca_certificate.pem > all_cacerts.pem
启用对等方验证
在服务器端,对等方验证主要通过两个配置选项控制:ssl_options.verify 和 ssl_options.fail_if_no_peer_cert。
将 ssl_options.fail_if_no_peer_cert 选项设置为 false 会告知节点接受不提供证书的客户端(例如,未配置使用证书的客户端)。
当 ssl_options.verify 选项设置为 verify_peer 时,客户端确实会发送证书,节点必须执行对等方验证。当设置为 verify_none 时,将禁用对等方验证,并且不会执行证书交换。
例如,以下配置将执行对等方验证并拒绝不提供证书的客户端:
listeners.ssl.default = 5671
ssl_options.cacertfile = /path/to/ca_certificate.pem
ssl_options.certfile = /path/to/server_certificate.pem
ssl_options.keyfile = /path/to/server_key.pem
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = true
在 经典配置格式 中的相同示例:
[
{rabbit, [
{ssl_listeners, [5671]},
{ssl_options, [{cacertfile,"/path/to/ca_certificate.pem"},
{certfile,"/path/to/server_certificate.pem"},
{keyfile,"/path/to/server_key.pem"},
{verify, verify_peer},
{fail_if_no_peer_cert, true}]}
]}
].
客户端库如何确切地配置对等方验证因库而异。 Java 和 .NET 客户端部分涵盖了这些客户端中的对等方验证。
强烈建议在生产环境中进行对等方验证。经过仔细考虑,在某些环境中(例如开发)禁用它可能是有意义的。
因此,可以创建一个加密的 TLS 连接,而无需验证证书。客户端库通常支持这两种操作模式。
当启用对等方验证时,客户端通常还会检查其连接的服务器的主机名是否与服务器证书中的以下两个字段之一匹配:SAN(主题备用名称)或 CN(通用名称)。当使用 通配符证书 时,主机名将与模式进行匹配。如果没有匹配,客户端也会导致对等方验证失败。主机名检查也是可选的,并且通常与客户端执行的证书链验证正交。
因此,了解生成证书时使用的 SAN(主题备用名称)或 CN(通用名称)值非常重要。如果在某台主机上生成了证书并在另一台主机上使用,则应将 $(hostname) 值替换为目标服务器的正确主机名。
tls-gen 将使用本地计算机的主机名作为这两个值。同样,在 手动生成证书/密钥对部分 中,本地计算机的主机名被指定为 ...-subj /CN=$(hostname)/... 到某些 OpenSSL CLI 工具命令。
证书链和验证深度
当使用由中间 CA 签名的客户端证书时,可能需要将 RabbitMQ 服务器配置为使用更高的验证深度。
深度是有效证书路径中可能跟随对等方证书的非自颁发中间证书的最大数量。因此,如果深度为 0,则对等方(例如客户端)证书必须由受信任的 CA 直接签名;如果为 1,则路径可以是“对等方、CA、受信任 CA”;如果为 2,则为“对等方、CA、CA、受信任 CA”,依此类推。默认深度为 1。
以下示例演示了如何为 RabbitMQ 服务器配置证书验证深度:
listeners.ssl.default = 5671
ssl_options.cacertfile = /path/to/ca_certificate.pem
ssl_options.certfile = /path/to/server_certificate.pem
ssl_options.keyfile = /path/to/server_key.pem
ssl_options.verify = verify_peer
ssl_options.depth = 2
ssl_options.fail_if_no_peer_cert = false
在 经典配置格式 中的相同示例:
[
{rabbit, [
{ssl_listeners, [5671]},
{ssl_options, [{cacertfile,"/path/to/ca_certificate.pem"},
{certfile,"/path/to/server_certificate.pem"},
{keyfile,"/path/to/server_key.pem"},
{depth, 2},
{verify,verify_peer},
{fail_if_no_peer_cert,false}]}
]}
].
当使用 RabbitMQ 插件(如 Federation 或 Shovel)并结合 TLS 时,可能需要配置这些插件底层使用的 Erlang 客户端的验证深度,如 下文所述。
在 Java 客户端中使用 TLS
在 RabbitMQ Java 客户端中启用 TLS 有两个主要部分:设置密钥库并进行一些 Java 安全框架的配置,以及实现所需的对等方验证策略。
密钥管理器、信任管理器和存储
Java 安全框架有三个主要组件:密钥管理器、信任管理器和密钥存储。
密钥管理器由对等方(在此例中为客户端连接)使用来管理其证书。在 TLS 连接/会话协商期间,密钥管理器将控制要发送给远程对等方的证书。
信任管理器由对等方使用来管理远程证书。在 TLS 连接/会话协商期间,信任管理器将控制哪些证书是远程对等方可信的。信任管理器可用于实现任何证书链验证逻辑。
密钥存储是证书存储概念的 Java 封装。所有证书必须存储为 Java 特定的二进制格式 (JKS) 或 PKCS#12 格式。这些格式使用 KeyStore 类进行管理。在以下示例中,JKS 格式用于将受信任的(服务器)证书添加到存储中,而对于客户端密钥/证书对,将使用 tls-gen 生成的 PKCS#12 密钥文件。
Java 客户端中的所有 TLS 相关设置都通过 ConnectionFactory 进行配置。
使用 TLS 连接
此基本示例将展示一个简单的客户端通过 TLS 连接到 RabbitMQ 服务器,而不验证服务器证书,也不向服务器提供任何客户端证书。
import java.io.*;
import java.security.*;
import com.rabbitmq.client.*;
public class Example1 {
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5671);
factory.useSslProtocol();
// Tells the library to setup the default Key and Trust managers for you
// which do not do any form of remote server trust verification
Connection conn = factory.newConnection();
Channel channel = conn.createChannel();
// non-durable, exclusive, auto-delete queue
channel.queueDeclare("rabbitmq-java-test", false, true, true, null);
channel.basicPublish("", "rabbitmq-java-test", null, "Hello, World".getBytes());
GetResponse chResponse = channel.basicGet("rabbitmq-java-test", false);
if (chResponse == null) {
System.out.println("No message retrieved");
} else {
byte[] body = chResponse.getBody();
System.out.println("Received: " + new String(body));
}
channel.close();
conn.close();
}
}
这个简单的示例是一个回显客户端和服务器。它创建一个通道并发布到默认的 direct 交换,然后读回已发布的内容并将其回显。它使用一个 独占、非持久、自动删除的队列,该队列将在连接关闭后不久删除。
连接并启用对等方验证
为了让 Java 客户端信任服务器,必须将服务器证书添加到信任存储中,该存储将用于实例化 信任管理器。JDK 提供了一个名为 keytool 的工具来管理证书存储。要将证书导入存储,请使用 keytool -import。
keytool -import -alias server1 -file /path/to/server_certificate.pem -keystore /path/to/rabbitstore
上面的命令会将 server/certificate.pem 导入到 rabbitstore 文件中,使用 JKS 格式。在信任存储中,该证书将被引用为 server1。所有证书和密钥在存储中都必须具有唯一的名称。
keytool 将确认证书是受信任的,并询问密码。密码用于保护信任存储免受任何篡改。
然后使用 PKCS#12 文件中的客户端证书和密钥。请注意,Java 原生支持 PKCS#12 格式,无需转换。
以下示例演示了如何将密钥存储和信任存储与 密钥管理器 和 信任管理器 一起使用。
import java.io.*;
import java.security.*;
import javax.net.ssl.*;
import com.rabbitmq.client.*;
public class Example2 {
public static void main(String[] args) throws Exception {
char[] keyPassphrase = "MySecretPassword".toCharArray();
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream("/path/to/client_key.p12"), keyPassphrase);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, keyPassphrase);
char[] trustPassphrase = "rabbitstore".toCharArray();
KeyStore tks = KeyStore.getInstance("JKS");
tks.load(new FileInputStream("/path/to/trustStore"), trustPassphrase);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(tks);
SSLContext c = SSLContext.getInstance("TLSv1.2");
c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5671);
factory.useSslProtocol(c);
factory.enableHostnameVerification();
Connection conn = factory.newConnection();
Channel channel = conn.createChannel();
channel.queueDeclare("rabbitmq-java-test", false, true, true, null);
channel.basicPublish("", "rabbitmq-java-test", null, "Hello, World".getBytes());
GetResponse chResponse = channel.basicGet("rabbitmq-java-test", false);
if (chResponse == null) {
System.out.println("No message retrieved");
} else {
byte[] body = chResponse.getBody();
System.out.println("Received: " + new String(body));
}
channel.close();
conn.close();
}
}
为确保上述代码在不受信任的证书下按预期工作,请设置一个未导入到密钥存储中的证书的 RabbitMQ 节点,并观察连接失败。
服务器主机名验证
必须使用 ConnectionFactory#enableHostnameVerification() 方法单独启用主机名验证。例如,在上面的示例中,这是这样做的:
import java.io.*;
import java.security.*;
import javax.net.ssl.*;
import com.rabbitmq.client.*;
public class Example2 {
public static void main(String[] args) throws Exception {
char[] keyPassphrase = "MySecretPassword".toCharArray();
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream("/path/to/client_key.p12"), keyPassphrase);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, keyPassphrase);
char[] trustPassphrase = "rabbitstore".toCharArray();
KeyStore tks = KeyStore.getInstance("JKS");
tks.load(new FileInputStream("/path/to/trustStore"), trustPassphrase);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(tks);
SSLContext c = SSLContext.getInstance("TLSv1.2");
c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5671);
factory.useSslProtocol(c);
factory.enableHostnameVerification();
// this connection will both perform peer verification
// and server hostname verification
Connection conn = factory.newConnection();
// snip ...
}
}
这将验证服务器证书是否已针对客户端正在连接的主机名颁发。与证书链验证不同,此功能是客户端特定的(服务器通常不执行)。
在 Java 客户端中配置 TLS 版本
就像 RabbitMQ 服务器可以 配置为仅支持特定的 TLS 版本 一样,可能需要配置 Java 客户端的首选 TLS 版本。这是使用接受协议版本名称或 SSLContext 的 ConnectionFactory#useSslProtocol 重载来完成的。
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5671);
factory.useSslProtocol("TLSv1.2");
该库的现代版本将尝试使用运行时支持的最新 TLS 版本。
在 .NET 客户端中使用 TLS
要使客户端证书在 .NET 平台上被理解,它们可以是 DER 和 PKCS#12 等多种格式,但不能是 PEM 格式。对于 DER 格式,.NET 期望它们存储在扩展名为 .cer 的文件中。tls-gen 会生成 PEM 和 PKCS#12 文件。
.NET 信任存储
在 .NET 平台上,受信任的证书是通过将它们放入多个存储中的任何一个来管理的。所有这些存储的管理都使用 'certmgr' 工具完成。
注意:在某些 Windows 版本中,该命令有两个版本:一个随操作系统提供,仅提供图形界面;另一个随 Windows SDK 提供,并同时提供图形界面和命令行界面。两者都可以完成工作,但下面的示例基于后者。
对我们来说,因为我们单独提供 PKCS#12 文件中的客户端证书/密钥对,所以我们只需要做的是将根证书颁发机构的证书导入到 Root (Windows) 或 Trust (Mono) 存储中。由该存储中的任何证书签名的所有证书都会被自动信任。
与 Java 客户端不同,Java 客户端可以接受在不进行对等方验证的情况下进行 TLS 连接,而 .NET 客户端默认情况下要求此验证成功。要抑制验证,应用程序可以在 SslOption 中设置 System.Net.Security.SslPolicyErrors.RemoteCertificateNotAvailable 和 System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors 标志。
使用 Certmgr 进行证书管理
certmgr 是一个命令行工具,用于管理指定存储中的证书,例如添加和删除证书。这些存储可以是用户特定的存储,也可以是系统范围的存储。只有管理员用户才能对系统范围的存储进行写访问。
以下示例将证书添加到用户 Root(在某些 .NET 实现中也称为 Trust)的存储中:
# Windows
certmgr -add -all \path\to\cacert.cer -s Root
# Linux with Mono
certmgr -add -c Trust /path/to/cacert.cer
要将证书添加到系统范围(计算机)证书存储,请运行:
# Windows
certmgr -add -all \path\to\cacert.cer -s -r localMachine Root
# Linux with Mono
certmgr -add -c -m Trust /path/to/cacert.cer
将证书添加到存储后,可以使用 -all(Mono 版为 -list)开关查看该存储的内容:
certmgr -all -s Root
# … snip …
Self-signed X.509 v3 Certificate
Serial Number: AC3F2B74ECDD9EEA00
Issuer Name: CN=MyTestCA
Subject Name: CN=MyTestCA
valid From: 25/08/2018 14:03:01
valid Until: 24/09/2018 14:03:01
Unique Hash: 1F04D1D2C20B97BDD5DB70B9EB2013550697A05E
certmgr -list -c Trust
# … snip …
Self-signed X.509 v3 Certificate
Serial Number: AC3F2B74ECDD9EEA00
Issuer Name: CN=MyTestCA
Subject Name: CN=MyTestCA
valid From: 25/08/2018 14:03:01
valid Until: 24/09/2018 14:03:01
Unique Hash: 1F04D1D2C20B97BDD5DB70B9EB2013550697A05E
根据上面的输出,信任存储中有一个 Self-signed X.509 v3 Certificate。唯一哈希值唯一标识该存储中的此证书。要删除此证书,请使用唯一哈希值:
# Windows
certmgr -del -c -sha1 1F04D1D2C20B97BDD5DB70B9EB2013550697A05E -s Root
# … snip …
Certificate removed from store.
# Linux with Mono
certmgr -del -c Trust 1F04D1D2C20B97BDD5DB70B9EB2013550697A05E
# … snip …
Certificate removed from store.
连接 TLS 设置
要创建到 RabbitMQ 的 TLS 连接,我们需要设置 ConnectionFactory 的 Parameters 字段中的一些新字段。为了简化,有一个新的字段 Parameters.Ssl,它像一个命名空间一样包含我们需要设置的所有其他字段。这些字段是:
| 属性 | 描述 |
|---|---|
Ssl.CertPath | 如果您的服务器需要客户端身份验证,这是客户端证书(PKCS#12 格式)的路径。这是可选的。 |
Ssl.CertPassphrase | 如果您使用的是 PKCS#12 格式的客户端证书,它可能有一个密码,您可以在此字段中指定。 |
Ssl.Enabled | 这是一个布尔字段,用于打开或关闭 TLS 支持。默认情况下是关闭的。 |
Ssl.ServerName | .NET 要求此值与服务器发送的证书上的主题备用名称 (SAN) 或通用名称 (CN) 匹配。 |
TLS 版本
TLS 自 20 世纪 90 年代中期以来就已存在,有多种 TLS 版本正在使用。尽管如此,随着新版本和更安全版本的开发,旧版本正在被行业淘汰 。
就像 RabbitMQ 服务器可以 配置为仅支持特定的 TLS 版本 一样,可能需要配置 .NET 客户端的首选 TLS 版本。这是通过 ConnectionFactory#Ssl 访问的 TLS 选项完成的。
支持的 TLS 版本值是 System.Security.Authentication.SslProtocols 枚举 的值。
using System.Security.Authentication;
// ...
ConnectionFactory cf = new ConnectionFactory();
cf.Ssl.Enabled = true;
cf.Ssl.ServerName = System.Net.Dns.GetHostName();
cf.Ssl.CertPath = "/path/to/client_key.p12";
cf.Ssl.CertPassphrase = "MySecretPassword";
// Use TLSv1.2 for this connection
cf.Ssl.Version = SslProtocols.Tls12;
RabbitMQ .NET 客户端 5.x 系列默认使用 TLSv1.0。
从 RabbitMQ .NET 客户端 6.0 开始,默认更改为 SslProtocols.None,这意味着默认值 由 .NET 框架或操作系统决定,具体取决于 app context switches。
如果使用 SslProtocols.None 来选择合适的 TLS 版本的连接失败,客户端将尝试显式启用 TLSv1.2 进行重试。这减少了在禁用自动 TLS 版本选择的环境中,或在不可用或无法依赖它的情况下,对应用程序开发人员进行显式配置的需求。
现代 .NET 框架版本 默认使用 TLSv1.2。
代码示例
这基本上是 Java 客户端示例 的直接移植。它创建一个通道并发布到默认的 direct 交换,然后读回已发布的内容并将其回显。请注意,我们使用了一个 独占、非持久、自动删除的队列,因此我们不必担心手动清理。
using System;
using System.IO;
using System.Text;
using RabbitMQ.client;
namespace RabbitMQ.client.Examples
{
public class TestSSL
{
public static async Task<int> Main(string[] args)
{
ConnectionFactory cf = new ConnectionFactory();
cf.Ssl.Enabled = true;
cf.Ssl.ServerName = System.Net.Dns.GetHostName();
cf.Ssl.CertPath = "/path/to/client_key.p12";
cf.Ssl.CertPassphrase = "MySecretPassword";
using (IConnection conn = await cf.CreateConnectionAsync())
{
using (IChannel ch = await conn.CreateChannelAsync())
{
Console.WriteLine("Successfully connected and opened a channel");
await ch.QueueDeclareAsync("rabbitmq-dotnet-test", false, false, false, null);
Console.WriteLine("Successfully declared a queue");
await ch.QueueDeleteAsync("rabbitmq-dotnet-test");
Console.WriteLine("Successfully deleted the queue");
}
}
return 0;
}
}
}
在 .NET 客户端中进行 TLS 对等方验证
TLS 提供对等方验证(验证),这是一种客户端和服务器根据对等方的证书信息验证彼此身份的方法。当启用对等方验证时,通常需要将您正在连接的服务器的主机名与服务器证书上的CN(通用名称)字段匹配,否则证书将被拒绝。但是,一般而言,对等方验证不限于 CN 和主机名匹配。
这就是为什么本指南开头的命令指定 ...-subj /CN=$(hostname)/...,它动态查找您的主机名。如果您在一台机器上生成证书并在另一台机器上使用它们,请确保替换 $(hostname) 部分,并将其替换为服务器的正确主机名。
在 .NET 平台上,RemoteCertificateValidationCallback 控制 TLS 验证行为。
在 RabbitMQ .NET 客户端中,可以使用 RabbitMQ.client.SslOption.CertificatevalidationCallback 来提供一个 RemoteCertificateValidationCallback 委托。该委托将用于使用适合应用程序的任何逻辑来验证对等方(RabbitMQ 节点)身份。
如果未指定此项,则默认回调将与 AcceptablePolicyErrors 属性结合使用,以确定远程服务器证书是否有效。
RabbitMQ.client.SslOption.AcceptablePolicyErrors 中的 System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch 标志可用于禁用对等方验证(不建议在生产环境中使用!)。
RabbitMQ.client.SslOption.CertificateSelectionCallback 可用于提供一个 LocalCertificateSelectionCallback,该回调将选择用于对等方验证的本地证书。
限制服务器使用的 TLS 版本
为何限制 TLS 版本
TLS(以前称为 SSL)已随着时间的推移而发展,并且有多个版本在使用中。每个版本都改进了前版本的缺点。大多数情况下,缺点导致了影响特定 TLS(和 SSL)版本的已知攻击。禁用旧 TLS 版本是缓解许多此类攻击的一种方法(另一种技术是 禁用受影响的密码套件)。
出于以上原因,Erlang 的最新版本系列默认仅启用最新支持的 TLS 版本,如下表所示:
| Erlang 系列 | 默认启用的 TLS 版本 |
| 27.x | TLSv1.3 和 TLSv1.2 |
| 26.x | TLSv1.3 和 TLSv1.2 |
鼓励使用 较旧的支持 Erlang 版本 的用户,如果可能,将支持的 TLS 版本限制为仅 1.2 及更高版本。请将 TLSv1.0 和 TLSv1.1 视为行业已弃用。
为何不限制 TLS 版本
将 TLS 版本限制为仅 TLSv1.3 甚至仅 TLSv1.2 意味着仅支持旧 TLS 版本的客户端将无法连接。
如果支持使用这些旧运行时环境的应用程序很重要,则服务器必须配置为支持旧版本的 TLS。在大多数情况下,支持 TLSv1.2 应该足够了。
要限制启用的 TLS 协议版本,请使用 ssl_options.versions 设置。
以下示例仅接受 TLSv1.3(最新且最安全的版本),并要求节点运行在编译为最新 OpenSSL 的 Erlang 26 上。使用旧运行时环境(例如 JDK、.NET、Python)且不支持 TLSv1.3 的客户端将无法在此设置下连接。
listeners.ssl.1 = 5671
ssl_options.cacertfile = /path/to/ca_certificate.pem
ssl_options.certfile = /path/to/server_certificate.pem
ssl_options.keyfile = /path/to/server_key.pem
ssl_options.versions.1 = tlsv1.3
# Limits enable cipher suites to only those used by TLSv1.3.
# There are no cipher suites supported by both TLSv1.3 and TLSv1.2.
ssl_options.ciphers.1 = TLS_AES_256_GCM_SHA384
ssl_options.ciphers.2 = TLS_AES_128_GCM_SHA256
ssl_options.ciphers.3 = TLS_CHACHA20_POLY1305_SHA256
ssl_options.ciphers.4 = TLS_AES_128_CCM_SHA256
ssl_options.ciphers.5 = TLS_AES_128_CCM_8_SHA256
以下示例禁用 TLSv1.2 以下的版本:
listeners.ssl.1 = 5671
ssl_options.cacertfile = /path/to/ca_certificate.pem
ssl_options.certfile = /path/to/server_certificate.pem
ssl_options.keyfile = /path/to/server_key.pem
ssl_options.versions.1 = tlsv1.2
验证启用的 TLS 版本
要验证提供的 TLS 版本,请使用 openssl s_client 并带有适当的 TLS 版本标志。
# connect using TLSv1.3
openssl s_client -connect 127.0.0.1:5671 -tls1_3
并在输出中查找以下内容:
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
在下面的示例中,使用了 TLSv1.2:
# connect using TLSv1.2
openssl s_client -connect 127.0.0.1:5671 -tls1_2
输出中的协议和协商的密码套件将如下所示:
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES256-GCM-SHA384
TLSv1.3
TLS 1.3 是 TLS 标准集合的主要修订版。它有意打破了与所有早期版本的向后兼容性。
虽然大多数流行编程语言和运行时环境的现代版本都已支持 TLS 1.3 多年,但采用此版本需要规划,应视为一项会影响使用 TLS 的应用程序的更改。
如果某些应用程序或其运行时无法轻松升级以使用此新版本,那么在 RabbitMQ 端采用 TLS 1.3 可能会成为一个障碍。
TLSv1.3 是 TLS 协议的主要修订版。它是最新且最安全的选择。
TLSv1.3 支持要求节点运行在 Erlang 27 或 26 上,并编译为最新 OpenSSL。
使用旧运行时环境(例如 JDK、.NET、Python)且不支持 TLSv1.3 的客户端无法连接到仅接受 TLSv1.3 连接的 RabbitMQ 节点。
由于 TLSv1.3 与早期 TLS 版本不共享任何密码套件,因此在启用 TLSv1.3 时,请列出 TLSv1.3 特定的密码套件集。
listeners.ssl.1 = 5671
ssl_options.cacertfile = /path/to/ca_certificate.pem
ssl_options.certfile = /path/to/server_certificate.pem
ssl_options.keyfile = /path/to/server_key.pem
ssl_options.versions.1 = tlsv1.3
# Limits enable cipher suites to only those used by TLSv1.3.
# There are no cipher suites supported by both TLSv1.3 and TLSv1.2.
ssl_options.ciphers.1 = TLS_AES_256_GCM_SHA384
ssl_options.ciphers.2 = TLS_AES_128_GCM_SHA256
ssl_options.ciphers.3 = TLS_CHACHA20_POLY1305_SHA256
ssl_options.ciphers.4 = TLS_AES_128_CCM_SHA256
ssl_options.ciphers.5 = TLS_AES_128_CCM_8_SHA256
客户端方面也可能需要显式配置密码套件。
要验证提供的 TLS 版本,请使用 openssl s_client,如 上文所述。
JDK 和 .NET 的 TLS 版本支持表
禁用 TLSv1.0 会限制支持的客户端平台数量。下表解释了哪些 TLS 版本被哪些 JDK 和 .NET 版本支持。
| TLS 版本 | 最低 JDK 版本 | 最低 .NET 版本 |
| TLS 1.3 | JDK 8(从 JDK8u261 开始),JDK 11+ | .NET 4.7(在支持 TLSv1.3 的 Windows 版本上) |
| TLS 1.2 | JDK 7(参见 Protocols,推荐 JDK 8) | .NET 4.5 |
| TLS 1.1 | JDK 7(参见 Protocols,推荐 JDK 8) | .NET 4.5 |
Oracle JDK 有一个关于密码学和相关标准的公开路线图,该路线图概述了何时某些密码套件或 TLS 版本将被弃用或删除。
公钥用法选项
公钥(证书)包含一些字段,用于描述密钥的预期用途场景。这些字段限制了各种工具如何允许使用密钥。例如,公钥可用于验证证书签名(充当证书颁发机构密钥)。
这些字段还会影响 RabbitMQ 节点和客户端在连接协商(更具体地说,TLS 握手)期间将使用哪些密码套件,因此解释其影响很重要。
本指南将以一些有意简化的方式涵盖它们。广义上讲,这些字段分为三类之一:
有些字段是布尔值,有些是其他类型,例如可以设置或取消设置的选项集(位)。
数据服务在很大程度上与使用的约束和密钥用法选项无关。然而,其中一些对于本指南中描述的用例至关重要:
- 服务器身份验证(向客户端提供服务器节点身份)
- 客户端身份验证(向服务器提供客户端身份)
- 验证数字签名
- 密钥加密
前两个选项用于对等方验证。它们必须在生成公钥时为服务器和客户端证书分别设置。一个证书可以同时设置这两个选项。
tls-gen 将确保这些约束和扩展正确设置。在手动生成证书时,这是生成密钥对的操作员或密钥对提供者的责任。
扩展及其对接受的密码套件的影响(密码套件过滤)
两个关键扩展对于两类主要的密码套件至关重要:
digitalSignature用于基于 ECC(椭圆曲线密码学)的套件。keyEncipherment用于基于 RSA 的套件。
强烈建议为 RabbitMQ 节点和客户端库将上述两个选项(位)都设置为真。如果未设置这些位,TLS 实现将从考虑中排除一整类密码套件,可能导致连接时出现令人困惑的“未找到合适的密码套件”警报(错误消息)。
检查证书扩展
要查看为公钥设置的约束和扩展,请使用 openssl x509 命令:
openssl x509 -in /path/to/certificate.pem -text -noout
其输出将包含一个嵌套的扩展和约束列表,看起来如下:
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Key Usage:
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Client Authentication
上述扩展集表明这是一个公钥,可用于对客户端进行身份验证(向 RabbitMQ 节点提供客户端身份),不能用作证书颁发机构证书,并且可用于密钥加密和数字签名。
对于本指南的目的,这是一个适合用于客户端连接的证书(公钥)。
以下是一个适合服务器身份验证(提供 RabbitMQ 节点身份)以及客户端身份验证(可能出于可用性原因)的公钥证书示例:
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Key Usage:
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
密码套件
可以配置 RabbitMQ 使用的密码套件。请注意,并非所有套件在所有系统上都可用。例如,要使用椭圆曲线密码,必须使用最新的支持的 Erlang 版本。
RabbitMQ 节点和客户端使用的密码套件也可以通过公钥用法字段及其值有效地限制。在继续配置密码套件之前,确保这些密钥用法选项是可接受的很重要。
列出 RabbitMQ 节点上可用的密码套件
要列出正在运行的节点 Erlang 运行时支持的密码套件,请使用 rabbitmq-diagnostics cipher_suites --format openssl。
rabbitmq-diagnostics cipher_suites --format openssl -q
这将以 OpenSSL 格式生成密码套件列表。
请注意,如果您使用 --format erlang
rabbitmq-diagnostics cipher_suites --format erlang -q
则 rabbitmq-diagnostics cipher_suites 将以仅在 经典配置格式 中接受的格式列出密码套件。OpenSSL 格式可以被两种配置格式接受。请注意,在新的风格配置格式中,密码套件不加引号,但在经典格式中需要双引号。
上述命令列出的密码套件的格式可用于入站和出站(例如,Shovel、Federation)客户端 TLS 连接。它们不同于 配置值加密使用的密码套件。
在覆盖密码套件时,强烈建议强制执行服务器首选的 密码套件顺序。
配置密码套件
密码套件使用 ssl_options.ciphers 配置选项(经典配置格式中为 rabbit.ssl_options.ciphers)进行配置。
以下示例演示了该选项的用法。
listeners.ssl.1 = 5671
ssl_options.cacertfile = /path/to/ca_certificate.pem
ssl_options.certfile = /path/to/server_certificate.pem
ssl_options.keyfile = /path/to/server_key.pem
ssl_options.versions.1 = tlsv1.2
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = false
ssl_options.ciphers.1 = ECDHE-ECDSA-AES256-GCM-SHA384
ssl_options.ciphers.2 = ECDHE-RSA-AES256-GCM-SHA384
ssl_options.ciphers.3 = ECDH-ECDSA-AES256-GCM-SHA384
ssl_options.ciphers.4 = ECDH-RSA-AES256-GCM-SHA384
ssl_options.ciphers.5 = DHE-RSA-AES256-GCM-SHA384
ssl_options.ciphers.6 = DHE-DSS-AES256-GCM-SHA384
ssl_options.ciphers.7 = ECDHE-ECDSA-AES128-GCM-SHA256
ssl_options.ciphers.8 = ECDHE-RSA-AES128-GCM-SHA256
ssl_options.ciphers.9 = ECDH-ECDSA-AES128-GCM-SHA256
ssl_options.ciphers.10 = ECDH-RSA-AES128-GCM-SHA256
ssl_options.ciphers.11 = DHE-RSA-AES128-GCM-SHA256
ssl_options.ciphers.12 = DHE-DSS-AES128-GCM-SHA256
# these MUST be disabled if TLSv1.3 is used
ssl_options.honor_cipher_order = true
ssl_options.honor_ecc_order = true
在 经典配置格式 中:
%% list allowed ciphers
[
{ssl, [{versions, ['tlsv1.2', 'tlsv1.1']}]},
{rabbit, [
{ssl_listeners, [5671]},
{ssl_options, [{cacertfile,"/path/to/ca_certificate.pem"},
{certfile, "/path/to/server_certificate.pem"},
{keyfile, "/path/to/server_key.pem"},
{versions, ['tlsv1.2', 'tlsv1.1']},
%% This list is just an example!
%% Not all cipher suites are available on all machines.
%% Cipher suite order is important: preferred suites
%% should be listed first.
%% Different suites have different security and CPU load characteristics.
{ciphers, [
"ECDHE-ECDSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES256-GCM-SHA384",
"ECDH-ECDSA-AES256-GCM-SHA384",
"ECDH-RSA-AES256-GCM-SHA384",
"DHE-RSA-AES256-GCM-SHA384",
"DHE-DSS-AES256-GCM-SHA384",
"ECDHE-ECDSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDH-ECDSA-AES128-GCM-SHA256",
"ECDH-RSA-AES128-GCM-SHA256",
"DHE-RSA-AES128-GCM-SHA256",
"DHE-DSS-AES128-GCM-SHA256"
]}
]}
]}
].
密码套件顺序
在 TLS 连接协商期间,服务器和客户端会协商要使用的密码套件。可以强制服务器的 TLS 实现来指示其偏好(密码套件顺序),以避免恶意客户端故意协商弱密码套件,为发动攻击做准备。为此,请将 honor_cipher_order 和 honor_ecc_order 配置为 true。
listeners.ssl.1 = 5671
ssl_options.cacertfile = /path/to/ca_certificate.pem
ssl_options.certfile = /path/to/server_certificate.pem
ssl_options.keyfile = /path/to/server_key.pem
ssl_options.versions.1 = tlsv1.2
ssl_options.honor_cipher_order = true
ssl_options.honor_ecc_order = true
或者,在经典配置格式中:
%% Enforce server-provided cipher suite order (preference)
[
{ssl, [{versions, ['tlsv1.2', 'tlsv1.1']}]},
{rabbit, [
{ssl_listeners, [5671]},
{ssl_options, [{cacertfile, "/path/to/ca_certificate.pem"},
{certfile, "/path/to/server_certificate.pem"},
{keyfile, "/path/to/server_key.pem"},
{versions, ['tlsv1.2', 'tlsv1.1']},
%% ...
{honor_cipher_order, true},
{honor_ecc_order, true},
]}
]}
].
推荐的密码套件(TLS 1.3)
请参阅 TLS 1.3 和 评估 TLS 设置安全性 部分。
推荐的密码套件(TLS 1.2)
请参阅 Mozilla 的 服务器端 TLS 安全 建议和 评估 TLS 设置安全性。
已知的 TLS 漏洞及其缓解措施
ROBOT
ROBOT 攻击会影响依赖 RSA 密码套件且运行在 19.3.6.4 和 20.1.7 之前的 Erlang/OTP 版本的 RabbitMQ 安装。要缓解此问题,请升级 Erlang/OTP 到已修补的版本,并考虑限制支持的密码套件列表。
POODLE
POODLE 是一种已知的 SSL/TLS 攻击,最初会破坏 SSLv3。从 3.4.0 版本开始,RabbitMQ 服务器拒绝接受 SSLv3 连接。2014 年 12 月,一种影响 TLSv1.0 的 POODLE 攻击的修改版本被公布。因此,建议运行 Erlang 18.0 或更高版本,该版本消除了 TLS 1.0 实现中对 POODLE 的漏洞,或者禁用 TLSv1.0 支持。
BEAST
已知的BEAST 攻击会影响 TLSv1.0。为缓解此问题,请禁用 TLSv1.0 支持。
评估 TLS 设置的安全性
由于 TLS 有许多可配置参数,其中一些参数由于历史原因具有次优的默认值,因此 TLS 设置安全性评估是一种推荐的做法。存在多种工具可以对启用 TLS 的服务器端点执行各种测试,例如,测试其是否容易受到 POODLE、BEAST 等已知攻击。
testssl.sh
testssl.sh 是一个成熟且广泛的 TLS 端点测试工具。它可以与不提供 HTTPS 的协议端点一起使用。
该工具执行许多测试(例如,在某些机器上仅运行超过 350 项密码套件测试),但通过所有测试对于每个环境可能都有意义,也可能没有意义。例如,许多生产部署不使用 CRL(证书吊销列表);大多数开发环境使用自签名证书,并且不必担心已启用密码套件的最优集合;等等。
要运行 testssl.sh,请以 {hostname}:5671 的形式提供要测试的端点。
./testssl.sh localhost:5671
TLS 1.3 设置的评估
以下示例配置接受 TLSv1.3 连接,并在 Erlang 26 上通过关键的 testssl.sh 测试。
listeners.ssl.1 = 5671
ssl_options.cacertfile = /path/to/ca_certificate.pem
ssl_options.certfile = /path/to/server_certificate.pem
ssl_options.keyfile = /path/to/server_key.pem
ssl_options.versions.1 = tlsv1.3
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = true
ssl_options.ciphers.1 = TLS_AES_256_GCM_SHA384
ssl_options.ciphers.2 = TLS_AES_128_GCM_SHA256
ssl_options.ciphers.3 = TLS_CHACHA20_POLY1305_SHA256
ssl_options.ciphers.4 = TLS_AES_128_CCM_SHA256
ssl_options.ciphers.5 = TLS_AES_128_CCM_8_SHA256
ssl_options.honor_cipher_order = true
ssl_options.honor_ecc_order = true
此仅 TLSv1.3 的设置报告为未受漏洞影响。
Using "OpenSSL 3.3.1 4 Jun 2024 (Library: OpenSSL 3.3.1 4 Jun 2024)" [~94 ciphers]
on [redacted]:/opt/homebrew/bin/openssl
(built: "Jun 4 12:53:04 2024", platform: "darwin64-arm64-cc")
Start 2024-08-08 11:56:02 -->> 127.0.0.1:5671 (localhost) <<--
A record via: /etc/hosts
rDNS (127.0.0.1): localhost.
Service detected: Couldn't determine what's running on port 5671, assuming no HTTP service => skipping all HTTP checks
Testing protocols via sockets except NPN+ALPN
SSLv2 not offered (OK)
SSLv3 not offered (OK)
TLS 1 not offered
TLS 1.1 not offered
TLS 1.2 not offered
TLS 1.3 offered (OK): final
NPN/SPDY not offered
ALPN/HTTP2 not offered
Testing cipher categories
NULL ciphers (no encryption) not offered (OK)
Anonymous NULL Ciphers (no authentication) not offered (OK)
Export ciphers (w/o ADH+NULL) not offered (OK)
LOW: 64 Bit + DES, RC[2,4], MD5 (w/o export) not offered (OK)
Triple DES Ciphers / IDEA not offered
Obsoleted CBC ciphers (AES, ARIA etc.) not offered
Strong encryption (AEAD ciphers) with no FS not offered
Forward Secrecy strong encryption (AEAD ciphers) offered (OK)
Testing server's cipher preferences
Hexcode Cipher Suite Name (OpenSSL) KeyExch. Encryption Bits Cipher Suite Name (IANA/RFC)
-----------------------------------------------------------------------------------------------------------------------------
SSLv2
-
SSLv3
-
TLSv1
-
TLSv1.1
-
TLSv1.2
-
TLSv1.3 (listed by strength)
x1302 TLS_AES_256_GCM_SHA384 ECDH 253 AESGCM 256 TLS_AES_256_GCM_SHA384
x1303 TLS_CHACHA20_POLY1305_SHA256 ECDH 253 ChaCha20 256 TLS_CHACHA20_POLY1305_SHA256
x1301 TLS_AES_128_GCM_SHA256 ECDH 253 AESGCM 128 TLS_AES_128_GCM_SHA256
x1304 TLS_AES_128_CCM_SHA256 ECDH 253 AESCCM 128 TLS_AES_128_CCM_SHA256
x1305 TLS_AES_128_CCM_8_SHA256 ECDH 253 AESCCM8 128 TLS_AES_128_CCM_8_SHA256
Has server cipher order? no (TLS 1.3 only)
(limited sense as client will pick)
Testing robust forward secrecy (FS) -- omitting Null Authentication/Encryption, 3DES, RC4
FS is offered (OK) TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256 TLS_AES_128_CCM_SHA256 TLS_AES_128_CCM_8_SHA256
Elliptic curves offered: prime256v1 secp384r1 X25519 X448
TLS 1.3 sig_algs offered: RSA-PSS-RSAE+SHA256 RSA-PSS-RSAE+SHA384 RSA-PSS-RSAE+SHA512
Testing server defaults (Server Hello)
TLS extensions (standard) "key share/#51" "supported versions/#43" "signature algorithms/#13" "certificate authorities/#47"
Session Ticket RFC 5077 hint no -- no lifetime advertised
SSL Session ID support no
Session Resumption Tickets no, ID: no
TLS clock skew Random values, no fingerprinting possible
Certificate Compression none
Client Authentication optional
CA List for Client Auth L=$$$$,CN=TLSGenSelfSignedtRootCA 2022-03-22T11:27:45.010198
Signature Algorithm SHA256 with RSA
Server key size RSA 2048 bits (exponent is 65537)
Server key usage Digital Signature, Key Encipherment
Server extended key usage TLS Web Server Authentication
Serial 01 (OK: length 1)
Fingerprints SHA1 A4346FA6FDC61FCD4C0199EA14B8AE0F5D5121B1
SHA256 C81025DA6F9BB646239659420D58E73F62CEB7D2AD5AC13FF12A9DE057394953
Common Name (CN) [redacted]
subjectAltName (SAN) [redacted] localhost
Trust (hostname) Ok via SAN (same w/o SNI)
Chain of trust NOT ok (self signed CA in chain)
EV cert (experimental) no
Certificate Validity (UTC) 2779 >= 60 days (2022-03-22 07:27 --> 2032-03-19 07:27)
>= 10 years is way too long
ETS/"eTLS", visibility info not present
Certificate Revocation List --
OCSP URI --
NOT ok -- neither CRL nor OCSP URI provided
OCSP stapling not offered
OCSP must staple extension --
DNS CAA RR (experimental) not offered
Certificate Transparency N/A
Certificates provided 2
Issuer TLSGenSelfSignedtRootCA 2022-03-22T11:27:45.010198
Intermediate cert validity #1: ok > 40 days (2032-03-19 07:27). $$$$ <-- $$$$
Intermediate Bad OCSP (exp.) Ok
Testing vulnerabilities
Heartbleed (CVE-2014-0160) not vulnerable (OK), no heartbeat extension
CCS (CVE-2014-0224) not vulnerable (OK)
Ticketbleed (CVE-2016-9244), experiment. (applicable only for HTTPS)
ROBOT Server does not support any cipher suites that use RSA key transport
Secure Renegotiation (RFC 5746) not vulnerable (OK)
Secure Client-Initiated Renegotiation not vulnerable (OK)
CRIME, TLS (CVE-2012-4929) not vulnerable (OK)
POODLE, SSL (CVE-2014-3566) not vulnerable (OK), no SSLv3 support
TLS_FALLBACK_SCSV (RFC 7507) No fallback possible (OK), TLS 1.3 is the only protocol
SWEET32 (CVE-2016-2183, CVE-2016-6329) not vulnerable (OK)
FREAK (CVE-2015-0204) not vulnerable (OK)
DROWN (CVE-2016-0800, CVE-2016-0703) not vulnerable on this host and port (OK)
make sure you don't use this certificate elsewhere with SSLv2 enabled services, see
https://search.censys.io/search?resource=hosts&virtual_hosts=INCLUDE&q=C81025DA6F9BB646239659420D58E73F62CEB7D2AD5AC13FF12A9DE057394953
LOGJAM (CVE-2015-4000), experimental not vulnerable (OK): no DH EXPORT ciphers, no DH key detected with <= TLS 1.2
BEAST (CVE-2011-3389) not vulnerable (OK), no SSL3 or TLS1
LUCKY13 (CVE-2013-0169), experimental not vulnerable (OK)
Winshock (CVE-2014-6321), experimental not vulnerable (OK)
RC4 (CVE-2013-2566, CVE-2015-2808) not vulnerable (OK)
Could not determine the protocol, only simulating generic clients.
Running client simulations via sockets
Browser Protocol Cipher Suite Name (OpenSSL) Forward Secrecy
------------------------------------------------------------------------------------------------
Android 8.1 (native) No connection
Android 9.0 (native) TLSv1.3 TLS_AES_128_GCM_SHA256 253 bit ECDH (X25519)
Android 10.0 (native) TLSv1.3 TLS_AES_128_GCM_SHA256 253 bit ECDH (X25519)
Android 11 (native) TLSv1.3 TLS_AES_128_GCM_SHA256 253 bit ECDH (X25519)
Android 12 (native) TLSv1.3 TLS_AES_128_GCM_SHA256 253 bit ECDH (X25519)
Java 7u25 No connection
Java 8u161 No connection
Java 11.0.2 (OpenJDK) TLSv1.3 TLS_AES_128_GCM_SHA256 256 bit ECDH (P-256)
Java 17.0.3 (OpenJDK) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519)
go 1.17.8 TLSv1.3 TLS_AES_128_GCM_SHA256 253 bit ECDH (X25519)
LibreSSL 2.8.3 (Apple) No connection
OpenSSL 1.0.2e No connection
OpenSSL 1.1.0l (Debian) No connection
OpenSSL 1.1.1d (Debian) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519)
OpenSSL 3.0.3 (git) TLSv1.3 TLS_AES_256_GCM_SHA384 253 bit ECDH (X25519)
具有受限密码套件的 TLS 1.2 设置的评估
以下示例配置接受 TLSv1.2 连接,并在 Erlang 26.2 上通过关键的 testssl.sh 测试。
listeners.ssl.default = 5671
ssl_options.cacertfile = /path/to/ca_certificate.pem
ssl_options.certfile = /path/to/server_certificate.pem
ssl_options.keyfile = /path/to/server_key.pem
ssl_options.versions.1 = tlsv1.2
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = false
ssl_options.honor_cipher_order = true
ssl_options.honor_ecc_order = true
# These are highly recommended for TLSv1.2 but cannot be used
# with TLSv1.3. If TLSv1.3 is enabled, these lines MUST be removed.
ssl_options.client_renegotiation = false
ssl_options.secure_renegotiate = true
ssl_options.ciphers.1 = ECDHE-ECDSA-AES256-GCM-SHA384
ssl_options.ciphers.2 = ECDHE-RSA-AES256-GCM-SHA384
ssl_options.ciphers.3 = ECDH-ECDSA-AES256-GCM-SHA384
ssl_options.ciphers.4 = ECDH-RSA-AES256-GCM-SHA384
ssl_options.ciphers.5 = DHE-RSA-AES256-GCM-SHA384
ssl_options.ciphers.6 = DHE-DSS-AES256-GCM-SHA384
ssl_options.ciphers.7 = ECDHE-ECDSA-AES128-GCM-SHA256
ssl_options.ciphers.8 = ECDHE-RSA-AES128-GCM-SHA256
ssl_options.ciphers.9 = ECDH-ECDSA-AES128-GCM-SHA256
ssl_options.ciphers.10 = ECDH-RSA-AES128-GCM-SHA256
ssl_options.ciphers.11 = DHE-RSA-AES128-GCM-SHA256
ssl_options.ciphers.12 = DHE-DSS-AES128-GCM-SHA256
此启用 TLSv1.2 的设置报告为未受一组已知的高危漏洞影响。
Using "OpenSSL 3.3.1 4 Jun 2024 (Library: OpenSSL 3.3.1 4 Jun 2024)" [~94 ciphers]
on [redacted]:/opt/homebrew/bin/openssl
(built: "Jun 4 12:53:04 2024", platform: "darwin64-arm64-cc")
Start 2024-08-08 13:42:36 -->> 127.0.0.1:5671 (localhost) <<--
A record via: /etc/hosts
rDNS (127.0.0.1): localhost.
Service detected: certificate-based authentication without providing client certificate and private key => skipping all HTTP checks
Testing protocols via sockets except NPN+ALPN
SSLv2 not offered (OK)
SSLv3 not offered (OK)
TLS 1 not offered
TLS 1.1 not offered
TLS 1.2 offered (OK)
TLS 1.3 not offered and downgraded to a weaker protocol
NPN/SPDY not offered
ALPN/HTTP2 not offered
Testing cipher categories
NULL ciphers (no encryption) not offered (OK)
Anonymous NULL Ciphers (no authentication) not offered (OK)
Export ciphers (w/o ADH+NULL) not offered (OK)
LOW: 64 Bit + DES, RC[2,4], MD5 (w/o export) not offered (OK)
Triple DES Ciphers / IDEA not offered
Obsoleted CBC ciphers (AES, ARIA etc.) not offered
Strong encryption (AEAD ciphers) with no FS not offered
Forward Secrecy strong encryption (AEAD ciphers) offered (OK)
Testing server's cipher preferences
Hexcode Cipher Suite Name (OpenSSL) KeyExch. Encryption Bits Cipher Suite Name (IANA/RFC)
-----------------------------------------------------------------------------------------------------------------------------
SSLv2
-
SSLv3
-
TLSv1
-
TLSv1.1
-
TLSv1.2 (server order)
xc030 ECDHE-RSA-AES256-GCM-SHA384 ECDH 253 AESGCM 256 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
x9f DHE-RSA-AES256-GCM-SHA384 DH 2048 AESGCM 256 TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
xc02f ECDHE-RSA-AES128-GCM-SHA256 ECDH 253 AESGCM 128 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
x9e DHE-RSA-AES128-GCM-SHA256 DH 2048 AESGCM 128 TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
TLSv1.3
-
Has server cipher order? yes (OK)
Testing robust forward secrecy (FS) -- omitting Null Authentication/Encryption, 3DES, RC4
FS is offered (OK) ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-GCM-SHA256
Elliptic curves offered: prime256v1 secp384r1 secp521r1 brainpoolP256r1 brainpoolP384r1 brainpoolP512r1 X25519 X448
DH group offered: RFC3526/Oakley Group 14 (2048 bits)
TLS 1.2 sig_algs offered: RSA+SHA256 RSA+SHA384 RSA+SHA512 RSA-PSS-RSAE+SHA256
Testing server defaults (Server Hello)
TLS extensions (standard) "renegotiation info/#65281" "EC point formats/#11" "max fragment length/#1"
Session Ticket RFC 5077 hint no -- no lifetime advertised
SSL Session ID support yes
Session Resumption Tickets no, Client Auth: ID resumption test not supported
TLS clock skew -1 sec from localtime
Client Authentication required
CA List for Client Auth L=$$$$,CN=TLSGenSelfSignedtRootCA 2022-03-22T11:27:45.010198
Signature Algorithm SHA256 with RSA
Server key size RSA 2048 bits (exponent is 65537)
Server key usage Digital Signature, Key Encipherment
Server extended key usage TLS Web Server Authentication
Serial 01 (OK: length 1)
Fingerprints SHA1 A4346FA6FDC61FCD4C0199EA14B8AE0F5D5121B1
SHA256 C81025DA6F9BB646239659420D58E73F62CEB7D2AD5AC13FF12A9DE057394953
Common Name (CN) [redacted]
subjectAltName (SAN) [redacted] localhost
Trust (hostname) Ok via SAN (same w/o SNI)
Chain of trust NOT ok (self signed CA in chain)
EV cert (experimental) no
Certificate Validity (UTC) 2779 >= 60 days (2022-03-22 07:27 --> 2032-03-19 07:27)
>= 10 years is way too long
ETS/"eTLS", visibility info not present
Certificate Revocation List --
OCSP URI --
NOT ok -- neither CRL nor OCSP URI provided
OCSP stapling not offered
OCSP must staple extension --
DNS CAA RR (experimental) not offered
Certificate Transparency --
Certificates provided 2
Issuer TLSGenSelfSignedtRootCA 2022-03-22T11:27:45.010198
Intermediate cert validity #1: ok > 40 days (2032-03-19 07:27). $$$$ <-- $$$$
Intermediate Bad OCSP (exp.) Ok
Testing vulnerabilities
Heartbleed (CVE-2014-0160) not vulnerable (OK), no heartbeat extension
CCS (CVE-2014-0224) not vulnerable (OK)
Ticketbleed (CVE-2016-9244), experiment. not vulnerable (OK), no session ticket extension
ROBOT Server does not support any cipher suites that use RSA key transport
Secure Renegotiation (RFC 5746) supported (OK)
Secure Client-Initiated Renegotiation not having provided client certificate and private key file, the client x509-based authentication prevents this from being tested
CRIME, TLS (CVE-2012-4929) not vulnerable (OK)
BREACH (CVE-2013-3587) not having provided client certificate and private key file, the client x509-based authentication prevents this from being tested
POODLE, SSL (CVE-2014-3566) not vulnerable (OK), no SSLv3 support
TLS_FALLBACK_SCSV (RFC 7507) No fallback possible (OK), no protocol below TLS 1.2 offered
SWEET32 (CVE-2016-2183, CVE-2016-6329) not vulnerable (OK)
FREAK (CVE-2015-0204) not vulnerable (OK)
DROWN (CVE-2016-0800, CVE-2016-0703) not vulnerable on this host and port (OK)
make sure you don't use this certificate elsewhere with SSLv2 enabled services, see
https://search.censys.io/search?resource=hosts&virtual_hosts=INCLUDE&q=C81025DA6F9BB646239659420D58E73F62CEB7D2AD5AC13FF12A9DE057394953
LOGJAM (CVE-2015-4000), experimental common prime with 2048 bits detected: RFC3526/Oakley Group 14 (2048 bits),
but no DH EXPORT ciphers
BEAST (CVE-2011-3389) not vulnerable (OK), no SSL3 or TLS1
LUCKY13 (CVE-2013-0169), experimental not vulnerable (OK)
Winshock (CVE-2014-6321), experimental not vulnerable (OK) - CAMELLIA or ECDHE_RSA GCM ciphers found
RC4 (CVE-2013-2566, CVE-2015-2808) no RC4 ciphers detected (OK)
Could not determine the protocol, only simulating generic clients.
Running client simulations via sockets
Browser Protocol Cipher Suite Name (OpenSSL) Forward Secrecy
------------------------------------------------------------------------------------------------
Android 8.1 (native) TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384 253 bit ECDH (X25519)
Android 9.0 (native) TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384 253 bit ECDH (X25519)
Android 10.0 (native) TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384 253 bit ECDH (X25519)
Android 11 (native) TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384 253 bit ECDH (X25519)
Android 12 (native) TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384 253 bit ECDH (X25519)
Java 7u25 No connection
Java 8u161 TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384 521 bit ECDH (P-521)
Java 11.0.2 (OpenJDK) TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384 521 bit ECDH (P-521)
Java 17.0.3 (OpenJDK) TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384 253 bit ECDH (X25519)
go 1.17.8 TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384 253 bit ECDH (X25519)
LibreSSL 2.8.3 (Apple) TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384 253 bit ECDH (X25519)
OpenSSL 1.0.2e TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384 521 bit ECDH (P-521)
OpenSSL 1.1.0l (Debian) TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384 253 bit ECDH (X25519)
OpenSSL 1.1.1d (Debian) TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384 253 bit ECDH (X25519)
OpenSSL 3.0.3 (git) TLSv1.2 ECDHE-RSA-AES256-GCM-SHA384 253 bit ECDH (X25519)
TLS 证书和私钥轮换
服务器 TLS 证书(公钥)和私钥都有到期日期,并且需要定期更换(轮换)。
更换过程包括以下步骤:
- 更换磁盘上的文件
- 清除节点上的证书和私钥存储缓存
如果没有第二步,新证书/密钥对将在一段时间后被节点使用,因为运行时环境中的 TLS 实现会清除其证书存储缓存。
更换磁盘上的证书和私钥文件
只需将服务器证书、服务器私钥以及(如果需要)证书颁发机构捆绑包文件替换为其新版本即可。
清除证书和私钥存储缓存
- bash
- PowerShell
- cmd
rabbitmqctl eval -n [target-node@hostname] 'ssl:clear_pem_cache().'
rabbitmqctl.bat eval -n [target-node@hostname] 'ssl:clear_pem_cache().'
rabbitmqctl.bat eval -n [target-node@hostname] "ssl:clear_pem_cache()."
信任存储插件
rabbitmq_trust_store 是一个插件,针对对等验证使用广泛且受信任证书列表相当动态的环境。也就是说,受信任的叶客户端证书更改相当频繁,因此将其列入白名单比使用证书吊销或中间证书更有意义。
信任存储插件支持两种受信任叶客户端证书的来源:
- 本地证书目录
- 一组遵循特定约定的 HTTPS 端点
请参阅插件的文档指南,了解如何设置这两种选项。
在 Erlang 客户端中使用 TLS
在 RabbitMQ Erlang 客户端中启用 TLS 与配置其他与网络相关的设置类似。#amqp_params_network 记录提供了一个 ssl_options 字段,用于所有标准的 Erlang TLS 选项。
Erlang TLS 选项
必须提供三个重要选项:
cacertfile选项指定我们要隐式信任的根证书颁发机构的证书。certfile是客户端自己的 PEM 格式证书。keyfile是客户端的 PEM 格式私钥文件。
server_name_indication - 将此选项设置为要连接到的服务器的主机名,以启用对服务器提供的证书的“服务器名称指示”验证。这可确保在 TLS 连接建立期间验证服务器证书的 CN= 值。您可以通过将 server_name_indication 设置为不同的主机名或特殊值 disable 来禁用此验证。请注意,默认情况下,SNI **未**启用。此默认值将在未来的 RabbitMQ Erlang 客户端版本中更改。
verify - 将此选项设置为 verify_peer 以启用 X509 证书链验证。depth 选项配置证书验证深度。请注意,默认情况下,verify 设置为 verify_none,禁用证书链验证。此默认值将在未来的 RabbitMQ Erlang 客户端版本中更改。
代码示例
SslOpts = [{cacertfile, "/path/to/ca_certificate.pem"},
{certfile, "/path/to/client/certificate.pem"},
{keyfile, "/path/to/client/private_key.pem"},
%% only necessary with intermediate CAs
%% {depth, 2},
%% Note: it is recommended to set 'verify' to
%% to 'verify_peer' to ensure that X509
%% certificate chain validation is enabled
%%
%% Do not set 'verify' or set it to verify_none
%% if x509 certificate chain validation is
%% not desired
{verify, verify_peer},
%% If Server Name Indication validation is desired,
%% set the following option to the host name to which
%% the connection is made. If necessary, this option
%% may be set to another host name to match the server
%% certificate's CN= value.
%% Do not set this option or set it to the atom 'disable'
%% to disable SNI validation
{server_name_indication, "my.rmq-server.net"}],
Params = #amqp_params_network{host = "my.rmq-server.net",
port = 5671,
ssl_options = SslOpts}
{ok, Conn} = amqp_connection:start(Params),
您现在可以像使用普通连接一样使用 Conn。
手动生成 CA、证书和私钥
本指南的这一部分解释了如何生成证书颁发机构 (CA) 并使用它来生成和签名两个证书/密钥对,一个用于服务器,一个用于客户端库。请注意,该过程可以使用现有工具进行自动化,这是推荐的做法。本节适用于希望加深对过程、OpenSSL 命令行工具和某些重要 OpenSSL 配置方面的理解的读者。
本指南假定您使用的是类 Unix 操作系统(Linux、MacOS、BSD 变体等)以及 PATH 中可用的最新版本 OpenSSL。
首先,让我们为测试证书颁发机构创建一个目录。
mkdir testca
cd testca
mkdir certs private
chmod 700 private
echo 01 > serial
touch index.txt
现在,在新建的 testca 目录中添加以下 OpenSSL 配置文件 openssl.cnf。
[ ca ]
default_ca = testca
[ testca ]
dir = .
certificate = $dir/ca_certificate.pem
database = $dir/index.txt
new_certs_dir = $dir/certs
private_key = $dir/private/ca_private_key.pem
serial = $dir/serial
default_crl_days = 7
default_days = 365
default_md = sha256
policy = testca_policy
x509_extensions = certificate_extensions
[ testca_policy ]
commonName = supplied
stateOrProvinceName = optional
countryName = optional
emailAddress = optional
organizationName = optional
organizationalUnitName = optional
domainComponent = optional
[ certificate_extensions ]
basicConstraints = CA:false
[ req ]
default_bits = 2048
default_keyfile = ./private/ca_private_key.pem
default_md = sha256
prompt = yes
distinguished_name = root_ca_distinguished_name
x509_extensions = root_ca_extensions
[ root_ca_distinguished_name ]
commonName = hostname
[ root_ca_extensions ]
basicConstraints = CA:true
keyUsage = keyCertSign, cRLSign
[ client_ca_extensions ]
basicConstraints = CA:false
keyUsage = digitalSignature,keyEncipherment
extendedKeyUsage = 1.3.6.1.5.5.7.3.2
[ server_ca_extensions ]
basicConstraints = CA:false
keyUsage = digitalSignature,keyEncipherment
extendedKeyUsage = 1.3.6.1.5.5.7.3.1
接下来,我们需要生成我们的测试证书颁发机构将使用的密钥和证书。仍在 testca 目录中。
openssl req -x509 -config openssl.cnf -newkey rsa:2048 -days 365 \
-out ca_certificate.pem -outform PEM -subj /CN=MyTestCA/ -nodes
openssl x509 -in ca_certificate.pem -out ca_certificate.cer -outform DER
这就是生成测试证书颁发机构所需的一切。根证书位于 ca_certificate.pem 中,也位于 testca/ca_certificate.cer 中。这两个文件包含相同的信息,但格式不同:PEM 和 DER。大多数软件使用前者,但某些工具需要后者。
设置好证书颁发机构后,我们现在需要为客户端和服务器生成私钥和证书。RabbitMQ 代理使用 PEM 格式的证书和私钥。某些客户端库使用 PEM 格式,而其他库则需要转换为其他格式(例如 PKCS#12)。
Java 和 .NET 客户端使用一种称为 PKCS#12 的证书格式以及自定义证书存储。证书存储同时包含客户端的证书和密钥。PKCS 存储通常受密码保护,因此必须提供密码。
创建服务器和客户端证书的过程非常相似。首先是服务器。
cd ..
ls
# => testca
mkdir server
cd server
openssl genrsa -out private_key.pem 2048
openssl req -new -key private_key.pem -out req.pem -outform PEM \
-subj /CN=$(hostname)/O=server/ -nodes
cd ../testca
openssl ca -config openssl.cnf -in ../server/req.pem -out \
../server/server_certificate.pem -notext -batch -extensions server_ca_extensions
cd ../server
openssl pkcs12 -export -out server_certificate.p12 -in server_certificate.pem -inkey private_key.pem \
-passout pass:MySecretPassword
现在是客户端。
cd ..
ls
# => server testca
mkdir client
cd client
openssl genrsa -out private_key.pem 2048
openssl req -new -key private_key.pem -out req.pem -outform PEM \
-subj /CN=$(hostname)/O=client/ -nodes
cd ../testca
openssl ca -config openssl.cnf -in ../client/req.pem -out \
../client/client_certificate.pem -notext -batch -extensions client_ca_extensions
cd ../client
openssl pkcs12 -export -out client_certificate.p12 -in client_certificate.pem -inkey private_key.pem \
-passout pass:MySecretPassword
上面的两个示例生成 2048 位大小的私钥。通过为 openssl genrsa 提供不同的值,可以生成更长(因此更安全但生成速度更慢)的密钥,例如:
openssl genrsa -out private_key.pem 4096
另一个选择是使用椭圆曲线密码学生成密钥。使用 openssl ecparam 代替 openssl genrsa,如下所示:
openssl ecparam -out private_key.pem -genkey -name prime256v1
上面示例中的 prime256v1 是椭圆曲线的名称。不同版本的 OpenSSL 将具有不同的可用曲线集,使用 openssl ecparam -list_curves 列出它们。