【译】不愿意请教别人,但又是你应该知道的关于证书和PKI的一切知识

首先感谢Mike Malone写的这篇文章,原文地址如下 。这篇文章是我见过的介绍PKI最好的入门文章了。其次,我想说一下这篇文章的受众。我自己是后端开发工程师,所以我极其推荐后端开发工程师和架构师阅读本文。但是其实本文适合所有从事和web开发工作的相关人员,包括但不限定于前端开发工程师,移动端开发工程师,系统维护工程师和测试开发工程师。为什么后端工程师要知道PKI相关知识呢?因为PKI是一套通用的加密方案,可以保证你的系统安全的运行和通讯。安全是一切的基石,没有安全保护的系统就像穿着"新衣”的国王。你的系统用户越多,被入侵时受到的损害就越大。不要对安全抱有任何的侥幸,除非你的系统一点价值都没有。译者的水平有限,加之不是安全专业人士,翻译的错误和不准确在所难免,如有发现请留言或者发邮件到zwzm85@gmail.com。未经授权,请勿转载。 文章有点长,希望大家可以耐心看完。 翻译过程中有些注意事项列在此:

  1. 图片中的英文没有翻译。因为首先图片中的英文比较少,其次重新画图比较麻烦,而且很难画的原图一样。(BTW 如果大家的需求强烈,有时间会重画一些文字较多的图片)
  2. 有些英文我没有翻译,保持原文。基本上两个原因:第一,中文比较难以表达一些细微的差别;第二,国内也是广泛使用该英文,翻译以后大家可能更困扰
  3. 有些我自己对文章的额外说明,会写在译者语

证书和公钥基础设施(PKI)很难。这并不是和你开玩笑。我认识的很多聪明人都会刻意回避这个坑。我自己也回避了它很久,并且很羞愧没有去了解更多这方面的知识。导致一个恶性循环:因为虚荣心,我不再敢问这方面的任何问题,所以我再也没学到任何关于证书和PKI的知识。

出来混还是要还的。最终我还是被迫去学了这方面的知识,因为PKI可以让你的系统具有加密性。这是一个通用的、供应商无关的方案。它可以在任何地方都发挥作用,因此你的系统可以安全的运行在任何地方并且进行安全的通讯。在概念上,它很简单,并且超级灵活。它让你可以使用TLS,还有访问VPNs。你甚至可以忽略你的网络中的一切,却仍然拥有强安全性。所以它非常重要。

现在我已经掌握这项技能,但是我还是后悔没有早点去学它。PKI非常强大,非常有趣。其背后涉及的数学知识很复杂,并且其标准也是相当愚蠢且不合常理,但是其核心概念实际上特别简单。鉴定代码和设备身份的最好方式就是证书,并且身份信息在安全、监控、度量等其他很多领域都有着极其重要的作用。使用证书没有那么难,至少并不比学习一门新的语言或新的数据库更难。造成这些困难的是其令人厌烦且内容糟糕的文档。

这是一份缺失已久的学习手册。我认为大部分工程师可以在一个小时之内学会大部分重要的概念和常见的怪癖(译者语:PKI中有很多在现在看来很奇怪的地方,后文都有涉及)。这也正是这篇文章要实现的目标。一个小时的学习其实是一个非常小的投资,你一定会物超所值。

这篇文章的主要动机是教学。但是我使用了两个我在smallstep公司创建的开源项目来做不同场景演示,它们是:step CLIstep certificates。如果你想跟随本文一起,可以使用 brew intsall step 命令一次性安装好这两个软件(可以在这里参考完全安装指南)。

首先我们用一句话概括证书和PKI的作用:证书和PKI的目标就是把名称绑定到公钥上。就是这么简单。剩下的仅仅是实现细节。

概述和一些你应该了解的词语


后面会用到一些技术术语,所以在开始正文之前先定义它们。

实体(entity) 表示一切存在的东西,甚至只是存在于逻辑上或者概念上。你的电脑就是一个实体。你写的代码也是,你本身也是,你午饭吃的卷饼也是。甚至你在六岁时看见的鬼也是如此— 即使你妈妈是对的,那仅仅是你想象中虚构出来的东西。

每一个实体都有一个身份(identity) 。身份是什么,其实很难定义。身份就是让你成为你,听明白了吗?在计算机中,身份通常使用一系列属性来表示:群组,年纪,地址,最喜欢的颜色,鞋子的尺码等等。识别码(identifier) 和身份不是同样的东西。它是某些拥有身份的实体的唯一引用。我是迈克,但是迈克并不是我的身份。这只是个名称—识别码和名称可以认为是同义词,可以相互替换(至少在本文中如此)。

实体可以声明(claim)其拥有某些特定的名称。其他的实体应该可以验证该声明的正确性。但是声明并不一定要与名称有关系:我可以声明任何事情,包括我的年龄、你的年龄、访问权限和生命的意义等等。验证(Authentication)就是一个确认声明正确性的过程。

证书所有者(subject)这个实体在PKI中被称为订阅者(subscriber)或者终端实体(end entity)。给订阅者发放证书的实体被称为 证书颁发机构(certificate authority),简称CA,也被称为证书颁发者(issuer)。属于订阅者的证书有时也被称为终端实体证书或者叶子证书(leaf certificates)— 在我们讨论证书链的时候,这个称呼更加直观。属于CA的证书通常被称为根证书(root certificates)或者中间证书(intermediate certificates),具体属于哪一种要根据持有该证书的CA类型来确定(译者语:后面有具体说明,简单来说:如果某个证书的颁发者是它自己,它就是根证书)。最后,证书用户被称为依赖方(relying party,缩写为RP。译者语:请记住,后面文章出了很多次这个缩写),它会验证然后信任由CA颁发的证书。更令人困扰的是:一个实体既可以是订阅者同时又可以是依赖方。因为一个实体可以拥有其自己的证书,同时使用其他证书来校验远端设备(比如在双向TLS中就会发生)。

以上的背景知识足够我们开始后面的学习了。但是如果你想了解更多,你可以下载RFC 4949到你的电子书中。对于其他的小伙伴,让我们开始干货的学习吧。如何在实际中运用声明和验证呢?我们先从加密开始讲起。

MAC和签名认证


消息验证码(message authentication code,缩写为MAC),是一串用来验证实体发送消息的数据,以确保消息不会被篡改。其基本思路就是使用散列函数对于消息和双方共享密钥(密码)进行摘要计算。散列函数的输出就是MAC。然后把MAC和其对应的消息一起发送给接收者。

译者语:MAC可以看成一个带有密钥参数的升级版散列函数,其公式如下:

mac = hmac(data, key)

hmac是散列函数,data是原始消息,key是共享密钥

接收者也有共享密钥,所以他可以从接收到的原始数据中计算出他自己的MAC,然后和传递过来的MAC进行对比。散列函数有个特性:同样的输入,计算两次的结果一定是一样的。如果输入不同— 哪怕只有一位的差别—最后的结果也是完全不同的。所以如果接收者的MAC和消息携带的MAC匹配,那么至少可以确定这个消息的发送者一定知道共享密钥。假设只有值得信赖的实体才拥有共享密钥(换句话说,就是共享密钥没有泄漏),那么接收者可以信赖这条消息。

散列函数是单向的:想通过散列函数的输出反向算出输入,在计算上基本是不可行的。如何保证共享密钥的安全性就至关重要了:不然攻击者可能获取到你的MAC,反向执行散列函数,进而获取你的密钥。这就太糟糕了。是否可以拥有这个特性,关键取决于你创建MAC的散列函数的实现细节。我不会在本文中进一步讨论这些微妙的细节。只是郑重提醒大家:千万不要试图去创造自己的MAC算法(译者语:同样的道理,千万不要试图去创造自己的加密算法,除非你是安全领域的专家)。请使用HMAC

image

上述所有关于MAC的讨论仅仅是序曲:我们真正的主角是签名(signatures)。签名和MAC在概念上类似,但是使用密钥对(马上会有其定义)替换了共享密钥。在使用MAC的时候,至少有两个实体需要知道共享密钥:发送者和接收者。任何一方都可以生成有效的MAC,但是无法确定到底是哪一方(译者语:所以MAC不具备不可抵赖性)。签名不一样,其使用公钥验证,但是只能通过对应的私钥生成。因此,只拥有公钥的接收者只能验证签名,但是不能生成签名。这就让你可以牢牢地掌控谁可以签名。如果只有一个实体拥有私钥,那么就具备了不可抵赖性(non-repudiation):私钥的拥有者不能否认他们已经签名的事实。

