错误:无法验证nodejs中的第一个证书

279
我试图使用URL从Jira服务器下载文件,但是我遇到了一个错误。如何在代码中包含证书以进行验证? 错误:
Error: unable to verify the first certificate in nodejs

at Error (native)
    at TLSSocket.<anonymous> (_tls_wrap.js:929:36)
   
  at TLSSocket.emit (events.js:104:17)

at TLSSocket._finishInit (_tls_wrap.js:460:8)

我的Nodejs代码:

var https = require("https");
var fs = require('fs');
var options = {
    host: 'jira.example.com',
    path: '/secure/attachment/206906/update.xlsx'
};

https.get(options, function (http_res) {
    
    var data = "";

  
    http_res.on("data", function (chunk) {
       
        data += chunk;
    });

   
    http_res.on("end", function () {
      
        var file = fs.createWriteStream("file.xlsx");
        data.pipe(file);
      
    });
});

你能解决这个问题吗? - sharad jain
3
我使用了另一种方法,比如禁用证书验证,已经完成。 - Labeo
你能再详细解释一下吗?这对我来说会非常有帮助。 - sharad jain
为了验证证书,我们需要设置rejectUnauthorized。 - Labeo
22个回答

235

无法验证第一个证书

证书链不完整。

这意味着您正在连接的Web服务器配置有误,并未在发送给您的证书链中包含中间证书。

证书链

它最可能如下所示:

  1. 服务器证书 - 存储由中间证书签名的证书。
  2. 中间证书 - 存储由根证书签名的证书。
  3. 根证书 - 存储自签名证书。

中间证书应与服务器证书一起安装在服务器上。
根证书嵌入到软件应用程序、浏览器和操作系统中。

颁发证书的应用程序必须发送完整的证书链,这意味着服务器证书本身和所有中间证书。客户端应该知道根证书。

重现问题

使用浏览器前往https://incomplete-chain.badssl.com

它不会显示任何错误(地址栏中的挂锁是绿色的)。
这是因为浏览器倾向于补全证书链,如果服务器没有发送,则会自动补全。

现在,使用Node连接到https://incomplete-chain.badssl.com

// index.js
const axios = require('axios');

axios.get('https://incomplete-chain.badssl.com')
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

日志: "Error: unable to verify the first certificate".

解决方案

您需要自己完成证书链。

方法如下:

1: 您需要获取缺失的中间证书,格式为 .pem,然后

2a: 使用 NODE_EXTRA_CA_CERTS 扩展 Node 的内置证书存储库,或者

2b: 使用 ca 选项传递自己的证书包(中间证书和根证书)。

1. 如何获取中间证书?

使用 openssl (附带于 Git for Windows)。

保存远程服务器的证书详细信息:

openssl s_client -connect incomplete-chain.badssl.com:443 -servername incomplete-chain.badssl.com | tee logcertfile
我们正在寻找颁发者(中间证书是服务器证书的颁发者/签名者):

我们正在寻找颁发者(中间证书是服务器证书的颁发者/签名者):

openssl x509 -in logcertfile -noout -text | grep -i "issuer"

它应该为您提供签名证书的URI。下载它:

curl --output intermediate.crt http://cacerts.digicert.com/DigiCertSHA2SecureServerCA.crt

最后将其转换为.pem

openssl x509 -inform DER -in intermediate.crt -out intermediate.pem -text

2a. NODE_EXTRA_CA_CERTS

我正在使用cross-envpackage.json文件中设置环境变量:

"start": "cross-env NODE_EXTRA_CA_CERTS=\"C:\\Users\\USERNAME\\Desktop\\ssl-connect\\intermediate.pem\" node index.js"

2b. ca选项

该选项将覆盖Node内置的根证书颁发机构。

这就是为什么我们需要创建自己的根证书颁发机构。使用ssl-root-cas

然后,创建一个自定义的https代理,并配置我们的证书包(根证书和中间证书)。在进行请求时,将此代理传递给axios

// index.js
const axios = require('axios');
const path = require('path');
const https = require('https');
const rootCas = require('ssl-root-cas').create();

rootCas.addFile(path.resolve(__dirname, 'intermediate.pem'));
const httpsAgent = new https.Agent({ca: rootCas});

axios.get('https://incomplete-chain.badssl.com', { httpsAgent })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

不需要创建自定义的 https 代理并将其传递给 axios,您可以将证书放在全局的 https 代理上:

