理解公钥基础设施和 X.509 证书

作者:Jeff Woods

从零开始介绍 PKI、TLS 和 X.509。

公钥基础设施 (PKI) 提供了一个加密和数据通信标准框架,用于保护公共网络上的通信安全。PKI 的核心是在客户端、服务器和证书颁发机构 (CA) 之间建立的信任。这种信任通过证书的生成、交换和验证来建立和传播。

本文重点介绍用于在客户端和服务器之间建立信任的证书。这些证书是 PKI 最可见的部分(尤其是在出现问题时!),因此理解它们将有助于理解和纠正许多常见错误。

作为一个简短的介绍,想象一下您想连接到您的银行来安排账单支付,但您想确保您的通信是安全的。在这种情况下,“安全”不仅意味着内容保持机密,还意味着与您通信的服务器实际上属于您的银行。

如果不保护您在传输过程中的信息,位于您和您的银行之间的某人可能会观察到您用于登录服务器的凭据、您的帐户信息,或者可能是您的付款发送给的收款方。如果无法确认服务器的身份,您可能会惊讶地发现您正在与冒名顶替者交谈(他现在可以访问您的帐户信息)。

传输层安全 (TLS) 是一套协议,用于使用 PKI 协商安全连接。TLS 构建于 1990 年代后期的 SSL 标准之上,并且在互联网上使用它来保护客户端到服务器的连接已变得无处不在。不幸的是,它仍然是最不被理解的技术之一,错误(通常是由网站配置不正确引起的)已成为日常生活的一部分。由于这些错误很不方便,用户经常在不加思索的情况下点击通过它们。

理解 X.509 证书(在 RFC 5280 中有完整定义)是理解这些错误的关键。不幸的是,这些证书因其不透明且难以管理而声名狼藉。考虑到用于编码它们的多种格式,这种声誉是当之无愧的。

X.509 证书是一个结构化的二进制记录。此记录由几个键值对组成。键表示字段名称,值可以是简单类型(数字、字符串)到更复杂的结构(列表)。从键/值对到结构化二进制记录的编码是使用称为 ASN.1(抽象语法表示法,一)的标准完成的,这是一种平台无关的编码格式。

ASN.1 专为在各种主机上高效运行而设计,它允许使用其“基本编码规则” (BER) 以多种方式编码值。正如您很快将看到的,相同数据的多种编码不适用于您的证书,因此 X.509 标准指定了 BER 的一个子集作为“区分编码规则” (DER)。DER 的使用确保了任何值都只有一种编码方式。

证书可以以原始的二进制 DER 格式分发。由于二进制数据对终端或电子邮件不友好,因此通常应用隐私增强邮件 (PEM) 标准中定义的编码。尽管 PEM 在很大程度上是一个过时的标准,但它定义了以文本安全格式编码二进制材料的方法。最重要的是,它定义了用于将二进制数据编码为文本的 base64 方案,并指定了封装边界的使用,以指示编码内容的开始和结束。在 Linux 服务器上,通常首选使用 PEM 格式(本文稍后将详细介绍)。

证书也可以以多种其他格式分发。重要的是要理解,无论编码格式如何——DER、PEM、PFX 或其他格式——所有证书在解码时基本上都是相同的。诸如 OpenSSL 之类的工具能够轻松读取或转换任何这些格式。

证书编码了两个非常重要的信息:服务器的公钥和一个可用于确认证书真实性的数字签名。此外,证书还将包含 CA 用于跟踪证书的元数据,并提供有关如何使用公钥的指南。

公钥密码学,也称为非对称密钥密码学,提供了一种在不安全网络上建立安全通信通道的机制。使用服务器的公钥,客户端和服务器能够安全地协商一个共享对称密钥,该密钥可用于保护通信安全。

加密:对称与非对称

两种广泛使用的加密类型用于保护网络流量的安全:对称密钥和非对称密钥。

