前言

证书(Certificates)与 PKI(Public Key Infrastructure,公钥基础设施)很难,很多人也会选择绕过这一主题。 因为在安全领域很多事情我们无法做到完美,加之涉及的领域又比较广泛和深入。例如会涉及数学,加密,算法等等,但最让人摸不着头脑的是各种证书格式以及加密算法,光记住名称对于大多数人来说就已经很头疼了!

但最终,我还是硬着头皮学习了这些东西。为此,我曾经还看了一本《严肃的密码学:实用现代加密技术》,花了很长时间,虽然最终看完了,但是收货并不大!

后来又学习 Smallstep 的开源项目,尝试部署私有 ACME Server 等。一路摸索过来发现——证书和 PKI 的目的其实很简单:将“名字”关联到公钥!

为什么要学习 PKI

我觉得 PKI 能使一个人在加解密层面(乃至更大的安全层面)去思考如何定义一个系统。具体来说,PKI 技术

  • 都是通用的、厂商无关的(universal and vendor neutral);
  • 适用于任何地方,因此即使系统可分布在世界各地,彼此之间也能安全地通信;
  • 在概念上很简单,并且非常灵活;如果使用我们的 TLS everywhere 模型, 那甚至连 VPN 都不需要了。

TL;DR

证书和 PKI 的目的:将名字关联到公钥。这是关于证书和 PKI 的最高抽象,其他都属于实现细节。

概念

X.509

X.509是密码学里公钥证书的格式标准。

ASN.1

ASN.1(Abstract Syntax Notation One) 是一套标准,是描述数据的表示、编码、传输、解码的灵活的记法。

编码格式

常见的编码格式又如下两种:

  • DER:一种二进制编码格式;
  • PEM:一种文本编码格式,通常以 .pen.crt.cer 为后缀。

实体(Entity)

Entity 是任何存在的东西(Anything that exists) —— 即使 只在逻辑或概念上存在(Even if only exists logically or conceptually)。例如:

  • 你用的计算机是一个 Entity;
  • 你写的代码也是一个 Entity;
  • 你自己也是一个 Entity;
  • 你早餐吃的杂粮饼也是一个 Entity;
  • 你六岁时见过的鬼也是一个 Entity —— 即使你妈妈告诉你世界上并没有鬼,这只是你的臆想。

身份(Identity)

每个 entity(实体)都有一个 identity(身份)。 要精确定义这个概念比较困难,这么来说吧:identity 是使你之所以为你 (what makes you you)的东西,懂吗?

具体到计算机领域,identity 通常用一系列属性来表示,描述某个具体的 entity, 这里的属性包括 group、age、location、favorite color、shoe size 等等。

身份标识符(Identifier)

Identifier 跟 Identity 并不是一个东西:每个 Identifier 都是一个唯一标识符, 也唯一地关联到某个有 Identity 的 Entity。

例如:我是 George,但 George 并不是我的 Identity,而只是个 Name —— 虽然二者在我们小范围的讨论中是同义的。

声明(Claim)

一个 Entity 能 Claim 说,它拥有某个或某些 Name。

Claim 不是只能关联到 Name,还可以关联到别的东西。例如:我能 Claim 任何东西:年龄、性别、权限 等等。

认证(Authentication)

其他 Entity 能够对这个 Claim 进行认证(Authenticate),以确认这份声明的真假。一般来说,认证的目的是确认某些 Claim 的合法性。

Subscriber

能作为一个证书的 Subject 的 Entity,称为 Subscriber(证书 Owner)或 End Entity。

对应地,Subscriber 的证书有时也称为 End Entity Certificates 或 Leaf Certificates,原因在后面讨论 Certificate Chains 时会介绍。

CA

CA(Certificate Authority,证书权威)是给 Subscriber 颁发证书的 Entity,是一种 Certificate Issuer(证书颁发者)。

CA 的证书,通常称为 Root Certificate 或 Intermediate Certificate,具体取决于 CA 类型。

Relying Party

Relying Party 是使用证书的用户(Certificate User),它验证由 CA 颁发(给 Subscriber)的证书是否合法。

一个 Entity 可以同时是一个 Subscriber 和一个 Relying Party。 也就是说,单个 Entity 既有自己的证书,又使用其他证书来认证 Remote Peers,例如双向 TLS(mutual TLS,mTLS)场景。

