Pages

Friday, 21 May 2021

SSL / TLS证书入门十问

 SSL(Secure Sockets Layer)安全套接字协议,用于在因特网上加密通信,现在已经被更新后的 TLS(Transport Layer Security)传输层安全协议所取代。

SSL/TLS 的主要应用场合是客户端同服务器间的通信,当然也可以应用于邮件加密、VoIP  等非安全网络环境的数据传输。

1] 为什么使用 SSL

如果诸如电子邮件ID、用户ID、密码、信用卡/借记卡详细信息以及银行帐户详细信息之类的信息通过不受保护的协议传输,就会存在很大的风险,使此类隐私信息在传输过程中会被网络犯罪份子直接获取。这种对正在传输的数据拦截称为中间人(MITM)攻击。

这就是我们需要保护数据传输过程安全的原因。

2] 什么是 SSL 证书

SSL 证书是网站服务器上安装的文件。它只是一个数据文件,其中包含公钥和网站所有者的身份以及其他信息。基本上是证书签发方的详细信息。

信息包括:域名、证书有效期、证书颁发机构(CA)详细信息、公钥、密钥算法、证书签名算法、SSL / TLS版本、指纹、指纹算法、组织名称、网站所有者、地址、城市、州、国家。

没有 SSL 证书,则访问该网站时就不能使用 TLS 进行加密通信。

3] SSL 证书有什么作用

SSL 协议可以对链路和正在传输的数据进行加密。浏览器可以使用 SSL 安全协议与 Web 服务器进行交互。为此,他们需要 SSL 证书来建立安全连接。SSL 证书最常见的用法是使用 HTTPS 协议进行加密 Web 浏览。

4] 什么是加密

众所周知, SSL 证书可以用于加密。

而所谓的加密,就是将原本网络上明文传输的内容转换成不可以直接了解内容的数据。

在启用 HTTPS 的网站上发送任何数据,则该信息将被转换为不可读的字符串。例如,如果密码是1234,则可能会将其转换为类似 ^%jfdgrt5 / * u 的密码在网络问传输。这几乎使任何黑客都无法解释信息的初始内容,即使他们设法以某种方式截获到传输的数据。

5] SSL/TLS 如何工作

让我们通过一个非常简单的示例来解释它。

当我们访问网站时,PC 或移动设备的 Web 浏览器与网站的 Web 服务器之间就会进行通信。信息在浏览器与服务器之间传输。

然后就可以看到 SSL / TLS 握手过程。通过 TCP 握手打开 TCP 连接后,会发生 TLS 握手。每当其他任何通信使用HTTPS(包括 API 调用和 HTTPS 查询上的 DNS)时,也会发生TLS握手。

6] 什么是 SSL/TLS 握手

TLS 会话通过 TLS 握手建立,过程大致如下:

  • 浏览器:通过 SSL / TLS (HTTPS)协议连接到服务器
  • 服务器:将包含公钥的 SSL 证书复本发送给浏览器
  • 浏览器:检查证书,如果可用,则根据服务器证书的公钥创建并发送给服务器用于加密会话的密钥对
  • 服务器:用私钥解密密钥对,并用解密出的密钥加密会话数据返回给浏览器,表示握手完成,可以通过密钥对进行加密传输

7] 网站如何获得 SSL 证书

网站所有者需要从证书颁发机构(CA)获取 SSL 证书,然后将其安装在其 Web 服务器上(通常 Web 主机可以处理此过程)。证书颁发机构是可以确认网站所有者身份的第三方机构,他们会保留所颁发证书的副本。

 8] HTTP和HTTPS有何区别

“HTTPS”中的 S 代表”安全”。HTTPS 只是具有 SSL / TLS 的 HTTP。具有 HTTPS 地址的网站具有由证书颁发机构颁发的合法 SSL 证书,并且使用 SSL / TLS 协议对往返于该网站的流量进行身份验证和加密。

在浏览器上哪里可以找到可信证书颁发机构的详细信息?

9] 为什么浏览器默认信任一些 CA

