Error: unable to verify the first certificate in nodejs

Error: unable to verify the first certificate in nodejs

我正在尝试使用 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);
      
    });
});

无法验证 nodejs 中的第一个证书需要未经授权

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

尝试添加适当的根证书

与盲目接受未经授权的端点相比,这始终是一个更安全的选择,后者只能作为最后的手段使用。

这可以像添加一样简单

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

到您的应用程序。

SSL Root CAs npm package(此处使用)是解决此问题的非常有用的软件包。

您尝试从中下载的服务器可能配置不正确。即使它在您的浏览器中工作,它也可能不包含缓存空客户端验证所需的链中的所有 public 证书。

我建议在 SSLlabs 工具中检查站点:https://www.ssllabs.com/ssltest/

查找此错误:

This server's certificate chain is incomplete.

还有这个:

Chain issues.........Incomplete

您可以通过如下修改请求选项来完成此操作。如果您使用的是 self-signed 证书或缺少中介,将 strictSSL 设置为 false 将不会强制请求包验证证书。

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

GoDaddy SSL CCertificate

我在尝试使用 GoDaddy 证书连接到我们的后端 API 服务器时遇到过这种情况,这是我用来解决问题的代码。

var rootCas = require('ssl-root-cas/latest').create();

rootCas
  .addFile(path.join(__dirname, '../config/ssl/gd_bundle-g2-g1.crt'))
  ;

// will work with all https requests will all libraries (i.e. request.js)
require('https').globalAgent.options.ca = rootCas;

PS:

使用捆绑证书并且不要忘记安装库npm install ssl-root-cas

另一个肮脏的黑客,这将使您的所有请求不安全:

process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0

我正在使用 nodemailer npm 模块。下面的代码解决了这个问题

     tls: {
     // do not fail on invalid certs
     rejectUnauthorized: false
     }

这实际上为我解决了问题,来自 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
});

这对我有用 => 添加代理并将 'rejectUnauthorized' 设置为 false

const https = require('https'); //Add This
const bindingGridData = async () => {
  const url = `your URL-Here`;
  const request = new Request(url, {
    method: 'GET',
    headers: new Headers({
      Authorization: `Your Token If Any`,
      'Content-Type': 'application/json',
    }),
    //Add The Below
    agent: new https.Agent({
      rejectUnauthorized: false,
    }),
  });
  return await fetch(request)
    .then((response: any) => {
      return response.json();
    })
    .then((response: any) => {
      console.log('response is', response);
      return response;
    })
    .catch((err: any) => {
      console.log('This is Error', err);
      return;
    });
};

解决此问题的另一种方法是使用以下模块。

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

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

unable to verify the first certificate

The certificate chain is incomplete.

It means that the webserver you are connecting to is misconfigured and did not include the intermediate certificate in the certificate chain it sent to you.

Certificate chain

It most likely looks as follows:

  1. Server certificate - stores a certificate signed by intermediate.
  2. Intermediate certificate - stores a certificate signed by root.
  3. Root certificate - stores a self-signed certificate.

Intermediate certificate should be installed on the server, along with the server certificate.
Root certificates are embedded into the software applications, browsers and operating systems.

The application serving the certificate has to send the complete chain, this means the server certificate itself and all the intermediates. The root certificate is supposed to be known by the client.

Recreate the problem

Go to https://incomplete-chain.badssl.com using your browser.

It doesn't show any error (padlock in the address bar is green).
It's because browsers tend to complete the chain if it’s not sent from the server.

Now, connect to https://incomplete-chain.badssl.com using Node:

// 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);
  });

Logs: "Error: unable to verify the first certificate".

Solution

You need to complete the certificate chain yourself.

To do that:

1: You need to get the missing intermediate certificate in .pem format, then

2a: extend Node’s built-in certificate store using NODE_EXTRA_CA_CERTS,

2b: or pass your own certificate bundle (intermediates and root) using ca option.

1. How do I get intermediate certificate?

Using openssl (comes with Git for Windows).

Save the remote server's certificate details:

openssl s_client -connect incomplete-chain.badssl.com:443 -servername incomplete-chain.badssl.com | tee logcertfile

We're looking for the issuer (the intermediate certificate is the issuer / signer of the server certificate):

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

It should give you URI of the signing certificate. Download it:

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

Finally, convert it to .pem:

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

2a. NODE_EXTRA_CERTS

I'm using cross-env to set environment variables in package.json file:

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

2b. ca option

This option is going to overwrite the Node's built-in root CAs.

That's why we need to create our own root CA. Use ssl-root-cas.

Then, create a custom https agent configured with our certificate bundle (root and intermediate). Pass this agent to axios when making request.

// 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);
  });

Instead of creating a custom https agent and passing it to axios, you can place the certifcates on the https global agent:

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