对于我们接下来的讨论,这些术语就够了。下面将进入正题,看如何在实际中实现 证书的声明和认证。更多术语请参考 RFC 4949

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

前面说道,公钥加密系统使我们能知道和谁在通信,但这个的前提是: 要知道(有)对方的公钥。

那么,如果对方不知道我的公钥怎么办? 这就轮到证书出场了。

想一下,我们需求其实非常简单:

  • 首先要将公钥和它的 owner 信息发给对方;
  • 但光有这个信息还不行,还要让对方相信这些信息。

证书就是用来解决这个问题的,解决方式是请一个双方都信任的权威机构 对以上信息作出证明(签名)。

证书的内容:公钥和名字

  • 证书是一个数据结构,其中包含一个 Public Key 和一个 Name;
  • 权威机构对证书进行签名,签名的大概意思是:Public Key XXX 关联到了 Name XXX;

对证书进行签名的 Entity 称为 Issuer(或 Certificate Authority, CA), 证书中的 Entity 称为 Subject。

举个例子,如果某个 Issuer 为 Bob 签发了一张证书,其中的内容就可以解读如下:

证书是权威机构颁发的身份证明,并没有什么神奇之处

其中 Some Issuer 是证书的签发者(证书权威),证书是为了证明这是 Bob 的公钥, Some Issuer 也是这个声明的签字方。

证书的本质: 基于对 Issuer 公钥的信任来学习其他公钥

由上可知,如果知道 Some Issuer 的公钥,就可以通过验证签名的方式来 对它(用私钥)签发的证书进行认证(Authenticate)。 如果如果你信任 Some Issuer,那你就可以信任这个声明。

因此,证书使大家能基于对 Issuer 公钥的信任和知识,来学习到其他 Entity 的公钥 (上面的例子中就是 Bob)。这就是证书的本质。

与驾照的类比

证书就像是计算机/代码的驾照或护照。如果你之前从未见过我,但信任车管局,那你可以用我的驾照做认证:

  • 首先验证驾照是真的(检查 Hologram 等);
  • 然后人脸和照片上对的上;
  • 然后看名字是我,等等。

权威机构颁发证书

计算机用证书做类似的事情:如果之前从未和其他电脑通信,但信任一些证书权威,那可以用证书来认证:

  • 首先验证证书是合法的(用证书签发者的公钥检查签名等);
  • 然后提取证书中的(subscriber 的)公钥和名字;
  • 然后用 subscriber 的公钥,通过网络验证该 subscriber 的签名;
  • 查看名字是否正确等等。

还是与驾照类比:

  • 驾照:描述了你是否有资格开车;
  • 证书:描述你是否是一个 CA,你的公钥能否用来签名或加密;
  • 二者都有有效期。

上图中有大量的细节,很多东西将在下面讨论到。但归根结底还是本文最开始总结的那句话 :证书不过是一个将名字关联到公钥(Bind Names To Public Keys)的东西。 其他都是实现细节。

PKI

至此我们已经知道了证书的来历和样子,但这仅仅是本文的一半。 下面看证书是如何创建和使用的。

Public key infrastructure (PKI) 是一个统称,包括了我们在 如下与证书和秘钥管理及交互操作时需要用到的所有东西:签发、分发、存放、使用、验证、撤回等等。 就像“数据库基础设施” 一样,这个名词是有意取的这样模糊的。

  • 证书是大部分 PKI 的构建模块,而证书权威是其基础。
  • PKI 包括了 libraries, cron jobs, protocols, conventions, clients, servers, people, processes, names, discovery mechanisms, and all the other stuff you’ll need to use public key cryptography effectively。

自己从头开始构建一个 PKI 是一件极其庞大的工作, 但实际上 一些简单的 PKI 甚至并不使用证书。例如,

  • 编辑 ~/.ssh/authorized_keys 文件时,就是在配置 一个简单的无证书形式的(Certificate-less)PKI,SSH 通过这种方式在扁平文件内 实现 Public Key 和 Name 的绑定;
  • PGP 用证书,但不用 CA,而是用一个 web-of-trust model;
  • 甚至可以 用区块链 来 Assign Name 并将它们 Bind 到 Public key。

