Skip to content

搞英语 → 看世界

翻译英文优质信息和名人推特

Menu
  • 首页
  • 作者列表
  • 独立博客
  • 专业媒体
  • 名人推特
  • 邮件列表
  • 关于本站
Menu

为什么我的 https 网站不再有旧式证书

Posted on 2025-05-24

2023年初,我写了一篇文章,解释为什么我的https网站仍然保留着“老式证书”。现在情况已经发生了变化,是时候解释一下原因了。

我对 ACME 协议了解有一段时间了。我最早有 2018 年的技术笔记,每次看到它,我都吓得退缩不前。这整件事相当于“把我们能用到的每一点网络技术都加进去”,想要安全彻底地实现它,真的很难。

许多现有的客户端代码也相当吓人,我本来不打算在我的机器上运行它们。它们粗心大意,根本没资格以我的私钥权限运行,也没资格以 root 权限窃取 Web 服务器的权限!

这意味着我陷入了困境:不愿意让自己去处理协议,同时又不愿意让现有项目的繁琐代码进入我的生活。

嗯,时间流逝,我设法突破了一些自身的障碍。不过,这并不是靠利用其他项目。我开始深入研究它们,试图弄清楚规范究竟是如何运作的,并开始逐一解决其中非常非常小的问题。这需要一种特殊的强制机制才能让我从困境中走出来,开始行动起来。

大约六个月前,我意识到或许是时候放弃 Gandi 的注册商和 SSL 提供商(经销商)了。几年前,他们被私募股权公司收购,现在情况越来越糟。他们“不胡扯”的口号已经消失,价格也一路攀升。我的域名已经续费好几年了,暂时还算安全,但到了 2025 年,情况就可能成问题了。

给他们“yeet”没什么大不了的,但那个该死的rbtb证书可就麻烦了。我是要每年都为这个蠢东西付更多的钱,还是最终还是忍痛割爱,去ACME那里买个东西?

但问题依然存在,那就是如何克服我对整个协议与生俱来的厌恶,以及不得不处理它们强加给你的所有这些编码。我迈向解决方案的第一步是编写一些非常小巧、愚蠢的实用函数和库,这些函数和库以后会派上用场。我指的是包装 jansson(一个处理 JSON 的 C 库),让它在我的 C++ 世界中变得有意义,并且我可以导入 JSON(我尽量少用它)。诸如此类的事情。

这也意味着我会走进一些死胡同,比如我注意到有些库据说可以帮你创建一些东西(比如 JWK),但后来发现它们不会让我的生活更轻松。我会试着去尝试,直到达到极限,然后咒骂几句,再走开几天。

这种情况持续了一段时间。我做了一系列的笔记,记录着我遇到的问题,我会抓住其中的一部分,纠结一会儿,觉得恶心,然后就放下去做别的事情。这种情况一直在发生,但我慢慢地取得了进展,一些小的、可以完成任务的部分,然后它们互相连接起来,如此反复。

这一切中的一个积极进展是,我发现了一个可以在独立的虚假系统上运行的“pebble”测试服务器。它可以充当 ACME 服务器,让我用自己笨拙的客户端实现来骚扰它,而不是去打扰真正的 CA。毕竟,即使是“staging”服务器也应该比活跃的开发环境得到更好的待遇。

好吧,在经历了一大堆乱码、死胡同、重写和其他糟糕的事情之后,我终于搞定了一个很棒的小工具,它可以接收 CSR 文件,做完所有那些愚蠢的操作,最后生成证书。我把它指向 Let’s Encrypt 的 staging 版本,成功了。然后我又指向他们的生产环境,结果也成功了。于是我又把它用在了真正的版本上([www.]rachelbythebay.com),*结果*成功了,我把它放到了实际位置。

因此,在过去的几周里,如果您一直在访问我的网站的 https 版本,那么您就一直在使用新的设置。

现在,我记下了这些内容,并想分享一些我最初关于如何实现这个想法的即兴想法,供那些同样脑子有病、想看看效果的人参考。需要说明的是,我根据第一个成功的例子写了这篇文章,并不一定反映我几周后的实际实施情况。

…

为您的网站创建一个 RSA 密钥。然后为其创建一个 CSR,设置 CN 并添加一个匹配的 altname 作为扩展名。其他字段都无关紧要。反正没人会看这些,而且无论您在其中填写的内容多么平淡或精确,它们都不会影响您的最终证书。