如果你已经晕了,请冷静下来。它们之所以被称为签名,是因为它们确实类似现实世界中的签名。你肯定有过需要其他人审批的经历,对吧?你肯定想确保事后可以证明他们确实已经同意过了,对吧?很好,你要做的就是,写下你要申请的事情,然后让他们签字。

公钥加密技术让电脑可以"看见"


证书和PKI建立在公钥加密技术public key cryptography,也称为非对称加密技术—asymmetric cryptography)之上,使用密钥对(key pair)。一个密钥对包含一个可以向全世界分发和共享的公钥(public key),和一个必须被所有者秘密保管的对应私钥(private key)

我们来回顾一下上一个段落的内容,因为它十分重要:公钥加密体系的安全性取决于私钥的保密性。

你可以使用密钥对做两件事:

  • 使用公钥加密(encrtpt)数据。只有对应的私钥可以解密那些数据。
  • 使用私钥对数据签名。任何拥有对应公钥的人都可以验证该签名,证明是哪个私钥生成了该签名。

公钥加密技术是数学给计算机科学的一份神奇礼物。涉及到的数学知识很复杂,但是你并不需要理解就可以欣赏它的价值。公钥加密技术可以让计算机做到一些看似不可能的事情:它让计算机可以看见(译者语:原文使用了see,直译是看见,而且原作者也强调其和视觉的关系,所以这里还是翻译成看见。但是个人觉得"辨认"更加确切)。

好了,我来进一步解释一下—— 公钥加密技术让一台计算机(或者一段代码)可以向另一台计算机证明它知道一些事情,但是却不用直接分享它。正常情况下,为了证明你有某个密码,你必须分享它。但是任何你分享密码的人都可以自己去使用它。使用私钥就不会存在这样的问题。这类似视觉。如果你觉得我看起来像谁,那么你就知道我是谁— 通过看我的样子来校验我的身份(译者语:不要去考虑双胞胎,或者碟中谍电影中的易容术)。其他人不可能改变外形来模仿我。

公钥加密技术做了类似的事情。如果你知道了我的公钥(就是我的样子),那你就可以使用它跨越网络看见我。比如,你可以给我发送一个随机的大数字。我接到以后,对这个数字进行签名,然后再把签名发送给你。成功验证了那个签名,就是你在和我沟通的很好证明。这就有效的解决了计算机通过网络在和谁通讯的问题了。这种能力超级有用,只是在现实中我们司空见惯,认为其没什么了不起。但是在网络的世界中,这真的是一种魔法。感谢数学的力量。

证书:计算机和代码的驾照


如果你还不知道我的公钥该怎么办呢?别着急,这就是证书的使命。

本质上,证书真的十分简单。它就是一个包含公钥和名称的数据结构。然后对这个数据结构进行签名(译者语:颁发该证书的CA会对其签名)。这个签名把公钥绑定到了名称上。对证书进行签名的实体称为颁发者(或者证书颁发机构),拥有证书中的名称的实体称为所有者

假如,某个颁发者为Bob签署了一个证书,那么证书可以被解释为:"某个颁发者说Bob的公钥是01:23:42…"。这是某个颁发者对Bob的声明,并且颁发者还签名了该声明。所以你如果知道那个颁发者的公钥(译者语:如果知道CA的公钥会在后面说明,见根证书相关部分),你可以通过校验签名来验证其正确性。如果你信任那个颁发者,那你就可以信任该声明。因此,证书让你使用信任关系和颁发者的公钥,来获取其他实体的公钥(上文中Bob的公钥)。就是如此。本质上,这就是证书的全部。

image

证书就像计算机和代码的驾照。即使你从来没见过我,但是你信任DMV(译者语:Department of Motor Vehicles,车辆管理局),你就可以用我的驾照来做验证:证实驾照是合法的(比如检查防伪全息影像等),查看照片,看我本人的相貌,看我的名字。计算机使用证书做同样的事情:如果你从没见某个计算机,但是你信任某个证书颁发机构,你就可以使用证书来做验证:证实证书是合法的(比如检查签名等),查看公钥,通过网络"查看私钥“(就像上文描述那样,译者语:私钥无法查看,因为私钥必须被拥有着秘密保管,上文说过可以给私钥持有者发送一个随机大数字,让其签名,然后验证该签名。间接查看私钥),看证书所有者的名字。

image

让我们快速浏览一下证书证书的结构:

image

是的,我把故事做了简化。像驾照一样,证书还有其他的内容。驾照上会说明你是否是器官捐赠者(译者语:你如果是,驾照上会有个红心。在你意外死亡的时候,你的遗体可以被用作器官移植)以及你是否可以驾驶商用车辆。证书上会说明你是否是CA以及公钥是否可以用于签名或加密。两者也都有有效期。

会有大量的细节存在,但是都不会改变我以前说过的话:本质上,证书只是把公钥绑定到名称上。

X.509, ASN.1, OIDs, DER, PEM, PKCS, 哦我的天啊。。。


让我们看一下证书是如何表示成位和字节的。这部分内容真的非常复杂。事实上,我怀疑PKI中大部分的混乱和挫败感都源于此:证书和密钥的编码方式过于深奥,并且定义方式也很不明确。有点蠢,不过没办法(译者语:木已成舟,不过对于普通的开发者来说,基本不太需要了解这些)。

正常来说,如果人们不使用其他的限定词来讨论证书,指的就是X.509 v3证书(译者语:还可能有残存的X.509 v2证书或者PGP证书等)。更具体的说,他们通常讨论的是在RFC 5280中描述的PKIX变种,并通过CA/Brower Forum的基线进一步细化的证书。换句话说,指的就是基于HTTPS并且浏览器可以理解的证书。当然还存在其他类型的证书。SSH和PGP都有自己的证书类型。但是我们只讨论X.509证书。如果你能很好的理解X.509证书,你也可以很快理解其他证书(译者语:毕竟证书作用都是一样的)。

因为这些证书都有非常广泛的支持—有良好的工具库等等— 所以它们也经常被用于其他场景。它们也是internal PKI(后面定义)颁发的证书的最常见格式。重要的是,这些证书对于支持TLS和HTTPS的客户端和服务器,是开箱即用的。

X.509历史久远。X.509在1988年第一次被标准化,当时作为ITU-T(国际电信联盟标准机构)主导的范围更加广泛的X.500项目的子项目。X.500是当时电信公司想要建立一个全球电话簿的产物。这个目标没有实现,但是其中部分内容至今仍然发挥作用。如果你曾查看过X.509证书,肯定会好奇为什么一个为互联网设计的东西需要对地点、洲和国家进行编码。答案就是:X.509根本不是为互联网设计的。它是在三十年前为电话簿设计的。

image