浏览器访问一些 HTTPS 网站时,会默认信任该网站的证书,而不会提示证书不可靠。这是由于浏览器(可能还有操作系统)附带了受信任的 CA 列表。这些预安装的证书用作信任锚,以从中获取所有进一步的信任。当访问 HTTPS 网站时,浏览器将验证服务器在 TLS 握手期间提供的信任链是否以本地信任的根证书之一结尾。如果验证成功,则会默认信息该网站的 CA 证书。

10] 根证书会过期么

根证书确实会过期,但是它们的有效期往往非常长(通常约20年)。这样,就可以期望,通过更新浏览器或操作系统,在旧证书过期之前获得新的根证书。

-----------------

浏览器是如何校验证书的?


现如今的 Web,HTTPS 早已经成为标配,公开的 HTTP 网站已经和 Flash 一样,慢慢在消亡了。

启用 HTTPS 的核心是一个叫做 证书 的东西。不知道大家是否有留意,前几年上 12306 的时候,浏览器都会提示「您的链接不是私密链接」,这其实就是因为 12306 的证书有问题。如果点击「继续前往」,打开 12306 网站,它会提示你下载安装它提供的“根证书”。

那么,证书是什么?里面含有什么内容?浏览器为什么会不信任 12306 的证书?为什么下载 12306 提供的根证书就可以解决这个问题?根证书又是什么?

证书,公钥和私钥

我们先来简单回顾一下 TLS 和 HTTPS。关于这个话题,我在 从零开始搭建一个 HTTPS 网站 这篇博客中有详细的论述,对细节感兴趣的朋友可以先去读一下这篇博客。

HTTPS 全称是 HTTP Over TLS,也就是使用 TLS 进行 HTTP 通信。

TLS (Transport Layer Security) 是一个安全通信协议,用于建立一个安全通道来交换数据。

看名字可以知道这是一个传输层协议,因此应用层协议对它是没有感知的,HTTP, FTP, SMTP 都可以和 TLS 配合使用。

Tip: 关于网络协议,阮一峰的这篇博客 互联网协议入门(一) 写的很好,值得一看。

TLS 创建安全链接的步骤如下:

  • 双方协商使用的协议版本,加密算法等细节
  • 服务器发送 证书 给客户端
  • 客户端校验证书有效性
  • 双方根据握手的一些参数生成一个对称秘钥,此后所有的内容使用这个秘钥来加密

我们先来看证书,证书是一个文件,里面含有目标网站的各种信息。

例如网站的域名,证书的有效时间,签发机构等,其中最重要的是这两个:

  • 用于生成对称秘钥的公钥
  • 由上级证书签发的签名

证书文件的格式叫做 X.509,由 RFC5280 规范详细定义。存储上分为两种,一种叫做 DER,是二进制的,还有一种叫做 PEM,是基于 Base64 的。

关于 RSA 的公钥和私钥记住一点就行:我们可以使用算法生成一对钥匙,他们满足一个性质:公钥加密的私钥可以解开,私钥加密的公钥可以解开

Tip: RSA 算法的具体工作原理可以参考我这篇博客 RSA 的原理与实现

证书,顾名思义,是用来证明自己身份的。因为发送证书的时候是明文的(这一步也没法加密),所以证书内容是可以被中间设备篡改的。

那么要怎样设计一套机制保证当我访问 github.com 的时候,收到的证书确实是 github.com 的证书,而不是某个中间设备随意发来的证书?

大家可以思考一下这个问题🤔。

解决办法是采用「信任链」。

首先,有一批证书颁发机构(Certificate Authority,简称为 CA),由他们生成秘钥对,其中私钥保存好,公钥以证书的格式安装在我们的操作系统中,这就是 根证书

我们的手机、电脑、电视机的操作系统中都预装了 CA 的根证书,他们是所有信任构建的基石。当然,我们也可以自己下载任意的根证书进行安装。

