上回说到SSL通过证书(certificate)验证服务器或客户端的身份。今天就来解答两个问题:

1.证书是什么?

2.如何使用证书验证服务器(少见地,也可能是客户端)的身份?

证书是什么?里面有什么

首先,我们用openssl自己造一张证书:

openssl genrsa -out server.key

openssl是什么不用我多说,genrsa指产生rsa密钥。输出到server.key中。这个输出的文件写的是“私钥”,其实里面公私钥和生成公私钥的数应该是都有的。

看一下里面有什么:

cat server.key

输出:

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEApSUo2NghUOYaQGhL6WwLzDDtD1SxEibolifYW4XacaeN3suz
a/5MD5pvTW6dGVf1ah4vfPEqMbG3tglpE+1bQG2Aqy5p+ODcx/4f7rDBMt/Lgthy
QuSXILwTxVE88CthMwnFRSxARAWjQk74CEOQMLRKpS7SnizPP9hTQE0CUi+KXngR
WI9Za20D5gsx4Fzb3w1phNPXZvouBKK2Zsj1sohqVxz8DH5h28/iU3Ynk0tUxmGU
hJJWycOC/NImKeYVhzGN3rFWWcW6lXT9lFoieYuEdcp43kLA3aRzF4cTq0ln//6H
lORJN+uvNqv74NcBax/TbGv9zgUShjr5NkAHLwIDAQABAoIBACL9sp/ve2+msZ0/
/AWjTQSgwTnkWfhcoh9epesOe3Blbhvs0UFqijcsI7UmYMcD6g3oA2vXtXb+DszV
RZxhSFpoWUDr/qwrCNyfLajnbqgW3woEToF56cOX1iCSy/SfXGLyCQhVDfDbuPHN
xaQKnLyVFtPVDe9R0z/V2B3hs0IyVVDC8rJWkHDJ1YteWnNK5UJjsCZB4mhB3crC
kx/JxBMllcLN1DCsIIHliMfwDZa6xUZ37bHCD3Ch3Kmz1Vr6IT8CHjq7EEpHi2Vb
jLmQKl/LUdg3qCS0H8etmUPeKrc1brdxAUUUokNFvYOssyCeZr8PBJW1CieUvjrt
rj1BerECgYEA1SSl17nREmIIHGT/7VJepZfSF5jW3lkWijMGcciKKlsVwS9BrftX
415MDAaTaVWuR+JwicG3MFY/z1U6bHEu3sGpvoFI9R9eqvr8ofiRBdo6avR+kEtk
rEx8rqQ0Yt7pt+RN8ReswROfLytOztAHBrfwsivMYorHg+IeuOGKaNMCgYEAxlnb
mbiHutzPrbQ5SXcSJkCTzVUSYg/pgGkSjfL/MXWUDL0oWTFoQFxwuZ+4khdCpBQJ
crVKs6guGUfwPB8nmD1DuNuFmadl9fYBTDZosYw5EOK4fSjuJcC7MyYpPHFfLFAN
fciVmoFvxZhbdxRlzPPAMIpyjUbfy8fEU+R6LrUCgYEApLKR19VEwZCwtcgxnl+E
tS5QcF1wwxVR/m4ZL7kHUl1ZvRJYDSlnq1CFMNGv/LfxWKiXz0q51AcBfaNc5si6
r/Qzxvo9tvOLglE4/6uD3GLZuyW72qH3Y9AilAxcUn3vWWJ3+7EQk40m9xre5Z4H
n+BohDSl1QtpyfXjSXc2LXMCgYBG+cJ92C0hbYAW/SV+p0/kRjldQTLJyj3YyEBu
cORmM1ed01YLzIUseqePlJq8E/yxr8XNuReY//Y276oEKXXGoS2JiWveFquCftvq
BJIj7jRBWY+AodPNyJBz9hTNXxgaSC77snnuBqETSLh5/N+MnjBIblIdQZ41Ui9r
gZdC/QKBgB3Ty8gu+c68YfTIG3Qzn9ai1iUc6ZeOczPH0H6jdRXBVptD7Ms7PPbp
Pkjj3mea9lAC1Y6der0fBZ+gRYQNhO0Z/vlgWkWSpyic+K5FBlDasZTDSyknLPNO
kfQpMWI6YrnUe8w9qwPxL5J5RFDzZmJvtBSeN3PAzzitydTKdXmz
-----END RSA PRIVATE KEY-----