X.509构建在ASN.1之上,这是另一个ITU-T标准(在X.208和X.680中被定义)。ASN.1代表抽象语法标记(Abstract Syntax Notion)。ASN.1是用来定义数据类型的标记。你可以把它想象成为X.509设计的JSON,但是它其实更像protobuf,thrift或者SQL DDL(译者语:ASN.1是用来定义数据类型的,所以更像JSON Scheme)。RFC 5280使用ASN.1把X.509证书定义为一个包含多种信息的对象,其中包括:名称,签名等等。(译者语:如果想进一步学习ASN.1,可以前往这个地址

ASN.1包含常用的数据类型,比如整型(integers),字符串(strings),集合(sets)和序列(sequences)。除此之外,它还有一个罕见但是非常重要的数据类型:对象标识符(object identifiers,缩写为OIDs)。OID类似URI,但是更加繁琐。它们是(应该是)全局唯一的标识符(译者语:可以在该网站查询到所有注册的OID)。结构上,OID是有层级关系的命名空间的数字列表。你可以使用OID为数据标注一个类型。字符串本身是一个字符串,但是如果我为这个字符串标注上2.5.4.3的OID(译者语:这个OID具体信息可以查询这里),那么它就不再是普通字符串了—— 而成为了X.509中的common name,简称CN(译者语:这里不对common name进行翻译,感觉翻译不能帮助理解。其含义就是全路径域名,在下图可以看到例子)。

image

ASN.1是抽象概念,其标准并没有规定如何把内容编码成位和字节。所以对于如何具体表示ASN.1的数据类型,就存在多种不同的编码规则。这个额外的抽象层原本应该很有用,但是实际上只是徒增麻烦。就像unicode和utf8之间的不同一样(译者语:如果对unicode,utf8,utf16,utf32感兴趣,可以看这里)。

ASN.1的编码规则有很多,但是只有一种在X.509证书和其他加密任务中被广泛使用:唯一编码规则(distinguished encoding rules,缩写为DER)(偶尔也会使用非权威的基本编码规则— basic encoding rules—缩写为BER)。DER是一种相当简单的类型—长度—值格式的编码方式,但是其实你不用关心这些,因为工具库会帮你做掉大部分的脏活累活。(译者语:这里就不举DER编码的例子了,大家可以网上找找。如果真的有人感兴趣,请留言,后面找机会再写一篇ASN.1和DER的文章。不过很枯燥,绝大部分读者应该用不到)

不幸的是,故事并没有到此结束。虽然你不用太多担心如何使用DER进行编码和解码,但是你必须先确定某个证书是普通的DER编码格式的X.509证书,还是其他编码格式的证书。常常会在两个维度上产生潜在的变化:我们可能看到一个不仅仅是原始DER格式的证书,或者我们看到的不单单是一个证书。

先看第一个维度,DER是二进制,但是二进制数据很难复制粘贴,也就很难通过网络传播。所以大部分证书都是分装在一个PEM格式的文件中(代表Privacy Enhanced EMail,另一个古怪的历史遗迹)。如果你曾经用过MIME,PEM和它类似:一个base54编码的payload(译者语:payload中文含义是有效载荷,翻译以后并不好理解,并且这个单词是常用词,所以本文对这个词不做翻译)夹在头部和脚注之间。PEM的头部有一个用来描述payload的标签。令人震惊的是,如此简单的工作却完成的一团糟,PEM标签在不同工具中常常是不一致的(RFC 7468试图标准化它,但是没有完成,并且往往也不被采用)。不再废话了,下面是一个PEM编码格式的X.509 v3证书:

-----BEGIN CERTIFICATE-----
MIIBwzCCAWqgAwIBAgIRAIi5QRl9kz1wb+SUP20gB1kwCgYIKoZIzj0EAwIwGzEZ
MBcGA1UEAxMQTDVkIFRlc3QgUm9vdCBDQTAeFw0xODExMDYyMjA0MDNaFw0yODEx
MDMyMjA0MDNaMCMxITAfBgNVBAMTGEw1ZCBUZXN0IEludGVybWVkaWF0ZSBDQTBZ
MBMGByqGSM49AgEGCCqGSM49AwEHA0IABAST8h+JftPkPocZyuZ5CVuPUk3vUtgo
cgRbkYk7Ong7ey/fM5fJdRNdeW6SouV5h3nF9JvYKEXuoymSNjGbKomjgYYwgYMw
DgYDVR0PAQH/BAQDAgGmMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAS
BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRc+LHppFk8sflIpm/XKpbNMwx3
SDAfBgNVHSMEGDAWgBTirEpzC7/gexnnz7ozjWKd71lzzwzm85gmailcomQDAgNH
ADBEAiAejDEfua7dud78lxWe9eYxYcM93mlUMFIzbWlOJzg+rgIgcdtU9wIKmn5q
FU3iOiRP5VyLNmrsQD3/ItjUN1f1ouY=
-----END CERTIFICATE-----

PEM编码格式证书的扩展名通常是.pem, .crt, 或者 .cer。原始DER编码格式证书的扩展名通常是.der。不幸的是,并不是所有证书都准守上面的扩展名规则,所以你可能会遇到不一样的情况。

再来看另一个变化维度:除了是否使用PEM编码会不一样,证书还可能会被封装在一个包装中。有好几种定义更大数据结构(还是使用ASN.1)的包装格式,可以容纳证书,密钥和其他内容。有些事情要求"证书",但是实际上需要的是一个含有证书的包装。所以请小心。

你可能遇到的包装格式是名为PKCS(公钥加密标准,Public Key Cryptography Standards)的标准套件的一部分,其由RSA实验室(实际的故事稍微复杂一些,但是我们不关心)发布。首先是PKCS#7,被IETF重塑为加密消息语法(Cryptographic Message Syntax,缩写为CMS),可以包含一个或者多个证书(简单点说,就是编码了一个完整的证书链)。PKCS#7通常在Java中使用,常见的扩展名是.p7b.p7c。另一种常见的包装格式是PKCS#12,它包含一个证书链(类似PKCS#7)和一个加密后的私钥。PKCS#12常用于微软公司的产品中。常见的扩展名是.pfx.p12。PKCS#7和PKCS#12也使用ASN.1定义数据格式。这就意味着它们两者都可以编码为原始DER或者BER或者PEM格式。根据我的经验,大部分都是原始DER格式。

密钥的编码同样的错综复杂,但是基本模式是一样的:使用ASN.1表示密钥格式,使用DER作为二进制编码格式,并且可能使用PEM(希望可以带上有用的头部信息)作为更加友好的展示格式。辨认你正在查看的密钥的类型,是一件半艺术半科学的事。如果你很幸运,RFC 7468可以给你很好的指导来判断PEM格式payload是什么。椭圆曲线密钥常常被这样标示,虽然这样做并没有被标准化。其他的密钥在PEM中仅仅简单的表示为"PRIVATE KEY"(译者语:这里原封不动的保留PEM中的文本描述)。这通常意味这是一个PKCS#8的payload,是一个包含密钥类型和其他元数据的私钥包装。这里是一个PEM编码格式的椭圆曲线密钥例子:

$ step crypto keypair --kty EC --no-password --insecure ec.pub ec.prv
$ cat ec.pub ec.prv
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc73/+JOESKlqWlhf0UzcRjEe7inF
uu2z1DWxr+2YRLfTaJOm9huerJCh71z5lugg+QVLZBedKGEff5jgTssXHg==
-----END PUBLIC KEY-----
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICjpa3i7ICHSIqZPZfkJpcRim/EAmUtMFGJg6QjkMqDMoAoGCCqGSM49
AwEHoUQDQgAEc73/+JOESKlqWlhf0Uzczwzm85gmailcom1DWxr+2LfTaJOm9hue
rJCh71z5lugg+QVLZBedKGEff5jgTssXHg==
-----END EC PRIVATE KEY-----

使用密码(共享密钥或者对称密钥)加密私钥也相当常见。如下所示(PEM包括Proc-TypeDEK-Info 两部分,显示这个PEM的payload使用AES-256-CBC 格式加密)(译者语:AES-256-CBC的含义是,使用AES加密算法,密钥长度是256位,加密模式是CBC):

-----BEGIN EC PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,b3fd6578bf18d12a76c98bda947c4ac9

qdV5u+wrywkbO0Ai8VUuwZO1cqhwsNaDQwTiYUwohvot7Vw851rW/43poPhH07So
sdLFVCKPd9v6F9n2dkdWCeeFlI4hfx+EwzXzwzm85gmailcomOj7kofyRyd4pEt+
Mj60xqLkaRtphh9HWKgaHsdBki68LQbObLOz4c6SyxI=
-----END EC PRIVATE KEY-----

PKCS#8对象也可以被加密,根据RFC 7468规定需要添加"ENCRYPTED PRIVATE KEY"这个头部标签。在这种情况下,你将不会拥有Proc-TypeDEK-Info 头部,因为这些信息会被编码在payload里。

公钥文件的扩展名通常是.pub 或者.pem 。私钥文件的扩展名是.prv, .key 或者.pem 。再次强调,并不全是这样。

快速总结一下。ASN.1用来为证书和密钥定义数据类型。DER是一系列把ASN.1转换成位和字节的编码规则。X.509使用ASN.1定义其结构。PKCS#7和PKCS#12是更大的数据结构,也使用ASN.1定义,可以包含证书和其他内容。它们通常被Java和Microsoft各自采用。因为原始二进制DER很难在网络中传播,所以大部分证书都是PEM编码格式的,也就是对DER进行base64编码并且为其加上标签。私钥通常被表示成PEM编码格式的PKCS#8对象。有时候也使用密码加密它。

如果你感到困惑,这不是你的错。世界就是这样混乱,我已经尽力让它简单清楚了。

公钥基础设施


了解证书是什么,这很好,但是这只是故事的一小半。我们来看看证书是如何生成和使用的。

公钥基础设施(Public key infrastructure,缩写为PKI)是颁发、分发、存储、使用、验证、撤销和其他管理证书和密钥并与之交互所需的所有内容的总称。这是一个有意模糊其具体含义的术语,类似"数据库基础设施"。

对于大多数PKI来说,证书是积木,证书颁发机构是地基。虽然这样说,但是PKI还包含许多其他东西。它包括库、定时作业、协议、约定、客户端、服务器、人员、进程、名称、发现机制和其他有效使用公钥加密所需的内容。

如果你从头开始构建自己的PKI,你会享有很多决策权。就像你构建自己的数据库基础设施一样。事实上,很多简单的PKI甚至不使用证书。当你在编辑自己电脑中的~/.ssh/authorized_keys 文件时,你就是在配置一个简单的无需证书的PKI,SSH会使用这个文件的内容把公钥绑定到名称上。PGP使用证书,但是不需要CA。它使用网络信任(web-of-trust)模型替代CA。你甚至可以使用区块链分配名称,再给它们绑定上公钥。如果你自己从头构建PKI,唯一强制的事情是:你需要使用公钥。其他任何事情都可以改变。

不过,你可能并不想完全从头构建一个PKI。我们将专注在网络上使用的PKI(译者语:即Web PKI,面向广大互联网用户的PKI。与之对应的是Internal PKI,面向企业内部用户,外面不可访问),Internal PKI(译者语:Internal PKI和Web PKI不做翻译,保持英文,因为其出现频率较高,对于开发者更加直观)也是基于Web PKI的技术栈,复用现有的标准和组件。

我们继续前进,不过还是要记住证书和PKI的简单目标:绑定名称到公钥上。

Web PKI vs Internal PKI

无论何时你访问一个HTTPS协议的URL— 比如当你加载这个网站,你就在通过浏览器和Web PKI进行交互。这是许多人(至少略微地,译者语:作者是想说很多人只是略微熟悉PKI,他不认为人们访问HTTPS URL就熟悉PKI)唯一熟悉的PKI。PKI看起来吱吱作响、颤颤巍巍,但是它通常都起作用。尽管它自身有很多问题,但是它极大提升了网络安全性,并且对用户几乎是透明的(译者语:透明到几乎大部分使用者都不知道PKI这个东西。。。)。你应该在任何需要和外部通过因特网通讯的时候都使用它。

Web PKI的标准大部分定义在RFC 5280中,然后被CA/Browser Forum(缩写为,CA/B或者CAB Forum)重新定义。它有时也被称为"Internet PKI"或者PKIX(纪念创造它的工作组)。PKIX和CAB Forum文档涵盖了很多方面。它们定义了上节讨论的各种证书。它们也定义了:"名称"是什么以及它在证书中的位置,什么签名算法可以运用,依赖方如何确定证书的颁发者,如何指定证书的有效期(发放日期和过期日期),如何撤销证书,证书链验证如何工作,CA如何确定某人是否拥有一个域名,还有很多其他内容。

Web PKI很重要,因为在默认情况下,Web PKI证书适用于浏览器和其他使用TLS的所有东西。

Internal PKI是你自己运行的PKI,为你自己的东西提供身份认定:基础设施产品,比如服务、容器和虚拟机;企业IT产品;企业终端,比如笔记本和手机;和其他一切需要的代码和设备。它允许你验证和建立加密通道,这样你的产品可以运行在任何地方并可以安全通讯,甚至可以在公共互联网上进行。

既然有了Web PKI,那为什么还要维护自己的Internal PKI呢?简单点回答就是Web PKI不是为支持内部用途而设计的。即使像Let's Encrypt这样的CA,虽然它提供免费的证书和自动化供给(译者语:指证书的审批和发放),你还需要解决限流可用性的问题。所以你如果有很多服务并需要在任何时候进行部署,Web PKI就不能满足你的需求了。

此外,使用Web PKI,你几乎不能控制一些重要的细节,比如证书生命周期,撤销机制,延期进程,密钥类型和加密算法(所有重要的内容后续我们都会解释)。

最后,CA/Browser Forum Baseline Requirements 禁止Web PKI CA绑定私有IP(比如,属于10.0.0.0/8子网段的ip)或者私有DNS名称 — 其不完全合格,且不能被全球公共DNS解析(比如,不能绑定kubernetes集群的DNS名称,类似foo.ns.svc.cluster.local,译者语:.local不能在互联网中注册成为顶级域名(Top Level Domain),所以你不能注册一个以.local结尾的公共URL)。如果你需要在证书中绑定很多这种类型的名称、发放很多证书、或者控制证书细节,你需要搭建自己的Internal PKI。

在下一节中,我们将会看到信任(或者缺少信任)也是避免在内部环境使用Web PKI的另一个原因。简而言之,在公共网站和公共API中使用Web PKI。在其他情况使用你自己的Internal PKI。

信任和值得信任

信任的故事

前面我们学过可以将证书理解成一个声明,类似:"颁发者说所有者的公钥是吧啦吧啦吧啦"。颁发者会对这个声明进行签名,这样第三方就可以验证了。在这个描述中,我们掩盖了一些重要的事情:第三方如何知晓颁发者的公钥?

答案很简单:第三方在信任存储(trust store)中提前配置好一个可信赖的根证书(root certificate,也叫信任锚点)列表。如何提前配置在任何形式PKI都是一个很重要的内容。其中一个选择是用另一个PKI来引导:你可能有一些自动化工具,它可以使用SSH把根证书拷贝给第三方,这利用的是前面讨论过的SSH PKI。如果运行在云上,你的SSH PKI反而是由Web PKI加上云提供商做的验证— 包括创建账户时的验证和提供你信用卡的验证 — 来引导。如果你一直沿着这个信任链往后足够远,你通常会发现人的踪迹:每个信任链都终结于真实世界。

image

在信任存储中的根证书都是自签名(self-signed)的。颁发者和所有者相同。逻辑上类似这样的声明:"Mike说Mike的公钥是吧啦吧啦吧啦"。自签名证书的签名保证所有者/颁发者知道对应的私钥,但是任何人都可以生成带有他想要的名称的自签名证书。所以源头特别关键:只有自签名证书被放入信任存储的过程是可被信任的,其本身才是被信任的。macOS使用keychain管理信任存储。在很多Linux发行版中,信任存储就是一些简单的存在于/etc目录或者磁盘其他地方的文件。如果你的用户可以修改这些文件,那么你最好还是信任你所有的用户(译者语:既然这些用户可以修改这些文件,意味着这些用户可以随时在其中添加自己想要的根证书,而根证书有可以颁发其他叶子证书。所以这些用户在PKI上的权限无穷大,应该禁止这样的用户,即使有这样的超级管理员,也应该记录其操作日志,定期审核这些日志)。

那么信任存储从哪里来呢?对于Web PKI来说,最重要的依赖方就是浏览器。主流浏览器和其他使用TLS的依赖方使用的默认信任存储来源于以下四个组织的维护:

操作系统一般都自己携带信任存储。FireFox浏览器则带有自己的信任存储(通过TLS从mozilla.org获取— 使用其他信任存储来启动Web PKI)。编程语言和其他非浏览器软件,比如curl,基本都是默认使用操作系统的信任存储。因此,默认情况下使用的信任存储已被预先安装,并且可以通过软件升级进行更新(通常使用其他PKI来进行代码签名)。

这些程序所维护的信任存储通常包含100多个证书颁发机构。你可能已经知晓了其中几个规模较大的CA:Let's Encrypt,Symantec,DigiCert和Entrust等等。详细了解他们会很有趣。如果你要在编程中使用它们,Cloudflare的cfssl项目维护了一个github仓库,其中包含来自各个信任存储的授信证书和配套的证书包(我们马上会讨论到)。更加人性化的体验是,你可以查询Censys来知晓哪些证书被Mozilla苹果微软信任。

值得信任

有一百多个证书颁发机构在描述层面被信任— 浏览器和其他工具默认信任这些CA颁发的证书。但是这并不意味着他们在道德层面上也是值得信赖的。恰恰相反,有很多Web PKI证书颁发机构为政府提供虚假证书来窥探网络流量和仿冒站点的案例被记载。有些"被信赖”的CA在司法权专制的区域(比如:。。。译者语:这个真不能翻译,怕被和谐)运作。当然民主国家在这些问题上也并不处于道德制高点。NSA(译者语:美国国家安全局)利用一切机会暗中颠覆Web PKI。在2011年,“被信赖”的DigiNotar和Comodo两家证书颁发机构都被入侵过。DigiNotar的入侵者可能是NSA。CA错误颁发畸形或者不兼容的证书也是屡见不鲜。因此,尽管这些CA事实上是可靠的,但是作为一个群体,依据经验来看他们不值得信赖。我们很快就可以看见,Web PKI安全性等同于最不安全的一个CA,所以很糟糕。

浏览器社区采取了一些行动来解决这个问题。CA/Browser Forum基线需求制订了一些授信证书颁发机构在颁发证书前需要遵守的规则。作为WebTrust审计程序的一部分,CA将被审核是否符合这些规则。这是某些根证书程序添加CA到其信任存储(比如Mozilla)中的前提条件。

然而,如果你在内部环境使用TLS,你可能并不希望信任这些公共CA。如果你那样做了,你可能就已经给NSA和其他人打开了一扇门。你正在接受一个事实,你的安全取决于那100多家组织(译者语:授信证书颁发机构)。可能你不在意,但是还要提醒你这个隐患。

联邦

更糟糕的是,Web PKI依赖方(RPs)信任信任存储中的每个CA对任何订阅者签署的证书(译者语:只要一个CA在信任存储中,那么它对任何网站颁发的证书都会被依赖方信任,即使某个网站并不使用该CA提供的服务。见后面Google的例子)。这导致Web PKI整体安全性只等同于最不安全的那个Web PKI CA。2011年DigiNotar的攻击事件揭示这个问题:攻击导致一个伪造的google.com证书被颁发出去了。尽管Google和DigiNotar没半毛钱关系,但是这个证书还是被主流web浏览器和操作系统信任。更多其他公司的伪造证书也被颁发出去,包括Yahoo!, Mozilla和Tor Project。最终,DigiNotar根证书被从几个主要的信任存储中删除,但是已经造成了大量无法挽回的损失。

最近,森海塞尔(译者语:一个著名的耳机厂商)被曝出使用他们的HeadSetup软件在信任存储中安装自签名根证书,并且把对应的私钥嵌入在软件的配置中。任何人都可以抽取这个私钥,然后使用它颁发任何域名的证书。任何在其信任存储中安装森海塞尔证书的电脑都将信任这些伪造证书。哎呦,这完全摧毁了TLS。

有些机制可以减轻这些风险。证书颁发机构授权(Certificate Authority Authorization,简称CAA)可以让你使用一种特殊的DNS记录限制哪些CA可以为你的域名颁发证书。证书透明化(Certificate Transparency,简称CT)(RFC 6962)要求CA提交其颁发的所有证书信息给一个中立的观察者,该观察者维护一个用来检测欺诈证书的公开证书日志。证书透明化提交后的加密证据会被添加到颁发的证书中。HTTP公钥锁定(HTTP Public Key Pinning,简称HPKP或者pining)允许订阅者(网站)告知RP(浏览器)对于特定的域名只接受证书中某些公钥(译者语:Chrome已经在版本72中删除了对HPKP的支持)。

上述这些问题都需要RP支持。CAB Forum现在强制在浏览器中进行CAA检查。有些浏览器也支持CT和HPKP。对于其他RP(比如,大多数的TLS标准库的实现)而言,这些工作都不是强制性的。这样的问题还会反复出现:很多证书策略必须强制RP实施,但是RP基本不会去做(编者语:RP中浏览器支持这些策略速度最快)。如果RP不检查CAA记录也不校验CT提交的证据,那这些工作不会有任何效果。

在任何情况下,如果你运行自己的Internal PKI,那么你应该为内部维护一个单独的信任存储。也就是,不要把你的根证书加入已有的系统信任存储中,而应该配置内部TLS请求只使用你的根证书。如果你想要更好的内部联邦(比如,你想要限制内部CA可以颁发什么证书),你可以尝试CAA记录,然后正确配置RP。你可能还希望查看一下SPIFFE,这是一个不断发展进化的标准化工作,解决了这个问题以及其他和Internal PKI相关的问题。

证书颁发机构是什么

我们已经谈论了很多有关证书颁发机构(CA)的话题,但是还没有真正定义过它。CA就是一个被信赖的证书颁发者。它通过签名证书保证公钥和名字的绑定关系。本质上,证书颁发机构仅仅是另一个证书和一个其对应的用来签名其他证书的私钥。

很明显,需要其他的逻辑和流程来辅助它们。CA需要将其证书分发到信任存储中,接受并处理证书请求,然后颁发证书给订阅者。暴露远程API接口自动化完成这些任务的CA被称为在线CA。在信任存储中有自签名根证书的CA被称为根CA。

中间证书,信任链和证书包

CAB Forum基线需求 规定Web PKI根CA的私钥只能通过命令来签发证书。也就是,Web PKI根CA不能自动化签发证书。他们不能够在线。这对于任何大规模CA的操作都是一个麻烦。你不可能让一个人在电脑旁手动敲击命令来满足所有的证书需求。

这个规定主要是考虑安全性。Web PKI根证书广泛分布在信任存储中,很难被撤回。泄露根CA私钥将会影响上亿的用户和设备。因此,最佳实践就是让根私钥处于离线状态,理想情况下,位于一些专用硬件连接的空气隔离机器上(语者语:原文是air gapped machine,其含义是没有任何网络接口的机器,即不能通过外部网络连接),拥有良好的物理安全性,并且严格执行操作程序。

尽管不是那么必要,但是很多Internal PKI也采用相同的实践。如果你可以自动化根证书的轮换工作(比如,使用配置管理或者编排工具更新你的信任存储),那么你就可以轻而易举的更换被泄露的根密钥。通常人们特别困惑如何进行Internal PKI的根私钥的管理,以至于会推迟或者阻止Internal PKI的部署。你的AWS账户凭据至少同样的敏感。你是怎么管理这些凭据的呢?

为了在根CA离线的情况下,证书颁发依然具有可扩展性(比如,尽可能的自动化),根私钥只被用来偶尔签发少量的中间证书。中间CA(也称为次级CA)使用中间私钥来给订阅者签发叶子证书。中间证书一般不会包含在信任存储中,这使得撤销和更换它们更加容易,所以一般中间CA颁发证书都是在线的、自动化的。

证书包— 叶子,中间,根— 形成一条链(称为证书链)。中间证书签名叶子证书,根证书签名中间证书,根证书自己签名。

image

在技术层面,这又是一种简化。你完全可以创造出一个更长的信任链甚至更复杂的图(比如,通过交叉证书)。一般来说不鼓励这样做,因为会在很短时间内变得特别复杂。在任何情况下,终端实体证书在图中都是叶子结点。因此被称为"叶子证书"。

当你在配置订阅者(比如,一个web服务器,可以是Apache或者Nginx或者Linkerd或者Envoy)时,你不仅要提供叶子证书,还需要提供包含中间证书的证书包。因为可以包含一个完整的证书链,PKCS#7和PKCS#12有时会被运用在此。更多的时候,证书链被编码成一系列用线分割的PEM对象。有些订阅者期待证书按照从叶子到根的顺序排列,另一些期待从根到叶子,还有一些不限定顺序。不一致更加讨厌。请到Google和StackOverflow上寻求帮助,或者自己试错。

这里有个例子:

$ cat server.crt
-----BEGIN CERTIFICATE-----
MIICFDCCAbmgAwIBAgIRANE187UXf5fn5TgXSq65CMQwCgYIKoZIzj0EAwIwHzEd
MBsGA1UEAxMUVGVzdCBJbnRlcm1lZGlhdGUgQ0EwHhcNMTgxMjA1MTc0OTQ0WhcN
MTgxMjA2MTc0OTQ0WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwWTATBgcqhkjOPQIB
BggqhkjOPQMBBwNCAAQqE2VPZ+uS5q/XiZd6x6vZSKAYFM4xrYa/ANmXeZ/gh/n0
vhsmXIKNCg6vZh69FCbBMZdYEVOb7BRQIR8Q1qjGo4HgMIHdMA4GA1UdDwEB/wQE
AwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFHee
8N698LZWzJg6SQ9F6/gQBGkmMB8GA1UdIwzwzm85gmailcomjCINd6ztucMf8Bun
D++sMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDBWBgwrBgEEAYKkZMYoQAEERjBEAgEB
BBJtaWtlQHNtYWxsc3RlcC5jb20EK0lxOWItOEdEUWg1SmxZaUJwSTBBRW01eHN5
YzM0d0dNUkJWRXE4ck5pQzQwCgYIKoZIzj0EAwIDSQAwRgIhAPL4SgbHIbLwfRqO
HO3iTsozZsCuqA34HMaqXveiEie4AiEAhUjjb7vCGuPpTmn8HenA5hJplr+Ql8s1
d+SmYsT0jDU=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBuzCCAWKgAwIBAgIRAKBv/7Xs6GPAK4Y8z4udSbswCgYIKoZIzj0EAwIwFzEV
MBMGA1UEAxMMVGVzdCBSb290IENBMB4XDTE4MTIwNTE3MzgzOFoXDTI4MTIwMjE3
MzgzOFowHzEdMBsGA1UEAxMUVGVzdCBJbnRlcm1lZGlhdGUgQ0EwWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAAT8r2WCVhPGeh2J2EFdmdMQi5YhpMp3hyVZWu6XNDbn
xd8QBUNZTHqdsMKDtXoNfmhH//dwz78/kRnbka+acJQ9o4GGMIGDMA4GA1UdDwEB
/wQEAwIBpjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0TAQH/
BAgwBgEB/wIBADAdBzwzm85gmailcomnSg25G1V3rO25wx/wG6cP76wwHwYDVR0j
BBgwFoAUcITNjk2XmInW+xfLJjMYVMG7fMswCgYIKoZIzj0EAwIDRwAwRAIgTCgI
BRvPAJZb+soYP0tnObqWdplmO+krWmHqCWtK8hcCIHS/es7GBEj3bmGMus+8n4Q1
x8YmK7ASLmSCffCTct9Y
-----END CERTIFICATE-----

依然令人讨厌且怪诞,但并不是高深的学问。

证书链验证

因为中间证书并没有被放入信任存储,所以它们需要和叶子证书一样被分发和验证。上文说过,在配置订阅者时需要提供中间证书。然后订阅者会把它们一起发送给第三方。在TLS中,这会发生在建立TLS连接的握手阶段。当订阅者给第三方发送包含可以追溯到信任根的中间证书的它的证书以后,第三方校验叶子和中间证书的过程被称为证书链验证。

image

完整的证书链验证算法很复杂。包括校验证书有效期、撤销状态、各种证书策略、密钥使用限制和很多其他内容。PKI第三方能否正确实现该算法及其重要。人们会很随意的禁用证书链验证功能(比如,给curl加上-k标志)。请你不要这样做。

千万不要禁用证书链验证。正确使用TLS并不困难,而证书链验证在TLS中扮演身份验证的角色。人们有时会辩解整个通道还是加密的,所以禁用证书链验证无所谓。这是错误的,其关系重大。缺少身份验证的加密没有任何价值。这很像告解室:你的谈话是私密的,但是你并不知道窗帘另一边是谁。但是这里并不是教堂(译者语:告解室一般在教堂中,大家应该在欧美电影中看过类似的场景),而是互联网。所以千万不要禁用证书链验证

密钥和证书生命周期

在TLS中使用证书之前,你需要弄清楚如何从CA获取它。抽象来说,这是一个相当简单的过程:需要证书的订阅者先生成一个密钥对,然后把证书请求提交给证书颁发机构。CA会确保将要绑定到证书上的名称是正确的,如果正确,签名并返回一个证书。

证书会过期,在此以后任何第三方都将不会再信任它。如果你任然想用那个过期的证书,你需要重新申请然后替换它。如果想要第三方在证书过期前不再信任某个证书,你(有时)可以撤销它。

和很多PKI一样,这个简单的过程其实错综复杂。在被隐藏的细节中,包含两个计算机科学最难的问题:失效缓存和命名。当然,在你弄明白怎么回事以后,这一切都很容易推理出来。

命名

历史原因,X.509使用X.500的唯一名称(distiguished name,简称DN)来命名证书的所有者(订阅者)。DN就是一个普通名字(对我来说,就是"Mike Malone")。它可以包含地域,国家,组织,部门和其他一些不相关的东西(回忆一下,这东西最初是用在数字电话簿上的)。没人理解唯一名称。它们不适合网络。避免使用它们。如果你必须要使用,请务必保证它们简单。你不必使用每个字段。事实上,你也不应该那样做。普通名称可能能满足的需求,当然如果你是一个寻求刺激的人,可能会需要组织名。

PKIX最初规定网站的DNS域名应该绑定到DN的普通名称上。最近,CAB Forum已经弃用这个实践并且使整个DN都变成可选项(见Baseline Requirements的7.1.4.2节)。取而代之的是,使用所有者可选名称(subject alternative name,简称SAN)绑定名称成为最佳实践。

常见有四种SAN,全部都绑定到被广泛使用的名称上:域名(DNS),电子邮件地址,IP地址,和URI。这些已经在我们感兴趣的环境中被认为是有唯一性的,并且它们很好的映射到我们有兴趣鉴定身份的东西上:电子邮件地址代表人,域名和IP地址代表机器和代码,URI满足其他幻想。请使用SAN。

image

也请注意,Web PKI允许多个名称绑定到同一个证书上,也允许在名称中使用通配符。一个证书可以有多个SAN,也可以拥有类似*.smallstep.com这样的SAN。这对于需要响应多个域名(比如,smallstep.comwww.smallstep.com)的网站非常有用。

生成密钥对

获取名称以后,我们在创建证书之前还需要生成密钥对。回忆一下,PKI的安全性极其依赖一个简单的不变量:唯一知道私钥的就是在证书中拥有相关名称的订阅者。为了确保这个不变量成立,最佳实践就是订阅者自己生成密钥对,这样就只有他知道。一定不要通过网络传递私钥。

你需要确定要用什么类型的密钥。这完全是另一片文章该解决的事,但是这里有一些快速指导(截止到2018年12月)。目前有从RSA转换到椭圆曲线密钥(ECDSA或者EdDSA)的缓慢趋势。如果你决定使用RSA,那么密钥长度至少是2048位,但是不要超过4096位。如果你使用ECDSA,P-256可能是最好的选择(openssl中的secp256k1或者prime256v1)…如果你担心NSA(译者语:NSA在算法中设置了后门,详情见这个网址),你可以选择类似EdDAS的Curve25519的其他方案(但是这些密钥的支持还不太好)。

这是一个使用openssl生成椭圆曲线P-256密钥对的例子:

openssl ecparam -name prime256v1 -genkey -out k.prv
openssl ec -in es256.key -pubout -out k.pub

这是使用step生成同类密钥对的例子:

step crypto keypair —key EC —curve P-256 k.pub k.prv

你也可以通过编程方式现实,并且可以让私钥不落盘(译者语:这样更加安全,比起磁盘文件,黑客更难获取内存中的信息)。

请做出你的选择。

颁发

一旦订阅者有了名称和密钥对,下一步就是从CA获取叶子证书。CA希望校验(证明)两件事:

  • 绑定到证书上的公钥是订阅者的公钥(比如,订阅者知道对应的私钥)
  • 绑定到证书的名称是订阅者的名称

前者可以通过一个简单的技术机制实现:证书签名请求。后者更加困难。抽象来说,这个过程称为身份证明或者注册。

证书签名请求

订阅者请求证书时,会提交一个证书签名请求(CSR)给证书颁发机构。CSR也是一个ASN.1的数据结构,使用PKCS#10定义。

类似证书,CSR是一个包含公钥,名称和签名的数据结构。它使用于CSR中的公钥相对应的私钥进行自签名。这个签名可以证明创建CSR的人知道私钥。这也使得CSR可以被传播,而不用担心被入侵者修改(译者语:入侵者不知道私钥,修改CSR以后,没办法生成合法的签名)。

CSR包含很多指定证书细节的可选项。但实际上,大多数CA都会忽略这些内容。取而代之,大多数CA会提供模板或者管理界面来收集这些信息。

你在step中可以使用一条如下命令就生成密钥对同时创建CSR:

step certificate create -csr test.smallstep.com test.csr test.key

OpenSSL功能更加强大,但是更加繁琐

身份证明

一旦CA接受CSR并且验证它的签名,下一步它要做的事情就是弄清楚证书绑定的名字是不是订阅者真正的名字。这个问题很棘手。证书的重点就是允许RP对订阅者进行身份验证,但是在颁发证书之前,CA应该如何鉴定订阅者的身份呢?

答案是:看情况而定。对于Web PKI,有三种证书。它们之间最大的区别在于如何鉴定订阅者的身份和使用何种身份证明的类型。它们是:域名验证证书(domain validation,简称DV),企业验证证书(organization validation,简称OV),和增强验证证书(extended validation,简称EV。译者语:也有翻译成扩展验证的)。

DV证书绑定DNS名称,基于可以证明拥有该域名来颁发。证明通常通过一种简单的方式进行,比如发送确认邮件给位于WHOIS记录中的通讯地址。ACME协议,最初由Let's Encrypt开发并应用,提升该过程的自动化性:实现ACME的CA不会发送验证邮件,而是给订阅者发送一个需要完成口令验证以证明其拥有该域名。ACME规范中规定口令验证部分可以扩展,常见的口令验证有在给定URL中插入随机数(HTTP口令验证,语者语:CA会给你一个验证口令challenge,你需要放一个文件在类似http://<Your_Domain>/<challenge>上,然后订阅者告知CA文件已经准备好了,CA会去校验文件是否存在和已经文件内容是否合法,具体细节CA之间会有差异,可以自行到你的CA官网查询)和在DNS TXT记录中放置随机数(DNS口令验证)。

OV和EV证书构建在DV证书之上,包含拥有绑定域名的机构的名称和位置信息。它们不只是把证书和域名关联,还关联到拥有它的合法实体。各个CA校验OV证书的过程并不一致。为了解决这个问题,CAB Forum引入了EV证书。其包含了相同基本信息但是强制执行严格的验证(身份证明)。EV证书申请过程可能会持续数天或者数周的时间,会包含公司官员签名(用笔)的公开记录搜索和各种证明(纸质)(译者语:这里有很多种证明,比如godaddy需要个人账单证明,账单证明等)。万事俱备之后,当你在访问使用EV证书的网站时,有些浏览器会在地址栏中展示企业名称。除了在浏览器中的这点好处之外,Web PKI依赖方并没有大范围使用或者强制使用EV证书(编者语:其实很多大型网站都没有使用EV证书,主要原因应该是很少有用户会在意在地址栏中展示的企业名,而且EV证书比较贵,投入产出比太低。更糟糕的是有些浏览器已经取消了这个展示,比如iOS12中的Safari)。

image

本质上,Web PKI的RP只需要可以证明域名拥有权的DV证书。重要的是弄清楚DV证书真正证明了什么。它应该证明了请求证书的实体拥有对应的域。但实际上,它证明:在某个时间点,请求证书的实体可以读取确认邮件、或者配置该域的DNS、或者通过HTTP提供密钥。但是这些过程依赖的DNS、email和BGP的底层安全性堪忧。已经有通过攻击这些基础设施来获取伪造的证书的事情发生了

对于Internal PKI你可以使用任何方式来进行身份验证。你完全可以有比在Web PKI中依赖DNS或者email验证更好的方案。乍一听似乎很难,但实际并不是。你可以复用现有的信任基础设施:你提供的东西,你也应该能证明其身份。如果你信任Chef或Puppet或Ansible或Kubernetes部署代码到服务器上,那么你可以信任它们的身份证明。如果在AWS上使用原生的AMI,你可以使用实例身份文档GCP(Google云)和Azure(微软云)也有类似的功能)。

