如何添加自定义证书授权(CA)到 nodejs

我正在使用 CLI 工具来建立一个混合的移动应用程序,它有一个很酷的上传功能,这样我就可以在设备上测试应用程序,而不需要通过应用程序商店(它是 ionic-CLI)。然而,在我的公司,像许多其他公司一样,TLS 请求是用公司自己的定制 CA 证书重新签署的,我在我的机器上的密钥链(OS X)。但是,nodejs 并不使用密钥链来获得其可信任的 CA 列表。我不能控制 ionic-cli 应用程序,所以我不能简单地将{ ca: }属性传递给 https 模块。我也可以看到这是一个问题,任何节点应用程序,我不控制。有没有可能告诉 nodejs 去信任一个 CA?

我不确定这是否属于信息安全或其他交易所。

158951 次浏览

This is not currently possible unless you compile a custom version of nodejs with custom CA certs. Hard-baked CA certs is a current limitation of nodejs until someone submits a PR and it's merged. It's a problem for others as well.

Below I have some copies of workarounds which might help some people but probably not the OP.

As far as I know OP can:

  • Custom compile nodejs
  • submit a PR for nodejs to fix the issue
  • file an issue or PR with ionic-cli to support custom CA certs: https://github.com/driftyco/ionic-cli (as suggested by @Nate)
  • Force less security (no TLS or silence verification also suggested by @Nate)

Others, if you control the nodejs app in question you have more options. You can of course specify the ca cert in each request. Some clever people have shared some workarounds in the github issue https://github.com/nodejs/node/issues/4175. I haven't tried any of these myself yet so no promises, I'm just sharing what I've read.

DuBistKomisch explains how to get nodejs to use the operating system's CA certs:

My workaround is to load and parse the system CA certs manually. Then, as recommended by the request docs, pass them in with the ca option everywhere we make a request. I presume you could also just set ca on the global agent if that works for your use case.

fs.readFileSync('/etc/ssl/certs/ca-certificates.crt')
.toString()
.split(/-----END CERTIFICATE-----\n?/)
// may include an extra empty string at the end
.filter(function (cert) { return cert !== ''; })
// effectively split after delimiter by adding it back
.map(function (cert) { return cert + '-----END CERTIFICATE-----\n'; })

mwain explains how to set the CA certs globally and not on each https request:

Had similar issues with this, have internal apps using an internally signed cert. Opted to use https.globalAgent and set an array of CA's which are defined in a config and updated on an env basis.

const trustedCa = [
'/etc/pki/tls/certs/ca-bundle.crt',
'/path/to/custom/cert.crt'
];


https.globalAgent.options.ca = [];
for (const ca of trustedCa) {
https.globalAgent.options.ca.push(fs.readFileSync(ca));
}

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

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]

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

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

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