如果从头开始构建一个 PKI,唯一确定的事情是:你需要用到公钥(public keys), 其他东西都随设计而异。

下文将主要关注 Web 领域使用的 PKI,以及基于 Web PKI 技术、遵循现有标准的 internal PKI。

证书和 PKI 的目标其实很简单:将名字关联到公钥(Bind names to public keys)。 在下面的内容中,不要忘了这一点。

Web PKI vs Internal PKI

浏览器访问 HTTPS 链接时会用到 Web PKI。虽然也有一些问题,但它大大提升了 web 的安全性,而且基本上对用户透明。在访问互联网 web 服务时,应该在所有可能的情 况下都启用它。

  • Web PKI 由 RFC 5280 定义, CA/Browser Forum (a.k.a., CA/B or CAB Forum) 对其进行了进一步完善。
  • 有时也称为 “Internet PKI” 或 PKIX (After the working group that created it).

PKIX 和 CAB Forum 文档涵盖了很大内容。 它们定义了前面讨论的各种证书、还定义什么是 “name” 以及位于证书中什么位置、能使用什么签名算法、 RP 如何判断 Issuer 的证书、如何指定证书的 Validity Period (Issue and expiry dates)、 撤回、Certificate Path Validation、CA 判断某人是否拥有一个域名等等。

Web PKI 很重要,是因为浏览器默认使用 Web PKI 证书。

Internal PKI 是用户为自己的产品基础设施使用的 PKI,这些产品包括

  • 服务、容器、虚拟机等;
  • 企业 IT 应用;
  • 公司终端设备,例如笔记本电脑、手机等;
  • 其他需要识别的代码或设备。

Internal PKI 使你能认证和建立加密通道,这样你的服务就可以安全地在公网上的任意位置互相通信了。

有了 Web PKI,为什么还要使用自己的 Internal PKI?

首先,简单来说:Web PKI 设计中并没有考虑内部使用场景。 即使有了 Let’s Encrypt 这样的提供免费证书和自动化交付的 CA, 用户还是需要自己处理 Rate limits 和 Availability 之类的事情。 如果有很多 Service,部署很频繁,就非常不方便。

另外,Web PKI 中,用户对证书生命周期、撤回机制、续约过程、秘钥类型、算法等等很 多重要的细节都没有控制权,或只有很少控制权。而下文将会看到,这些都是非常重要的东西。

最后,CA/Browser Forum Baseline Requirements 实际上禁止将 Web PKI CA 关联到 Internal IPs (e.g., 10.0.0.0/8) 及 Internal DNS names that aren’t fully-qualified and resolvable in public global DNS (e.g., You can’t bind a kubernetes cluster DNS name like foo.ns.svc.cluster.local)。 如果需要在证书中绑定到这些 Name,或者签发大量证书,或者控制证书细节,就需要自己的 Internal PKI.

下面一节将看到,信任(或缺乏信任)是避免将 Web PKI 用于内部场景的另一个原因。

总结起来,建议:

  • 面向公网的服务或 API,使用 Web PKI;
  • 其他所有场景,都使用 Internal PKI。

Trust & Trustworthiness

Trust Stores(信任仓库)

前面介绍到,证书可解读为一个 Statement 或 Claim,例如:

Issuer(签发者)说:该 Subject 的公钥是 XXX。

Issuer 会对这份声明进行签名,Relying Party 能(通过 Issuer 的公钥)验证(Authenticate)签名是否合法。 但这里其实跳过了一个重要问题:Relying Party 是如何知道 Issuer 的公钥的?

预配置信任的根证书

答案其实很简单:Relying Parties 在自己的 Trust store(信任库)预先配置了一个它 信任的根证书(Trusted root certificates,也称为 Trust anchors)列表。

预配置的具体方式(The manner in which this pre-configuration occurs), 是 PKI 非常重要的一面:

  • 一种方式是从另一个 PKI 来 Bootstrap:可以用一些自动化工具,通过 SSH 将根证书拷贝到 Relying Party。这里用到里前面提到的 SSH PKI。
  • 如果是在 Cloud 上,那 PKI 依赖层次(信任链)又深了一步:SSH PKI 是由 Web PKI 加上认证方式来 Bootstrap 的,这里的认证是你创建 Cloud 账户时选择的认证方式。