你的服务供应基础设施(译者语:原文是provisioning infrastructure。单词provisioning原本的含义是"供应","预备"。但是这样翻译比较奇怪。在电信行业一般翻译成服务开通或者服务供应,在此采用后者。)必须具备一定的身份,这样才可以把正确的代码部署在正确的地方,然后启动。在你的Internal PKI中,你可以利用这个信息和信任关系来配置RP的信任存储和引导订阅者。你需要做的就是想办法让服务供应基础设施告诉CA正在运行的节点身份是什么。顺便提一下,step证书就是为此而生的。

有效期

正常情况下,证书会过期。这本身并不是硬性要求,但是既成事实了。证书有效期非常重要,因为使用证书是去中心化的(译者语:原文是disaggregated,含义是分列的,分类的。这里作者主要是想表明证书使用没有一个中央机关来管理,所以翻译成去中心化):在RP验证证书时,通常并不存在一个中央机关可以查询。如果不会过期,那么证书将永远被信任。安全领域中有个经验法则是:当我们永远被信任时(译者语:原文是approach,表示"处理","接近"。但是结合上下文意思,翻译称为"被信任"),凭证被泄露的可能性将接近100%。因此,证书会过期。

实际上,X.509证书有一个有效期:一个颁发时间,一个不早于时间和一个不晚于时间。时间一直向前流逝,最终会越过不晚于时间,证书也就寿终正寝了。表面看起来没什么问题,但有几个微妙之处需要注意。