接下来,只要设计一个体系,能够证明 A 证书签发了 B 证书即可。这样对于收到的任何一个证书,顺藤摸瓜,只要最上面的根证书在系统中存在,即可证明该证书有效。

比如说,我们收到了服务器发过来的 C 证书,我们验证了 C 是由 B 签发的,然后又验证了 B 是由 A 签发的,而 A 在我们的系统中存在,那也就证明了 C 这个证书的有效性。

这其中,A 是根证书,B 是中间证书,C 是叶证书(类似树中的叶节点)。中间证书可以有很多个,信任的链条可以任意长,只要最终能到根证书即可。

得益于 RSA 的非对称性质,验证 A 是否签发了 B 证书很简单:

  • 计算 B 的 hash 值(算法随便,比如 SHA1)
  • 使用 A 的 私钥 对该 hash 进行加密,加密以后的内容叫做「签名(Signature)」
  • 将该「签名」附在 B 证书中

A 使用自己的私钥给 B 生成签名的过程也就是「签发证书」,其中 A 叫做 Issuer,B 叫做 Subject。

这样,当我们收到 B 证书时,首先使用 A 证书的公钥(公钥存储在证书中)解开签名获得 hash,然后计算 B 的 hash,如果两个 hash 匹配,说明 B 确实是由 A 签发的。

重复上面的过程,直到根证书,就可以验证某个证书的有效性。

接下来我们来问几个问题。

为什么需要中间证书?

为什么要设计中间证书这个环节?直接使用根证书进行签发不好吗?

这是因为根证书的私钥安全性至关重要,一旦被泄露,将引起巨大的安全问题。

所以,根证书的私钥都是被保存在离线的计算机中,有严格的操作规章,每次需要使用时,会有专人将数据通过 USB 拷贝过去,操作完了以后,再将数据带出来。

在这套流程下,直接将根证书用于签发普通证书是不现实的。想想这个世界上有多少网站,每天对证书的需求量都是巨大的,根证书的操作效率无法满足要求,因为不能批量和自动化。

同时,对根证书私钥的操作越多,泄露的风险也就越大,因此,人们就发明了中间证书。

使用根证书签发一些中间证书,这些中间证书就可以用来签发大量的叶证书,这个过程完全可以是自动化的,就像 Let’s Encrypt 那样。

同时,即便中间证书的私钥泄露了也不要紧,可以使用根证书把它们撤销掉,具体怎么撤销是另外一个话题了,这里不再展开。

通过使用中间证书,我们就可以做到既方便,又安全。

浏览器如何获取中间证书?

一般来说,服务器会将中间证书一并发送过来。也就是说,当我们访问某个网站时,收到的不是一个证书,而是一系列证书。

当然,这不是强制要求,在服务器不发送的情况下,浏览器也会使用一些方法去定位中间证书,比如

  • 缓存之前下载过的证书
  • 证书文件中的 Authority Information Access (AIA) Extension 里面含有上级证书的相关信息,浏览器可以使用这个信息去下载上级证书

根证书有时效吗?过期了怎么办?

每个证书都有有效时间,根证书自然也不例外。

在 Mac 上,打开 Keychain Access,选择侧边的 System Roots 就可以看到我们系统中安装的所有根证书。

其中很明显地显示了一栏叫做「Expires」,过期时间。

那么,根证书过期了怎么办?

答案是不怎么办,在过期前 CA 会生成新的根证书,并和各大产商合作,在操作系统升级的时候安装到我们的设备上。

老的根证书过期以后只是无法再作为信任锚点为其他的证书提供信任而已。

根证书的生命周期一般是 20 年,Let’s Encrypt 之前使用的根证书是 DST Root CA X3,2021 年九月就要过期了,这个证书是 2000 年创建的。

Let’s Encrypt 在 2015 年创建了一个新的根证书 ISRG Root X1,在 2018 年被完全认可并逐步安装到各种设备上。

安装新的根证书的唯一方式是通过系统更新,但是这其实是不可控的事情,因为总有一些设备不会更新。