在两种类型的加密中,密钥都指用于加密或解密数据的密码短语(或类似物)。如果没有密钥,尝试读取数据的实体将无法读取加密内容。同样,恶意内容也很难生成。

对称密钥加密在通信通道的两端使用相同的密钥来加密或解密数据。对称密钥加密在诸如 AES 或 DES 等算法中实现。它的优点是速度非常快且开销低,但两方之间必须存在安全通道,以便密钥可以安全交换。

非对称密钥加密使用一对密钥,称为私钥和公钥。这些密钥是不同的值。使用私钥加密的数据只能使用公钥解密。反之亦然:使用公钥加密的数据只能使用私钥解密。非对称加密算法包括诸如 RSA、DSA 和 ECDSA 等算法。

虽然对称密钥对于通信具有理想的属性,但它们必须以安全的方式生成和交换。非对称算法适合这种需求,您可以将它们用作构建安全通道的基础。

但是,您(作为客户端)如何知道公钥可以被信任为真实的?您可以使用证书颁发机构 (CA),这是一个受信任的第三方,作为某种调解人。通过提交证书以由 CA 签名,服务器的所有者同意证书由 CA “验证”。如果客户端信任 CA 已正确验证服务器,并且可以确认签名有效,则可以信任该证书。

哈希(通常使用 SHA256 算法)是数据的数字指纹。如果您更改数据中的单个位,哈希值也会随之更改。通过计算证书的 DER 编码公钥部分上的哈希,然后使用其自己的私钥对哈希进行签名,CA 正在对证书进行批准。此签名哈希值是附加到证书的签名。

当客户端收到服务器的证书时,客户端可以创建证书中与 CA 签名的数据相同的哈希。如果客户端能够使用 CA 的公钥解密签名,并且它与客户端自己计算的哈希匹配,则客户端可以确定服务器的证书已获得 CA 的认可。

最后一块拼图是理解客户端必须显式信任 CA。这是通过将 CA 的公钥添加到客户端的“受信任密钥库”来完成的。作为常用网络服务的用户,您无需过多考虑证书颁发机构。您的操作系统和 Web 浏览器都附带经过精心策划的授权机构列表,这些机构已被预先选择为对您可信。尽管如此,有时可能需要安装来自非标准 CA 的证书。

现在您已经了解了证书是如何组合在一起的基础知识,让我们看一个真实的示例。使用以下命令,您可以从 google.com 域拉取证书


$ openssl s_client -showcerts -connect google.com:443 </dev/null

此命令使用 TLS 连接到远程服务器,并将有关 TLS 握手的大量信息转储到您的控制台。如果您没有将标准输入从 /dev/null 重定向,则连接将保持打开状态,允许您直接在端口 443 上与服务器交互(您可以使用 Ctrl-D 显式关闭此连接)。

以下是输出的(缩写)视图,我将在本文的其余部分中进行探讨


$ openssl s_client -showcerts -connect google.com:443
 ↪</dev/null
CONNECTED(00000003)
depth=2 OU = GlobalSign Root CA - R2, O = GlobalSign, CN =
 ↪GlobalSign
verify return:1
depth=1 C = US, O = Google Trust Services, CN = Google
 ↪Internet Authority G3
verify return:1
depth=0 C = US, ST = California, L = Mountain View, O =
 ↪Google LLC, CN = *.google.com
verify return:1
---
Certificate chain
0 s:/C=US/ST=California/L=Mountain View/O=Google
 ↪LLC/CN=*.google.com
  i:/C=US/O=Google Trust Services/CN=Google Internet
 ↪Authority G3
-----BEGIN CERTIFICATE-----
MIIIgjCCB2qgAwIBAgIITFQTbb/xK/QwDQYJKoZIhvcNAQELBQAwVDELMAkGA1UE
BhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczElMCMGA1UEAxMc
R29vZ2xlIEludGVybmV0IEF1dGhvcml0eSBHMzAeFw0xODEwMzAxMzE1MDVaFw0x
        <... content omitted ...>
