How to fix “Error: unable to verify the first certificate” in nodejs

How to fix “Error: unable to verify the first certificate” in nodejs

解决问题的经过

今天在检查node服务日志的时候,发现一个问题:

请求一些网络资源的时候(使用的requset),报了错误“Error: unable to verify the first certificate”。

可以看出是和证书相关的问题。因为我的网站是用的“Let’s Encrypt”生成的证书,“Let’s Encrypt”虽然已经被各个浏览器信任,但是在系统的根证书里并没有包含它。所以猜测应该是“Let’s Encrypt”导致的。
在查看了另一个同样出现报错的网站的证书之后,验证了我这个想法。
接下来就是怎么解决这个问题了。

搜索到了 stackoverflow 的一篇文章:
https://stackoverflow.com/questions/31673587/error-unable-to-verify-the-first-certificate-in-nodejs

最高投票的回答是这个

require(‘https’).globalAgent.options.ca = require(‘ssl-root-cas/latest’).create();

尝试了按照这个写了。结果发现还是报同样的错。
打开“node_modules”下的“ssl-root-cas”目录,发现里面有个pems,存储了许多证书发放机构的根证书,找了一下没有发现“Let’s Encrypt”的,可以猜测这个模块目前未将“Let’s Encrypt”列入可信任的机构里。于是需要尝试其他的办法。
还是上面的文章,往下翻可以看到一个投票数不高的回答(写这篇文章时的投票数是24),包含关键词“How do I get intermediate certificate”,提到(翻译)

1.需要获取缺失的.pem格式的中间证书(本例也就是“Let’s Encrypt”的证书,而不是“Let’s Encrypt”签署给网站的证书)
2a.使用node环境变量NODE_EXTRA_CA_CERTS加入证书
2b.或者,使用上面的 “ssl-root-cas”添加到 “ca”中

我选择的是第二种解决方案。

解决方案

首先,要先找到“Let’s Encrypt”的根证书
方法1:

#blog.woniufun.com为网站的域名,自行替换
openssl s_client -connect blog.woniufun.com:443 -servername blog.woniufun.com | tee logcertfile
openssl x509 -in logcertfile -noout -text | grep -i "issuer"
#本例输出“ CA Issuers - URI:http://cert.int-x3.letsencrypt.org/”

#下载证书文件
curl --output letsencrypt.crt "http://cert.int-x3.letsencrypt.org/"
#转换为pem格式
openssl x509 -inform DER -in letsencrypt.crt -out letsencrypt.pem -text

方法2(适用于有浏览器,以chrome为例):
1. chrome打开随便一个“Let’s Encrypt”签署证书的网址,如本站。
1. 点击地址栏左边查看证书。
1. 切换到“证书路径”标签。
1. 选中“Let’s Encrypt Authority X3”,点击“查看证书”。

1. 在新打开的“证书”窗口,切换到“详细信息”标签页,点击“复制到文件”
1. 导出证书的时候,格式选择“DER编码”

1. 点击下一步即可以选择一个证书存储路径,保存成一个后缀名是.cer的证书。
1. 如果你查看这一步导出的.cer文件和方法1下载的crt文件的md5值,你会发现它们是一样的,也就是同一个文件。
1. 转换成pem格式 openssl x509 -inform DER -in xxxx.crt -out letsencrypt.pem -text

其次,将pem文件放到node代码可以访问到的地方,如“/opt/ssl/letsencrypt.pem”

最后,在node代码中使用

let rootCas = require('ssl-root-cas/latest').create();
rootCas.addFile("/opt/ssl/letsencrypt.pem");
require('https').globalAgent.options.ca = rootCas;

此时再去请求网络资源,就不会再报错了。

这个解决方案适合所有发起网络请求的模块,包括但不限制于:https,fetch,request,axios…

不建议的解决方案

process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0

这个配置项,将忽略所有TLS的错误,这个直接将https的优势全部舍弃了。

request({method: "GET", 
        "rejectUnauthorized": false, //添加这个选项
        "url": url})

同上,在请求的时候忽略证书授权错误,同样将https的优势全部舍弃了。

参考资料

https://stackoverflow.com/questions/31673587/error-unable-to-verify-the-first-certificate-in-nodejs
https://www.npmjs.com/package/ssl-root-cas

Comments are closed.