Error: unable to verify the first certificate in nodejs
Asked Answered
I

22

294

I'm trying to download a file from jira server using an URL but I'm getting an error. how to include certificate in the code to verify?

Error:

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)

My Nodejs code:

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);
      
    });
});
Ilia answered 28/7, 2015 at 10:23 Comment(4)
were you able to solve this ?Dyal
i used another procedure like disabling certificate verification and doneIlia
can you elaborate a little more? This will be really helpful for meDyal
see below answer for validation of certificate we need to have rejectUnauthorizedIlia
L
172

Try adding the appropriate root certificate

This is always going to be a much safer option than just blindly accepting unauthorised end points, which should in turn only be used as a last resort.

This can be as simple as adding

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

to your application.

The SSL Root CAs npm package (as used here) is a very useful package regarding this problem.

Louisalouisburg answered 7/9, 2015 at 13:35 Comment(9)
This answer should be used in most cases as it actually fixes the problem rather than disables the entire benefit of SSL.Anubis
As stated in the ssl-root-cas module README, one of the most common causes for this issue is that your certificate does not embed its intermediate CA certificates. Try fixing your certificate before trying anything else ;)Albrecht
You may not even require the SSL-root-cas package. Just set the globalAgents.option.cert to a fullchain certificate. That's what solved my problem.Howey
mkcert does not creates a "fullchain" certificate. You have to concatenate your certificate with the root cert available at $(mkcert -CAROOT)/rootCA.pem in a new certificate file and do something like https.globalAgent.options.ca = fs.readFileSync('fullchain.pem') See github.com/FiloSottile/mkcert/issues/76Caravel
For the security minded, ssl-root-cas npm module has a request to mozilla.org hardcoded git.coolaj86.com/coolaj86/ssl-root-cas.js/src/branch/master/… . It's probably safe because Mozilla but it seems like an attack vector.Listed
this did not work for me but this one did: github.com/arvind-agarwal/node_extra_ca_certs_mozilla_bundleVerne
Related to what @LaurentVB said - I was accidentally using 'cert.pem' instead of 'fullchain.pem'. Switching fixed it.Jamshedpur
what is the version for ES6 modules, using imports ?Monarch
where to add this?Esme
F
256

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_CA_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
Fayola answered 1/2, 2020 at 19:39 Comment(4)
So, I've my server running with SSL and I've a nextjs app trying to connect to the endpoint and this error pops up. Where's the problem? The server or the nextjs app?Doe
@Doe this configuration has to be done server-side.Comical
That's where I got confused everyone is soo focused to get the trick done that no one suggested to check ssl on server side. I fixed this by using let's encrypt's fullchain.pem instead of some other file.Doe
incomplete-chain.badssl.com says certificate expired. Is there any other site to check this out?Starcrossed
P
198

Another dirty hack, which will make all your requests insecure:

process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
Pulverulent answered 24/2, 2018 at 8:41 Comment(10)
This seems not different from Labeo's answer above, as just as dangerous.Recline
It is different, it doesnt require any coding changes as the env variable can be set outside the source code.Glider
This answer is dangerous. You are disabling any security that TLS provides.Ivatts
This worked for me, super helpful. In my case, I'm just talking to localhost , so the security isn't the problem.Everybody
Fine indeed just to test localhost. Just make sure you remove it after your tests.Benn
I'm very curious of there's a non-hack way to do this while testing against localhost. Running dotnet dev-certs https --trust changed nothing, nor did requiring the certs from ssl-root-cas.Mastin
I still can't understand why there's no consideration for developers who want to treat Localhost or Local Area Network IP as safe just for development purpose. And I fully understand that a Production Web App can still use Localhost services and LAN IP and therefore must be secured too. But with my Laptop, my Phone, I should be able to say it's all safe for me on a particular Application. And yet, only this solution does that, so it's a Very Good Solution for Local Dev.Crush
I was developping with localstack (mocking aws locally), and this solution was the best for me, because I'm enabling only on localTertius
Spending too much time trying to solve this issue when what I really want locally is to run some tools, very useful to bypass this security gate. Let the security guys deal with this kind of sh*t in their environments, locally I just want to get stuff done!Metacarpal
This case is good where security is not a problemWashbowl
L
172