OTAxMjIxMzE1MDBaMGYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh
pvxysdjrJ8qfUyD0AY/Z8dCs1RQfx8SKbXuoML9e0X5uxRmeyjQ0s+BPJDIQG5b8
IGSRGSm8vWtg9vz/GDZIErtEO1kgXOslBBGL5NSCFpxkp1lh/Usi3nFzPcU6Fbvx
WMSdoZZKpgy5+6GGjYv/dEyEdnXYzg==
-----END CERTIFICATE-----
1 s:/C=US/O=Google Trust Services/CN=Google Internet
 ↪Authority G3
  i:/OU=GlobalSign Root CA - R2/O=GlobalSign/CN=GlobalSign
-----BEGIN CERTIFICATE-----
MIIEXDCCA0SgAwIBAgINAeOpMBz8cgY4P5pTHTANBgkqhkiG9w0BAQsFADBMMSAw
HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs
U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy
        <... content omitted ...>
FIwsIONGl1p3A8CgxkqI/UAih3JaGOqcpcdaCIzkBaR9uYQ1X4k2Vg5APRLouzVy
7a8IVk6wuy6pm+T7HT4LY8ibS5FEZlfAFLSW8NwsVz9SBK2Vqn1N0PIMn5xA6NZV
c7o835DLAFshEWfC7TIe3g==
-----END CERTIFICATE-----
---
        <... content omitted ...>
---
DONE

此输出显示服务器返回了两个 PEM 编码的证书,而不是一个。每个证书都用 -----BEGIN CERTIFICATE----------END CERTIFICATE----- 标记括起来。尽管它们看起来令人生畏,但请记住,它们只不过是 base64 编码的 DER 数据,并且您有可以查看内部的工具!

OpenSSL 在每个证书的 BEGIN 标记上方提供了一点上下文,带有 s:(主题)和 i:(颁发者)标记。主题告诉您证书是为哪个服务器(或其他实体)生成的,而颁发者告诉您哪个证书颁发机构签署了该证书。如果主题与您连接的服务器匹配,并且您信任颁发者,那么您就可以继续了。

此时,让我们暂停一下,注意用于编码证书的 X.509 标准源自与 LDAP 相同的 X.500 标准系列。您可以在用于标识主题和颁发者的目录语法中看到一些这种共同的血统


C=US,ST=California,L=Mountain View,O=Google LLC,CN=*.google.com

如果您不熟悉 LDAP 命名标准,最重要的是要理解 “CN” 是证书所有者的 “通用名称”,名称的其余部分用于目录中的组织。

您可以使用 OpenSSL 解码这些证书。将第一个证书 (CN=*.google.com) 提取到名为 “google_com.crt” 的文件中,并将第二个证书 (CN=Google Internet Authority G3) 提取到名为 “google_authority_g3.crt” 的文件中。包括包装每个证书的 BEGINEND,但不要包含更多内容。完成后,您应该有两个看起来像这样的文件


$ cat google_com.crt
-----BEGIN CERTIFICATE-----
MIIIgjCCB2qgAwIBAgIITFQTbb/xK/QwDQYJKoZIhvcNAQELBQAwVDELMAkGA1UE
BhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczElMCMGA1UEAxMc
R29vZ2xlIEludGVybmV0IEF1dGhvcml0eSBHMzAeFw0xODEwMzAxMzE1MDVaFw0x
        <... content omitted ...>
OTAxMjIxMzE1MDBaMGYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh
pvxysdjrJ8qfUyD0AY/Z8dCs1RQfx8SKbXuoML9e0X5uxRmeyjQ0s+BPJDIQG5b8
IGSRGSm8vWtg9vz/GDZIErtEO1kgXOslBBGL5NSCFpxkp1lh/Usi3nFzPcU6Fbvx
WMSdoZZKpgy5+6GGjYv/dEyEdnXYzg==
-----END CERTIFICATE-----