我们可以想象一下,如果一个设备停止更新,同时里面内置的所有根证书都过期了,那么这个设备就无法再进行 HTTPS 通信了。

根证书有什么特征?根证书的签名是什么?

每个证书中都含有当前证书对象 Subject 的信息以及该证书的签发者 Issuer 的信息。

根证书的特征是 Subject 和 Issuer 的信息是一致的,也就是所谓的「自签名」(Self-Signed)。

因为证书的签名是由上级证书的私钥来签的,根证书没有上级证书,所有根证书的签名是用自己的私钥签的。

我们可以来验证一下,以 GitHub 使用的根证书 DigiCert High Assurance EV Root CA 为例。

# 首先,打开 Keychain Access,找到上述证书,拖拽出来
# 重命名为 root.cer

# 转换 DER 到 PEM 格式
$ openssl x509 -inform der -in root.cer -out root.pem

# 查看证书的签名,可以看到签名所使用的的 hash 算法是 sha1
$ openssl x509 -in root.pem -text -noout -certopt ca_default -certopt no_validity -certopt no_serial -certopt no_subject -certopt no_extensions -certopt no_signame
    Signature Algorithm: sha1WithRSAEncryption
         1c:1a:06:97:dc:d7:9c:9f:3c:88:66:06:08:57:21:db:21:47:
         f8:2a:67:aa:bf:18:32:76:40:10:57:c1:8a:f3:7a:d9:11:65:
         8e:35:fa:9e:fc:45:b5:9e:d9:4c:31:4b:b8:91:e8:43:2c:8e:
         b3:78:ce:db:e3:53:79:71:d6:e5:21:94:01:da:55:87:9a:24:
         64:f6:8a:66:cc:de:9c:37:cd:a8:34:b1:69:9b:23:c8:9e:78:
         22:2b:70:43:e3:55:47:31:61:19:ef:58:c5:85:2f:4e:30:f6:
         a0:31:16:23:c8:e7:e2:65:16:33:cb:bf:1a:1b:a0:3d:f8:ca:
         5e:8b:31:8b:60:08:89:2d:0c:06:5c:52:b7:c4:f9:0a:98:d1:
         15:5f:9f:12:be:7c:36:63:38:bd:44:a4:7f:e4:26:2b:0a:c4:
         97:69:0d:e9:8c:e2:c0:10:57:b8:c8:76:12:91:55:f2:48:69:
         d8:bc:2a:02:5b:0f:44:d4:20:31:db:f4:ba:70:26:5d:90:60:
         9e:bc:4b:17:09:2f:b4:cb:1e:43:68:c9:07:27:c1:d2:5c:f7:
         ea:21:b9:68:12:9c:3c:9c:bf:9e:fc:80:5c:9b:63:cd:ec:47:
         aa:25:27:67:a0:37:f3:00:82:7d:54:d7:a9:f8:e9:2e:13:a3:
         77:e8:1f:4a

# 提取签名内容到文件中
$ openssl x509 -in root.pem -text -noout -certopt ca_default -certopt no_validity -certopt no_serial -certopt no_subject -certopt no_extensions -certopt no_signame | grep -v 'Signature Algorithm' | tr -d '[:space:]:' | xxd -r -p > root-signature.bin

# 提取根证书中含有的公钥
$ openssl x509 -in root.pem -noout -pubkey > root-pub.pem

# 使用公钥解密签名
$ openssl rsautl -verify -inkey root-pub.pem -in root-signature.bin -pubin > root-signature-decrypted.bin

# 查看解密后的内容
# 可以看到,签名中存储的 hash 值为 E35E...13A8
$ openssl asn1parse -inform DER -in root-signature-decrypted.bin
    0:d=0  hl=2 l=  33 cons: SEQUENCE
    2:d=1  hl=2 l=   9 cons: SEQUENCE
    4:d=2  hl=2 l=   5 prim: OBJECT            :sha1
   11:d=2  hl=2 l=   0 prim: NULL
   13:d=1  hl=2 l=  20 prim: OCTET STRING      [HEX DUMP]:E35EF08D884F0A0ADE2F75E96301CE6230F213A8