// Applies to ALL requests (whether using https directly or the request module)
https.globalAgent.options.ca = rootCas;

资源:

  1. 如何解决 Node.js 应用在涉及 SSL 调用时出现证书错误
  2. ssl-root-cas 包
  3. Node.js 问题:Unable to Verify the First Certificate
  4. 如何检查 CA 链的安装
  5. 如何将远程服务器的 SSL 证书保存为本地文件
  6. 如何将 .crt 文件转换为 .pem 格式

1
更好的解释和解决方案,不可能的,谢谢! - ffcc
1
@lazycipher 这个配置必须在服务器端完成。 - Gilbert
这就是我感到困惑的地方,每个人都非常专注于完成技巧,但没有人建议在服务器端检查SSL。我通过使用Let's Encrypt的fullchain.pem文件来解决了这个问题,而不是使用其他文件。 - lazycipher
1
https://incomplete-chain.badssl.com显示证书已过期。 有没有其他网站可以检查这个问题? - Muhammad Hassan Javed
1
亲爱的,你是我的英雄。 - mst4ash
显示剩余2条评论

187

另一个糟糕的方法,会让你所有的请求都变得不安全:

process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0

11
这似乎与Labeo在上面的答案没有什么不同,同样危险。 - ocramot
5
它与众不同,因为它不需要进行任何编码更改,由于环境变量可以在源代码之外设置。 - jzacharuk
2
这个答案很危险。你正在禁用TLS提供的任何安全性。 - Flimm
6
这对我很有用,非常有帮助。在我的情况中,我只是与 https://localhost 进行通讯,所以安全性不是问题。 - Mike S
7
确实很好,只是为了测试本地主机。确保在测试后将其删除。 - Nico
显示剩余5条评论

170

尝试添加适当的根证书

这总是比盲目接受未授权的终点更安全,它们只应作为最后的选择。

这可以很简单地实现,只需添加

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

针对这个问题,SSL Root CAs npm包(在这里使用)是一个非常有用的工具包。

to your application.


11
大多数情况下,应使用此答案,因为它实际上修复了问题,而不是禁用SSL的全部好处。 - mikemaccana
15
如在ssl-root-cas模块自述文件中所述,此问题最常见的原因之一是您的证书未嵌入其中间CA证书。在尝试其他方法之前,请尝试修复您的证书;) - Laurent VB
7
mkcert 不会创建 "fullchain" 证书。您必须将您的证书与 $(mkcert -CAROOT)/rootCA.pem 中可用的根证书连接起来,并在一个新的证书文件中执行类似于 https.globalAgent.options.ca = fs.readFileSync('fullchain.pem') 的操作。 请参见 https://github.com/FiloSottile/mkcert/issues/76。 - Maxime Pacary
1
对于注重安全的人来说,ssl-root-cas npm 模块在 https://git.coolaj86.com/coolaj86/ssl-root-cas.js/src/branch/master/ca-store-generator.js#L8 中硬编码了一个请求到 mozilla.org。这可能是安全的,因为 Mozilla 可信,但它似乎也是一种攻击向量。 - Avindra Goolcharan
1
这个对我没用,但是这个可以:https://github.com/arvind-agarwal/node_extra_ca_certs_mozilla_bundle - mikestaub
显示剩余4条评论

49