信任链

如果沿着这个信任链(chain of trust)回溯足够远,最后总能找到人(People):每个信任链都终结在现实世界(Meatspace)。

信任链

下面这个图画地更清楚一些:

信任链

根证书自签名

信任仓库中的根证书是自签名的(Self-signed):Issuer 和 Subject 相同。

逻辑上这种 Statement 表示的是:Mike 说:Mike 的公钥是 XXX。

自签名的证书保证了该证书的 Subject/Issuer 知道对应的私钥,但任何人都可以生成一个自签名的证书,这个证书中可以写任何他们想写的名字(Name)。

因此证书的起源(Provenance)就非常关键:一个自签名的证书,只有当它进入信任仓库的过程是可信任时,才应该信任这个根证书。

  • 在 macOS 上,信任仓库是由 Keychain 管理的;
  • 在一些 Linux 发行版上,可能只是 /etc 或其他路径下面的一些文件;
  • 如果你的用户能修改这些文件,那最好先确认是你信任这些用户的。

信任仓库的来源

所以,信任仓库又从哪里来?对于 Web PKI 来说,最重要的 relying parties 就是浏览器。主流浏览器默认使用的信任仓库 —— 及其他任何使用 TLS 的东西 —— 都是由四个组织维护的:

  • Apple’s root certificate:iOS/macOS 程序
  • Microsoft’s root certificate program:Windows 使用
  • Mozilla’s root certificate program: Mozilla 产品使用,由于其开放和透明,也作为其他一些信任仓库从基础 (e.g., for many Linux distributions)
  • Google 未维护 root certificate program (Chrome 通常使用所在计算的操作系统的信任仓库),但 维护了自己的黑名单, 列出了自己不信任的根证书或特定证书。 (ChromeOS builds off of Mozilla’s certificate program)

操作系统的信任仓库

操作系统中的信任仓库通常都是系统自带的。

  • Firefox 自带了自己的信任仓库(通过 TLS 从 mozilla.org 分发 —— bootstrapping off of Web PKI using some other trust store)。
  • 编程语言和其他非浏览器的东西例如 curl,通过默认用操作系统的信任仓库。

因此,这个信任仓库通常情况下,会被该系统上预装的很多东西默认使用;通过软件更新( 通常使用另一个 PKI 来签名)而更新。

信任仓库中通常包含了超过 100 个由这些程序维护的常见证书权威(certificate authorities)。 其中一些著名的:

  • Let’s Encrypt
  • Symantec
  • DigiCert
  • Entrust

如果想编程控制:

  • Cloudflare’s cfssl project maintains a github repository that includes the trusted certificates from various trust stores to assist with certificate bundling (which we’ll discuss momentarily).
  • For a more human-friendly experience you can query Censys to see which certificates are trusted by Mozilla, Apple, and Microsoft.

可靠性(Trustworthiness)

这 100 多个证书权威在理论上是可信的(Trusted) —— 浏览器和其他 一些软件默认情况下信任由这些权威颁发的证书。

但是,这并不意味着它们是可靠的(Trustworthy)。 已经出现过 Web PKI 证书权威向政府机构提供假证书的事故,以便 窥探流量(snoop on traffic)或仿冒某些网站。 这类“受信任的” CA 中,其中在司法管辖权之外的地方运营 —— 包括民主国家和专制国家。

  • NSA 利用每个可能的机会来削弱 Web PKI。2011 年,两个“受信任的”证书权威 DigiNotar and Comodo 都 被攻陷了。 DigiNotar 证书泄露可能与 NSA 相关。
  • 此外,还有大量 CA 签发格式不对或不兼容的证书。因此,虽然按业界规范来说 这些 CA 是受信的,但按照经验来说它们是不可靠(不靠谱)的。

我们很快就会看到,Web PKI 的安全性取决于安全性最弱的权威(the least secure CA)的安全性。 这显然不是我们希望的。

浏览器社区已经在采取行动来解决这些问题。 CA/Browser Forum Baseline Requirements 规定了这些受信的证书权威在签发证书时应该遵守的规则。 作为 WebTrust audit 项目的一部分,在将 CA 加入到某些信任仓库(例如 Mozilla 的)之前,会对 CA 合规性进行审计。