# 接下来我们计算证书的 hash 值

# 首先提取证书的 body
# 因为证书中含有签名,签名是不包含在 hash 值计算中的
# 所以不能简单地对整个证书文件进行 hash 运算
$ openssl asn1parse -in root.pem -strparse 4 -out root-body.bin &> /dev/null

# 计算 sha1 哈希值
$ openssl dgst -sha1 root-body.bin
SHA1(root-body.bin)= e35ef08d884f0a0ade2f75e96301ce6230f213a8

hash 值匹配,这也就说明根证书确实是自签名的,用自己的私钥给自己签名。

纸上得来终觉浅

理论知识我们已经全部具备了,接下来我们来完整走一遍流程,以 github.com 为例,校验一下它的证书是否有效。

# 新建一个文件夹 github 保存所有的文件
$ mkdir github && cd github

# 首先,我们下载 github.com 发送的证书
$ openssl s_client -connect github.com:443 -showcerts 2>/dev/null </dev/null | sed -n '/-----BEGIN/,/-----END/p' > github.com.crt

# github.com.crt 是 PEM 格式的文本文件
# 打开可以发现里面有两段 -----BEGIN CERTIFICATE----
# 这说明有两个证书,也就是 github.com 把中间证书也一并发过来了

# 接下来我们把两个证书提取出来
$ awk '/BEGIN/,/END/{ if(/BEGIN/){a++}; out="cert"a".tmpcrt"; print >out}' < github.com.crt && for cert in *.tmpcrt; do newname=$(openssl x509 -noout -subject -in $cert | sed -n 's/^.*CN=\(.*\)$/\1/; s/[ ,.*]/_/g; s/__/_/g; s/^_//g;p').pem; mv $cert $newname; done

# 我们得到了两个证书文件
# github_com.pem 和 DigiCert_SHA2_High_Assurance_Server_CA.pem

# 首先,验证 github_com.pem 证书确实
# 是由 DigiCert_SHA2_High_Assurance_Server_CA.pem 签发的

# 提取 DigiCert_SHA2_High_Assurance_Server_CA 的公钥
# 命名为 issuer-pub.pem
$ openssl x509 -in DigiCert_SHA2_High_Assurance_Server_CA.pem -noout -pubkey > issuer-pub.pem

# 查看 github_com.pem 的签名
# 可以看到 hash 算法是 sha256
$ openssl x509 -in github_com.pem -text -noout -certopt ca_default -certopt no_validity -certopt no_serial -certopt no_subject -certopt no_extensions -certopt no_signame
    Signature Algorithm: sha256WithRSAEncryption
         86:32:8f:9c:15:b8:af:e8:d1:de:08:3a:44:0e:71:20:24:d6:
         fc:0e:58:31:cc:aa:b4:ad:1c:d5:0c:c5:af:c4:bb:fe:5f:ac:
         90:6a:42:c8:21:eb:25:f1:6b:2c:37:b2:2a:a8:1a:6e:f2:d1:
         4f:a6:2f:bc:cf:3a:d8:c1:9f:30:c0:ec:93:eb:0a:5a:dc:cb:
         6c:32:1c:60:6e:ec:6e:f8:86:a5:4f:a0:b4:6d:6a:07:4a:21:
         58:d0:29:7d:65:8a:c8:da:6a:ba:ab:f0:75:21:33:00:40:6f:
         85:c5:13:e6:27:73:6c:ae:ea:e3:96:d0:53:db:c1:21:68:10:
         cf:e3:d8:50:b0:14:ec:a9:98:cf:b8:ce:61:5d:3d:a3:6d:93:
         34:c4:13:fa:11:66:a3:dd:be:10:19:70:49:e2:04:4d:81:2c:
         1f:2e:59:c6:2c:53:45:3b:ee:f6:13:f4:d0:2c:84:6e:28:6d:
         e4:e4:ca:e4:48:89:1b:ab:ec:22:1f:ee:12:d4:6c:75:e9:cc:
         0b:15:74:e9:6d:9f:db:40:1f:e2:24:85:a3:4b:a4:e9:cd:6b:
         c8:77:9f:87:4f:05:73:00:38:a5:23:54:68:fc:a2:3d:bf:18:
         19:0e:a8:fd:b9:5e:8c:5c:e8:fc:e4:a2:52:70:ee:79:a7:d2:
         27:4a:7a:49

