跳至主要内容
版本:4.0

TLS 支持

目录

RabbitMQ 内置支持 TLS。这包括客户端连接和适用的流行插件,例如 联邦链接。也可以使用 TLS 来 加密集群中的节点间连接

本指南涵盖了与 RabbitMQ 中 TLS 相关的各种主题,重点关注客户端连接。

等等。

在哪里学习 TLS 基础知识

本指南尝试 解释 TLS 的基础知识,但它并非 TLS、加密、公钥基础设施 和相关主题的入门指南,因此这些概念的介绍非常简略。

网络上还有许多面向初学者的 TLS 入门指南。

TLS 和消息协议

可以为 RabbitMQ 支持的所有协议启用 TLS。但是,本指南主要侧重于 AMQP 1.0 和 AMQP 0-9-1 侦听器。请参阅 MQTTSTOMP 及其 各自的 WebSocket 传输 指南,以获取这些协议的 TLS 配置示例。

HTTP API节点间和 CLI 工具流量 也可以配置为使用 TLS (HTTPS)。

要使用 RabbitMQ 集群运算符在 Kubernetes 上配置 TLS,请参阅 配置 TLS 的指南。

有关常见 TLS 故障排除技术的概述,请参阅 TLS 相关问题的故障排除网络故障排除

RabbitMQ 客户端连接的 TLS 常用方法

对于客户端连接,有两种常用方法。

  • 配置 RabbitMQ 以处理 TLS 连接。
  • 使用代理或负载均衡器(例如 HAproxy)执行 TLS 终止 客户端连接,并使用纯 TCP 连接到 RabbitMQ 节点。

这两种方法都是有效的,并且各有优缺点。本指南将重点介绍第一种方法。本指南的某些部分对于选择第二种方法的环境仍然适用。

Erlang/OTP 对 TLS 支持的要求

为了支持 TLS 连接,RabbitMQ 需要在 Erlang/OTP 安装中提供 TLS 和与加密相关的模块。建议与 TLS 一起使用的 Erlang/OTP 版本是最新的 受支持的 Erlang 版本。较早的版本,即使它们受支持,也可能适用于大多数证书,但存在已知的限制(请参见下文)。

必须安装并启用 Erlang 的 asn1cryptopublic_keyssl 库(应用程序)。在 Debian 和 Ubuntu 上,这分别由 erlang-asn1erlang-cryptoerlang-public-keyerlang-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 及其最佳使用方法,我们建议使用其他资源,例如 使用 OpenSSL 进行网络安全

TLS 的两个主要目标

TLS 有两个主要目的:加密连接流量并提供一种对等方身份验证(验证)的方式,以减轻 中间人攻击。两者都是使用一组称为 公钥基础设施 (PKI) 的角色、策略和程序来实现的。

PKI 基于数字身份的概念,这些身份可以通过加密方式(数学方式)进行验证。这些身份称为证书,更准确地说是证书/密钥对。每个启用 TLS 的服务器通常都有自己的证书/密钥对,它用于计算特定于连接的密钥,该密钥将用于加密在连接上发送的流量。

此外,如果被要求,它可以将其证书(公钥)呈现给连接对等方。客户端可能有也可能没有自己的证书。在消息传递和 RabbitMQ 等工具的上下文中,客户端也使用证书/密钥对非常常见,以便服务器可以验证其身份。

证书、私钥和证书颁发机构

证书/密钥对由 OpenSSL 等工具生成,并由称为证书颁发机构 (CA) 的实体签名。CA 发布用户(应用程序或其他 CA)使用的证书。当证书由 CA 签名时,它们会形成一个信任链。此类链可能包含多个 CA,但最终会签名应用程序使用的证书/密钥对(叶子最终用户证书)。CA 证书链通常在一个文件中一起分发。此类文件称为CA 捆绑包

以下是一个包含一个根 CA 和一个叶子(服务器或客户端)证书的最基本链的示例。

Root CA and leaf certificates

包含中间证书的链可能如下所示。

Root CA, intermediate and leaf certificates

有一些组织签发和发布证书/密钥对。其中大部分是广受信任的 CA,并对其服务收取费用。