看来里面只有私钥,奇怪的是这里面似乎只有可见字符。查询资料后得知,这是BASE64编码后的结果:

Base64是一种基于64个可打印字符来表示二进制数据的表示方法。

如果想要看里面真的有什么,还是需要openssl的帮助:

openssl rsa -in server.key -text

这命令会将公私钥一同打出。

好,我们已经有了密钥对,可以来自己签一张证书了。

openssl req -new -key server.key -out server.csr

注意,这个命令是在生成一个“证书请求”。填好必要信息后就有了server.csr

下面,用自己的私钥对这个“证书请求”签名,得到证书:

openssl x509 -req -in server.csr -out server.crt -signkey server.key -days 3650

这“证书请求是”什么东西呢?简单地说, “证书请求”和证书是很相近的,或者说,“证书请求”就是未经签名的证书。我们将想要写入的身份信息写入“证书请求”,然后交给CA进行验证和签名后,就有了证书。要理解具体的过程,需要看一下证书的定义:

 Certificate  ::=  SEQUENCE  {        
tbsCertificate       TBSCertificate,        
signatureAlgorithm   AlgorithmIdentifier,
signatureValue       BIT STRING  }
 

这是RFC 5280中对于证书的定义,这种奇怪的表示方法,叫做ASN.1,可以理解为一种表示数据的方法(其实在我看来就是定义数据结构……),也就说,证书,就是tbsCertficate 后面接 signatireAlgorithm 再后面接 signatureValue。

具体是什么样的过程呢?简单的说,就是openssl对“证书请求”进行了一下加工,将对应的部分填入tbsCertificate。再根据请求的签名算法,对证书进行签名。其中,所请求的签名算法,就是signatureAlgorithm字段。

可以看到,这个过程的重点,是CA校验“证书请求”的真实性,并进行签名。CA如何校验“证书请求”的真实性呢?或者说,“证书请求”的请求者,必须要满足什么条件呢?

用下面的命令查看“证书请求”的内容:

 openssl req -in server.csr -noout -text

输出如下:

Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: C=CN, ST=GuangDong, L=GuangZhou, O=Sun Yet-san University, OU=Stu, CN=google.com/[email protected]
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    ......
                Exponent: 65537 (0x10001)
        Attributes:
            a0:00
    Signature Algorithm: sha256WithRSAEncryption
         ......

想一想的话,这里面最重要的,无疑就是CN = google.com这个信息了。请求这张证书的请求者必须证明自己就是google.com(直接通过DNS解析),或者google.com这个域名的所有权归请求者所有(通过DNS的TXT记录)。

好,假设请求者可以证明这点,CA就可以签发证书了。现在只需要把证书最后的signatureValue填好,证书就完成了。具体来说,就是将之前做好的tbsCertificate用请求的hash算法取得摘要,再用自己的rsa私钥进行加密(也可以用别的非对称加密算法),将加密的结果填入signatureValue。

什么?RSA私钥还可以用作加密?这个……确实是的。用私钥加密后,可以用公钥解密。

最终产生的证书的内容,和“证书请求”的内容基本相同,用以下命令查看:

openssl x509 -in server_.cer -text -noout

得到输出:

Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            99:2f:c7:9b:ed:6d:d0:93
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=CN, ST=GuangDong, L=GuangZhou, O=Sun Yet-san University, OU=Stu, CN=google.com/[email protected]
        Validity
            Not Before: Oct  6 07:23:29 2019 GMT
            Not After : Oct  3 07:23:29 2029 GMT
        Subject: C=CN, ST=GuangDong, L=GuangZhou, O=Sun Yet-san University, OU=Stu, CN=google.com/[email protected]
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    ......
                Exponent: 65537 (0x10001)
    Signature Algorithm: sha256WithRSAEncryption
         ......

