TLS 支持
目录
RabbitMQ 内置了对 TLS 的支持。这包括客户端连接和流行的插件(如果适用),例如 Federation 链接。也可以使用 TLS 来加密集群中的节点间连接。
本指南涵盖了 RabbitMQ 中与 TLS 相关的各种主题,重点是客户端连接
- 两种用于客户端连接的 TLS 使用方式:直接方式或通过 TLS 终端代理方式
- TLS 支持的 Erlang/OTP 要求
- 在 RabbitMQ 中 启用 TLS
- 如何为开发和 QA 环境生成自签名证书:使用 tls-gen 或 手动
- Java 和 .NET 客户端中的 TLS 配置
- 客户端连接或双向(“mTLS”)的对等方(证书链)验证
- 与 RabbitMQ 相关的公共 密钥用途扩展
- 如何控制启用哪些 TLS 版本和 密码套件
- TLSv1.3 支持
- 可用于评估 TLS 设置的工具
- 证书和密钥轮换
- 用于频繁更改受信任证书的环境的 信任存储插件
- 已知的 TLS 主要漏洞及其缓解措施
- 如何使用私钥密码
等等。
在哪里学习 TLS 基础知识
本指南尝试 解释 TLS 的基础知识,但它不是 TLS、加密、公钥基础设施和相关主题的入门读物,因此这些概念仅被非常简要地介绍。
网络上还有许多面向初学者的 TLS 入门读物
TLS 和消息传递协议
可以为 RabbitMQ 支持的所有协议启用 TLS。但是,本指南主要关注 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 需要在 Erlang/OTP 安装中提供 TLS 和加密相关模块。与 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 以及如何充分利用它,我们建议使用其他资源,例如 使用 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:它需要在 PATH
中安装 Python 3.5+
、make
和 openssl
。
请注意,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 证书都由下一个证书签名。例如,如果证书 B 由 A 签名,而 C 由 B 签名,则链为 A, B, C
(此处逗号用于清晰起见)。链的“最顶层”(第一个或唯一的)CA 通常被称为链的根 CA。根 CA 可以由著名的证书颁发机构(商业供应商)或任何其他方(自签名)颁发。
以下是最基本的链的示例,其中包含一个根 CA 和一个叶(服务器或客户端)证书
带有中间证书的链可能如下所示
在对等方验证期间,TLS 连接客户端(或服务器)遍历对等方呈现的证书链,如果找到受信任的证书,则认为对等方受信任。
双向对等方验证(双向 TLS 身份验证或 mTLS)
当双方都执行此对等方验证过程时,这称为双向 TLS 身份验证或 mTLS。
启用双向对等方验证涉及两件事
- 在 RabbitMQ 端为客户端连接启用对等方验证
- 在应用程序代码中启用服务器的对等方验证
换句话说,双向对等方验证(“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 服务器本身,受信任的证书管理方法可能不同。
例如,Linux 和其他类 UNIX 系统上的 OpenSSL 和 OpenSSL 命令行工具(如 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.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();
}
}
这个简单的示例是一个回显客户端和服务器。 它创建一个通道并发布到默认的直连交换机,然后取回已发布的内容并将其回显出来。 它使用一个独占的、非持久的、自动删除的队列,该队列将在连接关闭后不久被删除。
启用对等方验证的情况下连接
为了使 Java 客户端信任服务器,必须将服务器证书添加到信任存储库中,该存储库将用于实例化 信任管理器。 JDK 附带一个名为 keytool
的工具,用于管理证书存储库。 要将证书导入到存储库,请使用 keytool -import
keytool -import -alias server1 -file /path/to/server_certificate.pem -keystore /path/to/rabbitstore
上面的命令会将 server/certificate.pem
导入到使用 JKS 格式的 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 = "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
根据上面的输出,信任存储库中有一个自签名 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 Framework 或操作系统 根据 应用程序上下文切换 选择。
如果使用 SslProtocols.None
选择合适的 TLS 版本的连接失败,客户端将显式启用 TLSv1.2 重试。 这减少了在应用程序开发人员端进行显式配置的需要,在自动 TLS 版本选择被禁用、不可用或在其他方面无法依赖的环境中。
现代 .NET Framework 版本 默认为 TLSv1.2。
代码示例
这或多或少是 Java 客户端示例 的直接移植。 它创建一个通道并发布到默认的直接交换,然后读取已发布的内容并将其回显出来。 请注意,我们使用一个独占、非持久、自动删除队列,因此我们不必担心手动清理自己
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 提供对等方验证(validation),这是一种客户端和服务器基于对等方的证书信息来验证彼此身份的方式。 启用对等方验证后,通常您连接的服务器的主机名需要与服务器证书上的 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 会限制支持的客户端平台的数量。 下表解释了哪些 JDK 和 .NET 版本支持哪些 TLS 版本。
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 将确保正确设置这些约束和扩展。 当手动生成证书时,这是生成密钥对的操作员或密钥对提供商的责任。
扩展及其对接受的密码套件的影响(密码套件过滤)
两个密钥扩展对于两种主要类型的密码套件至关重要
- 用于基于 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 格式被两种配置格式都接受。 请注意,密码套件在新样式配置格式中未加引号,但在经典格式中需要双引号。
上述命令列出的密码套件的格式可用于入站和出站(例如 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 漏洞及其缓解
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
记录为所有标准 Erlang TLS 选项提供了一个字段 ssl_options
。
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, "/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、证书和私钥
本指南的这一节解释了如何生成证书颁发机构 (Certificate Authority) 并使用它来生成和签名两个证书/密钥对,一个用于服务器,另一个用于客户端库。 请注意,此过程可以使用现有工具自动化,这是推荐的做法。 本节旨在帮助那些希望加深对该过程、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 broker 使用 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
列出它们。