最近收到的几封读者邮件,都是询问为什么在 Nginx 中无法开启 OCSP Stapling。具体现象是在 Nginx 中明明配置了
ssl_stapling on
,但通过 SSL Labs 等工具查看,OCSP stapling
这一项并没有生效。本文就这个问题详细探讨下,如果你只关心结论,直接跳到最后一节即可。
我之前在《TLS 握手优化详解》这篇文章中介绍了 OCSP 是什么,为什么要开启 OCSP Stapling,这里再简单介绍下:
OCSP(Online Certificate Status Protocol,在线证书状态协议)是用来检验证书合法性的在线查询服务,一般由证书所属 CA 提供。某些客户端会在 TLS 握手阶段进一步协商时,实时查询 OCSP 接口,并在获得结果前阻塞后续流程。OCSP 查询本质是一次完整的 HTTP 请求 - 响应,这中间 DNS 查询、建立 TCP、服务端处理等环节都可能耗费很长时间,导致最终建立 TLS 连接时间变得更长。
而 OCSP Stapling(OCSP 封套),是指服务端主动获取 OCSP 查询结果并随着证书一起发送给客户端,从而让客户端跳过自己去验证的过程,提高 TLS 握手效率。
通过前面提到的 SSL Labs 这个强大的在线服务,可以轻松验证指定网站是否开启 OCSP Stapling。但这个网站功能太多,国内网站很可能要花十分钟才能完成全部检测项看到结果。
使用 Wireshark 这个神器,设置好抓包条件和过滤器后,也可以很方便地验证某个网站是否开启 OCSP Stapling,有兴趣的同学可以自己试一下。
本文介绍如何使用 openssl 这个命令行工具来完成同样的任务。
多说一句,Mac 系统自带的 openssl 版本太低了,建议通过 brew 装上新版:
$ brew install openssl
$ brew link openssl --force
如果使用其它系统,也请通过 openssl version
检查一下 openssl 的版本,不要太旧了。
服务端启用 OCSP Stapling 之后,客户端还需要在建立 TLS 时,在 Client Hello 中启用 status_request 这个
TLS 扩展,告诉服务端自己希望得到 OCSP Response。目前主流浏览器都会带上 status_request,而在 openssl 中需要指定
-status
参数。完整命令如下:
$ openssl s_client -connect imququ.com:443 -status -tlsextdebug < /dev/null
2>&1 | grep -i "OCSP response"
如果你的服务器上部署了多个 HTTPS 站点,可能还需要加上 -servername
参数启用 SNI:
$ openssl s_client -connect imququ.com:443 -servername imququ.com -status
-tlsextdebug < /dev/null 2>&1 | grep -i "OCSP response"
如果结果是下面这样,说明 OCSP Stapling 已开启:
OCSP response:
OCSP Response Data:
OCSP Response Status: successful (0x0)
Response Type: Basic OCSP Response
而这样显然是未开启:
OCSP response: no response sent
了解如何通过 openssl 验证 OCSP Stapling 状态后,我们再来看看 OCSP Response 的完整内容,去掉前面命令中的 grep 就可以看到。例如这是本站的:
$ openssl s_client -connect imququ.com:443 -status -tlsextdebug < /dev/null
2>&1
OCSP response:
======================================
OCSP Response Data:
OCSP Response Status: successful (0x0)
Response Type: Basic OCSP Response
Version: 1 (0x0)
Responder Id: 87BAEBE8F7B12700EC9CD1A04EE0E123E57D809E
Produced At: Mar 11 07:56:56 2016 GMT
Responses:
Certificate ID:
Hash Algorithm: sha1
Issuer Name Hash: 7C8E4E54532DB74C235073AAF1CDCF2C2423F86B
Issuer Key Hash: F3B5560CC409B0B4CF1FAAF9DD2356F077E8A1F9
Serial Number: 5A26
Cert Status: good
This Update: Mar 11 07:56:56 2016 GMT
Next Update: Mar 18 07:56:56 2016 GMT
Signature Algorithm: sha1WithRSAEncryption
8a:81:d6:a5:aa:8a:92:05:6f:39:97:f5:da:d0:bc:06:86:f2:
... ...
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 8 (0x8)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, O=GeoTrust Inc., CN=RapidSSL SHA256 CA - G4
Validity
Not Before: Jul 10 18:18:29 2015 GMT
Not After : May 22 18:18:29 2016 GMT
Subject: CN=RapidSSL SHA256 CA - G4 OCSP Responder
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:9d:e9:7b:75:81:1e:00:ab:b3:b4:cc:3f:a3:2d:
... ...
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Authority Key Identifier:
keyid:F3:B5:56:0C:C4:09:B0:B4:CF:1F:AA:F9:DD:23:56:F0:77:E8:A1:F9
OCSP No Check:
X509v3 Subject Key Identifier:
87:BA:EB:E8:F7:B1:27:00:EC:9C:D1:A0:4E:E0:E1:23:E5:7D:80:9E
X509v3 Extended Key Usage:
OCSP Signing
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature
X509v3 Subject Alternative Name:
DirName:/CN=TGV-C-26
Signature Algorithm: sha256WithRSAEncryption
bb:ac:c3:3e:8b:20:be:a0:a7:4d:bb:e1:d1:c3:98:17:8e:58:
... ...
-----BEGIN CERTIFICATE-----
MIIDnTCCAoWgAwIBAgIBCDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQGEwJVUzEW
... ...
bmgyvaosG4GykSUnasMqfbA=
-----END CERTIFICATE-----
======================================
可以看到 OCSP Response 由两部分组成:OCSP Response Data 和 Certificate。OCSP Response Data 是本站证书的验证信息;而 Certificate 则是用来验证 OCSP Response Data。本例中的 Certificate 的 Common Name 是 RapidSSL SHA256 CA - G4 OCSP Responder,可以看出它专属于 RapidSSL 的 OCSP 服务。后面我们会发现,并不是每一家 CA 的 OCSP Response 都会提供 Certificate 信息。
上面这段 OCSP Response 信息是通过服务端 OCSP Stapling 获取的。下面介绍如何通过 openssl 在本地获取证书 OCSP Response。
首先需要准备好待验证网站证书链上的所有证书。证书链一般由根证书、一个或多个中间证书、站点证书组成。根证书内置在操作系统或浏览器内(Firefox),可以直接 导出,或者去各大 CA 官方下载;中间证书、站点证书在建立 TLS 连接时由服务端发送,可以这样获取:
$ openssl s_client -connect imququ.com:443 -showcerts < /dev/null 2>&1
CONNECTED(00000003)
depth=2 C = US, O = GeoTrust Inc., OU = (c) 2008 GeoTrust Inc. - For authorized use only, CN = GeoTrust Primary Certification Authority - G3
verify return:1
depth=1 C = US, O = GeoTrust Inc., CN = RapidSSL SHA256 CA - G4
verify return:1
depth=0 CN = www.imququ.com
verify return:1
---
Certificate chain
0 s:/CN=www.imququ.com
i:/C=US/O=GeoTrust Inc./CN=RapidSSL SHA256 CA - G4
-----BEGIN CERTIFICATE-----
MIIFMDCCBBigAwIBAgICWiYwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCVVMx
... ...
fBv5YysJ/pgFe75P9RVALMiPUPHvH2FGI47pxlvzs5+7Gt2p
-----END CERTIFICATE-----
1 s:/C=US/O=GeoTrust Inc./CN=RapidSSL SHA256 CA - G4
i:/C=US/O=GeoTrust Inc./OU=(c) 2008 GeoTrust Inc. - For authorized use only/CN=GeoTrust Primary Certification Authority - G3
-----BEGIN CERTIFICATE-----
MIIEpjCCA46gAwIBAgIQKByJKWYUQ4BCY1U6MkCuszANBgkqhkiG9w0BAQsFADCB
... ...
nPvdJAq9WZFKQgM4EnEyiHagjny7Mu+IKhvUam9QuVJni6sw+h/94ySa
-----END CERTIFICATE-----
---
Server certificate
subject=/CN=www.imququ.com
issuer=/C=US/O=GeoTrust Inc./CN=RapidSSL SHA256 CA - G4
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 3460 bytes and written 434 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 4096 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-GCM-SHA256
Session-ID: B6A0F49F6DAD0BD8AFB63F87D134FFCBC2B1487CD81440C26D165B5738A5C3EC
Session-ID-ctx:
Master-Key: 72871B14BC37B08F51F818285264169C512B865D13839C9B824175115F008801781FBAC64D01FC76376BCAB85E6B8F84
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
TLS session ticket lifetime hint: 86400 (seconds)
TLS session ticket:
0000 - 56 f8 0d dd 0e ea 7d 0b-09 70 0b dd 52 da b7 a8 V.....}..p..R...
... ... ... ...
00a0 - c2 25 af a9 46 69 64 73-69 16 ea 64 94 c7 f4 a4 .%..Fidsi..d....
Start Time: 1457861201
Timeout : 300 (sec)
Verify return code: 0 (ok)
---
DONE
以上内容中 Certificate Chain 这一节,编号为 0 的证书是站点证书;编号为 1 的证书是中间证书。我的证书链一共是三级,服务端只需要发送两个证书,对于四级证书链,服务端就需要发送三个证书了。总之,只有根证书无需发送。
将站点证书保存为 site.pem;中间证书保存为 intermediate.pem(如果有多个中间证书,按照子证书在上的顺序保存);再从系统中导出对应的根证书存为 root.pem。这样,证书链上的所有证书都搞定了。为了确保无误,建议再验证一下每个证书的 Common Name:
$ openssl x509 -in site.pem -noout -subject
subject= /CN=www.imququ.com
$ openssl x509 -in intermediate.pem -noout -subject
subject= /C=US/O=GeoTrust Inc./CN=RapidSSL SHA256 CA - G4
$ openssl x509 -in root.pem -noout -subject
subject= /C=US/O=GeoTrust Inc./OU=(c) 2008 GeoTrust Inc. - For authorized use only/CN=GeoTrust Primary Certification Authority - G3
接着,获取站点证书的 OCSP 服务地址:
$ openssl x509 -in site.pem -noout -ocsp_uri
http://gz.symcd.com
现在万事具备,使用以下命令即可获得站点证书的 OCSP Response:
$ openssl ocsp -issuer intermediate.pem -cert site.pem -no_nonce -text -url
http://gz.symcd.com
OCSP Request Data:
Version: 1 (0x0)
Requestor List:
Certificate ID:
Hash Algorithm: sha1
Issuer Name Hash: 7C8E4E54532DB74C235073AAF1CDCF2C2423F86B
Issuer Key Hash: F3B5560CC409B0B4CF1FAAF9DD2356F077E8A1F9
Serial Number: 5A26
OCSP Response Data:
OCSP Response Status: successful (0x0)
Response Type: Basic OCSP Response
Version: 1 (0x0)
Responder Id: 87BAEBE8F7B12700EC9CD1A04EE0E123E57D809E
Produced At: Mar 11 07:56:56 2016 GMT
Responses:
Certificate ID:
Hash Algorithm: sha1
Issuer Name Hash: 7C8E4E54532DB74C235073AAF1CDCF2C2423F86B
Issuer Key Hash: F3B5560CC409B0B4CF1FAAF9DD2356F077E8A1F9
Serial Number: 5A26
Cert Status: good
This Update: Mar 11 07:56:56 2016 GMT
Next Update: Mar 18 07:56:56 2016 GMT
Signature Algorithm: sha1WithRSAEncryption
8a:81:d6:a5:aa:8a:92:05:6f:39:97:f5:da:d0:bc:06:86:f2:
... ...
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 8 (0x8)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, O=GeoTrust Inc., CN=RapidSSL SHA256 CA - G4
Validity
Not Before: Jul 10 18:18:29 2015 GMT
Not After : May 22 18:18:29 2016 GMT
Subject: CN=RapidSSL SHA256 CA - G4 OCSP Responder
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:9d:e9:7b:75:81:1e:00:ab:b3:b4:cc:3f:a3:2d:
... ...
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Authority Key Identifier:
keyid:F3:B5:56:0C:C4:09:B0:B4:CF:1F:AA:F9:DD:23:56:F0:77:E8:A1:F9
OCSP No Check:
X509v3 Subject Key Identifier:
87:BA:EB:E8:F7:B1:27:00:EC:9C:D1:A0:4E:E0:E1:23:E5:7D:80:9E
X509v3 Extended Key Usage:
OCSP Signing
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature
X509v3 Subject Alternative Name:
DirName:/CN=TGV-C-26
Signature Algorithm: sha256WithRSAEncryption
... ...
-----BEGIN CERTIFICATE-----
MIIDnTCCAoWgAwIBAgIBCDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQGEwJVUzEW
... ...
bmgyvaosG4GykSUnasMqfbA=
-----END CERTIFICATE-----
Response Verify Failure
140735166222416:error:27069065:OCSP routines:OCSP_basic_verify:certificate verify error:ocsp_vfy.c:138:Verify error:unable to get local issuer certificate
site.pem: good
This Update: Mar 11 07:56:56 2016 GMT
Next Update: Mar 18 07:56:56 2016 GMT
可以看到,自己获取的 OCSP Response 和服务端发送的 OCSP Stapling 完全一致,响应中的 Cert Status: good
表示证书合法。
但是,这段内容最后,多了这样的奇怪信息:
Response Verify Failure
140735166222416:error:27069065:OCSP routines:OCSP_basic_verify:certificate verify error:ocsp_vfy.c:138:Verify error:unable to get local issuer certificate
实际上,这是因为我们没有告诉 openssl 应该信任哪些证书,openssl 无法验证 OCSP Response 内容而报的错。这个错误可以通过加上
-noverify
参数屏蔽,但更好的做法是通过 -CAfile
指定信任证书。
将根证书、全部中间证书按照子证书在上的顺序,保存为 chain.pem
。再次执行:
$ openssl ocsp -CAfile chain.pem -issuer intermediate.pem -cert site.pem
-no_nonce -text -url http://gz.symcd.com
这下,最终的结果就是 Response verify OK 了。
Nginx 在实现 OCSP Stapling 这部分逻辑时,直接使用了 OpenSSL 库(这也解释了为什么 Nginx + BoringSSL 不支持 OCSP Stapling)。我们再来看看 Nginx 中与 OCSP Stapling 有关的三个重要配置项:
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /your/path/to/chained.pem;
ssl_stapling
的作用自然不用说,ssl_trusted_certificate
相当于前面命令中的
-CAfile
。还记得前面说过「如果不指定 -CAfile
就得加上 -noverify
」么,换句话说,如果开启了
Nginx 的 ssl_stapling_verify
,但没有正确配置 ssl_trusted_certificate
,就会导致
OCSP Response 验证失败,OCSP Stapling 自然不会生效。
昨天给我写信的那位同学就是栽在这里。实际上,检查 Nginx 的 error_log,应该有类似这样的错误:
[error] 5594#0: OCSP_basic_verify() failed (SSL: error:27069065:OCSP
routines:OCSP_basic_verify:certificate verify error:Verify error:unable to get
local issuer certificate) while requesting certificate status, responder:
gz.symcd.com
可以看到,这个错误确实是 OpenSSL 库报的。
看到这里,有些同学可能会说:不对,我明明开启了 ssl_stapling_verify
,也没有指定
ssl_trusted_certificate
,为什么我的 OCSP Stapling 功能也是正常的呢?
昨天那位同学的邮件中也问到这个奇怪的问题:
我 Nginx 目前的 OCSP stapling 配置为:
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 223.5.5.5 valid=300s;
resolver_timeout 5s;
有个奇怪的问题 :
我在使用 AlphaSSL 证书的域名测试 OCSP stapling 显示 NO,但是用配置了 COMODO ECC 证书的域名 OCSP stapling 就为 Yes。两个域名的 Nginx 配置完全相同。
看来这个问题远远没这么简单,难道还跟证书有关?我找了一个同样使用 COMODO 证书的站点,使用前面讲过的方法获得了它的 OCSP Response:
$ openssl ocsp -CAfile root.pem -issuer intermediate.pem -cert site.pem
-no_nonce -text -url http://ocsp.comodoca.com
OCSP Request Data:
Version: 1 (0x0)
Requestor List:
Certificate ID:
Hash Algorithm: sha1
Issuer Name Hash: 7AE13EE8A0C42A2CB428CBE7A605461940E2A1E9
Issuer Key Hash: 90AF6A3A945A0BD890EA125673DF43B43A28DAE7
Serial Number: 15F3C026B44BEEF870A7A496640BC484
OCSP Response Data:
OCSP Response Status: successful (0x0)
Response Type: Basic OCSP Response
Version: 1 (0x0)
Responder Id: 90AF6A3A945A0BD890EA125673DF43B43A28DAE7
Produced At: Mar 12 23:59:22 2016 GMT
Responses:
Certificate ID:
Hash Algorithm: sha1
Issuer Name Hash: 7AE13EE8A0C42A2CB428CBE7A605461940E2A1E9
Issuer Key Hash: 90AF6A3A945A0BD890EA125673DF43B43A28DAE7
Serial Number: 15F3C026B44BEEF870A7A496640BC484
Cert Status: good
This Update: Mar 12 23:59:22 2016 GMT
Next Update: Mar 16 23:59:22 2016 GMT
Signature Algorithm: sha256WithRSAEncryption
29:a8:b4:a3:60:98:d9:c3:4f:56:4b:72:6c:9a:9e:7f:51:2d:
... ...
Response Verify Failure
140735166222416:error:27069076:OCSP routines:OCSP_basic_verify:signer certificate not found:ocsp_vfy.c:92:
site.pem: good
This Update: Mar 12 23:59:22 2016 GMT
Next Update: Mar 16 23:59:22 2016 GMT
这次测试所有步骤跟之前一样,证书状态正常,也指定了 -CAfile
,但 OCSP Response 验证还是失败:
Response Verify Failure
140735166222416:error:27069076:OCSP routines:OCSP_basic_verify:signer certificate not found:ocsp_vfy.c:92:
从错误信息中可以得知,COMODO 的 OCSP Response 没有提供 Certificate
信息,导致无法验证其内容。进一步测试发现,对于这种情况,Nginx 直接忽略了 ssl_stapling_verify
参数,无论是否配置,都不执行 verify 操作。
经过测试,Let's Encrypt 的 OCSP 服务也没有返回 Certificate,所以使用 Let's Encrypt 证书,开启 OCSP
Stapling 也无需配置 ssl_trusted_certificate
。
顺便说一下,获取 Let's Encrypt 证书的 OCSP Response 一定要指定 Host,就像这样:
openssl ocsp -CAfile chain.pem -issuer intermediate.pem -cert site.pem
-no_nonce -text -url http://ocsp.int-x1.letsencrypt.org/ -header "HOST"
"ocsp.int-x1.letsencrypt.org"
在 Nginx 中配置 ssl_stapling on
并 reload 后,Nginx 并不会马上获取 OCSP
Response,它要等第一个请求过来,再发起异步 OCSP 请求,所以刚开始几个响应,很可能不带 OCSP Stapling。另外,有时候由于 OCSP
域名无法解析,或者服务器无法访问造成 OCSP Response 获取失败,也会导致 OCSP Stapling 无法生效。这是首先需要排查的地方,一般在
Nginx 的 error_log 中会有这样的错误:
[error] 5225#0: xxx.com could not be resolved (110: Operation timed out) while
requesting certificate status, responder: xxx.com
如果 OCSP Response 包含了 Certificate 信息,并且 Nginx 配置了 ssl_stapling_verify on
,那么需要确保正确配置了 ssl_trusted_certificate
参数,这个参数应该指向一个包含根证书、中间证书的文件(顺序是子证书在上、父证书在下),否则 OCSP Stapling 无法生效。这时候 Nginx 的
error_log 中会出现类似这样的错误:
[error] 4832#0: OCSP_basic_verify() failed (SSL: error:27069065:OCSP
routines:OCSP_basic_verify:certificate verify error:Verify error:unable to get
local issuer certificate) while requesting certificate status, responder:
xxx.com
如果证书 OCSP Response 没有包含 Certificate 信息,例如 COMODO、Let's Encrypt 家的部分证书,那么
ssl_stapling_verify
和 ssl_trusted_certificate
两个配置可以忽略,完全不影响开启
OCSP Stapling。
好了,本文先写到这里。本站近期开始接受赞助,如果你认为本站文章对你有帮助,欢迎捐赠本站。详情请点击这里 »
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8