# 提取签名到文件中
$ openssl x509 -in github_com.pem -text -noout -certopt ca_default -certopt no_validity -certopt no_serial -certopt no_subject -certopt no_extensions -certopt no_signame | grep -v 'Signature Algorithm' | tr -d '[:space:]:' | xxd -r -p > github_com-signature.bin

# 使用上级证书的公钥解密签名
$ openssl rsautl -verify -inkey issuer-pub.pem -in github_com-signature.bin -pubin > github_com-signature-decrypted.bin

# 查看解密后的信息
$ openssl asn1parse -inform DER -in github_com-signature-decrypted.bin
    0:d=0  hl=2 l=  49 cons: SEQUENCE
    2:d=1  hl=2 l=  13 cons: SEQUENCE
    4:d=2  hl=2 l=   9 prim: OBJECT            :sha256
   15:d=2  hl=2 l=   0 prim: NULL
   17:d=1  hl=2 l=  32 prim: OCTET STRING      [HEX DUMP]:A8AA3F746FE780B1E2E5451CE4383A9633C4399E89AA3637252F38F324DFFD5F

# 可以发现,hash 值是 A8AA...FD5F

# 接下来计算 github_com.pem 的 hash 值

# 提取证书的 body 部分
$ openssl asn1parse -in github_com.pem -strparse 4 -out github_com-body.bin &> /dev/null

# 计算 hash 值
$ openssl dgst -sha256 github_com-body.bin
SHA256(github_com-body.bin)= a8aa3f746fe780b1e2e5451ce4383a9633c4399e89aa3637252f38f324dffd5f

hash 值匹配,我们成功校验了 github.pem 这个证书确实是由 DigiCert_SHA2_High_Assurance_Server_CA.pem 这个证书来签发的。

上面的流程比较繁琐,其实也可以直接让 openssl 来帮我们验证。

$ openssl dgst -sha256 -verify issuer-pub.pem -signature github_com-signature.bin  github_com-body.bin
Verified OK

接下来的过程是上面一样,首先,我们获取上级证书的信息。

# 获取上级证书的名字
$ openssl x509 -in DigiCert_SHA2_High_Assurance_Server_CA.pem -text -noout | grep Issuer:
        Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance EV Root CA

然后就是校验 DigiCert_SHA2_High_Assurance_Server_CA 是由 DigiCert High Assurance EV Root CA 来签发的,同时这个证书存在于我们系统中。

这一步我就不再重复了,留给大家作为练习~

自签名

最后,我们来看看怎么自己签发证书用于本地测试。

我们可以使用 openssl 这样的底层工具来完成这个任务,但是会很繁琐:生成根证书、生成下级证书,使用根证书签发下级证书、安装根证书。

程序员首先要解放的就是自己的生产力,这里我们使用一个工具 mkcert

brew intall mkcert 进行安装,然后使用 mkcert -install 将它的根证书安装到我们的系统中。

接下来我们就可以任意生成我们想要的证书了,以 localhost 为例,运行 mkcert localhost,我们得到了两个文件,其中 localhost-key.pem 是私钥,localhost.pem 是证书,用于发送给客户端。

使用一个 HTTP Server 来验证一下,这里我们使用 local-web-server

yarn global add local-web-server 安装完毕以后,使用 ws --key localhost-key.pem --cert localhost.pem 启动我们的服务器,打开 https://localhost:8000,可以看到地址栏有了一把可爱的小锁.

from http://web.archive.org/web/20210410065000/https://cjting.me/2021/03/02/how-to-validate-tls-certificate/

No comments:

Post a Comment