Try adding the appropriate root certificate

This is always going to be a much safer option than just blindly accepting unauthorised end points, which should in turn only be used as a last resort.

This can be as simple as adding

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

to your application.

The SSL Root CAs npm package (as used here) is a very useful package regarding this problem.

Louisalouisburg answered 7/9, 2015 at 13:35 Comment(9)
This answer should be used in most cases as it actually fixes the problem rather than disables the entire benefit of SSL.Anubis
As stated in the ssl-root-cas module README, one of the most common causes for this issue is that your certificate does not embed its intermediate CA certificates. Try fixing your certificate before trying anything else ;)Albrecht
You may not even require the SSL-root-cas package. Just set the globalAgents.option.cert to a fullchain certificate. That's what solved my problem.Howey
mkcert does not creates a "fullchain" certificate. You have to concatenate your certificate with the root cert available at $(mkcert -CAROOT)/rootCA.pem in a new certificate file and do something like https.globalAgent.options.ca = fs.readFileSync('fullchain.pem') See github.com/FiloSottile/mkcert/issues/76Caravel
For the security minded, ssl-root-cas npm module has a request to mozilla.org hardcoded git.coolaj86.com/coolaj86/ssl-root-cas.js/src/branch/master/… . It's probably safe because Mozilla but it seems like an attack vector.Listed
this did not work for me but this one did: github.com/arvind-agarwal/node_extra_ca_certs_mozilla_bundleVerne
Related to what @LaurentVB said - I was accidentally using 'cert.pem' instead of 'fullchain.pem'. Switching fixed it.Jamshedpur
what is the version for ES6 modules, using imports ?Monarch
where to add this?Esme
I
50

for unable to verify the first certificate in nodejs reject unauthorized is needed

 request({method: "GET", 
        "rejectUnauthorized": false, 
        "url": url,
        "headers" : {"Content-Type": "application/json",
        function(err,data,body) {
    }).pipe(
       fs.createWriteStream('file.html'));
Ilia answered 28/8, 2015 at 10:16 Comment(12)
you need to add a header in your request "rejectUnauthorized": falseIlia
This answer is dangerous. The other one is safer.Anubis
what do you mean by dangerous?Ilia
Well by doing that, you remove the security provided by SSL, so it should be used for development only.Rivarivage
Not checking certificates means that you cannot be certain of the identity of the other party and so might be subject to a spoofed host. Even if you do not check certificates, however, you still get encrypted communication that cannot be (easily) spied on. So adding this line does not "remove the security" of SSL nor, as another commenter said "disable[] the entire benefit of SSL".Phallicism
Disabling SSL verification is NOT a solution to any problem.:-)Villegas
This works if you are using the node request library. Which I am. And thank you, it solves my immediate need for development.Perspicacious
Who in their right mind upvotes such a horrible answer? The whole point of SSL is to NOT do this.Zeller
@SetWhite It's also an awful idea to have dev, testing and production differ in such an important way (you're disabling lots of error checking there) - just makes your life harder and your testing worse. And I don't want to know how many devs disabled the check in their code base and never got around to fixing the underlying problem and ship insecure, broken code due to this.Zeller
I'm in a strange boat where the server I'm requesting is localhost, is managed by a different team, and it is using an invalid (self-signed) certificate. This seems the best option for my use case as well.Lorenelorens
Dangerous indeed, but in a safe situation it's a pain free solution.Etching
This also works with new WebSocket(..., {rejectUnauthorized}).Writeoff
I
41

The server you're trying to download from may be badly configured. Even if it works in your browser, it may not be including all the public certificates in the chain needed for a cache-empty client to verify.

I recommend checking the site in SSLlabs tool: https://www.ssllabs.com/ssltest/

Look for this error:

This server's certificate chain is incomplete.

And this:

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

Ivatts answered 1/11, 2016 at 11:52 Comment(3)
I get this issue (Chain issues.........Incomplete) for my cert which is authorized from DigiCert Inc., what is the procedure to fix this?Reprobation
@Reprobation In short, your server needs to serve not just the certificate for your domain, but also the intermediate certificates too. I can't fit more detail in this comment but hopefully that's enough information to point you in the right direction.Ivatts
Thanks you! I discovered my cert was incomplete, though it worked perfectly in chrome and firefox but did not work in electron app, and I fixed it on nginx side by cat domainname.crt domainname.ca-bundle > domainname-ssl-bundle.crtMalfeasance
D
17