创建一个 4096 位的 RSA 密钥。将其称为你的个人密钥。

编写一个可以读取 CSR 的代码。它需要提取 CN 和 SAN,至少要提取 DNS: 1。确保确实存在一个 CN [*] 和 SAN,并且 CN 位于 SAN 内。所以,是的,你必须至少有一个 SAN。

[* – 我现在知道你只用 SAN 就可以运行。我当时不知道。]

编写一个程序,对 ACME 服务运营商提供的 <directory URL> 执行 HTTP GET 操作。然后,它需要将主体解析为 JSON(否则会挂掉),并从顶级对象中提取一些字符串:至少包含“newNonce”、“newAccount”和“newOrder”。

编写一个程序来读取磁盘上的 RSA 密钥文件。它需要提取 publicExponent(可能是 65537,但你永远不知道……)和模数。让它读取你之前的个人密钥。

如果你最终使用“openssl rsa -in foo -noout -text”来执行此操作,模数将是一串打印出来的十六进制数字,例如“00:ff:11:ab:cd:ef:22:33”。它会对输出进行硬换行,并且缩进行数,所以你得先把这些都清理干净。

由于某些莫名其妙的原因,跳过了第一个 00。取出模数的其他字节,并将它们转换为实际的字符值,因此,按照在文件中找到它们的顺序,依次为 0xff、0x11、0xab、0xcd 等等。保留这些值以备后用。

编写一个程序,将一个普通整数转换成其等效的大端字节数,但不要将其填充到任何特定的对齐方式。你需要从 publicExponent 中取出“65537”,并将其转换为等效的字节数,即 0x01、0x00、0x01。没错,你刚刚把它转换成了二进制表示。

写一个能用 base64 编码的代码,但不太完全一样。编码集的最后两个字符通常是 + 和 /,但这对 webshit 来说不行,所以你需要用 – 和 _ 来代替。

取出那个 publicExponent (65537),将其通过大端字节袋编码器获取 0x01 0x00 0x01,然后将其通过“base64web”编码器获取“AQAB”。

创建一个新 JSON 对象。添加一个名为“e”的字符串,并将其设置为上一步的输出。所以,没错,你不是说“e”等于“65537”,而是说“e”等于“AQAB”。你是不是很高兴完成了这些额外的步骤?

添加另一个名为“kty”的字符串并将其设置为“RSA”。

添加另一个名为“n”的字符串,并将其设置为之前的模数字节袋的“base64web”版本。

将此 JSON 对象转换为已排序的紧凑字符串表示。这意味着它包含“e, kty, n”,并且所有常见的填充(空格)都被压缩了。将其称为 JWK 字符串,并保存以备后用。

创建第二个 JSON 对象。向其添加一个名为“termsOfServiceAgreed”的布尔值,并将其设置为 true。(看来你最好同意……)

从您之前获得的目录 JSON 中查找“newNonce”的 URL。

向该 URL 发起 HTTP HEAD 请求。仔细查看请求头(而不是请求体,因为 HEAD 中没有请求体),直到找到“Replay-Nonce”。提取该请求头的值。保留它以备后用。

从之前的目录 JSON 中查找“newAccount”的 URL。

创建第三个 JSON 对象。向其中添加一个名为“url”的字符串,并将其设置为该 (newAccount) URL。添加一个名为“alg”的字符串,并将其设置为“RS256”。添加一个名为“nonce”的字符串,并将其设置为最后一个 HTTP HEAD 请求中 *header* 的值。

向第三个名为“jwk”的对象添加一个对象。在其中,按照之前的操作添加“e”、“kty”和“n”(你懂的,这来自你稍后仍保留的“JWK 字符串”)。

将第三个 JSON 对象转储为已排序的紧凑字符串表示形式。将其命名为“protected”。

将第二个 JSON 对象转储为已排序的紧凑字符串表示形式。称之为“payload”。

创建一个字符串,将这两个先前的字符串逐字连接起来,这样它就是受保护的值,然后是实际的句点(如 0x2e、句号等),然后是有效载荷的值。

编写一个程序,对任意字符串进行 SHA256 摘要计算,并使用任意 RSA 密钥对其进行签名(例如“openssl dgst -sha256 -sign <key>”)。这个密钥是你的个人密钥。

将“<protected>.<payload>”字符串传入摘要函数。然后将其传入“base64web”编码器。这被称为签名。

