CDN如何生成证书

2020-06-26 05:20:09

在Fly的头几周是忙碌的,我边走边写东西,因为如果我必须学习,你也要学习。这会有点曲折,你得应付一下。

让我们从“什么是飞翔?”开始吧。简要地说:Fly是一个用于Docker容器的内容交付网络。动态托管的应用程序速度很快,因为它们运行在离用户很近的机器上。要做到这一点,我们在许多城市运行裸机服务器,并将容器托管在这些服务器上的“鞭炮”虚拟机中。我们通过全局WireGuard网格代理从边缘服务器到容器的流量。它比ECS或K8要容易得多,所以注册一个免费账户可能是体验它的最好方式,也是一种燃烧5-10分钟的愉快方式。

显然,要执行此类操作,您需要生成证书。2020年要做到这一点,合理的方式是使用LetsEncrypt。我们会自动为我们的用户做这件事,但是“它就是有效的”使得这篇文章相当枯燥,所以让我们看看我能做得有多复杂和多曲折。

LetsEncrypt不是验证来自“合格的独立信息源”的信息,而是简单地基于域的所有权证明进行域验证证书,并由名为ACME的协议驱动。Acme非常简单。它几乎是在纯Bourne shell中实现的。其中最复杂的是JWS签名,虽然很糟糕,但至少是标准化的。ACME协议本身是通过普通HTTP请求完成的;流程大致如下:

您创建一个帐户并将ECDSA/EdDSA密钥与其相关联,随后该密钥将对您的所有请求进行身份验证。

然后您发布证书的“订单”,指定您需要的DNS名称。作为回报,您将获得“授权”和“最终”URL。

您在自己端设置质询,然后轮询状态URL以验证质询是否已完成。

Acme质询旨在验证您对某个域的所有权。它们有三个(如果算上预授权,有四个,LetsEncrypt没有);最初,它们是:

tls-http-01,其中您将获得一个令牌,放在您的服务器上的/.well-now/acme-challengl下,并在80/tcp上服务于LetsEncrypt的客户机。这很容易描述和实现,但是需要您响应80/TCP上的HTTP请求,这是很多人(明智地)不想做的。

TLS-DNS-01,其中给您一个令牌以放入您的DNS区域的TXT记录中。这直接证明了对域的控制,但操作员很难做到这一点。特别是,尤其是在较大的组织中,需要证书的人不一定被授予访问DNS配置的权限。

tns-sni-01,其中您将获得一个令牌,用于将您向通过TLS SNI请求证书的TLS客户端提供的证书嵌入到SAN中,这相当于TLS的HTTP“Host”标头。这一点实现起来比较复杂,但却是最大的挑战:您所需要做的就是运行您本来要运行的TLS服务器。

但TLS-SNI-01已不复存在,因为它不安全。SNI挑战的问题是共享主机。

由于IP地址稀缺,许多主机提供商安排客户共享IP地址。当客户的请求到达时,它们基于SNI进行路由。

与您可以配置本地nginx以响应任何Host标头而不中断Internet的方式相同,托管提供商通常允许人们在其平台上“声明”任意主机名。这表面上无关紧要,因为如果没有对DNS的控制,您无法让人们与您声称的主机名对话。

你可能已经看到这是怎么回事了。下面是LetsEncrypt使用SNI验证域所有权的方法:

它为您生成了一个令牌,用于放入一个自签名证书,其形式为“.acme.invalid”主机名。

它解析了您在DNS中为其生成证书的主机名,并将其连接到该主机名。

如果主机提供商让您在“.Invalid”TLD中声明名称,并为它们上传您自己的证书,您可以获得为您IP上托管的所有客户颁发的证书。Heroku允许您这样做,AWS CloudFront也是如此,谁知道还会有谁呢。

LetsEncrypt在主机提供商部署修复程序的同时,迅速解决了SNI的挑战。最终,SNI被如此广泛地使用,以至于CA得出结论,将SNI用作挑战从根本上是不安全的,ACME SNI挑战被否决,并最终在去年被删除。

此攻击是一种称为“子域接管”的更广泛攻击类别的实例,该攻击类别是漏洞赏金猎人的中流砥柱。如果你想在一个晚上赚50美元左右,HackerOne会告诉你一切。

因此,每当您为客户域托管内容时,您都会遇到当客户停止使用您的服务时会发生什么的问题。正如您可能预期的那样,很多时候您会忘记停止向旧的过期服务转发DNS。但您在这些服务上的帐户已失效,这通常意味着其他人可以声明与您使用的名称相同的名称。由于您仍在将流量定向到该服务,新的索赔人现在已经劫持了您的一个子域。

这对于各种原因都是不好的;它允许您窃取cookie、违反CORS、绕过CSP;它甚至会影响OAuth2。

Fly通过不重复使用IP地址缓解了ALPN挑战的这一问题。每个应用程序都获得一个唯一的、可路由的IPv6地址,除非目标主机名通过CNAME解析为该IPv6地址,否则我们不会尝试让验证加密。(我们为DNS挑战做了类似的事情)。

回想一下TLS-SNI-01挑战的优点:它不需要您有权访问您的DNS配置,也不需要打开80/tcp。你想要这样的挑战。还有一个:新的第三届ACME挑战赛,TLS-ALPN-01。

要理解TLS-ALPN-01,您当然需要知道ALPN是什么。这是一个简单的概念:假设TLS本身就是一个传输协议,与TCP和UDP并驾齐驱;ALPN将是它的端口号。我是说,它们是字符串,不是数字,而是一样的概念。

为什么TLS需要这样的东西?大多数使用TLS的东西都已经有了自己的TCP端口。当然,答案是HTTP/2。HTTP/2与HTTP/1(它是为流水线优化的二进制协议)不是有线兼容的。但它不能有自己的TCP端口,因为如果有,就没有人能说出自己的话:互联网的大量数据都被锁定在端口80和443上。

