320

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);
      
    });
});
1
  • 3
    i used another procedure like disabling certificate verification and done Commented Aug 28, 2015 at 6:25

23 Answers 23

289

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
Sign up to request clarification or add additional context in comments.

6 Comments

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?
@lazycipher this configuration has to be done server-side.
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.
incomplete-chain.badssl.com says certificate expired. Is there any other site to check this out?
This certificate process works for me but I have to put username and password when using NTLM authentication in expressjs. Is there any way not to use username and password? In .NET NLTM with CredentialCache.DefaultNetworkCredentials works just fine without username and password. Any ideas please?
|
216
+350

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

process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0

5 Comments

This seems not different from Labeo's answer above, as just as dangerous.
It is different, it doesnt require any coding changes as the env variable can be set outside the source code.
This answer is dangerous. You are disabling any security that TLS provides.
Fine indeed just to test localhost. Just make sure you remove it after your tests.
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.
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.

6 Comments

This answer should be used in most cases as it actually fixes the problem rather than disables the entire benefit of SSL.
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 ;)
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/76
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.
this did not work for me but this one did: github.com/arvind-agarwal/node_extra_ca_certs_mozilla_bundle
|
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'));

9 Comments

This answer is dangerous. The other one is safer.
Well by doing that, you remove the security provided by SSL, so it should be used for development only.
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".
Disabling SSL verification is NOT a solution to any problem.:-)
Dangerous indeed, but in a safe situation it's a pain free solution.
|
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

3 Comments

I get this issue (Chain issues.........Incomplete) for my cert which is authorized from DigiCert Inc., what is the procedure to fix this?
@imarchuang 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.
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.crt
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.

1 Comment

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.
12

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

Comments

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.

1 Comment

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');`
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
}

Comments

5

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.

Comments

4

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

1 Comment

this worked for me except that while importing i had to use "ssl-root-cas" instead of "ssl-root-cas/latest".
4

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

1 Comment

This answer received an upvote from me because it offers the advantage of enabling a unit test to run without the need to modify the production code. In my situation, all I had to do was add an export NODE_EXTRA_CA_CERTS=/path/to/pem line to my bash script before executing the tests.
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;
    });
};

1 Comment

The important thing about security is to not remove security...
2

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
}

1 Comment

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 works
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.

1 Comment

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

Comments

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

Comments

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://stackoverflow.com/a/72582263/4652706

Comments

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

1 Comment

This solution is not safe and should be caveated as such
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

Comments

0

I also had this issue with imap getting letters from gmail.

imap.connect() not connected

The problem was with Avast Antivirus with its middleware. When I disable all avast email checking, it all works.

Comments

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

1 Comment

That is not safe to do
-5

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

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.