如何正确配置 node.js 以使用自签名根证书?

How to properly configure node.js to use Self Signed root certificates?

所以,在绝望的道路上,我想知道是否有人在某个地方可以帮助我配置 nodejs 以接受根 CA 自签名。我需要它才能通过带 TLS 的节点获取访问开发中的自定义 API。

环境

我正在处理的 API 是一个 PHP 脚本,它允许我的 nodejs 后端查询一些数据。

自签名根证书和 API 证书是用 openssl 生成的,非常好,因为我可以使用 HTTPS 从浏览器查询 API 没有任何问题。

错误

当尝试从 nodejs 后端查询 API 时,出现此错误:

FetchError: request to https://myapi.dev.local failed, reason: self signed certificate
    at ClientRequest.<anonymous> (./node_modules/node-fetch/lib/index.js:1461:11)
    at ClientRequest.emit (node:events:369:20)
    at TLSSocket.socketErrorListener (node:_http_client:462:9)
    at TLSSocket.emit (node:events:369:20)
    at emitErrorNT (node:internal/streams/destroy:188:8)
    at emitErrorCloseNT (node:internal/streams/destroy:153:3)
    at processTicksAndRejections (node:internal/process/task_queues:81:21)"

尝试与失败

首先,我尝试使用 dpkg-reconfigure ca-certificates 在 ubuntu 上安装证书,但后来我发现 nodejs 使用硬编码列表。

所以,因为我不想使用 env 变量 NODE_TLS_REJECT_UNAUTHORIZED=0 为了安全起见,我尝试使用 NODE_EXTRA_CA_CERTS=pathToMycert.pem en 变量,但它没有改变任何东西我找不到任何信息来了解发生了什么。

在我的 nodejs 后端,如果我执行 console.log(process.env.NODE_EXTRA_CA_CERTS),它会打印正确的路径。

我试图通过此检查将我的 CA 与 tls.rootCertificates 相匹配:


const tls = require('tls');
const fs = require('fs');

const ca = await fs.readFileSync(process.env.NODE_EXTRA_CA_CERTS, 'utf8');
console.log(ca); //successfully print the CA, so it exists.
const inList = tls.rootCertificates.some( cert =>{
    console.log('testing ca : \n',cert);
    return cert == ca;
});
console.log(`CA is ${ !inList ? 'not' : '' } in rootCertificates list...`);

它打印 'CA is not in rootCertificates list'。不足为奇。

因此,我尝试对 tls secureContext 进行 monkeypatch 以包含我的证书:


const tls = require('tls');
const fs = require('fs');

const origCreateSecureContext = tls.createSecureContext;

tls.createSecureContext = options => {
    const context = origCreateSecureContext(options);

    const list = (process.env.NODE_EXTRA_CA_CERTS || '').split(',');
    list.forEach(extraCert => {
        const pem = fs.readFileSync(extraCert, { encoding : 'utf8' }).replace(/\r\n/g, "\n");
        const certs = pem.match(/-----BEGIN CERTIFICATE-----\n[\s\S]+?\n-----END CERTIFICATE-----/g);
        if(!certs) throw new Error(
            `SelfSignedCertSupport : Invalid extra certificate ${extraCert}`
        );
        certs.forEach(cert => context.context.addCACert(cert.trim()));
    });

    return context;
};

无效。

我试过(在这个问题之后:https://github.com/nodejs/node/issues/27079)这样做:


const tls = require('tls');
const fs = require('fs');

const additionalCerts = [];
const list = (process.env.NODE_EXTRA_CA_CERTS || '').split(',');
list.forEach(extraCert => {
    const pem = fs.readFileSync(extraCert, { encoding : 'utf8' }).replace(/\r\n/g, "\n");
    const certs = pem.match(/-----BEGIN CERTIFICATE-----\n[\s\S]+?\n-----END CERTIFICATE-----/g);
    if(!certs) throw new Error(
        `SelfSignedCertSupport : Invalid extra certificate ${extraCert}`
    );
    additionalCerts.push(...certs);
});

tls.rootCertificates = [
    ...tls.rootCertificates,
    ...additionalCerts
];

运气不好。

我做错了什么?

我明白是怎么回事了。这是两个问题的结合。

首先,我使用相同的 CN 生成了我的 CA 证书和其他自签名证书 。它适用于所有浏览器和网络服务器,但不适用于节点。对于节点,请确保您的所有 CN 都具有 不同的名称(如 this answer 中所述)。

第二个问题是环境变量 NODE_EXTRA_CA_CERTS 在我的环境中出于某种原因无法正常工作。尝试在我尝试时尝试打补丁但很丑陋,因为 addCACert 不是 public nodejs API 的一部分。它不应该被使用。

因为我使用依赖于 https 包的 fetch API,所以我在后端 nodejs 应用程序的顶部创建了一个我需要的小模块:


if(!process.env.NODE_EXTRA_CA_CERTS) return;

const https = require('https');
const tls = require('tls');
const fs = require('fs');

const list = (process.env.NODE_EXTRA_CA_CERTS || '').split(',');
const additionalCerts = list.map(extraCert => fs.readFileSync(extraCert, 'utf8'));

https.globalAgent.options.ca = [
    ...tls.rootCertificates,
    ...additionalCerts
];

这样,所有使用 https 且未重新定义 ca 选项的请求将从 globalAgent 读取 ca 列表,而您不必污染您的代码库使用 ca 特定代码。就我而言,我不希望我的开发环境生成我必须在生产中删除的代码。

所以,现在它对我有用了,即使我不知道 NODE_EXTRA_CA_CERTS 环境变量是怎么回事,它没有发挥作用。