Set this in dev env:

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

Or, first set the environment variable

export NODE_TLS_REJECT_UNAUTHORIZED=0   

and then start the application:

node index.js

NOT suitable for the prod services.

Denominationalism answered 4/6, 2021 at 18:13 Comment(2)
For localhost development this is the working solution.Rostrum
Yeah just for localhost - (node:33505) Warning: Setting the NODE_TLS_REJECT_UNAUTHORIZED environment variable to '0' makes TLS connections and HTTPS requests insecure by disabling certificate verification. Angell
R
11

This actually solved it for me, from 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
});
Rainy answered 27/2, 2019 at 18:52 Comment(2)
That is the best solution imho, as it does not require additional libraries and is simpleRedingote
solved my problem as wellTiconderoga
L
9

Another approach to solve this is to use the following module.

node_extra_ca_certs_mozilla_bundle

This module can work without any code modification by generating a PEM file that includes all root and intermediate certificates trusted by Mozilla. You can use the following environment variable (Works with Nodejs v7.3+),

NODE_EXTRA_CA_CERTS

To generate the PEM file to use with the above environment variable. You can install the module using:

npm install --save node_extra_ca_certs_mozilla_bundle

and then launch your node script with an environment variable.

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

Other ways to use the generated PEM file are available at:

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

NOTE: I am the author of the above module.

Loritalorn answered 25/12, 2019 at 5:29 Comment(1)
Work for me ` 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');`Hitchhike
O
6

You may be able to do this by modifying the request options as below. If you are using a self-signed certificate or a missing intermediary, setting strictSSL to false will not force request package to validate the certificate.

var options = {
   host: 'jira.example.com',
   path: '/secure/attachment/206906/update.xlsx',
   strictSSL: false
}
Of answered 1/2, 2017 at 18:10 Comment(1)
This solved my problem, I'm using 'request' module instead of the 'http' . Thanks!Metempirics
S
4

You can disable certificate checking globally - no matter which package you are using for making requests - like this:

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

Or

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

Of course you shouldn't do this - but it's certainly handy for debugging and/or very basic scripting where you absolutely don't care about certificates being validated correctly.

Sharpeyed answered 22/11, 2020 at 10:12 Comment(0)
P
3

GoDaddy SSL CCertificate

I've experienced this while trying to connect to our backend API server with GoDaddy certificate and here is the code that I used to solve the problem.

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:

Use the bundled certificate and don't forget to install the library npm install ssl-root-cas

Purpure answered 30/4, 2017 at 2:48 Comment(1)
this worked for me except that while importing i had to use "ssl-root-cas" instead of "ssl-root-cas/latest".Boylan
T
2

This Worked For me => adding agent and 'rejectUnauthorized' set to 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;
    });
};
Tankersley answered 24/7, 2019 at 9:37 Comment(1)
The important thing about security is to not remove security...Overwrought
F
2

The answer provided by @sch helped me tremendously. There are a few additions I'd like to add. When working in a development environment where your SSL cert is issued by one of your own self-signed certificates (so there isn't an intermediate cert), it's this self-signed certificate that needs to be referenced by the NODE_EXTRA_CA_CERTS environment variable. The self-signed cert needs to saved in PEM format. I did the following once I had exported my cert:

set NODE_EXTRA_CA_CERTS=C:\rootCert.pem