首先,某个RP完全可能因为漏洞(或者糟糕的设计)而接受一个过期的证书。再次强调,使用证书是去中心化的。检查证书是否过期是每个RP的责任,而有时候它们会出错。比如你代码依赖的系统时钟没有正确的同步。一个常见的场景是系统的时钟被重新设置为unix 时间,那样其不会信任任何证书,因为它会认为现在是1970年1月1日(译者语:Java开发应该很熟悉这个时间,System.currentTimeMillis()方法返回的就是当前时间和协调世界时1970年1月1日午夜的时间差。具体见这里)— 早于任何近期颁发的证书的不早于时间。所以一定要确保你的时钟是正确同步的。

订阅者方面,证书过期以后,其对应的私钥需要被妥善处理。如果密钥对是用来签名/验证身份的(比如,在TLS中使用),一旦其不再有用,你要删除私钥。保留签名用的密钥会有不必要的安全风险:除了伪造签名别无他用。但是如果你的密钥对是用来加密的,那就另当别论了。只要你还需用使用该密钥加密数据,你就要保存好私钥。如果你曾经被告知不要使用同一个密钥对既签名又加密,这就是主要原因。使用同一个密钥对既签名又加密,会使得不再需要签名的私钥时候没法实现密钥管理的最佳实践:只要你需要解密数据,你就必须一直保留签名用的密钥。