如果内部场景(Internal stuff)已经在使用 TLS,你可能大部分情况下 并不需要信任这些 public CA。 如果信任了,就为 NSA 和其他组织打开了一扇地狱之门:你的系统安全性将取决于 100 多 个组织中安全性最弱的那一个。

Internal PKI 使用单独的信任仓库

在任何情况下,如果使用自己的 internal PKI,都应该为 internal 服务维护一个单独的信任仓库。 即,

  • 不要将你的根证书直接加到系统已有的信任仓库,
  • 而应该配置 internal TLS 只使用你自己的根证书。

Internal PKI 细粒度控制:CAA & SPIFFE

如果想在内部实现更好的联邦(federation) —— 例如限制 internal CA 能签发哪些证书,

  • 可以试试 CAA records 然后对 RPs 进行恰当配置。
  • 还可以看看 SPIFFE,这是一个还在不断发展的项目, 目标是对一些 internal PKI 相关的问题进行标准化。

什么是证书权威(Certificate Authority)

前面已经讨论了很多 CA 相关的东西,但我们还没定义什么是 CA。

  • 一个证书权威(CA)就是一个受信任的证书颁发者。
  • CA 通过对一个证书进行签名,对一个公钥和名字之间的绑定关系(binding)做担保。
  • 本质上来说,一个 CA 只不过是另一个证书加上用来签其他证书的相应私钥。

显然需要一些逻辑和过程来将这些东西串联起来。CA 需要将它的证书分发到信任仓库,接受和处理 证书请求,颁发证书给 subscriber。

  • 一个暴露此类 API 给外部调用、自动化这些过程的 CA 称为在线证书权威(online CA)。
  • 在信任仓库中那些自签名的根证书 称为根证书权威(root CA)。

Web PKI 不能自动化签发证书

CAB Forum Baseline Requirements 4.3.1 明确规定:一个 Web PKI CA 的 root private key 只能通过 issue a direct command 来签发证书。

  • 换句话说,Web PKI root CA 不能自动化证书签名(certificate signing)过程。
  • 对于任何大的 CA operation 来说,无法在线完成都是一个问题。 不可能每次签发一个证书时,都人工敲一个命令。

这样规定是出于安全考虑。

  • Web PKI root certificates 广泛存在于信任仓库中,很难被撤回。截获一个 root CA private key 理论上将影响几十亿的人和设备。
  • 因此,最佳实践就是,确保 root private keys 是离线的(offline),理想情况下在一些 专用硬件 上,连接到某些物理空间隔离的设备上,有很好的物理安全性,有严格的使用流程。

一些 internal PKI 也遵循类似的实践,但实际上并没有这个必要。

  • 如果能自动化 root certificate rotation (例如,通过配置管理或编排工具,更新信任仓库), 你就能轻松地 rotate 一个 compromised root key。
  • 由于人们如此沉迷于 internal PKI 的根秘钥管理,导致 internal PKI 的部署效率大大 降低。你的 AWS root account credentials 至少也是机密信息,你又是如何管理它的呢?

Intermediates, Chains, and Bundling

在 Root CA offline 的前提下,为使证书 Issuance 可扩展(例如,使自动化成为可能), Root private key 只在很少情况下使用,

  • 用来签发几个intermediate certificates。
  • 然后 Intermediate CA(也称为 Subordinate CAs)用相应的 Intermediate private keys 来签发 Leaf certificates to subscribers。

如下图所示:

证书颁发路径

Intermediates 通常并不包含在信任仓库中,所以撤回或 roate 比较容易, 因此通过 Intermediate CA,就实现了 Certificate issuance 的在线和自动化(Online and automated)。

这种 Leaf、Intermediate、Root 组成的证书捆绑(Bundle)机制, 形成了一个证书链(Certificate chain)。

  • Leaf 由 Intermediate 签发,
  • Intermediate 又由 Root 签发,
  • Root 自签名(Signs itself)。

技术上来说,上面都是简化的例子,你可以创建更长的 Chain 和更复杂的图(例如, Cross-certification)。 但不推荐这么做,因为复杂性很快会失控。在任何情况下, End entity certificates 都是叶子节点,这也是称为叶子证书(Leaf certificate)的原因。