(Worth noting that I'm running node on Windows, and the path to the PEM is not quoted)

Using {{node}} from the command-line, I was able to confirm whether or not I had resolved the issue, by calling:

https.get("https://my.dev-domain.local")
Frear answered 20/1, 2023 at 10:30 Comment(0)
A
1

I faced this issue few days back and this is the approach I followed and it works for me.

For me this was happening when i was trying to fetch data using axios or fetch libraries as i am under a corporate firewall, so we had certain particular certificates which node js certificate store was not able to point to.

So for my loclahost i followed this approach. I created a folder in my project and kept the entire chain of certificates in the folder and in my scripts for dev-server(package.json) i added this alongwith server script so that node js can reference the path.

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

For my servers(different environments),I created a new environment variable as below and added it.I was using Openshift,but i suppose the concept will be same for others as well.

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

I didn't generate any certificate in my case as the entire chain of certificates was already available for me.

Ankeny answered 11/12, 2020 at 17:42 Comment(1)
It's also important that this environment variable is set before Node is started. Otherwise it will be ignored. So, for example, NODE_EXTRA_CA_CERTS=certificates/certs-bundle.crt won't work, and neither will using something like the dotenv npm package.Keef
T
1

I met very rare case, but hopely it could help to someone: made a proxy service, which proxied requests to another service. And every request's error was "unable to verify the first certificate" even when i added all expected certificates.

The reason was pretty simple - i accidently re-sent also the "host" header. Just make sure you don't send "host" header explicitly.

Trinetta answered 24/12, 2020 at 19:33 Comment(0)
C
1

I was able to get certificates chain via browsers like mozilla or chrome.

  1. open website, go to certificate settings of the webpage and download certificates chain as filenames (first-chain.pem, second-chain.pem), should be in pem format like
----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
......
-----END CERTIFICATE-----
----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
......
-----END CERTIFICATE-----
  1. then in your nodejs code, i did it on typescript, I added 2 cas as I have 2 webserver requests
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')

then as I need to make websocket wss connection, I add agent with list of new cas to requests

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

also had to add definition file for ssl-root-cas filename ssl-root-cas.d.ts so that typescript does not complain

declare module 'ssl-root-cas' {
  function create(): string | Buffer | (string | Buffer)[] | undefined
}
Cozart answered 15/5, 2021 at 4:52 Comment(1)
for anyone having problem with the chain might try this as well. I am not sure how, but my chain was detected normally by nginx but not by nodesj. I checked the chain from the browser, copied it, and it worksRube
G
1

Are you using axios to send request and get this error ?

If so, consider that the error unable to verify the first certificate can be coming from axios, not related to the server. To solve this, you must configure axios (or other request making app) to allow unauthorized requests. Add an https.Agent to set rejectUnauthorized: false in the config of the request:

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

If you are using a custom axios instance then:

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
  })
})
Gibbsite answered 5/4, 2022 at 19:6 Comment(0)
R
0

We have provide valid Root.pem and Intermediate.pem certificate in agentOptions property of the request object

ex:

   agentOptions: {
        ca: [
            fs.readFileSync("./ROOT.pem"),
            fs.readFileSync("./Intermediate.pem"),
        ],
    },

for more information: https://mcmap.net/q/102065/-how-do-i-use-the-node-js-request-module-to-make-an-ssl-call-with-my-own-certificate

Repugnant answered 11/6, 2022 at 6:51 Comment(0)
G
0

Axios Request : Root cause of this issue is , your code cannot handel the certificate management. To solve this issue add below code.

import * as https from "https";

...

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

// And now pass the httpsAgent to axios request. Like below.

const { data } = await axios.get(url, { httpsAgent });
Gati answered 5/1, 2023 at 16:32 Comment(1)
This solution is not safe and should be caveated as suchEmigration
A
0

I had this problem in Flask when an 3rd party calls my HTTPS webserver, easily recreated via Postman which shows the message

error: unable to verify the first certificate

or via

openssl s_client -connect www.rc8.net:443 -servername www.rc8.com | tee logcertfile

after many many searches and trying different solutions I found the solution, lets encrpyt issues a fullchain.pem cert

use this on the Flask Run -cert option and not the cert.pem

I hope this saves someone some time

Abeabeam answered 26/4, 2023 at 10:44 Comment(0)
S
-3

THIS WORKED FOR ME

Do the folliwings

If you don't have these packages https and axios

You can install by npm install --save axios https

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

axios.defaults.httpsAgent = httpsAgent

Boom by doing that you will get ur response.

Shawnee answered 22/4, 2022 at 9:1 Comment(1)
That is not safe to doNeophyte
B
-5

I was using nodemailer npm module. The below code solved the issue

     tls: {
     // do not fail on invalid certs
     rejectUnauthorized: false
     }
Broida answered 30/10, 2018 at 11:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.