续签证书

如果你仍在使用一个即将过期的证书,你会想在其过期之前续签它。实际上,Web PKI并没有标准的续签流程— 不存在延长证书有效期的正式方法。你只需要使用新的证书替换掉已经过期的。所以续签证书的过程和发放的过程一样:生成并提交CSR然后满足身份证明的要求。

对于Internal PKI你可以做的更好。最简单的方法是你可以用类似双向TLS这样的协议对原先的证书进行续签。CA可以验证订阅者传递的客户端证书,使用延长后的有效期重新签署它,然后在响应中返回新的证书。这使得自动化续签证书非常简单,但是需要订阅者定期去中央机构登记。你可以用这个登记流程轻松的构建监控和撤销服务。

所有使用场景中最难的是,记住在证书过期之前续签它。几乎所有管理公共网站证书的人都有意外过期的经历,就像这个错误一样。我的建议是:如果被伤到了,那就多做(译者语:勤能补拙)。使用短生命周期的证书。那会迫使你优化流程来自动解决这个问题。Let's Encrypt使自动化变得简单,会颁发90天的证书,这对于Web PKI来说很好。对于Internal PKI,你应该选择更短的时间— 四个小时或更短。这里有一些实现上的挑战— 无痛证书更新会有点棘手— 但是付出是值得的。