$ cat google_authority_g3.crt
-----BEGIN CERTIFICATE-----
MIIEXDCCA0SgAwIBAgINAeOpMBz8cgY4P5pTHTANBgkqhkiG9w0BAQsFADBMMSAw
HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs
U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy
        <... content omitted ...>
FIwsIONGl1p3A8CgxkqI/UAih3JaGOqcpcdaCIzkBaR9uYQ1X4k2Vg5APRLouzVy
7a8IVk6wuy6pm+T7HT4LY8ibS5FEZlfAFLSW8NwsVz9SBK2Vqn1N0PIMn5xA6NZV
c7o835DLAFshEWfC7TIe3g==
-----END CERTIFICATE-----

现在,您可以使用以下命令解码任一证书的内容


$ openssl x509 -in google_com.crt -noout -text

这是一个经过稍微编辑的输出版本


Certificate:
   Data:
       Version: 3 (0x2)
       Serial Number: 5500042407018834932 (0x4c54136dbff12bf4)
   Signature Algorithm: sha256WithRSAEncryption
       Issuer: C = US, O = Google Trust Services, CN = Google
 ↪Internet Authority G3
       Validity
           Not Before: Oct 30 13:15:05 2018 GMT
           Not After : Jan 22 13:15:00 2019 GMT
       Subject: C = US, ST = California, L = Mountain View,
 ↪O = Google LLC, CN = *.google.com
       Subject Public Key Info:
           Public Key Algorithm: rsaEncryption
               Public-Key: (2048 bit)
               Modulus:
                  00:d1:bf:94:10:1f:94:15:bd:6c:3b:83:97:49:29:
                  ad:08:63:18:11:1b:57:7d:4d:b3:3f:9c:cd:62:ed:
                  eb:4d:d2:6b:78:3f:3f:01:48:43:a8:81:b6:42:f6:
                           <... content omitted ...>
                  e1:e4:24:b8:21:c4:9e:e5:86:c6:73:45:4f:a8:6f:
                  e0:81:f3:4e:46:03:3d:e9:d2:01:5b:6f:57:3c:22:
                  d4:83
               Exponent: 65537 (0x10001)
       X509v3 extensions:
           X509v3 Extended Key Usage:
               TLS Web Server Authentication
           X509v3 Subject Alternative Name:
               DNS:*.google.com, DNS:*.android.com,
 ↪<... content omitted ...>
           X509v3 CRL Distribution Points:
               Full Name:
                 URI:http://crl.pki.goog/GTSGIAG3.crl

   Signature Algorithm: sha256WithRSAEncryption
        c7:57:a4:97:ad:32:e1:5f:10:53:05:ba:03:c4:cd:2e:11:c9:
        7d:36:a9:4c:16:a8:46:a1:5a:30:c4:4f:04:86:8d:8b:e1:95:
        24:34:62:94:48:b9:8a:3d:d2:d7:49:eb:a5:6c:59:72:c3:64:
                <... content omitted ...>
        16:9c:64:a7:59:61:fd:4b:22:de:71:73:3d:c5:3a:15:bb:f1:
        58:c4:9d:a1:96:4a:a6:0c:b9:fb:a1:86:8d:8b:ff:74:4c:84:
        76:75:d8:ce

这看起来像是进展——您可以清楚地看到 google.com 服务器提供的证书的所有细节!让我们来看看重点。

在证书的顶部附近,您可以在 “Data” 部分中看到序列号。证书颁发机构在生成证书时为每个证书提供唯一的序列号。这允许在需要吊销证书时唯一标识证书。

两个 “Signature Algorithm” 块紧随 Data 块之后。第一个包含服务器的公钥信息以及 CA 启用的任何扩展(选项)。第二个块包含 CA 生成的签名哈希。

