引入一个新版本
为兼容未来而设计一个协议是非常困难的,因为如果没有反馈机制,那么很容易犯错误。这是TLS版本协商的例子:如果你部署错误的话,除了假想的未来版本,不会有任何问题发生。这样,对于开发人员来说,没有任何迹象表明这一部署是有漏洞的,因此可能没有人会注意到错误的发生。也就是说,直到部署了新版本的协议并且你的实现失败之前,你都不会注意到,但是到那个时候,已经部署了代码,并且每个人都需要花费数年的时间来升级。实际上,一些服务器未能正确处理“未来”的TLS版本的情况应该是在意料之中的。
TLS的版本协商机制尽管简单,但实际上它是协议设计反模式的一个例子。它演示了一种所谓“僵化”的协议设计。如果一个协议是用一个灵活的结构设计的,但是这种灵活性在实践中从来没有被使用过,那么一些部署就会假定它是永远不变的。Adam Langley将这一现象与生锈相比较。如果你很少打开一扇门,那么它的铰链就很可能会生锈。TLS的协议协商机制就是这样的铰链。
大约在TLS 1.2部署的时候,围绕设计一个雄心勃勃的新版本TLS的讨论开始了。人们准备将它称为TLS 1.3,而版本号选择的自然是3.4(或(3,4))。到2016年年中,TLS 1.3草案已经经过了15次迭代,版本号和协商机制基本上都已经设计好了,在这个时候,浏览器已经消除了不安全的回退。人们假定,那些未能正确执行TLS版本协商机制的服务器已经吸取了POODLE漏洞的教训,并最终正确地实现了版本协商。
但事实却并非如此,当收到3.4版本的问候信息时,大部分支持TLS 1.2的服务器会断开连接,而不是以3.3进行响应。Hanno Böck、David Benjamin、SSL Lab团队和其他一些人进行的互联网测试证明,TLS 1.3的失败率很高,在许多测试中超过了3%。这与升级到TLS 1.2时所面临的情况完全相同。
这一意想不到的挫折为参与协议设计的人员带来了一场危机。浏览器不希望重新启用不安全的降级以致于在接下来的5年里再次为协议协商而面临艰苦的工作。但是,如果不降级,使用TLS 1.3将会为了这些用户而破坏互联网的3%。
有争议的选择是,接受David Benjamin的建议,使第一个TLS 1.3消息(客户端问候信息)看起来像是TLS 1.2。客户端的版本号被更改为(3,3),并引入了一个新的“版本”扩展,其中包括所支持的版本列表。如果服务器支持TLS 1.3(3,3)或更早版本的话,那么它将返回一个这样的服务器问候信息(3,4)。在TLS 1.3的16号草案中,包含了这个新的“改进”的协议协商逻辑。
这个机制可以正常运转,大多数服务器对这种更改都能兼容,并能轻易退回到TLS 1.2,从而忽略了新的扩展。但这并不是故事的结局,僵化是一种变化无常的现象。它不仅影响客户端和服务器,而且还影响到与协议进行交互的网络上的一切。
现实世界中有太多的中间设备
我们博客的读者们可能还记得之前早些时候的一篇关于HTTPS拦截的文章。在这篇文章中,我们探讨了一项研究,它测量了现实中有多少“安全”的HTTPS连接被处于浏览器和web服务器之间的某个地方的监听软件或硬件所拦截并解码。也有一些被动式检查的中间设备,用来解析TLS协议,并阻止或转移那些基于可见数据的连接,例如那些使用TLS连接中的主机名信息服务(服务器名称指示)来阻止“被封禁”网站的互联网服务提供商(ISP)。
为了检查流量,这些网络设备需要部分或全部实现TLS。支持TLS的每一个新设备都引入了一个TLS实现,该实现可以假定协议应该如何执行。有越多的实现,就越有可能出现僵化现象。
在2017年2月,Chrome和Firefox两个浏览器都开始为他们的一部分客户提供TLS 1.3支持。结果出乎意料地可怕。与预期相比,TLS 1.3连接的失败比例要高得多。那么对于一些用户来说,情况就是:不管访问的是什么网站,TLS 1.2都可以正常运转,但是TLS 1.3却不行。
TLS 1.3的18号草案的成功率如下:
Firefox浏览器访问Cloudflare,
TLS 1.2成功率为97.8%,
TLS 1.3成功率为96.1%。
Chrome浏览器访问Gmail,
TLS 1.2成功率为98.3%,
TLS 1.3成功率为92.3%。
经过一些调查,发现一些被广泛部署的中间设备,无论是主动拦截还是被动检查,都会导致连接失败。
由于TLS协议在其整个历史上大体都是一样的,因此一些网络设备对该协议怎样随时间变化做出了假设。而当面对一个突破了这些假设的新版本时,这些设备就会遭遇不同方式的失败。
在TLS 1.3中改变的一些TLS特性仅仅是表面上的。例如,自从SSLv3被移除之后,ChangeCipherSpec、session_id以及压缩字段之类的东西就成为了协议的一部分。人们认为,这些字段对于一些中间设备来说成为了TLS的基本特性,而移除它们会导致连接失败情况的激增。
如果使用一个协议的时间足够长,并且具有类似的格式,那么围绕该协议构建工具的人们会假设该格式保持不变。这通常不是开发人员有意的选择,而是在实践中使用协议的意外结果。网络设备的开发人员可能并不理解互联网上使用的每个协议,因此他们经常测试他们在网络上看到的内容。如果一个原本应该很灵活的协议有一部分内容在实践中一直没有改变,那么就会有人认为它永远不会改变。而这很可能会实现更多的部署。
把所有的责任都推给这些中间设备的具体实现者是虚伪的。是的,他们创建了错误的TLS实现,但是如果我们换一种方式思考的话,其中TLS设计的本意就很容易导致这种失败。是实现者来实现协议的本质,而不是协议设计者的意图或规定文本来实现。在复杂的生态系统中,有多个实现者,那么未使用的节点就会“生锈”。
去除那些20年来已经成为了的协议的一部分的特性并且期望它仍然继续正常工作是一种痴心妄想。
让TLS 1.3正常工作
上个月在新加坡举行的IETF会议上,为了解决这一问题,针对TLS 1.3提出了一项新的更改。这项更改是基于Facebook公司的Kyle Nekritz的一个想法:让TLS 1.3对于中间设备看起来像TLS 1.2。这项更改重新引入了被删除的协议的许多部分(session_id、ChangeCipherSpec、一个空压缩字段),并引入了一些其它的更改,这使得对于那些存在缺陷的中间设备来说,TLS 1.3无论从哪个方面看起来都像是TLS 1.2。
这些更改的几次迭代是由BoringSSL公司开发的,并在Chrome浏览器中进行了几个月的测试。Facebook公司也进行了一些类似的实验,这两支团队对同一组更改进行了试验。
Chrome浏览器试验成功率:
TLS 1.2成功率为98.6%,
试验更改(Github的PR1091)成功率为98.8%。
Firefox浏览器试验成功率:
TLS 1.2成功率为98.42%,
试验更改(Github的PR1091)成功率为98.37%。
这些试验表明,可以修改TLS 1.3,使其与中间设备兼容。它们也显示出僵化现象。正如我们在前一节中所描述的,在客户端问候信息中,唯一可能会“生锈”的东西——版本协商——的确“生锈”了。这导致了16号草案的产生,该草案将版本协商转移到一个扩展上。作为客户端和服务器之间的中间层,中间设备也关心服务器的问候信息。这条信息有更多的“铰链”,它被认为是灵活的,但事实证明并非如此。几乎所有的这些都已经“生锈”了。新的“对中间设备友好”的更改将这一现实考虑在内。这些试验的更改被加入到TLS 1.3的22号草案的规定中。
确保这种问题不会再次发生
原始的协议协商机制已经被不可恢复地毁坏了。这意味着它可能无法在未来某个版本的TLS中使用而不会有严重的破坏。然而,许多其它的协议协商特性仍然是灵活的,例如密码套件的选择和扩展。保持这样的状态是非常棒的。
去年,Adam Langley写了一篇非常好的博客(https://www.imperialviolet.org/2016/05/16/agility.html),是关于加密协议设计的,和本文探讨的领域类似。在这篇文章中,他提出了这样一句格言:“只要有一个节点,就要让它保持润滑。”这对协议设计者来说是一个很好的建议。僵化现象是真实存在的,就像我们在TLS 1.3中看到的那样。
沿着这条路线,David Benjamin提出了一种方法,使那些最重要的节点始终保持润滑。他对TLS的润滑建议是,在协议应兼容新的值的地方,设计出随机抛出的值。如果主流实现在实际的部署中散布未知的密码、扩展和版本,那么实现者将不得不正确地处理它们。这种润滑就像是互联网上的WD-40润滑剂一样。
需要注意的一点是,润滑的目的是防止服务器僵化,而不是中间设备,因此在TLS中仍有可能出现更多类型的润滑。
即使是使用了润滑剂,人们还是发现,直到2017年12月,有一些服务器仍然对TLS 1.3不兼容。润滑剂只能识别那些对未知扩展不兼容的服务器,但是一些服务器可能仍然不能兼容特定扩展的id。例如,RSA的BSAFE库使用了扩展id 40来进行一个名为“extended_random”的试验性扩展,它与一个理论上的NSA后门有关。扩展id 40恰好是TLS 1.3密钥共享所使用的id。David Benjamin报告说,这个库仍然被一些打印机所使用,这使得它们无法兼容TLS 1.3。Matthew Green对这个兼容性问题进行了详细的描述。
帮助我们理解这个问题
Cloudflare公司一直在与Mozilla Firefox浏览器团队合作,以帮助测试这一现象,谷歌公司和Facebook公司也一直在做自己的测试。这些试验很难进行,因为开发人员需要将协议的变体引入到浏览器中,这可能导致用户需要花费浏览器的整个发布周期的时间(通常是几个月)才能看到问题。Cloudflare现在(有希望)支持最新的兼容中间设备的TLS 1.3草案版本(22号草案),但我们总是有可能找到一个与这个版本不兼容的中间设备。
为了避开浏览器的发布周期,我们走了一个捷径。我们建立了一个网站,通过这个网站,你可以站在浏览器的角度来查看TLS 1.3是否正常工作。这个测试是由我们的密码学实习生Peter Wu开发的。它使用Adobe Flash,因为这是唯一一种广泛应用的跨平台的可以从浏览器访问原始套接字的方式。
它是这样工作的:
我们将我们的基于golang的TLS 1.3客户端库(TLS-tris)交叉编译为JavaScript;
我们构建了一个JavaScript库(称为jssock),它通过Adobe Flash在底层的套接字接口网络上实现了tls-tris;
我们使用TLS 1.2和TLS 1.3连接到远程服务器,并比较结果。
如果存在不匹配的情况,Cloudflare将从连接的两端收集信息并将其发送回去以进行分析。
我们非常期待浏览器能默认启用TLS1.3。这个实验有望帮助证明,最新的技术将是用户的升级更安全!
领取专属 10元无门槛券
私享最新 技术干货