可以看到,主要的区别是多了一些字段,比如Issuer(用于表面签发者的身份),Serial Number(序列号)等等。

客户端如何根据证书验证服务端身份?

我们假设现在客户端在google上搜素什么东西。现在,服务端发送certificate。客户端收到了服务端www.google.com的一张证书。它是如何验证这张证书不是伪造的呢?

点击chrome浏览器的“锁”,你可以看到证书:

点击“证书路径”,你会看到:

为什么会有三张证书呢?这就要说到证书链了。

刚才在介绍证书内容时,我们介绍了“签名”就是将证书 的tbsCertificate部分进行HASH,将结果用CA的私钥加密后的值。很自然地,如果客户端要验证这张证书并非伪造,那么可以用CA的公钥解密“签名”,将解密后的值(HASH_1)和自己进行运算的值(HASH_2)进行对比,如果相同,那么这证书就不是伪造的。

那么就会有一个问题:CA公钥从哪里获得?如何保证CA公钥是正确的?

证书链的引入就是为了解决这个问题。首先,我们制作一些私钥绝对安全的根证书,这些证书只能是自签的(也就是说,加密自己证书的私钥是自己的,上面的即使一张自签证书),这是因为在此之前没有任何证书可以为他们签名。

然后,用这些根证书签名一些“CA证书”,这些CA证书可以为别的证书签名,但他们不是自签的。证书的扩展(Extension)字段中有一个选项是Basic Constraints,定义如下:

 BasicConstraints ::= SEQUENCE {        
cA                      BOOLEAN DEFAULT FALSE,        
pathLenConstraint       INTEGER (0..MAX) OPTIONAL }

只要这个字段中的cA选项为true,这张证书就可以为其他证书签名。也就是说,我们用root证书制作出cA选项为true的证书,就可以为其他证书签名了。这样就会有至少三级的证书链:

目标网站的证书 <= CA证书 <= root证书

这样一来,浏览器只要维护好root证书,就可以通过root证书验证CA证书的真伪,进而验证目标网站的证书的真伪了。至于CA证书如何得到,其实有很多方法,大概有三个:

1.浏览器自己维护。

2.服务器发送自己的证书时,也发送ca证书。

3.通过authorityInfoAccess扩展字段发送ca证书的url:

不管具体用什么方法,只要证书链上的一环不匹配,浏览器就可以发送alert消息,直接断开连接。

如果证书验证成功,客户端还要校验证书是否被吊销,这可以通过上面那张图中的OCSP验证,不再多说。

等等!如果有人下载了google.com的证书,自己用了起来,会怎么样呢?

这也不用担心。因为证书里的公钥,是google.com的,不是他的。他是无法正确地解密客户端发送的pre-master-key的,进而,他无法正确地发送加密握手消息Finished,客户端会直接发送alert,断开连接。

经过了漫长的旅途,我们终于完全验证了服务端的身份。只要root私钥、CA私钥的安全无虞,CA没有乱发证书,这种验证,就是无懈可击的。

诶?似乎有个东西叫做CDN吧!比如你在访问本站的时候就使用了cloudflare的CDN。这个时候,客户端实际获得的,是CDN的证书。比如本站的:

这是怎么做到的呢?要知道,只要证书和域名不匹配,就会使得浏览器报错:

那么,cdn是怎么做到在我访问本站的时候,给出一张sni.cloudflaresll.com的证书的呢?

哈哈!虽然RFC5280没有明确说,但是一般的证书的Subject字段只有一个CN(可以理解为域名,但也可以是别的)在这里就是sni.cloudflaresll.com了。我们查看一下这张证书,会发现它在Subject Alternative Name字段别有玄机:

这样一来,就可以让浏览器把这证书当作本站的证书了。

不过方便之余,细细一想也有点后怕:这cloudflare在我不知情的情况下,擅自发我域名的证书,如果有一天……

而且更让人担心的是,这时是客户端和cdn节点握手,然后cdn节点和我的服务器再握手,岂不相当于让cdn获得了我的数据??

不过出于其他因素的考虑,cdn还是利大于弊的,这也是本站挂了cdn的原因。

发表评论

电子邮件地址不会被公开。 必填项已用*标注