查看第一个 “Signature Algorithm” 块中的详细信息,您可以看到证书的主题、颁发者和有效期。一般来说,证书只能在名称与主题匹配且在指定日期范围内的服务器上使用。“Issuer”(CA)标识用于验证第二个块中签名的公钥。如果此颁发者的公钥在受信任的密钥库中,则证书有效。

也许最重要的是,您可以在此部分中看到服务器的公钥。这是客户端将用于加密内容的密钥,以便只能在服务器上解密。在这种情况下,服务器提供了一个 2048 位的 RSA 密钥。

在服务器的公钥下方,您会找到一个标记为 “X509v3 extensions” 的块。本节中的扩展由 CA 在证书签名时设置,可用于启用(或限制)证书的使用。这些扩展的完整详细信息在 RFC 5280 中定义,但在此我将介绍重点。

您可以在 “X509v3 Extended Key Usage” 部分中看到证书被授权用于 “TLS Web Server Authentication”。这意味着证书可以用于正面识别 Web 服务器。此处可能列出的其他常见用途包括充当 CA(允许为其他服务器签名证书)或授权证书用作客户端身份证明。

“Subject Alternative Names” 部分是可选的,但它可用于列出任何其他允许使用此证书的 Web 服务器。SAN 部分允许使用 DNS 名称或 IP 地址列出服务器。正如您在示例中看到的,支持通配符。如果证书的主题与服务器名称不匹配,但可以在此处进行匹配,则该证书被视为主题匹配。

在 X509v3 扩展块中要查看的最后一个条目是 “X509v3 CRL Distribution Points”。CRL 是 “证书吊销列表”。这些列表包含 CA 颁发的任何已泄露、已停用或(由于某些其他原因)失效的证书的序列号。

作为协商 TLS 连接的常规步骤,客户端应验证服务器提供的证书(或用作 CA 的证书的序列号)是否未出现在 CRL 上。理想情况下,每次验证证书时,都应针对域的 CRL 的当前版本检查这些序列号。在实践中,为每个连接拉取更新的 CRL 会给流程增加大量开销。大多数客户端将使用缓存,该缓存很少是最新的,但如果托管 CRL 的站点不可用,则可以避免问题。

第二个 “Signature Algorithm” 块包含第一个块中找到的 DER 编码 X.509 数据的签名哈希。您可以看到使用了 SHA256 哈希算法,并且 RSA 私钥用于对哈希进行签名。在使用 CA 的相应公钥解码后,此哈希必须与客户端为相同数据计算的值匹配。

这就是使用 DER 进行编码非常重要的原因:如果像使用 BER 一样,证书中的数据有多种编码方式,则哈希可能会采用多个不同的值。通过使用 DER,您可以保证值被一致地编码和解码为相同的字节序列。如果单个字节发生更改,则会创建不同的哈希,并且无法完成签名的验证!

证书包含很多细节,并且所有细节都必须对齐,客户端才能信任该证书。事情经常崩溃也就不足为奇了。

TLS 连接最常见的错误包括

  • 服务器名称不匹配,这意味着证书的 CN(或主题备用名称部分中的任何条目)与呈现它的主机不匹配。
  • 证书已过期,需要由 CA 重新颁发。
  • 用于签署证书的根 CA 不在客户端的受信任密钥库中。

凭借您在本文中获得的有关证书的知识,这些问题的含义以及(也许更重要的)解决方案应该变得更加清晰。

资源

Jeff Woods 在 IT 领域工作了 20 多年,在软件工程、数据工程、运营、安全工程和 DevOps 等领域拥有广泛的经验。他使用 Linux 的历史可以追溯到 1993 年,当时他开始使用 SLS 发行版。他目前在亚特兰大附近的一家全球金融科技公司担任 IT 架构师。除了他的 IT 经验外,Jeff 还是一位海军退伍军人、父亲,并且在过去的 12 年中担任过各种 BSA 项目的领导者。您可以通过 jcwoods@gmail.com 或通过 LinkedIn 联系他:https://www.linkedin.com/in/jeff-woods-a50b921

加载 Disqus 评论