启用 TLS 的 RabbitMQ 节点必须在文件中(CA 捆绑包)、证书(公钥)文件和私钥文件中有一组它认为受信任的证书颁发机构证书。这些文件将从本地文件系统读取。RabbitMQ 节点进程的有效用户必须能够读取它们。

启用 TLS 的连接的两端可以选择性地验证连接的另一端。在此过程中,它们会尝试在对等方提供的证书列表中找到受信任的证书颁发机构。当双方都执行此验证过程时,这被称为双向 TLS 身份验证mTLS。有关此内容的更多信息,请参阅对等方验证部分。

本指南假设用户可以访问证书颁发机构和两个证书/密钥对,这些证书/密钥对以多种格式提供,供不同的客户端库使用。最好使用现有工具来完成此操作,但对于那些希望更熟悉此主题和 OpenSSL 命令行工具的用户,可以参考单独的部分

在生产环境中,证书由商业证书颁发机构或内部安全团队颁发的证书颁发机构生成。在这些情况下,证书颁发机构捆绑文件很可能包含多个证书。只要满足相同的文件和路径要求基本要求,这不会改变在配置 RabbitMQ 时捆绑文件的使用方式。换句话说,无论证书是自签名的还是由受信任的 CA颁发的,它们的配置方式都相同。对等方验证部分详细介绍了这一点。

生成 CA、证书和密钥的简短路径

本指南假设用户可以访问 CA 证书捆绑文件和两个证书/密钥对。证书/密钥对由 RabbitMQ 和连接到服务器上的 TLS 启用端口的客户端使用。生成证书颁发机构和两个密钥对的过程相当繁琐,并且容易出错。在 MacOS 或 Linux 上生成所有这些内容的更简单方法是使用tls-gen:它需要Python 3.5+makeopenssl位于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 配置文件生成的证书链如下所示

Root CA and leaf certificates

在 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.verify是否应启用对等方验证
ssl_options.fail_if_no_peer_cert

设置为true时,如果客户端未能提供证书,则将拒绝 TLS 连接

这些选项在配置文件中提供。下面是一个配置文件示例,它将在此主机名上的所有接口上的端口 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

此配置还将执行对等方证书链验证,因此没有证书的客户端将被拒绝。

可以完全禁用常规(非 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 证书都由下一个证书签名。例如,如果证书 B 由 A 签名,而 C 由 B 签名,则链为A、B、C(此处使用逗号是为了清晰起见)。“最顶层”(第一个或唯一)CA 通常被称为链的根 CA。根 CA 可以由知名证书颁发机构(商业供应商)或任何其他方(自签名)颁发。

以下是一个包含一个根 CA 和一个叶子(服务器或客户端)证书的最基本链的示例。

Root CA and leaf certificates

包含中间证书的链可能如下所示。

Root CA, intermediate and leaf certificates

在对等方验证期间,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 实现而异。例如,除非显式添加到信任存储区,否则 CA 证书捆绑程序中的证书在 Python 中将不被视为受信任。

RabbitMQ 依赖于 Erlang 的 TLS 实现。它假设**所有受信任的 CA 证书都已添加到服务器证书捆绑包中**。

在执行对等方验证时,RabbitMQ 仅将根证书(列表中的第一个证书)视为受信任。任何中间证书都将被忽略。如果希望将中间证书也视为受信任,则必须将其添加到受信任证书列表:证书捆绑包。

虽然可以将最终的(“叶子”)证书(例如服务器和客户端使用的证书)放置到受信任的证书目录中,但更常见的做法是将 CA 证书添加到受信任的证书列表中。

将多个证书追加到另一个证书并将其用于单个证书颁发机构捆绑文件的最常用方法是简单地将它们连接起来。

cat rootca/ca_certificate.pem otherca/ca_certificate.pem > all_cacerts.pem

启用对等方验证

在服务器端,对等方验证主要使用两个配置选项进行控制:ssl_options.verifyssl_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 插件(如联合铲运机)与 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(&quot;localhost&quot;);
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(&quot;rabbitmq-java-test&quot;, false, true, true, null);
channel.basicPublish(&quot;&quot;, &quot;rabbitmq-java-test&quot;, null, &quot;Hello, World&quot;.getBytes());

GetResponse chResponse = channel.basicGet(&quot;rabbitmq-java-test&quot;, false);
if (chResponse == null) {
System.out.println(&quot;No message retrieved&quot;);
} else {
byte[] body = chResponse.getBody();
System.out.println(&quot;Received: &quot; + new String(body));
}

channel.close();
conn.close();
}
}