小提示,你可以使用step在命令行里校验证书的过期时间:

step certificate inspect cert.pem --format json | jq .validity.end
step certificate inspect https://smallstep.com --format json | jq .validity.end

这只是一件不起眼的小事,但是如果你在一个bash脚本中把它和DNS zone transfer(译者语:这里不做翻译,大部分的技术文档都是保留英文原文的,具体含义见这里)整合起来,你将可以有效的监控所有域的证书有效期,防微杜渐。

撤销证书

如果私钥泄露了或者只是不再需要某个证书了,你可能希望撤销它。也就是,你可能希望主动将其标记为非法,从而立刻阻止RP继续信任它,即使它还没过期。撤销X.509证书是个大坑。类似有效期,需要RP强制执行撤销。但是也和有效期不同,撤销状态不能编码在证书中。RP需要通过一些额外的流程来确定证书的撤销状态。除非明确配置,不然大多数Web PKI的TLS RP都不会关心证书的撤销状态。换句话说,默认情况下,大部分TLS实现都会愉快的接受已经被撤销的证书。

对于Internal PKI来说,趋势是接受现实并且使用被动撤销。即颁发过期时间足够近的证书,这样就不需要撤销了。如果你想"撤销"证书,只需要简单的禁止证书续签然后等待其过期。这依赖于你使用短生命周期的证书。到底要多短呢?这取决于你的威胁模型(这就是安全专业人士耸肩对你说的话,译者语:原文是 ¯_(ツ)_/¯,这是一个emoji表情🤷‍♀️,表示对某个话题不了解,或者不关心)。常见的有24小时,但是也有很多类似五分钟这样更短的过期时间。如果你选择的时间特别短,那么你会面临来之可扩展性和可用性的巨大挑战:每个续签都需要和在线CA进行交互,因此你的CA基础设施最好要有良好的扩展性和高可用性。缩短证书的生命周期同时,请记住保证你的时钟同步,不然你会痛苦不堪。