(顺便说一句,我们不是在运行;您可以在这里运行您想要的任何TCP服务。但我离题了)。

为了解决这个问题,当Google在设计SPDY(HTTP/2的前身)时,他们提出了NPN,即“下一协议协商”。NPN的工作方式是:

TLS客户端向其ClientHello添加了NPN扩展,即TLS客户端发送以打开连接的消息。

支持的TLS服务器将使用ServerHello进行响应,该服务器具有填充了其支持的协议的NPN扩展。

密钥交换已经完成,连接的两端都将开启加密。

TLS客户端将发送加密的NextProtocol消息,该消息选择下一个协议(从技术上讲,如果双方都试图偷偷摸摸的话,该协议可能是服务器列出的协议,也可能不是)。

通过这样做,Chrome可以在与谷歌服务器交谈时选择使用SPDY,而不需要花费往返的谈判时间。

当SPDY变成HTTP/2时,像NPN这样的东西也需要标准化。但是IETF TLS-WG并不喜欢NPN;尤其是,它颠倒了TLS协商的正常顺序,即客户端提议和服务器选择。因此,IETF提出了应用层协议协商(ALPN)。ALPN的工作原理如下:

TLS客户端将ALPN扩展添加到其ClientHello,以指示其支持的所有协议。

TLS服务器在其ServerHello中指示其在ALPN扩展中选择的协议。

这里有明显的隐私暗示,对吗?因为您可能要求的ALPN协议是“TOR”。IETF毁了一切。这是真的,但这很复杂。

首先,加密的NextProtocol帧提供的安全性有点粗略。以下是一次袭击的概要:

爱丽丝连接到鲍勃,马洛里非常想知道爱丽丝会要求什么协议。

Mallory MITMS连接并降低其安全级别。请记住,当“Finish”消息对握手进行了密码身份验证时,NPN正在运行/Inside/握手,而不是/After/It。

同时,Alice重新连接,因为这就是您要做的,并直接与Bob重复完全相同的过程,发送相同的NextProtocol。

在实践中,使用Firefox时,您可以通过发送一个假证书来完成此操作;Firefox将完成握手(包括NPN),即使证书没有验证。

(不管它有什么价值,这里的一些隐私问题在TLS 1.3中都有讨论)。

此外,尽管亚当·兰利(Adam Langley)在编写NPN规范时无疑考虑到了隐私问题,但更重要的问题可能是中间盒兼容性。

中间盒的工作方式是,企业购买它们。它们相当昂贵,而且企业一口气买了一大堆可怕的东西,所以供应商们真的很努力地赢得了这些交易。赢得预选赛的一个直接方法是比你的竞争对手拥有更多的功能。这里有一个特性:“根据客户端选择的应用程序协议过滤连接”。Chrome团队大概在一英里外看到了这个愚蠢的功能,通过加密NPN选择将其从桌面上删除。

(这听起来有点偏执,但前提是您从未使用过真实的TLS。在NPN vs.ALPN TLS-WG线程中,AGL引用了他们在英国发现的一家ISP,该ISP自行阻止了所有ECDHE密码组。为什么?谁知道呢?人们会做这样的事情。)。

最终,ALPN在TLS-WG中击败了NPN。但是,就在他们完成标准的时候,Mozilla的布莱恩·史密斯(也是Rust的戒指密码库的作者)把事情搞砸了。

根据Mozilla的经验,在某些情况下,当他们收到长度超过255个字节的ClientHello时,中间盒就会挂起。挂起是非常糟糕的,因为Mozilla需要超时逻辑来检测它,并尝试更简单的握手,但这种逻辑也会对使用糟糕的互联网连接的人触发,并产生阻止这些人使用现代TLS的效果。

神奇的是,一天后,F5的吴晓勇跳上了线程,解释说旧的F5软件将256字节的ClientHello帧与TLSv2混淆了。TLS帧长度为2字节宽;一旦ClientHello超过255字节,最高长度字节变为01h。该字节在帧中的位置与SSLv2中的消息类型相同。对于F5,帧可以是长客户端Hello…。或者非常长的SSLV2 MTCLIENTHELLO,也是01h。F5选择了SSLv2。

解决办法是什么?发送/更多/字节!在512字节时,高长字节不再是01h。因此诞生了“jpeg-of-cat”扩展,AGL将其重命名为“TLS ClientHello填充扩展”,以此为乐趣。

这有点平淡无奇,但是我们已经走了这么远,所以您不妨了解一下Fly(和其他CDN,以及Caddy之类的东西)是如何使用ACME生成证书的:

LetsEncrypt为我们提供了一个令牌,我们为它生成嵌入了令牌的自签名证书,并将其加载到分布式证书存储中。

我们(在ACME中)说“继续”,LetsEncrypt查找我们正在服务的主机名,连接到它,并发送一个将“acme-TLS/1”设置为ALPN协议的ClientHello。

我们的Rust代理捕获ACME ALPN案例,检索质询证书,并将其提供给LetsEncrypt。

LetsEncrypt丢弃连接,将质询设置为已完成,并允许我们完成证书生成。

ALPN挑战比SNI挑战更明确;我们必须专门设置子服务来为客户完成ALPN挑战,而不是在某种程度上基于我们本地的SNI处理来隐式完成。(根据我们的证书处理方式,我们无论如何都不会有问题,但这就是为什么ALPN可以,而SNI不可以的逻辑)。

这个过程几乎是无缝的;你所要做的就是说“是的,我想要我的应用程序自定义域的TLS证书”。不过,它只适用于单个主机名,这可能没有问题,但如果不适用,您可以与我们一起进行DNS质询,以生成通配符证书。