对于无法验证Node.js中的第一个证书,需要使用"reject unauthorized"

 request({method: "GET", 
        "rejectUnauthorized": false, 
        "url": url,
        "headers" : {"Content-Type": "application/json",
        function(err,data,body) {
    }).pipe(
       fs.createWriteStream('file.html'));

152
这个回答很危险。另一个更安全。 - mikemaccana
4
这样做会移除 SSL 提供的安全保障,因此应仅用于开发。 - Sylvain B
13
不检查证书意味着你无法确定对方的身份,因此可能会遭受欺骗主机的攻击。即使您不检查证书,仍然可以获得加密通信,这种通信很难被窃取。因此添加此行不会“削弱”SSL的安全性,也不会像另一位评论者所说的“禁用SSL的全部好处”。 - Bob Pollack
6
禁用SSL验证不是解决任何问题的方法。 :-) - Siddhu
11
如果你正在使用node request库,这个方法可行。而我正在使用该库。非常感谢,它解决了我开发中的迫切需求。 - Alan
显示剩余8条评论

40
您正在尝试下载的服务器可能存在配置问题。即使在您的浏览器中可以正常工作,也可能没有包含用于缓存清空客户端验证所需的所有公共证书链。
我建议使用SSLlabs工具检查该网站:https://www.ssllabs.com/ssltest/ 查找以下错误:
该服务器的证书链不完整。
以及:
链问题.........不完整。

我在使用由DigiCert Inc.授权的证书时遇到了问题(链式问题......不完整),请问修复的步骤是什么? - imarchuang
@imarchuang 简而言之,您的服务器不仅需要为您的域名提供证书,还需要提供中间证书。我无法在此评论中提供更多详细信息,但希望这足以指引您朝着正确的方向前进。 - Flimm
非常感谢,我们通过组合根证书找到了解决办法。 - imarchuang
谢谢!我发现我的证书不完整,虽然在Chrome和Firefox中运行良好,但在Electron应用程序中无法工作。我通过“cat domainname.crt domainname.ca-bundle>domainname-ssl-bundle.crt”在Nginx端修复了它。 - Ivan Borshchov

14

在开发环境中设置:

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

或者,首先设置环境变量

export NODE_TLS_REJECT_UNAUTHORIZED=0   

然后启动该应用程序:

node index.js

不适用于生产环境服务。


3
针对本地开发,这是可行的解决方案。 - Alex MAN
是的,只针对本地主机 - `(node:33505) 警告:将 NODE_TLS_REJECT_UNAUTHORIZED 环境变量设置为 '0' 会通过禁用证书验证使 TLS 连接和 HTTPS 请求不安全。 - Shani Kehati

11

这实际上解决了我的问题,来自https://www.npmjs.com/package/ssl-root-cas


// INCORRECT (but might still work)
var server = https.createServer({
  key: fs.readFileSync('privkey.pem', 'ascii'),
  cert: fs.readFileSync('cert.pem', 'ascii') // a PEM containing ONLY the SERVER certificate
});

// CORRECT (should always work)
var server = https.createServer({
  key: fs.readFileSync('privkey.pem', 'ascii'),
  cert: fs.readFileSync('fullchain.pem', 'ascii') // a PEM containing the SERVER and ALL INTERMEDIATES
});

2
这是我个人认为最佳解决方案,因为它不需要额外的库并且非常简单。 - Martin Schneider
也解决了我的问题 - Anatoly Strashkevich

9
另一种解决方法是使用以下模块:

node_extra_ca_certs_mozilla_bundle

该模块可以生成一个包含 Mozilla信任的所有根和中间证书的PEM文件,无需进行任何代码修改即可工作。您可以使用以下环境变量(适用于Nodejs v7.3 +):

NODE_EXTRA_CA_CERTS

生成与上述环境变量一起使用的 PEM 文件。您可以使用以下命令安装该模块:
npm install --save node_extra_ca_certs_mozilla_bundle

然后使用环境变量启动您的节点脚本。

NODE_EXTRA_CA_CERTS=node_modules/node_extra_ca_certs_mozilla_bundle/ca_bundle/ca_intermediate_root_bundle.pem node your_script.js

可以使用生成的PEM文件的其他方法,请访问:

https://github.com/arvind-agarwal/node_extra_ca_certs_mozilla_bundle

注意:我是上述模块的作者。


请为我工作` const https = require('https');https.globalAgent.options.ca = fs.readFileSync('node_modules/node_extra_ca_certs_mozilla_bundle/ca_bundle/ca_intermediate_root_bundle.pem');` - sankalpdev

5

您可以通过如下修改请求选项来实现此操作。如果您正在使用自签名证书或缺少中间件,则将strictSSL设置为false将不会强制请求包验证证书。

var options = {
   host: 'jira.example.com',
   path: '/secure/attachment/206906/update.xlsx',
   strictSSL: false
}

这解决了我的问题,我现在使用'request'模块而不是'http'。谢谢! - Bruno Nunes

4
您可以全局禁用证书检查-无论您使用哪个软件包进行请求-方法如下:

// Disable certificate errors globally
// (ES6 imports (eg typescript))
//
import * as https from 'https'
https.globalAgent.options.rejectUnauthorized = false

或者

// Disable certificate errors globally
// (vanilla nodejs)
//
require('https').globalAgent.options.rejectUnauthorized = false

当然你不应该这么做,但在调试和/或非常基本的脚本编写中,如果你完全不关心证书是否被正确验证,这么做确实是方便的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接