被动撤销不适用于web和其他场景,你的首要工作就是停止并且重新思考被动撤销。如果你真的必须撤销,你有两个选项:

  • 证书撤销列表(Certificate Revocation Lists,简称CRLs)
  • 在线证书状态协议(Online Certificate Status Protocol,简称OCSP,译者语:原文有误,写的是Online Certificate Signing Protocol)

CRL和大量其他事情被定义在RFC 5280中。其仅仅是一个的已撤销证书的序列号组成的列表。CRL分布点,一个包含在证书中的URL,会提供这个列表服务。需要第三方下载这个列表,然后在验证证书的时候查询列表获取撤销状态。这样会有几个明显的问题:CRL可能很大,并且分布点也会挂掉。如果RP完全检查CRL,其要缓存分发点的全部响应,并且只能定期同步(译者语:不可能实时更新CRL缓存)。CRL基本会被缓存数天时间。如果CRL传播时间太久了,你也可以考虑被动撤销(译者语:没办法的事情,想让世界上所有的CRL缓存都失效,基本不可控。你的证书有效期如果不长,真的可以考虑等着它自己过期)。PR允许异常依然可以访问(译者语:原文是fail open。这是安全领域一个基础概念,是指系统在异常情况下,允许访问。与之相反的是fail closed)— 如果CRL分布点挂了依然接受证书请求— 的情况也很常见。这会导致安全问题:你可以对CRL分布点进行拒绝服务攻击,让RP接受已经被撤销的证书。

即使你使用CRL,也应该考虑用短生命周期的证书,这样可以降低CRL的大小(译者语:RP同步CRL更快,也减轻CRL服务器的压力)。CRL只包含已经撤销但还没过期的证书的序列号。如果你使用短生命周期的证书,那么你的CRL也会更短(译者语:被撤销的证书如果已经过期,是不会存在于CRL中的)。

如果你不喜欢CRL,可以考虑OCSP,其允许RP以证书序列号为条件从OCSP响应者处获取该证书的撤销状态。类似CRL分布点,OCSP响应者的URL也包含在证书中。OCSP听上去很美好(很明显,译者语:不需要同步庞大的列表,撤销状态还是实时获取的,不存在脏数据。),但是也有自身的问题。对于Web PKI,它导致了严重的隐私问题:OCSP响应者可以根据我提交的证书状态校验请求获知我正在访问什么网站(译者语:进一步解释一下,OCSP响应者对于网站来说是第三方,如果访问网站的所有用户都要请求它,那么OCSP响应者就可以知道网站的访问量等信息)。它也会导致每个TLS连接有额外负担:需要额外请求校验撤销状态。类似CRL,很多RP(包括浏览器)在OCSP校验异常依然可以访问,因为其假设OCSP响应者挂掉或者返回错误时证书依然合法(译者语:不管是CRL还是OCSP都不靠谱,因为RP都执行fail open)。

OCSP装订(stapling)是OCSP的变种,用来解决上述问题。免除了依赖方和OCSP响应者的交互,直接由证书的拥有方—订阅者来承担。OCSP的响应是一个很快会过期的被签名过的证明,其表明证书还未被撤销。 在订阅者和RP进行TLS握手的时候会传递该证明("装订"在证书上,译者语:这有点类似去政府部门办理事情,工作人员需要你填写相关资料(可以类比成证书),还需要你的身份证复印件(类比成OCSP的响应),她们会把两者有订书机装订在一起提交上级处理或者归档。)。这给RP提供了相当实时的撤销状态,并且还不需要其直接查询OCSP响应者。订阅者可以多次使用被签名的OCSP响应直到其过期。这也减轻了响应者的压力,基本消除了性能问题也解决了OCSP的隐私问题。然而,这一切有点小题大做(译者语:原文是rube goldberg device,表示一种被设计成过度复杂的机械组合,以迂回曲折的方式去完成一些实际上非常简单的工作,比如倒杯水)。如果订阅者通过一些机构获取短生命周期的被签名的证明来表明证书还没过期,那么为什么不去除中间环节:直接使用短生命周期的证书。

使用证书

有了上述这些背景知识,实际中使用证书真的很容易。我们将用TLS来示范,其他的方案也很类似。

  • 配置PKI依赖方使用哪些根证书
  • 配置订阅者使用哪个证书和私钥(或者设置好私钥对的生成方法,让其自己通过CSR获取证书)

一个实体(代码,设备,服务器等等)即是RP又是订阅者的情况很常见。这些实体需要配置根证书,证书和私钥。最后,对于Web PKI来说,默认情况下都会信任正确的根证书(译者语:比如操作系统的信任存储中的根证书),因此你可以跳过该部分的设置。

这是一个完整的例子,示范了证书颁发,根证书分发和TLS客户端(RP)及服务端(订阅者)的配置过程。

image

希望这样可以演示正确部署Internal PKI是多么简单。所以你不需要使用自签名证书或者做一些危险的事情,例如禁止证书链验证(给curl传递-k参数)。

几乎每个TLS客户端和服务端都采用相同的参数。几乎所有人都对密钥和证书抱有不切实际的幻想:他们总是假设证书会神奇的出现在磁盘中,会自动被替换等等。这是最难的地方。再次提醒,如果你需要这些资料,请尝试step(译者语:原作者的广告时间,有需要可以尝试)。

总结


公钥加密技术让计算机之间通过网络可以互相"看见"。如果我有公钥,我就可以看见拥有对应私钥的你,但是我不能自己使用它。如果我没有公钥,证书可以帮助我。证书把公钥绑定到拥有对应私钥的人的名称上。它们类似计算机和代码使用的驾照。证书颁发机构(CA)会使用其自己的私钥来签名证书,以担保这些绑定关系的正确性。它们就像DMV一样。如果你是唯一像你的人,你出示来源于我信任的DMV颁发的驾照给我,那我就知道了你的名称。同理,如果你是唯一知道某个私钥的人,你发送给我一个来源于我信任的CA颁发的证书,那我就知道了你的名称。

现实世界中大部分证书都是X.509 v3证书。它们使用ASN.1定义并且常常使用DER序列化再用PEM编码。对应的私钥常常被表示成PKCS#8对象,也是使用DER序列化再用PEM编码。如果你使用Java或者微软技术栈,你可能还会遇到PKCS#7和PKCS#12包装格式。这里有很多历史包袱,会让你工作得十分奔溃,但是这更多是繁琐但不困难。

公钥基础设施是你构建以及高效使用公钥所需内容的总称,包含:名称、密钥类型、证书、证书颁发机构、定时任务和工具库等等。Web PKI是一种公开的PKI,web浏览器和绝大多数其他使用TLS的程序默认都使用它。Web PKI的CA是被信任的,但是并不一定值得信任。Internal PKI是一种属于你自己的PKI,你需要自己动手构建和运行它。你之所以会需要它,是因为Web PKI并不是为内部使用场景而设计的,并且Internal PKI更加容易自动化和扩展,同时还给你更多重要事物的控制权,比如命名和证书的生命周期。请在公开场景中使用Web PKI,在内部场景中使用你自己的Internal PKI(比如,使用TLS代替VPN)。Smallstep证书使得构建Internal PKI相当简单。

要获取证书,你需要名称和生成密钥对。使用SAN作为名称:代码和机器使用DNS类型的SAN,人使用EMAIL类型的SAN,其他情况使用URI类型的SAN(译者语:还记得前面说的SAN吗?其是证书的一个扩展项— Subject Alternative Name)。密钥类型是一个很广泛的话题,但是其基本上不太重要:你可以修改密钥类型并且加密也不会成为你的PKI中最薄弱的环节(译者语:当然还是尽量不要使用已经被认定不再安全的加密算法或者密钥的长度)。要从CA获取证书,你需要提交CSR并且证明你的身份。请使用短生命周期证书和被动撤销。自动化证书续签流程。一定不要禁用证书链验证。

请牢记:证书和PKI绑定名称到公钥上。剩下的都是实现细节。


译者语:第一次翻译这么长的文章,而且不是自己最擅长的领域,耗费的时间大大超出预期。同时也体会到了:看懂一篇外文文章和翻译它,完全不是一个难度级别的。这篇文章我自己在看的时候,有些不影响理解的词和句子我就直接跳过了,甚至有些提到的非主链路的技术或者解决方案我也是直接忽略。但是在翻译的时候,需要尽量保证自己理解的正确性和进一步理解这些技术出现的背景,查询了大量的资料,工作量翻了好几倍。希望这篇文章对大家有所帮助。v

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249