Introduction
When working with HTTPS requests in Node.js, you may encounter an error stating "unable to verify the first certificate." This typically occurs when there is a problem with the SSL/TLS certificate chain. Understanding how to handle this issue is crucial for securely interacting with web servers that might have misconfigured certificates.
Understanding Certificate Chains
SSL/TLS certificates are part of a hierarchy known as a certificate chain:
- Server Certificate: Issued by an intermediate CA, it identifies the server.
- Intermediate Certificate: Acts as a bridge between the root and server certificates.
- Root Certificate: Self-signed and stored in client devices or applications.
For secure communication, clients expect to receive both the server certificate and any necessary intermediates from the server. If these are incomplete, verification fails.
Problem Reproduction
To understand this issue better, let’s consider a scenario:
- Visit
https://incomplete-chain.badssl.com
using a web browser. The connection succeeds because browsers often automatically resolve incomplete chains. - Try accessing the same URL with Node.js, and you’ll encounter an error like "unable to verify the first certificate."
Solutions
There are multiple ways to address this issue in Node.js:
1. Retrieve and Use Intermediate Certificates
Steps to Get the Intermediate Certificate:
-
Extract Server Certificate Details:
openssl s_client -connect incomplete-chain.badssl.com:443 -servername incomplete-chain.badssl.com | tee logcertfile
-
Identify the Issuer:
openssl x509 -in logcertfile -noout -text | grep -i "issuer"
-
Download Intermediate Certificate:
Use the issuer URI to download, then convert it to.pem
format:curl --output intermediate.crt http://cacerts.digicert.com/DigiCertSHA2SecureServerCA.crt openssl x509 -inform DER -in intermediate.crt -out intermediate.pem -text
Using the Certificate in Node.js
-
Option 1: Set NODE_EXTRA_CA_CERTS Environment Variable
Use
cross-env
to include the intermediate certificate:"start": "cross-env NODE_EXTRA_CA_CERTS=\"C:\\Users\\USERNAME\\Desktop\\ssl-connect\\intermediate.pem\" node index.js"
-
Option 2: Custom HTTPS Agent
Utilize a package like
ssl-root-cas
to create an agent that includes the certificate: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); });
2. Modify Node.js Global Agent
For applications where you want all requests to trust the additional certificate:
const rootCas = require('ssl-root-cas').create();
rootCas.addFile(path.resolve(__dirname, 'intermediate.pem'));
https.globalAgent.options.ca = rootCas;
Important Considerations
- Security Risks: Disabling SSL verification (
NODE_TLS_REJECT_UNAUTHORIZED=0
) is a dangerous workaround that should only be used temporarily and in secure environments. - Best Practices: Always aim to use complete certificate chains and verify the server’s identity. Using trusted root certificates helps maintain security.
Conclusion
Handling SSL/TLS errors in Node.js involves understanding how certificate chains work and ensuring your application can properly validate them. By following these methods, you ensure that your application remains secure while interacting with HTTPS endpoints.