Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 24 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@

If you are trying to connect to a secure website via nodejs. Although, the site may work in the browser, you may run into errors such as

[CERT_UNTRUSTED](https://stackoverflow.com/questions/41390965/cert-untrusted-error-when-execute-https-request)

[UNABLE_TO_VERIFY_LEAF_SIGNATURE](https://stackoverflow.com/questions/20082893/unable-to-verify-leaf-signature)

[Unable to verify the first certificate](https://stackoverflow.com/questions/31673587/error-unable-to-verify-the-first-certificate-in-nodejs)
* [CERT_UNTRUSTED](https://stackoverflow.com/questions/41390965/cert-untrusted-error-when-execute-https-request)
* [UNABLE_TO_VERIFY_LEAF_SIGNATURE](https://stackoverflow.com/questions/20082893/unable-to-verify-leaf-signature)
* [Unable to verify the first certificate](https://stackoverflow.com/questions/31673587/error-unable-to-verify-the-first-certificate-in-nodejs)

It may be due to a couple of reasons.
The Root CA certificate is missing in nodejs
Or the site does not correctly install the intermediate certificates.

Typically you encounter these at the last minute, and usually, the server is not in your control; hence you cannot modify the certificate installation, and it is challenging to change code at that time.

Another possible error might be due to newer environments throwing an exception if certificates using a weak digest (SHA1WithRSA) are used:

* [CA signature digest algorithm too weak](https://serverfault.com/questions/1143995/tls-1-0-broken-with-newer-debian-openssl)

Instead of lowering the SECLEVEL from 1 to 0, there are also files generated that exclude certificates that use this weak digest.

### Node js added an Environment variable to address this issue:

### [NODE_EXTRA_CA_CERTS](https://nodejs.org/api/cli.html#cli_node_extra_ca_certs_file)
Expand All @@ -29,10 +33,17 @@ However, it is cumbersome to create the PEM file for missing certificates manual
* https://wiki.mozilla.org/CA/Included_Certificates
* https://wiki.mozilla.org/CA/Intermediate_Certificates

It generates three different bundles that can be used based on your needs:
* Intermediate certificates only bundle `ca_intermediate_bundle.pem`
* Root only certificates bundle `ca_root_bundle.pem`
* Intermediate and Root certificates bundle `ca_intermediate_root_bundle.pem`
It generates six different bundles that can be used based on your needs:

#### All intermediate/root certificates
* Intermediate certificates only bundle: `ca_intermediate_bundle.pem`
* Root only certificates bundle: `ca_root_bundle.pem`
* Intermediate and Root certificates bundle: `ca_intermediate_root_bundle.pem`

#### Only intermediate/root certificates with strong digests
* Intermediate certificates only bundle (no weak digests): `ca_intermediate_bundle.pem`
* Root only certificates bundle (no weak digests): `ca_root_bundle.pem`
* Intermediate and Root certificates bundle (no weak digests): `ca_intermediate_root_bundle.pem`

You can use any of the above bundles with NODE_EXTRA_CA_CERTS.

Expand All @@ -45,20 +56,20 @@ During the installation of the module, it downloads the latest certificates from
You can launch your script while using the above certificates using:

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

for Windows use:
```
npx cross-env NODE_EXTRA_CA_CERTS=node_modules/node_extra_ca_certs_mozilla_bundle/ca_bundle/ca_intermediate_root_bundle.pem node your_script.js
npx cross-env NODE_EXTRA_CA_CERTS=node_modules/node_extra_ca_certs_mozilla_bundle/ca_bundle/ca_strong_intermediate_root_bundle.pem node your_script.js
```

### To use the PEM file in code
This is useful when you want to run as root or listen on privilege port like 80. Since in those situations the above environment variable does not work.
```
const fs = require('fs');
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');
https.globalAgent.options.ca = fs.readFileSync('node_modules/node_extra_ca_certs_mozilla_bundle/ca_bundle/ca_strong_intermediate_root_bundle.pem');
```

### To include your custom PEM certificates in code along with this file
Expand All @@ -67,6 +78,6 @@ If you want to include your custom certificate and still want to connect to othe
```
const fs = require('fs');
const https = require('https');
https.globalAgent.options.ca = yourCertificatePEMcontent + fs.readFileSync('node_modules/node_extra_ca_certs_mozilla_bundle/ca_bundle/ca_intermediate_root_bundle.pem');
https.globalAgent.options.ca = yourCertificatePEMcontent + fs.readFileSync('node_modules/node_extra_ca_certs_mozilla_bundle/ca_bundle/ca_strong_intermediate_root_bundle.pem');
```

38 changes: 32 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ const csvtojson = require('csvtojson');
const fs = require('fs');
const request = require('axios');

// Create new files without certificates using weak hash algorithms, as this causes an
// exception on newer environments (CA signature digest algorithm too weak).
const weakHashList = [ 'SHA1WithRSA' ];


async function appendPEMFromUrl(url, individualFiles=false) {
async function appendPEMFromUrl(url, individualFiles=false, skipWeakHash=false) {
console.log(`Downloading from URL ${url}`);
const response = await request.get(url, { responseType: 'blob'});
console.log('Parsing csv file');
Expand All @@ -16,7 +18,13 @@ async function appendPEMFromUrl(url, individualFiles=false) {
let certPem = entry['PEM Info'].slice(1, -1);
const commonName = entry['Common Name or Certificate Name'] || entry['Certificate Subject Common Name'];
const serialNumber = entry['Certificate Serial Number'];
const issuerOrg = entry['Certificate Issuer Organization']
const issuerOrg = entry['Certificate Issuer Organization'];
const signatureHashAlgorithm = entry['Signature Hash Algorithm'];

if (skipWeakHash && weakHashList.includes(signatureHashAlgorithm)) {
// Certificate uses a weak hash, skip
continue;
}

// Remove empty lines in certPem some certificates were coming with blank line e.g.
// Shopper SSL
Expand Down Expand Up @@ -58,15 +66,33 @@ async function build() {
fs.writeFileSync('ca_bundle/ca_root_bundle.pem', ca_root_bundle);
fs.writeFileSync('ca_bundle/ca_intermediate_root_bundle.pem', ca_intermediate_bundle + ca_root_bundle);

// Add intermediate certificates, strong hashes only:
const ca_strong_intermediate_bundle = await appendPEMFromUrl('https://ccadb.my.salesforce-sites.com/mozilla/PublicAllIntermediateCertsWithPEMCSV', false, true);

// Add root certificates, strong hashes only:
const ca_strong_root_bundle = await appendPEMFromUrl('https://ccadb.my.salesforce-sites.com/mozilla/IncludedCACertificateReportPEMCSV', false, true);

fs.writeFileSync('ca_bundle/ca_strong_intermediate_bundle.pem', ca_strong_intermediate_bundle);
fs.writeFileSync('ca_bundle/ca_strong_root_bundle.pem', ca_strong_root_bundle);
fs.writeFileSync('ca_bundle/ca_strong_intermediate_root_bundle.pem', ca_strong_intermediate_bundle + ca_strong_root_bundle);



console.log();
console.log('Intermediate and Root Certificate Bundles from Mozilla generated');
console.log('----------------------------------------------------------------');
console.log('All certificates:')
console.log('Intermediate certificates only bundle at ca_bundle/ca_intermediate_bundle.pem');
console.log('Root only certificates bundle at ca_bundle/ca_root_bundle.pem');
console.log('Intermediate and Root certificates bundle at ca_bundle/ca_intermediate_root_bundle.pem');
console.log();
console.log('To run your Node script with the bundled certificate run:')
console.log('NODE_EXTRA_CA_CERTS=node_modules/node_extra_ca_certs_mozilla_bundle/ca_bundle/ca_intermediate_root_bundle.pem node your_script.js\n');
console.log('Only certificates not using a weak signature digest algorithm:')
console.log('Intermediate certificates only bundle at ca_bundle/ca_strong_intermediate_bundle.pem');
console.log('Root only certificates bundle at ca_bundle/ca_strong_root_bundle.pem');
console.log('Intermediate and Root certificates bundle at ca_bundle/ca_strong_intermediate_root_bundle.pem');
console.log();
console.log('To run your Node script with the bundled strong intermediate certificates run:')
console.log('NODE_EXTRA_CA_CERTS=node_modules/node_extra_ca_certs_mozilla_bundle/ca_bundle/ca_strong_intermediate_bundle.pem node your_script.js\n');
}

build();
build();