这个简单的示例是一个回显客户端和服务器。它创建一个通道并发布到默认的直接交换机,然后获取已发布的内容并将其回显出来。它使用一个专用的、非持久的、自动删除的队列,该队列将在连接关闭后不久被删除。

启用对等方验证连接

为了使 Java 客户端信任服务器,必须将服务器证书添加到信任存储区,该存储区将用于实例化信任管理器。JDK 附带一个名为 keytool 的工具,用于管理证书存储区。要将证书导入存储区,请使用 keytool -import

keytool -import -alias server1 -file /path/to/server_certificate.pem -keystore /path/to/rabbitstore

上述命令将使用 JKS 格式将 server/certificate.pem 导入到 rabbitstore 文件中。证书在信任存储区中将被称为 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 = &quot;MySecretPassword&quot;.toCharArray();
KeyStore ks = KeyStore.getInstance(&quot;PKCS12&quot;);
ks.load(new FileInputStream(&quot;/path/to/client_key.p12&quot;), keyPassphrase);

KeyManagerFactory kmf = KeyManagerFactory.getInstance(&quot;SunX509&quot;);
kmf.init(ks, keyPassphrase);

char[] trustPassphrase = &quot;rabbitstore&quot;.toCharArray();
KeyStore tks = KeyStore.getInstance(&quot;JKS&quot;);
tks.load(new FileInputStream(&quot;/path/to/trustStore&quot;), trustPassphrase);

TrustManagerFactory tmf = TrustManagerFactory.getInstance(&quot;SunX509&quot;);
tmf.init(tks);

SSLContext c = SSLContext.getInstance(&quot;TLSv1.2&quot;);
c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

ConnectionFactory factory = new ConnectionFactory();
factory.setHost(&quot;localhost&quot;);
factory.setPort(5671);
factory.useSslProtocol(c);
factory.enableHostnameVerification();

Connection conn = factory.newConnection();
Channel channel = conn.createChannel();

channel.queueDeclare(&quot;rabbitmq-java-test&quot;, false, true, true, null);
channel.basicPublish(&quot;&quot;, &quot;rabbitmq-java-test&quot;, null, &quot;Hello, World&quot;.getBytes());

