WolfSSL 也很糟糕,那现在怎么办?
Source: Hacker News
请提供您希望翻译的正文内容,我将为您翻译成简体中文并保留原有的格式。
加密库概览 – 快速思考
- OpenSSL – 通常可靠,但对某些项目来说可能显得笨重。
- BoringSSL(Google)– 为谷歌内部需求量身定制;并不总是完全适合外部使用场景。
- AWS‑LC(Amazon)– 为亚马逊的服务优化;可能缺乏更广泛的社区关注。
- GnuTLS – 历史上稳固,尽管个人使用体验参差不齐。
- LibreSSL – 值得称赞的尝试,但相较于同行仍缺少一些功能。
注意: 每个库都有其自身的设计目标和权衡。选择合适的库取决于你的具体需求、威胁模型以及生态系统约束。
现在发生了什么?
去年,HAProxy 在一篇文章中指出 OpenSSL 已经变得异常缓慢。该故事被多次传播,我于是决定帮助 FreeBSD 打包一个基于 wolfSSL 构建的 HAProxy 变体。
这似乎是让 wolfSSL 获得更广泛曝光的简便途径,因为大多数 Linux 发行版不太可能提供这种构建。实际上,唯一使用 wolfSSL 支持的 HAProxy 的人,都是那些懂行并自行编译的人。我还没有检查 Arch、Gentoo、Nix 等发行版是否有类似的包,但它们本应是最直接的 haproxy‑wolfssl 包候选者。
Bug
我使用 wolfSSL 编译了 HAProxy,在几个环境中运行后遇到了一个 bug。我报告了该问题,随后忘记了它,继续前进——直到再次遇到同样的问题。为了解决它,我重新打开了错误报告,并在代码中深入排查,最终找到了根本原因。
TLS 1.3 与中间盒
TLS 1.3 在 RFC 8446 中定义。它的工作方式与 TLS 1.2 完全不同,后者引入了大量兼容性问题。规范甚至指出:
“TLS 1.3 的设计受到广泛部署的非兼容 TLS 中间盒的限制。”
啊,臭名昭著的中间盒——那些看不见的网络基础设施部件,会篡改流量。你常常在它们给你带来麻烦之前根本不知道它们的存在,而它们确实会造成困扰。
Middlebox Hell
Middleboxes 几乎是在“地狱”中被发明的,无论怎样祈求都无法让它们消失。(也许有几个 Etsy 的巫师可以带来一点运气,但那是另一个故事……)
问题在于我们想要比 TLS 1.2 提供的更强的安全保证,但许多 middlebox 只懂 TLS 1.2。由于这些设备会破坏 TLS 1.3,TLS 1.3 必须能够 伪装 成 TLS 1.2。
RFC 定义的变通办法
TLS 1.3 规范包含一个 Middlebox Compatibility Mode(参见 RFC 8446, Appendix D.4)。思路很简单,但代价不小:
- ClientHello – 客户端包含一个 非空的 Session ID。这会欺骗期望 TLS 1.2 握手的 middlebox。
- Dummy ChangeCipherSpec records – 客户端和服务器交换占位的
ChangeCipherSpec消息,这些消息会被 TLS 1.3 忽略,但满足旧设备的预期。 - Proceed with TLS 1.3 – 在虚假交换之后,正常的 TLS 1.3 握手继续进行。
这意味着什么
- 兼容性 – 连接可以穿过只懂 TLS 1.2 的 middlebox。
- 延迟惩罚 – 额外的
Session ID和虚假的ChangeCipherSpec记录会给握手增加往返时间。 - 安全性 – 一旦越过 middlebox,会话即可享受 TLS 1.3 完整的安全保证。
简而言之,Middlebox Compatibility Mode 是一种务实(但低效)的桥梁,使得现代的 TLS 1.3 连接能够在被旧版 middlebox 主导的环境中生存。
颠倒世界
RFC 对此的预期实现方式已经相当明确。
兼容模式
-
此模式是部分协商的:
- 客户端可以提供会话 ID(也可以省略)。
- 如果会话 ID 存在,服务器必须回显该会话 ID。
-
如果客户端发送了非空的会话 ID,服务器必须按照附录所述发送
ChangeCipherSpec。
WolfSSL 的立场
WolfSSL 要求在编译库时使用
-DWOLFSSL_TLS13_MIDDLEBOX_COMPAT
以启用中间盒兼容功能。此标志强制库要么始终处于该模式,要么永不处于该模式——没有中间选项。
后果
- TLS 1.3 客户端 不能被信任能够可靠地与 WolfSSL 互操作。
- 兼容性现在完全取决于客户端实现的宽容程度,这并不令人信服。
- 原文末尾链接的 GitHub issue 评论表明,WolfSSL 团队并未将 RFC 合规性作为此功能的优先事项。
在 WolfSSL 中没有“折中”方案或替代实现来实现中间盒兼容性:要么符合 RFC 要么不符合,而当前实现属于后者。
原告
目前我只发现了一个受到此决定影响的受害者,但可能还有更多。
Erlang/OTP 自带了自己的 SSL 库实现,你可以合理地假设团队在加入 TLS 1.3 支持时遵循了 Joe Armstrong 的建议:
“先让它能工作,然后让它好看,如果真的、真的必须的话,再让它快。” – Joe Armstrong
为了自保,Erlang/OTP 开发者 默认启用了 middlebox_comp_mode(参见源码行 这里)。
- 如果你想要最大速度且确信安全,可以关闭此选项。
不幸的是,在默认设置下,所有使用 Elixir/Erlang(以及相关)HTTP 客户端的程序在 TLS 1.3 可用时,都会无法连接到 WolfSSL HTTPS 服务器。
我们接下来该怎么办?
OpenBSD 可能是对的:我们应该专注于 LibreSSL,而不要再尝试兼顾其他 TLS 库。正如 HAProxy 所指出的,它并不是 OpenSSL 3.0 “失误”的受害者,因为它更早就已经分叉,但它确实缺少一些优化。这是一个合理的权衡,缺口会在适当的时候被填补。
所以别像我一样。我的狂妄让我以为可以为我的站点获得更快的 TLS 终止,结果却浪费了大量时间去学习我根本不需要的东西,然后写了这篇博客文章。已警告。
Elixir PoC
针对 Elixir 1.17.3(使用 Erlang/OTP 26 编译)的概念验证(proof‑of‑concept)和下面的脚本一样简单。
#!/usr/bin/env elixir
url = "https://some-wolfssl-endpoint"
url = String.to_charlist(url)
{:ok, _} = Application.ensure_all_started(:inets)
{:ok, _} = Application.ensure_all_started(:ssl)
:logger.set_application_level(:ssl, :debug)
http_options = [
ssl: [
verify: :verify_peer,
cacerts: :public_key.cacerts_get(),
depth: 2,
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
],
versions: [:"tlsv1.2", :"tlsv1.3"],
middlebox_comp_mode: true # ← toggle this
]
]
options = [body_format: :binary]
:httpc.request(:get, {url, []}, http_options, options)
预期错误(当 middlebox_comp_mode: true 时)
11:00:44.996 [warning] Description: ~c"Failed to assert middlebox server message"
Reason: [missing: {:change_cipher_spec, 1}]
11:00:45.014 [notice] TLS :client: In state :hello_middlebox_assert at ssl_gen_statem.erl:821 generated CLIENT ALERT: Fatal - Unexpected Message
- {:unexpected_msg,
{:internal,
{:encrypted_extensions,
%{
elliptic_curves: {:supported_groups,
[:secp521r1, :secp384r1, :secp256r1, :x25519, :ffdhe2048]}
}}}}
当你看到上述输出时,就说明你已经被“抛向狼群”。
解决办法: 将 http_options 中的 middlebox_comp_mode 设置为 false,请求即可如预期成功。