创建第四个 JSON 对象。添加一个名为“protected”的字符串,并将其设置为您之前几步创建的内容。添加另一个名为“payload”的字符串,并对其进行同样的设置。然后添加一个名为“signature”的字符串,并对其进行同样的设置。

将第四个 JSON 对象转储为已排序的紧凑字符串表示形式。将其称为帖子正文。

从目录向“newAccount”URL 发出 HTTP POST 请求。将内容类型设置为“application/jose+json”。将 POST 数据设置为上一步中的 POST 正文字符串。

在响应头中仔细查找,找到名为“Location”的头。不要像重定向一样跟着它走。你为什么要跟着 HTTP 头中的 Location 头走呢?不,那是你用户账户的标识符!没错,你现在就是一个 URL。记住这个 URL,以后再用。

您现在有一个帐户。

你还有很多事要做。

…

从那时起我就不再写我对该协议的看法了。

再次,我的程序不再像这样工作,但在观察到一堆已经存在的其他东西之后,它就开始这样了。

到目前为止,我们至少有:RSA 密钥、SHA256 摘要、RSA 签名、base64(但不是真正的 base64)、字符串连接、JSON 中的 JSON、用作身份的位置标头(而不是具有 301 响应的目标)、HEAD 请求以获取作为标头埋入的单个值、发出一个请求(nonce)以发出任何其他请求,并且还有更多。

我们甚至还没有触及创建订单、处理授权和挑战、整个“密钥指纹”的事情、这些 TXT 记录中实际包含的内容以及所有其他有趣的东西。

…

补充一下:在查看现有的 ACME 客户端时,我发现至少有一个客户端对 publicExponent 的编码搞错了,最终将其解释为十六进制而不是十进制。也就是说,它读取的不是 65537(也就是 0x10001),而是 0x65537(也就是 415031)!

不知何故,这种异常确实存在,而且似乎没有破坏任何东西?我实际上还没有运行过有问题的客户端,但我猜想人们正在使用它,因为它在 apt 中。

…

这种复杂性必然会给某些人带来工作保障。或许对很多人来说也是如此。

原文: https://rachelbythebay.com/w/2025/05/22/ssl/

本站文章系自动翻译,站长会周期检查,如果有不当内容,请点此留言,非常感谢。
  • Abhinav
  • Abigail Pain
  • Adam Fortuna
  • Alberto Gallego
  • Alex Wlchan
  • Answer.AI
  • Arne Bahlo
  • Ben Carlson
  • Ben Kuhn
  • Bert Hubert
  • Bits about Money
  • Brian Krebs
  • ByteByteGo
  • Chip Huyen
  • Chips and Cheese
  • Christopher Butler
  • Colin Percival
  • Cool Infographics
  • Dan Sinker
  • David Walsh
  • Dmitry Dolzhenko
  • Dustin Curtis
  • Elad Gil
  • Ellie Huxtable
  • Ethan Marcotte
  • Exponential View
  • FAIL Blog
  • Founder Weekly
  • Geoffrey Huntley
  • Geoffrey Litt
  • Greg Mankiw
  • Henrique Dias
  • Hypercritical
  • IEEE Spectrum
  • Investment Talk
  • Jaz
  • Jeff Geerling
  • Jonas Hietala
  • Josh Comeau
  • Lenny Rachitsky
  • Liz Danzico
  • Lou Plummer
  • Luke Wroblewski
  • Matt Baer
  • Matt Stoller
  • Matthias Endler
  • Mert Bulan
  • Mostly metrics
  • News Letter
  • NextDraft
  • Non_Interactive
  • Not Boring
  • One Useful Thing
  • Phil Eaton
  • Product Market Fit
  • Readwise
  • ReedyBear
  • Robert Heaton
  • Ruben Schade
  • Sage Economics
  • Sam Altman
  • Sam Rose
  • selfh.st
  • Shtetl-Optimized
  • Simon schreibt
  • Slashdot
  • Small Good Things
  • Taylor Troesh
  • Telegram Blog
  • The Macro Compass
  • The Pomp Letter
  • thesephist
  • Thinking Deep & Wide
  • Tim Kellogg
  • Understanding AI
  • 英文媒体
  • 英文推特
  • 英文独立博客
©2025 搞英语 → 看世界 | Design: Newspaperly WordPress Theme