GetResponse chResponse = channel.basicGet(&quot;rabbitmq-java-test&quot;, false);
if (chResponse == null) {
System.out.println(&quot;No message retrieved&quot;);
} else {
byte[] body = chResponse.getBody();
System.out.println(&quot;Received: &quot; + 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 = &quot;MySecretPassword&quot;.toCharArray();
KeyStore ks = KeyStore.getInstance(&quot;PKCS12&quot;);
ks.load(new FileInputStream(&quot;/path/to/client_key.p12&quot;), keyPassphrase);

KeyManagerFactory kmf = KeyManagerFactory.getInstance(&quot;SunX509&quot;);
kmf.init(ks, passphrase);

char[] trustPassphrase = &quot;rabbitstore&quot;.toCharArray();
KeyStore tks = KeyStore.getInstance(&quot;JKS&quot;);
tks.load(new FileInputStream(&quot;/path/to/trustStore&quot;), trustPassphrase);

TrustManagerFactory tmf = TrustManagerFactory.getInstance(&quot;SunX509&quot;);
tmf.init(tks);

SSLContext c = SSLContext.getInstance(&quot;TLSv1.2&quot;);
c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

ConnectionFactory factory = new ConnectionFactory();
factory.setHost(&quot;localhost&quot;);
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 版本。这是使用 ConnectionFactory#useSslProtocol 重载来完成的,这些重载接受协议版本名称或 SSLContext

ConnectionFactory factory = new ConnectionFactory();
factory.setHost(&quot;localhost&quot;);
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 文件提供客户端证书/密钥对,所以我们只需将根证书颁发机构的证书导入到根(Windows)或信任(Mono)存储区即可。由该存储区中的任何证书签名的所有证书都会自动被信任。

与 Java 客户端(它很乐意在不执行对等方验证的情况下使用 TLS 连接)相比,.NET 客户端默认情况下需要此验证成功。要禁止验证,应用程序可以在 SslOption 中设置 System.Net.Security.SslPolicyErrors.RemoteCertificateNotAvailableSystem.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

根据以上输出,信任存储区中有一个自签名 X.509 v3 证书。唯一哈希在此存储区中唯一标识此证书。要删除此证书,请使用唯一哈希

# 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 从 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 框架或操作系统选择,具体取决于 应用程序上下文切换

如果使用 SslProtocols.None 选择合适的 TLS 版本的连接失败,客户端将尝试显式启用 TLSv1.2。这减少了在禁用自动 TLS 版本选择、不可用或无法依赖的环境中,应用程序开发人员端显式配置的需求。

现代 .NET 框架版本 默认使用 TLSv1.2

代码示例

这或多或少是 Java 客户端示例 的直接移植。它创建一个通道并发布到默认的直接交换机,然后读取已发布的内容并将其回显出来。请注意,我们使用的是 排他的、非持久的、自动删除的队列,因此我们不必担心手动清理。

using System;
using System.IO;
using System.Text;

using RabbitMQ.client;
using RabbitMQ.Util;

namespace RabbitMQ.client.Examples {
public class TestSSL {
public static 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 = cf.CreateConnection()) {
using (IModel ch = conn.CreateModel()) {
Console.WriteLine("Successfully connected and opened a channel");
ch.QueueDeclare("rabbitmq-dotnet-test", false, false, false, null);
Console.WriteLine("Successfully declared a queue");
ch.QueueDelete("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 属性结合使用,以确定远程服务器证书是否有效。

System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch 标志可以在 RabbitMQ.client.SslOption.AcceptablePolicyErrors 中用于禁用对等方验证(不建议在生产环境中使用!)。

RabbitMQ.client.SslOption.CertificateSelectionCallback 可用于提供 LocalCertificateSelectionCallback,该回调将选择用于对等方验证的本地证书。

限制服务器使用的 TLS 版本

为什么要限制 TLS 版本

TLS(前身为 SSL)随着时间的推移而发展,并且有多个版本正在使用。每个版本都建立在先前版本的缺点之上。大多数情况下,缺点会导致 已知的攻击,这些攻击会影响特定版本的 TLS(和 SSL)。禁用旧版 TLS 版本是缓解许多此类攻击的一种方法(另一种技术是 禁用受影响的密码套件)。

出于上述原因,Erlang 的最新版本系列默认仅启用最新支持的 TLS 版本,如下表所示。

Erlang 版本系列默认启用的 TLS 版本
26.xTLSv1.3 和 TLSv1.2
25.xTLSv1.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

TLSv1.3 是对 TLS 协议的重大修订。它是最新且最安全的选项。在 RabbitMQ 3.8.11 之前,TLSv1.3 支持被认为是实验性的,并且已禁用。

TLSv1.3 支持要求节点 在 Erlang 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 会限制支持的客户端平台数量。下表说明了不同 JDK 和 .NET 版本支持的 TLS 版本。

TLS 版本最低 JDK 版本最低 .NET 版本
TLS 1.3JDK 8 从 JDK8u261 开始,JDK 11+.NET 4.7支持 TLSv1.3 的 Windows 版本上
TLS 1.2JDK 7(参见 协议建议使用 JDK 8.NET 4.5
TLS 1.1JDK 7(参见 协议建议使用 JDK 8.NET 4.5

Oracle JDK 有一份关于密码学和相关标准的 公开路线图,概述了某些密码套件或 TLS 版本何时会被弃用或移除。

公钥使用选项

公钥(证书)包含许多字段,描述了密钥的预期使用场景。这些字段限制了各种工具如何使用密钥。例如,公钥可用于验证证书签名(充当 证书颁发机构 密钥)。

这些字段还会影响 RabbitMQ 节点和客户端在连接协商期间(更具体地说,是 TLS 握手)将使用哪些 密码套件,因此解释其影响非常重要。

本指南将以一定程度的简化来介绍它们。从广义上讲,这些字段分为以下三类

有些字段是布尔值,其他字段则具有不同的类型,例如可以设置或取消设置的一组选项(位)。

数据服务在很大程度上与使用的约束和密钥使用选项无关。但是,某些选项对于本指南中描述的使用案例至关重要

  • 服务器身份验证(向客户端提供服务器节点的身份)
  • 客户端身份验证(向服务器提供客户端的身份)
  • 数字签名的验证
  • 密钥加密

前两个选项用于 对等方验证。它们必须分别在公钥生成时为服务器和客户端证书设置。证书可以同时设置这两个选项。

tls-gen 将确保这些约束和扩展正确设置。当 手动生成证书 时,这是生成密钥对的操作员或密钥对提供者的责任。

扩展及其对接受的密码套件的影响(密码套件过滤)

两个关键扩展对于两种主要类型的 密码套件 至关重要

  • 基于 ECC(椭圆曲线密码术)的套件的digitalSignature
  • 基于 RSA 的套件的keyEncipherment

强烈建议为 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 格式被两种配置格式接受。请注意,在新的样式配置格式中,密码套件没有加引号,但在经典格式中需要双引号。

上述命令列出的密码套件采用可用于入站和出站(例如 ShovelFederation)客户端 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_orderhonor_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 漏洞及其缓解措施

ROBOT

ROBOT 攻击 会影响依赖于 RSA 密码套件并在 Erlang/OTP 版本 19.3.6.4 和 20.1.7 之前运行的 RabbitMQ 安装。要缓解此问题,请 将 Erlang/OTP 升级 到已修补的版本,并考虑 限制支持的密码套件列表

POODLE

POODLE 是一种已知的 SSL/TLS 攻击,最初会破坏 SSLv3。从 3.4.0 版本开始,RabbitMQ 服务器拒绝接受 SSLv3 连接。2014 年 12 月,宣布了一种影响 TLSv1.0 的 POODLE 攻击的修改版本 announced。因此,建议运行 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 证书(公钥)和私钥都有过期日期,需要定期更换(轮换)。

更换过程涉及以下步骤

  1. 替换磁盘上的文件
  2. 清除节点上的证书和私钥存储缓存

如果没有执行第二步,新证书/密钥对将在一段时间后被节点使用,因为运行时中的 TLS 实现会清除其证书存储缓存。

替换磁盘上的证书和私钥文件

只需将服务器证书、服务器私钥和(如果需要)证书颁发机构捆绑文件替换为新版本。

清除证书和私钥存储缓存

rabbitmqctl 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 连接的服务器的主机名,以启用服务器证书的“服务器名称指示”验证。这确保了在 TLS 连接建立期间会验证服务器证书的 CN= 值。您可以通过将 server_name_indication 设置为不同的主机名或特殊值 disable 来覆盖此行为以禁用此验证。请注意,默认情况下,SNI **未**启用。此默认值将在未来的 RabbitMQ Erlang 客户端版本中更改。

verify - 将此选项设置为 verify_peer 以启用 X509 证书链验证。depth 选项配置证书验证深度。请注意,默认情况下,verify 设置为 verify_none,这会禁用证书链验证。此默认值将在未来的 RabbitMQ Erlang 客户端版本中更改。

代码示例

SslOpts = [{cacertfile, &quot;/path/to/ca_certificate.pem&quot;},
{certfile, &quot;/path/to/client/certificate.pem&quot;},
{keyfile, &quot;/path/to/client/private_key.pem&quot;},

%% 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、证书和私钥

本指南的这一部分说明了如何生成证书颁发机构并使用它来生成和签名两个证书/密钥对,一个用于服务器,一个用于客户端库。请注意,此过程可以使用现有的工具自动化,建议这样做。本节旨在帮助那些希望提高他们对过程、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 genrsa,而是使用 openssl ecparam,如下所示

openssl ecparam -out private_key.pem -genkey -name prime256v1

上面示例中的 prime256v1 是椭圆曲线名称。不同版本的 OpenSSL 将具有不同的可用曲线集,使用 openssl ecparam -list_curves 列出它们。

© 2024 RabbitMQ. All rights reserved.