120

I'm using a CLI tool to build hybrid mobile apps which has a cool upload feature so I can test the app on a device without going through the app store (it's ionic-cli). However, in my company like so many other companies TLS requests are re-signed with the company's own custom CA certificate which I have on my machine in the keychain (OS X). However, nodejs does not use the keychain to get its list of CA's to trust. I don't control the ionic-cli app so I can't simply pass in a { ca: } property to the https module. I could also see this being a problem for any node app which I do not control. Is it possible to tell nodejs to trust a CA?

I wasn't sure if this belonged in Information Security or any of the other exchanges...

Mike Haas
  • 2,273
  • 3
  • 25
  • 30

5 Answers5

198

Node.js 7.3.0 (and the LTS versions 6.10.0 and 4.8.0) added NODE_EXTRA_CA_CERTS environment variable for you to pass the CA certificate file. It will be safer than disabling certificate verification using NODE_TLS_REJECT_UNAUTHORIZED.

$ export NODE_EXTRA_CA_CERTS=[your CA certificate file path]
Helen
  • 87,344
  • 17
  • 243
  • 314
Cheng-Lin Tsao
  • 2,124
  • 1
  • 11
  • 6
  • 9
    The [`cafile` configuration property](https://docs.npmjs.com/misc/config#cafile) works similarly: `npm config set cafile [your CA certificate file path]` – Paul Sep 14 '18 at 15:18
  • 31
    The main difference between `NODE_EXTRA_CA_CERTS` and the `cafile` config property is that the former *adds* a cert, whereas the `cafile` config property *replaces* the certs. For those that just want to add a corporate cert to the chain, `NODE_EXTRA_CA_CERTS` is the easier option. – Eric Sep 21 '18 at 13:08
  • 13
    does ca certificate means .pem/.cer file location ? – Ganesh Karamala Dec 06 '18 at 12:14
  • FYI sudo or adding port support to node for 80 means NODE_EXTRA_CA_CERTS is ignored :( – Chris DaMour Apr 05 '19 at 07:38
  • 2
    @ChrisDaMour it can work with sudo, you just need to edit the `sudoers` file with `visudo` and allow the `NODE_EXTRA_CA_CERTS` environment variable to work across a sudo boundary. – Joel Pearson May 23 '19 at 03:14
  • 2
    Can this be used to programmatically add certificates as the application is online? Or will this only work during initial startup with some pre-provided certs? – ivandov Jul 01 '19 at 20:26
  • @ivandov Using `process.env.NODE_EXTRA_CA_CERTS` in run time seems to not work. But you can use the modules mentioned in the answer [below](https://stackoverflow.com/a/39972054/6863710) – DavidCohen May 26 '20 at 07:54
  • Note that the variable must point to **single** file, **not directory** with certificates. If several certificates in PEM format need to be added they can be concatenated into single file. – HUB Dec 07 '20 at 13:09
  • 2
    In Windows Powershell the equivalent command would be `$env:NODE_EXTRA_CA_CERTS=[your CA certificate file path]` – burf Nov 03 '21 at 05:38
  • saved my day, this step was needed when trying to connect to AWS RDS PostgreSQL instance with SSL configured, had to download the cert from https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem – pktippa Feb 09 '22 at 14:49
  • Can the path be to a .cer file or does it need to be converted to a pem file? – Irish Redneck May 09 '23 at 15:25
36

You can specify a command line option to tell node to use the system CA store:

node --use-openssl-ca

Alternatively this can be specified as an environment variable, if you are not running the node CLI directly yourself:

NODE_OPTIONS=--use-openssl-ca
Jimbali
  • 2,065
  • 1
  • 20
  • 24
  • 1
    IMHO this is the best answer. – mark Oct 10 '22 at 04:27
  • can you please tell how to use this flag with self-signed CA certificate? – GoFindTruth Mar 07 '23 at 12:49
  • 1
    @GoFindTruth it depends what platform you're on, but if for example you are running your code in an Ubuntu Docker container then you would copy the CA file into the image under `/usr/local/share/ca-certificates/` and run `update-ca-certificates`. Then it would be in the system CA store and the flag should work. https://stackoverflow.com/a/42292623/1103530 – Jimbali Mar 08 '23 at 15:27
11

I'm aware of two npm modules that handle this problem when you control the app:

  1. https://github.com/fujifish/syswide-cas (I'm the author of this one)
  2. https://github.com/coolaj86/node-ssl-root-cas

node-ssl-root-cas bundles it's own copies of nodes root CAs and also enables adding your own CAs to trust. It places the certs on the https global agent, so it will only be used for https module, not pure tls connections. Also, you will need extra steps if you use a custom Agent instead of the global agent.

syswide-cas loads certificates from pre-defined directories (such as /etc/ssl/certs) and uses node internal API to add them to the trusted list of CAs in conjunction to the bundled root CAs. There is no need to use the ca option since it makes the change globally which affects all later TLS calls automatically. It's also possible to add CAs from other directories/files if needed. It was verified to work with node 0.10, node 5 and node 6.

Since you do not control the app you can create a wrapper script to enable syswide-cas (or node-ssl-root-cas) and then require the ionic-cli script:

require('syswide-cas'); // this adds your custom CAs in addition to bundled CAs
require('./path/to/real/script'); // this runs the actual script
fujifish
  • 951
  • 10
  • 10
11

There is an undocumented, seemingly stable, API for appending a certificate to the default list:

const tls = require('tls');

const secureContext = tls.createSecureContext();

// https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem.txt
secureContext.context.addCACert(`-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----`);

const sock = tls.connect(443, 'host', { secureContext });

For more information, checkout the open issue on the subject: https://github.com/nodejs/node/issues/27079

Cameron Tacklind
  • 5,764
  • 1
  • 36
  • 45
  • Is there a way to apply this globally? – Seán Hayes Feb 08 '19 at 03:52
  • @SeánHayes I assume you've seen the other answers here that reference `NODE_EXTRA_CA_CERTS` and `cafile` so I assume you mean can you do that in JavaScript? Researching this a little, it looks like the `NODE_EXTRA_CA_CERTS` is entirely implemented in node's C++ source with methods that are not available to the JavaScript layer. Search for `UseExtraCaCerts` in node source to see for yourself. Not sure what mechanism the `cafile` method uses exactly so you might investigate that but I doubt it is serious different. – Cameron Tacklind Feb 09 '19 at 23:51
2

This answer is more focused towards package maintainers/builders.

One can use this method if you do not want end users to rely on additional environment variables.

When nodejs is built from source, it (by default, can be overridden) embeds the Mozilla CA certificate database into the binary itself. One can add more certificates to this database using the following commands:

# Convert your PEM certificate to DER
openssl x509 -in /path/to/your/CA.pem -outform der -out CA.der

# Add converted certificate to certdata
nss-addbuiltin -n "MyCompany-CA" -t "CT,C,C" < CA.der >> tools/certdata.txt

# Regenerate src/node_root_certs.h header file
perl tools/mk-ca-bundle.pl

# Finally, compile
make install
Nehal J Wani
  • 16,071
  • 3
  • 64
  • 89