Resources:

  1. https://levelup.gitconnected.com/how-to-resolve-certificate-errors-in-nodejs-app-involving-ssl-calls-781ce48daded
  2. https://www.npmjs.com/package/ssl-root-cas
  3. https://github.com/nodejs/node/issues/16336
  4. https://www.namecheap.com/support/knowledgebase/article.aspx/9605/69/how-to-check-ca-chain-installation
  5. https://superuser.com/questions/97201/how-to-save-a-remote-server-ssl-certificate-locally-as-a-file/
  6. How to convert .crt to .pem

您可以全局禁用证书检查 - 无论您使用哪个包来发出请求 - 就像这样:

// 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

当然你不应该这样做 - 但它对于调试 and/or 非常基本的脚本来说肯定很方便,你绝对不关心证书是否被正确验证。

几天前我遇到了这个问题,这是我遵循的方法并且对我有用。

对我来说,这是在我尝试使用 axios 获取数据或获取库时发生的,因为我在公司防火墙下,所以我们有某些特定的证书,节点 js 证书存储无法指向。

所以对于我的 loclahost,我采用了这种方法。 我在我的项目中创建了一个文件夹,并将整个证书链保存在该文件夹和我的开发服务器脚本中(package.json)我将它与服务器脚本一起添加,以便node js可以引用路径。

"dev-server":set NODE_EXTRA_CA_CERTS=certificates/certs-bundle.crt

对于我的服务器(不同的环境),我创建了一个新的环境变量,如下所示,并添加了 it.I 使用 Openshift,但我想这个概念对其他人来说也是一样的。

"name":NODE_EXTRA_CA_CERTS
"value":certificates/certs-bundle.crt

我没有生成任何证书,因为整个证书链已经可供我使用。

我遇到了非常罕见的情况,但希望它能对某人有所帮助:制作了一个代理服务,将请求代理到另一个服务。即使我添加了所有预期的证书,每个请求的错误都是“无法验证第一个证书”。

原因很简单——我不小心re-sent也是“主人”header。 请确保您没有明确发送“主机”header。

我能够通过 mozilla 或 chrome 等浏览器获得证书链。

  1. 打开网站,转到网页的证书设置并下载证书链作为文件名(first-chain.pem,second-chain.pem),应该是 pem 格式,如
----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
......
-----END CERTIFICATE-----
----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
......
-----END CERTIFICATE-----
  1. 然后在你的 nodejs 代码中,我在打字稿上做了,我添加了 2 个 cas,因为我有 2 个网络服务器请求
import https from 'https'
import cas from 'ssl-root-cas'

......

 interface CaList extends Buffer {
  addFile(file: string): Buffer[]
 }
 const caList = cas.create() as CaList
 caList.addFile(process.env.PROJECT_PATH + 'certs/first-chain.pem')
 caList.addFile(process.env.PROJECT_PATH + 'certs/second-chain.pem')

然后因为我需要建立 websocket wss 连接,所以我将带有新 cas 列表的代理添加到请求中

this.client.connect(KtUrl, undefined, undefined, undefined, {
    agent: new https.Agent({
      ca: caList
    })
})

还必须为 ssl-root-cas filename ssl-root-cas.d.ts 添加定义文件,以便打字稿不抱怨

declare module 'ssl-root-cas' {
  function create(): string | Buffer | (string | Buffer)[] | undefined
}

在开发环境中设置:

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

或者,先设置环境变量

export NODE_TLS_REJECT_UNAUTHORIZED=0   

然后启动应用程序:

node index.js

NOT suitable for the prod services.

您是否使用 axios 发送请求并收到此错误?

如果是,则认为错误 unable to verify the first certificate 可以来自 axios,与服务器无关。要解决此问题,您必须配置 axios(或其他发出请求的应用程序)以允许未经授权的请求。添加一个 https.Agent 以在请求的配置中设置 rejectUnauthorized: false

import axios from "axios"
import https from "https"

const getCities = async () => {
    try {
        const result = await axios.get("https://your-site/api/v1/get-cities", {
            httpsAgent: new https.Agent({
              rejectUnauthorized: false // set to false
            })
        })

        console.log(result.data)
    } catch(err) {
        console.log(err?.message||err)
    }
}

如果您使用的是自定义 axios 实例,则:

import axios from "axios"
import https from "https"

export const request = axios.create({
  baseURL: process.env.BASE_URL,
  headers: {
    Authorization: cookies.YOUR_ACCESS_TOKEN,
  },
  httpsAgent: new https.Agent({
    rejectUnauthorized: false //set to false
  })
})

这对我有用

做以下动作

如果您没有这些包 httpsaxios

您可以通过以下方式安装 npm install --save axios https

import axios from 'axios';
import https from 'https';
const httpsAgent = new https.Agent({
    rejectUnauthorized: false,
})

axios.defaults.httpsAgent = httpsAgent

通过这样做你会得到你的回应。