当配置一个 Subscriber 时(例如:Apache、Nginx、Linkderd、Envoy),通常不仅需要叶子证书,还需要一个包含了 Intermediates 的 Certificate bundle。

  • 有时会用 PKCS#7 和 PKCS#12,因为它们能包含一个完整的证书链(Certificate chain)。
  • 更多情况下,证书链编码成一个简单的空行隔开的 PEM 对象(Sequence of line-separated PEM objects)。

下面是一个例子:

cat server.crt
  -----BEGIN CERTIFICATE-----
  MIICFDCCAbmgAwIBAgIRANE187UXf5fn5TgXSq65CMQwCgYIKoZIzj0EAwIwHzEd
  ...
  MBsGA1UEAxMUVGVzdCBJbnRlcm1lZGlhdGUgQ0EwHhcNMTgxMjA1MTc0OTQ0WhcN
  HO3iTsozZsCuqA34HMaqXveiEie4AiEAhUjjb7vCGuPpTmn8HenA5hJplr+Ql8s1
  d+SmYsT0jDU=
  -----END CERTIFICATE-----
  -----BEGIN CERTIFICATE-----
  MIIBuzCCAWKgAwIBAgIRAKBv/7Xs6GPAK4Y8z4udSbswCgYIKoZIzj0EAwIwFzEV
  ...
  BRvPAJZb+soYP0tnObqWdplmO+krWmHqCWtK8hcCIHS/es7GBEj3bmGMus+8n4Q1
  x8YmK7ASLmSCffCTct9Y
  -----END CERTIFICATE-----

RP:Certificate Path Validation

由于 Intermediate Certificates 并未包含在信任仓库中,因此需要与 Leaf certificates 一样分发和验证。

  • 前面已经介绍,配置 Subscriber 时需要提供这些 Intermediates,Subscribers 随后再将它们传给 RP。
  • 如果使用 TLS,那这个过程发生在 TLS 握手时。
  • 当一个 Subscriber 将它的证书发给 Relying party 时,其中会包含所有能证明来自信任的根证书的 Intermediates。
  • Relying party 通过一个称为 Certificate path validation 的过程来验证 Leaf 和 Intermediate certificates。

Certificate Path Validation

完整的 Certificate path validation 算法比较复杂。包括了:

  • Checking certificate expirations
  • Revocation status
  • Various certificate policies
  • Key use restrictions
  • A bunch of other stuff

显然,PKI RP 准确实现这个算法是非常关键的。

  • 如果关闭 Certificate path validation (例如,curl -k),用户将面临重大风险,所以不要关闭。
  • 完成正确的 TLS 并没有那么难,Certificate path validation 是 TLS 中完成认证(Authentication)的部分。

可能有人会说,Channel 已经是加密的了,因此关闭没关系,错!有关系。 没有认证(Authentication)的加密是毫无价值的!

这就像在教堂忏悔: 你说的话都是私密的,但却并不知道帘幕后面的人是谁 —— 只不过这里不是教堂,而是互联网。

秘钥和证书的生命周期

在能通过 TLS 等协议使用证书之前,要先配置如何从 CA 获取一个证书。 逻辑上来说这是一个相当简单的过程:

  • 需要证书的 Subscriber 自己先生成一个 Key pair,然后通过请求发送给 CA,
  • CA 检查其中关联的 Name 是否正确,如果正确就签名并返回一个证书。

证书会过期,过期的证书就不会被 RP 信任了。如果证书快过期了而还想继续用它,就需要 续期(Renew )并轮转(Rotate)它。如果想在一个证书过期之前就让 RP 停止信任它,就需要执行撤销(Revoke)。

与 PKI 相关的大部分东西一样,这些看似简单的过程实际上都充满坑。 其中也隐藏了计算机科学中最难的两个问题:缓存一致性和命名(Naming)。 但另一方面,一旦理解了背后的原理,再反过来看实际在用的一些东西就简单多了。

命名相关(Naming Things)

DN (Distinguished Names)

历史上,X.509 使用 X.500 Distinguished Names (DN) 来命名证书的使用者(Name the subject of a certificate),即 Subscriber。 一个 DN 包含了一个 Common Name (对作者我来说,就是 “Mike Malone”),此外还可以包含 Locality、Country、Organization、Organizational Unit 及其他一些东西。

  • 没人理解 DN,它在互联网上也没什么意义。
  • 应该避免使用 DN。如果真的要用,也要尽量保持简单。
  • 无需使用全部字段,实际上,也不应该使用全部字段。
  • Common Name 可能就是需要用到的全部字段了,如果你是一个 Thrill seeker ,可以在用上一个 Organization Name。

PKIX 规定一个网站的 DNS Hostname 应该关联到 DN Common name。最近,CAB Forum 已 经废弃了这个规定,使整个 DN 字段变成可选的(Baseline Requirements, sections 7.1.4.2)。

SAN (Subject Alternative Name)

现代最佳实践使用 Subject Alternative Name (SAN) X.509 Extension 来 Bind 证书中的 Name。

常用的 SAN 有四种类型,绑定的都是广泛使用的名字:

  • Domain names (DNS)
  • Email addresse
  • IP addresse
  • URI

在我们讨论的上下文中,这些都是唯一的,而且它们能很好地映射到我们想识别的东西:

  • Email addresses for people
  • Domain names and IP addresses for machines and code,
  • URIs if you want to get fancy

应该使用 SAN,如下第 11 行和第 30 行高亮的行所示:

step certificate inspect https://george.betterde.com
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 277028619957168996496866148377847610215553 (0x32e1d2b7678a3cde0a0aff3d8973da00c81)
    Signature Algorithm: SHA256-RSA
        Issuer: C=US,O=Let's Encrypt,CN=R3
        Validity
            Not Before: Nov 15 07:22:21 2022 UTC
            Not After : Feb 13 07:22:20 2023 UTC
        Subject: CN=george.betterde.com
        Subject Public Key Info:
            Public Key Algorithm: RSA
                Public-Key: (4096 bit)
                ........
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                Server Authentication, Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier:
                91:AC:86:05:F8:94:F1:26:6F:4A:CE:DA:04:F2:69:52:00:55:81:D2
            X509v3 Authority Key Identifier:
                keyid:14:2E:B3:17:B7:58:56:CB:AE:50:09:40:E6:1F:AF:9D:8B:14:C2:C6
            Authority Information Access:
                OCSP - URI:http://r3.o.lencr.org
                CA Issuers - URI:http://r3.i.lencr.org/
            X509v3 Subject Alternative Name:
                DNS:george.betterde.com

注意,Web PKI 允许在一个证书内绑定多个 Name,Name 也允许使用通配符。也就是说,

  • 对于域名通配符证书,一个证书可以有多个 SAN,也可以有类似 *.smallstep.com 这样的 SAN。
  • 这对有多个域名的的网站来说很有用。

生成 Key Pairs

有了 Name 之后,需要先生成一个密钥对,然后才能创建证书。前面提到:PKI 的安全性 在根本上取决于一个简单的事实:只有与证书中的 Subscriber Name 对应的 Entity,才应该拥有与该证书对应的私钥。 为确保这个条件成立,

  • 最佳实践是让 Subscriber 生成它自己的密钥对,这样就只有它自己知道私钥。

  • 绝对应该避免通过网络发送私钥。

  • 如今有一个缓慢但清晰的从 RSA 转向椭圆曲线秘钥的趋势( ECDSA 或 EdDSA)。

  • 如果决定使用 RSA 秘钥,确保它们至少是 2048 比特长,但也不要超过 4096 比特。

  • 如果使用 ECDSA,那 P-256 曲线可能是最好选择(secp256k1 or prime256v1 in openssl), 除非你担心 NSA,这种情况下你可以选择更 fancier 一些的东西,例如 EdDSA with Curve25519(但对这些秘钥的支持还不是太好)。

下面是用 openssl 生成一个椭圆曲线 P-256 key pair 的例子:

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

# 也可以用 step 生成
step crypto keypair --kty EC --curve P-256 k.pub k.prv

还可以通过编程来生成这些证书,这样能做到证书不落磁盘。

Issuance(确保证书中的信息都是对的)

Subscriber 有了一个 Name 和一对 Key 之后,下一步就是从 CA 获取一个 Leaf certificate。 对 CA 来说,它需要证明两件事情:

  • Subscriber 证书中的公钥,确实是该 Subscriber 的公钥(例如,验证该 Subscriber 知道对应的私钥);这一步通常通过一个简单的技术机制实现:证书签名请求(Certificate Signing Request, CSR)。

  • 证书中将要绑定的 Name,确实是该 Subscriber 的 Name。这一步要难很多。抽象来说,这个过程称为 Identity Proofing(身份证明)或 Registration(注册).

Certificate signing requests(证书签名请求,PKCS#10)

Subscriber 请求一个证书时,会向 CA 会提交一个 certificate signing request (CSR)。

  • CSR 也是一个 ASN.1 结构,定义在 PKCS#10。

  • 与证书类似,CSR 数据结构包括一个公钥、一个名字和一个签名。

  • CSR 是自签名的,用与 CRS 中公钥对应的私钥自签名。

    • 这个签名用于证明该 subscriber 有对应的私钥,能对任何用其公钥加密的东西进行解密。
    • 还使即使 CSR 被复制或转发,都没有能篡改其中的内容(篡改无效)。
  • CSR 中包括了很多证书细节配置项。但在实际中,大部分配置项都会被 CA 忽略。大部分 CA 都使用自己的固定模板, 或提供一个 administrative 接口来收集这些信息。 用 step 命令创建一个密钥对和 CSR 的例子:

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

总结

公钥加密系统使计算机能在网络上看到对方。

  • 如果我有公钥,就能“看到”你有对应的私钥,但我自己是无法使用这个私钥的。
  • 如果还没有对方的公钥,就需要证书来帮忙。证书将公钥和私钥拥有者的名字相关联, 它们就像是计算机和代码的驾照。
  • 证书权威(CA)用它们的私钥对证书进行签名,对这些绑定关系作出担保,它们就像是车管局(DMV),
  • 如果你出示一张车管局颁发的驾照,脸长得也和驾照上的照片一样,那别人就可以认为你就是驾照上这个人(名字)。 同理,如果你是唯一知道某个秘钥的 Entity,你给我的证书也是从我信任的某个 CA 来的,那我就认为证书中的 name 就是你。

现实中,大部分证书都是 X.509 v3 证书,用 ASN.1 格式定义,通常序列化为 PEM-encoded DER。相应的私钥通常表示为 PKCS#8 Objects,也序列化为 PEM-encoded DER。如果你用 Java 或微软的产品,可能会遇到 PKCS#7 and PKCS#12 封装格式。

加密领域有沉重的历史包袱,使当前的这些东西学起来、用起来非常让人沮丧,这比一项技术因为太难而不想学更加令人沮丧。

PKI 是使用公钥基础设施时涉及到的所有东西的统称:Names, Key types, Certificates, CAs, Cron jobs, Libraries 等。

  • Web PKI 是浏览器默认使用的 PKI。Web PKI CA 是受信但不可靠的(trusted but not trustworthy)。
  • Internal PKI 是用户自己构建和维护的 PKI。需要它是因为 Web PKI 并不是针对 internal 使用场景设计的, Internal PKI 更易于自动化和扩展,并且能让用户控制很多细节,例如 Naming and certificate lifetime。
  • 建议公网上使用 Web PKI,内网使用自己的 internal PKI (例如,use TLS 来替代 VPN)。
  • Smallstep Certificate Manager 使构建 internal PKI 非常简单。

要获得一个证书,需要命令和生成证书。建议 name 用 SAN:

  • DNS SANs for code and machines
  • EMAIL SANs for people
  • 如果这些都不能用,就用 URI SAN

秘钥类型(key type)是很大一个主题,但几乎不重要:你可以随便修改秘钥类型,而且实际上加密本身(crypto)并不是 PKI 中最弱的一环。

要从 CA 获取一个证书,需要提交一个 CSR 并证明申请者的身份(Identity)。使用生命周期较短的证书和 Passive Revocation。自动化证书续期过程。不要禁用 Certificate path validation。

最后还是那句话:证书和 PKI 将名字关联到公钥,其他都是细节。

I hope this is helpful, Happy hacking…