mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 13:48:44 +02:00
crypto: add tls.setDefaultCACertificates()
This API allows dynamically configuring CA certificates that will be used by the Node.js TLS clients by default. Once called, the provided certificates will become the default CA certificate list returned by `tls.getCACertificates('default')` and used by TLS connections that don't specify their own CA certificates. This function only affects the current Node.js thread. PR-URL: https://github.com/nodejs/node/pull/58822 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
This commit is contained in:
parent
a22c9c4f42
commit
edd66d0130
21 changed files with 1128 additions and 14 deletions
|
@ -2260,6 +2260,54 @@ openssl pkcs12 -certpbe AES-256-CBC -export -out client-cert.pem \
|
|||
The server can be tested by connecting to it using the example client from
|
||||
[`tls.connect()`][].
|
||||
|
||||
## `tls.setDefaultCACertificates(certs)`
|
||||
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `certs` {string\[]|ArrayBufferView\[]} An array of CA certificates in PEM format.
|
||||
|
||||
Sets the default CA certificates used by Node.js TLS clients. If the provided
|
||||
certificates are parsed successfully, they will become the default CA
|
||||
certificate list returned by [`tls.getCACertificates()`][] and used
|
||||
by subsequent TLS connections that don't specify their own CA certificates.
|
||||
The certificates will be deduplicated before being set as the default.
|
||||
|
||||
This function only affects the current Node.js thread. Previous
|
||||
sessions cached by the HTTPS agent won't be affected by this change, so
|
||||
this method should be called before any unwanted cachable TLS connections are
|
||||
made.
|
||||
|
||||
To use system CA certificates as the default:
|
||||
|
||||
```cjs
|
||||
const tls = require('node:tls');
|
||||
tls.setDefaultCACertificates(tls.getCACertificates('system'));
|
||||
```
|
||||
|
||||
```mjs
|
||||
import tls from 'node:tls';
|
||||
tls.setDefaultCACertificates(tls.getCACertificates('system'));
|
||||
```
|
||||
|
||||
This function completely replaces the default CA certificate list. To add additional
|
||||
certificates to the existing defaults, get the current certificates and append to them:
|
||||
|
||||
```cjs
|
||||
const tls = require('node:tls');
|
||||
const currentCerts = tls.getCACertificates('default');
|
||||
const additionalCerts = ['-----BEGIN CERTIFICATE-----\n...'];
|
||||
tls.setDefaultCACertificates([...currentCerts, ...additionalCerts]);
|
||||
```
|
||||
|
||||
```mjs
|
||||
import tls from 'node:tls';
|
||||
const currentCerts = tls.getCACertificates('default');
|
||||
const additionalCerts = ['-----BEGIN CERTIFICATE-----\n...'];
|
||||
tls.setDefaultCACertificates([...currentCerts, ...additionalCerts]);
|
||||
```
|
||||
|
||||
## `tls.getCACertificates([type])`
|
||||
|
||||
<!-- YAML
|
||||
|
|
32
lib/tls.js
32
lib/tls.js
|
@ -37,6 +37,7 @@ const {
|
|||
ERR_TLS_CERT_ALTNAME_INVALID,
|
||||
ERR_OUT_OF_RANGE,
|
||||
ERR_INVALID_ARG_VALUE,
|
||||
ERR_INVALID_ARG_TYPE,
|
||||
} = require('internal/errors').codes;
|
||||
const internalUtil = require('internal/util');
|
||||
internalUtil.assertCrypto();
|
||||
|
@ -51,6 +52,8 @@ const {
|
|||
getBundledRootCertificates,
|
||||
getExtraCACertificates,
|
||||
getSystemCACertificates,
|
||||
resetRootCertStore,
|
||||
getUserRootCertificates,
|
||||
getSSLCiphers,
|
||||
} = internalBinding('crypto');
|
||||
const { Buffer } = require('buffer');
|
||||
|
@ -122,8 +125,17 @@ function cacheSystemCACertificates() {
|
|||
}
|
||||
|
||||
let defaultCACertificates;
|
||||
let hasResetDefaultCACertificates = false;
|
||||
|
||||
function cacheDefaultCACertificates() {
|
||||
if (defaultCACertificates) { return defaultCACertificates; }
|
||||
|
||||
if (hasResetDefaultCACertificates) {
|
||||
defaultCACertificates = getUserRootCertificates();
|
||||
ObjectFreeze(defaultCACertificates);
|
||||
return defaultCACertificates;
|
||||
}
|
||||
|
||||
defaultCACertificates = [];
|
||||
|
||||
if (!getOptionValue('--use-openssl-ca')) {
|
||||
|
@ -171,6 +183,26 @@ function getCACertificates(type = 'default') {
|
|||
}
|
||||
exports.getCACertificates = getCACertificates;
|
||||
|
||||
function setDefaultCACertificates(certs) {
|
||||
if (!ArrayIsArray(certs)) {
|
||||
throw new ERR_INVALID_ARG_TYPE('certs', 'Array', certs);
|
||||
}
|
||||
|
||||
// Verify that all elements in the array are strings
|
||||
for (let i = 0; i < certs.length; i++) {
|
||||
if (typeof certs[i] !== 'string' && !isArrayBufferView(certs[i])) {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
`certs[${i}]`, ['string', 'ArrayBufferView'], certs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
resetRootCertStore(certs);
|
||||
defaultCACertificates = undefined; // Reset the cached default certificates
|
||||
hasResetDefaultCACertificates = true;
|
||||
}
|
||||
|
||||
exports.setDefaultCACertificates = setDefaultCACertificates;
|
||||
|
||||
// Convert protocols array into valid OpenSSL protocols list
|
||||
// ("\x06spdy/2\x08http/1.1\x08http/1.0")
|
||||
function convertProtocols(protocols) {
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
#include <wincrypt.h>
|
||||
#endif
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace node {
|
||||
|
||||
using ncrypto::BignumPointer;
|
||||
|
@ -83,10 +85,28 @@ static std::atomic<bool> has_cached_bundled_root_certs{false};
|
|||
static std::atomic<bool> has_cached_system_root_certs{false};
|
||||
static std::atomic<bool> has_cached_extra_root_certs{false};
|
||||
|
||||
// Used for sets of X509.
|
||||
struct X509Less {
|
||||
bool operator()(const X509* lhs, const X509* rhs) const noexcept {
|
||||
return X509_cmp(const_cast<X509*>(lhs), const_cast<X509*>(rhs)) < 0;
|
||||
}
|
||||
};
|
||||
using X509Set = std::set<X509*, X509Less>;
|
||||
|
||||
// Per-thread root cert store. See NewRootCertStore() on what it contains.
|
||||
static thread_local X509_STORE* root_cert_store = nullptr;
|
||||
// If the user calls tls.setDefaultCACertificates() this will be used
|
||||
// to hold the user-provided certificates, the root_cert_store and any new
|
||||
// copy generated by NewRootCertStore() will then contain the certificates
|
||||
// from this set.
|
||||
static thread_local std::unique_ptr<X509Set> root_certs_from_users;
|
||||
|
||||
X509_STORE* GetOrCreateRootCertStore() {
|
||||
// Guaranteed thread-safe by standard, just don't use -fno-threadsafe-statics.
|
||||
static X509_STORE* store = NewRootCertStore();
|
||||
return store;
|
||||
if (root_cert_store != nullptr) {
|
||||
return root_cert_store;
|
||||
}
|
||||
root_cert_store = NewRootCertStore();
|
||||
return root_cert_store;
|
||||
}
|
||||
|
||||
// Takes a string or buffer and loads it into a BIO.
|
||||
|
@ -227,14 +247,11 @@ int SSL_CTX_use_certificate_chain(SSL_CTX* ctx,
|
|||
issuer);
|
||||
}
|
||||
|
||||
static unsigned long LoadCertsFromFile( // NOLINT(runtime/int)
|
||||
static unsigned long LoadCertsFromBIO( // NOLINT(runtime/int)
|
||||
std::vector<X509*>* certs,
|
||||
const char* file) {
|
||||
BIOPointer bio) {
|
||||
MarkPopErrorOnReturn mark_pop_error_on_return;
|
||||
|
||||
auto bio = BIOPointer::NewFile(file, "r");
|
||||
if (!bio) return ERR_get_error();
|
||||
|
||||
while (X509* x509 = PEM_read_bio_X509(
|
||||
bio.get(), nullptr, NoPasswordCallback, nullptr)) {
|
||||
certs->push_back(x509);
|
||||
|
@ -250,6 +267,17 @@ static unsigned long LoadCertsFromFile( // NOLINT(runtime/int)
|
|||
}
|
||||
}
|
||||
|
||||
static unsigned long LoadCertsFromFile( // NOLINT(runtime/int)
|
||||
std::vector<X509*>* certs,
|
||||
const char* file) {
|
||||
MarkPopErrorOnReturn mark_pop_error_on_return;
|
||||
|
||||
auto bio = BIOPointer::NewFile(file, "r");
|
||||
if (!bio) return ERR_get_error();
|
||||
|
||||
return LoadCertsFromBIO(certs, std::move(bio));
|
||||
}
|
||||
|
||||
// Indicates the trust status of a certificate.
|
||||
enum class TrustStatus {
|
||||
// Trust status is unknown / uninitialized.
|
||||
|
@ -831,11 +859,24 @@ static std::vector<X509*>& GetExtraCACertificates() {
|
|||
// NODE_EXTRA_CA_CERTS are cached after first load. Certificates
|
||||
// from --use-system-ca are not cached and always reloaded from
|
||||
// disk.
|
||||
// 8. If users have reset the root cert store by calling
|
||||
// tls.setDefaultCACertificates(), the store will be populated with
|
||||
// the certificates provided by users.
|
||||
// TODO(joyeecheung): maybe these rules need a bit of consolidation?
|
||||
X509_STORE* NewRootCertStore() {
|
||||
X509_STORE* store = X509_STORE_new();
|
||||
CHECK_NOT_NULL(store);
|
||||
|
||||
// If the root cert store is already reset by users through
|
||||
// tls.setDefaultCACertificates(), just create a copy from the
|
||||
// user-provided certificates.
|
||||
if (root_certs_from_users != nullptr) {
|
||||
for (X509* cert : *root_certs_from_users) {
|
||||
CHECK_EQ(1, X509_STORE_add_cert(store, cert));
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
#ifdef NODE_OPENSSL_SYSTEM_CERT_PATH
|
||||
if constexpr (sizeof(NODE_OPENSSL_SYSTEM_CERT_PATH) > 1) {
|
||||
ERR_set_mark();
|
||||
|
@ -903,14 +944,57 @@ void GetBundledRootCertificates(const FunctionCallbackInfo<Value>& args) {
|
|||
Array::New(env->isolate(), result, arraysize(root_certs)));
|
||||
}
|
||||
|
||||
bool ArrayOfStringsToX509s(Local<Context> context,
|
||||
Local<Array> cert_array,
|
||||
std::vector<X509*>* certs) {
|
||||
ClearErrorOnReturn clear_error_on_return;
|
||||
Isolate* isolate = context->GetIsolate();
|
||||
Environment* env = Environment::GetCurrent(context);
|
||||
uint32_t array_length = cert_array->Length();
|
||||
|
||||
std::vector<v8::Global<Value>> cert_items;
|
||||
if (FromV8Array(context, cert_array, &cert_items).IsNothing()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < array_length; i++) {
|
||||
Local<Value> cert_val = cert_items[i].Get(isolate);
|
||||
// Parse the PEM certificate.
|
||||
BIOPointer bio(LoadBIO(env, cert_val));
|
||||
if (!bio) {
|
||||
ThrowCryptoError(env, ERR_get_error(), "Failed to load certificate data");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read all certificates from this PEM string
|
||||
size_t start = certs->size();
|
||||
auto err = LoadCertsFromBIO(certs, std::move(bio));
|
||||
if (err != 0) {
|
||||
size_t end = certs->size();
|
||||
// Clean up any certificates we've already parsed upon failure.
|
||||
for (size_t j = start; j < end; ++j) {
|
||||
X509_free((*certs)[j]);
|
||||
}
|
||||
ThrowCryptoError(env, err, "Failed to parse certificate");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
MaybeLocal<Array> X509sToArrayOfStrings(Environment* env,
|
||||
const std::vector<X509*>& certs) {
|
||||
It first,
|
||||
It last,
|
||||
size_t size) {
|
||||
ClearErrorOnReturn clear_error_on_return;
|
||||
EscapableHandleScope scope(env->isolate());
|
||||
|
||||
LocalVector<Value> result(env->isolate(), certs.size());
|
||||
for (size_t i = 0; i < certs.size(); ++i) {
|
||||
X509View view(certs[i]);
|
||||
LocalVector<Value> result(env->isolate(), size);
|
||||
size_t i = 0;
|
||||
for (It cur = first; cur != last; ++cur, ++i) {
|
||||
X509View view(*cur);
|
||||
auto pem_bio = view.toPEM();
|
||||
if (!pem_bio) {
|
||||
ThrowCryptoError(env, ERR_get_error(), "X509 to PEM conversion");
|
||||
|
@ -935,10 +1019,87 @@ MaybeLocal<Array> X509sToArrayOfStrings(Environment* env,
|
|||
return scope.Escape(Array::New(env->isolate(), result.data(), result.size()));
|
||||
}
|
||||
|
||||
void GetUserRootCertificates(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
CHECK_NOT_NULL(root_certs_from_users);
|
||||
Local<Array> results;
|
||||
if (X509sToArrayOfStrings(env,
|
||||
root_certs_from_users->begin(),
|
||||
root_certs_from_users->end(),
|
||||
root_certs_from_users->size())
|
||||
.ToLocal(&results)) {
|
||||
args.GetReturnValue().Set(results);
|
||||
}
|
||||
}
|
||||
|
||||
void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) {
|
||||
Local<Context> context = args.GetIsolate()->GetCurrentContext();
|
||||
CHECK(args[0]->IsArray());
|
||||
Local<Array> cert_array = args[0].As<Array>();
|
||||
|
||||
if (cert_array->Length() == 0) {
|
||||
// If the array is empty, just clear the user certs and reset the store.
|
||||
if (root_cert_store != nullptr) {
|
||||
X509_STORE_free(root_cert_store);
|
||||
root_cert_store = nullptr;
|
||||
}
|
||||
|
||||
// Free any existing certificates in the old set.
|
||||
if (root_certs_from_users != nullptr) {
|
||||
for (X509* cert : *root_certs_from_users) {
|
||||
X509_free(cert);
|
||||
}
|
||||
}
|
||||
root_certs_from_users = std::make_unique<X509Set>();
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse certificates from the array
|
||||
std::unique_ptr<std::vector<X509*>> certs =
|
||||
std::make_unique<std::vector<X509*>>();
|
||||
if (!ArrayOfStringsToX509s(context, cert_array, certs.get())) {
|
||||
// Error already thrown by ArrayOfStringsToX509s
|
||||
return;
|
||||
}
|
||||
|
||||
if (certs->empty()) {
|
||||
Environment* env = Environment::GetCurrent(context);
|
||||
return THROW_ERR_CRYPTO_OPERATION_FAILED(
|
||||
env, "No valid certificates found in the provided array");
|
||||
}
|
||||
|
||||
auto new_set = std::make_unique<X509Set>();
|
||||
for (X509* cert : *certs) {
|
||||
auto [it, inserted] = new_set->insert(cert);
|
||||
if (!inserted) { // Free duplicate certificates from the vector.
|
||||
X509_free(cert);
|
||||
}
|
||||
}
|
||||
|
||||
// Free any existing certificates in the old set.
|
||||
if (root_certs_from_users != nullptr) {
|
||||
for (X509* cert : *root_certs_from_users) {
|
||||
X509_free(cert);
|
||||
}
|
||||
}
|
||||
std::swap(root_certs_from_users, new_set);
|
||||
|
||||
// Reset the global root cert store and create a new one with the
|
||||
// certificates.
|
||||
if (root_cert_store != nullptr) {
|
||||
X509_STORE_free(root_cert_store);
|
||||
}
|
||||
|
||||
// TODO(joyeecheung): we can probably just reset it to nullptr
|
||||
// and let the next call to NewRootCertStore() create a new one.
|
||||
root_cert_store = NewRootCertStore();
|
||||
}
|
||||
|
||||
void GetSystemCACertificates(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
Local<Array> results;
|
||||
if (X509sToArrayOfStrings(env, GetSystemStoreCACertificates())
|
||||
std::vector<X509*>& certs = GetSystemStoreCACertificates();
|
||||
if (X509sToArrayOfStrings(env, certs.begin(), certs.end(), certs.size())
|
||||
.ToLocal(&results)) {
|
||||
args.GetReturnValue().Set(results);
|
||||
}
|
||||
|
@ -950,7 +1111,9 @@ void GetExtraCACertificates(const FunctionCallbackInfo<Value>& args) {
|
|||
return args.GetReturnValue().Set(Array::New(env->isolate()));
|
||||
}
|
||||
Local<Array> results;
|
||||
if (X509sToArrayOfStrings(env, GetExtraCACertificates()).ToLocal(&results)) {
|
||||
std::vector<X509*>& certs = GetExtraCACertificates();
|
||||
if (X509sToArrayOfStrings(env, certs.begin(), certs.end(), certs.size())
|
||||
.ToLocal(&results)) {
|
||||
args.GetReturnValue().Set(results);
|
||||
}
|
||||
}
|
||||
|
@ -1046,6 +1209,9 @@ void SecureContext::Initialize(Environment* env, Local<Object> target) {
|
|||
context, target, "getSystemCACertificates", GetSystemCACertificates);
|
||||
SetMethodNoSideEffect(
|
||||
context, target, "getExtraCACertificates", GetExtraCACertificates);
|
||||
SetMethod(context, target, "resetRootCertStore", ResetRootCertStore);
|
||||
SetMethodNoSideEffect(
|
||||
context, target, "getUserRootCertificates", GetUserRootCertificates);
|
||||
}
|
||||
|
||||
void SecureContext::RegisterExternalReferences(
|
||||
|
@ -1088,6 +1254,8 @@ void SecureContext::RegisterExternalReferences(
|
|||
registry->Register(GetBundledRootCertificates);
|
||||
registry->Register(GetSystemCACertificates);
|
||||
registry->Register(GetExtraCACertificates);
|
||||
registry->Register(ResetRootCertStore);
|
||||
registry->Register(GetUserRootCertificates);
|
||||
}
|
||||
|
||||
SecureContext* SecureContext::Create(Environment* env) {
|
||||
|
|
|
@ -186,4 +186,36 @@ exports.assertIsCAArray = function assertIsCAArray(certs) {
|
|||
}
|
||||
};
|
||||
|
||||
function extractMetadata(cert) {
|
||||
const x509 = new crypto.X509Certificate(cert);
|
||||
return {
|
||||
serialNumber: x509.serialNumber,
|
||||
issuer: x509.issuer,
|
||||
subject: x509.subject,
|
||||
};
|
||||
}
|
||||
|
||||
// To compare two certificates, we can just compare serialNumber, issuer,
|
||||
// and subject like X509_comp(). We can't just compare two strings because
|
||||
// the line endings or order of the fields may differ after PEM serdes by
|
||||
// OpenSSL.
|
||||
exports.assertEqualCerts = function assertEqualCerts(a, b) {
|
||||
const setA = new Set(a.map(extractMetadata));
|
||||
const setB = new Set(b.map(extractMetadata));
|
||||
assert.deepStrictEqual(setA, setB);
|
||||
};
|
||||
|
||||
exports.includesCert = function includesCert(certs, cert) {
|
||||
const metadata = extractMetadata(cert);
|
||||
for (const c of certs) {
|
||||
const cMetadata = extractMetadata(c);
|
||||
if (cMetadata.serialNumber === metadata.serialNumber &&
|
||||
cMetadata.issuer === metadata.issuer &&
|
||||
cMetadata.subject === metadata.subject) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
exports.TestTLSSocket = TestTLSSocket;
|
||||
|
|
6
test/fixtures/es-modules/custom-condition/load.cjs
vendored
Normal file
6
test/fixtures/es-modules/custom-condition/load.cjs
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
exports.cjs = function(key) {
|
||||
return require(key);
|
||||
};
|
||||
exports.esm = function(key) {
|
||||
return import(key);
|
||||
};
|
50
test/fixtures/tls-extra-ca-override.js
vendored
Normal file
50
test/fixtures/tls-extra-ca-override.js
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
'use strict';
|
||||
|
||||
// Test script for overidding NODE_EXTRA_CA_CERTS with tls.setDefaultCACertificates().
|
||||
|
||||
const tls = require('tls');
|
||||
const assert = require('assert');
|
||||
const { assertEqualCerts, includesCert } = require('../common/tls');
|
||||
|
||||
// Assert that NODE_EXTRA_CA_CERTS is set
|
||||
assert(process.env.NODE_EXTRA_CA_CERTS, 'NODE_EXTRA_CA_CERTS environment variable should be set');
|
||||
|
||||
// Get initial state with extra CA
|
||||
const initialDefaults = tls.getCACertificates('default');
|
||||
const systemCerts = tls.getCACertificates('system');
|
||||
const bundledCerts = tls.getCACertificates('bundled');
|
||||
const extraCerts = tls.getCACertificates('extra');
|
||||
|
||||
// For this test to work the extra certs must not be in bundled certs
|
||||
assert.notStrictEqual(bundledCerts.length, 0);
|
||||
for (const cert of extraCerts) {
|
||||
assert(!includesCert(bundledCerts, cert));
|
||||
}
|
||||
|
||||
// Test setting it to initial defaults.
|
||||
tls.setDefaultCACertificates(initialDefaults);
|
||||
assertEqualCerts(tls.getCACertificates('default'), initialDefaults);
|
||||
assertEqualCerts(tls.getCACertificates('default'), initialDefaults);
|
||||
|
||||
// Test setting it to the bundled certificates.
|
||||
tls.setDefaultCACertificates(bundledCerts);
|
||||
assertEqualCerts(tls.getCACertificates('default'), bundledCerts);
|
||||
assertEqualCerts(tls.getCACertificates('default'), bundledCerts);
|
||||
|
||||
// Test setting it to just the extra certificates.
|
||||
tls.setDefaultCACertificates(extraCerts);
|
||||
assertEqualCerts(tls.getCACertificates('default'), extraCerts);
|
||||
assertEqualCerts(tls.getCACertificates('default'), extraCerts);
|
||||
|
||||
// Test setting it to an empty array.
|
||||
tls.setDefaultCACertificates([]);
|
||||
assert.deepStrictEqual(tls.getCACertificates('default'), []);
|
||||
|
||||
// Test bundled and extra certs are unaffected
|
||||
assertEqualCerts(tls.getCACertificates('bundled'), bundledCerts);
|
||||
assertEqualCerts(tls.getCACertificates('extra'), extraCerts);
|
||||
|
||||
if (systemCerts.length > 0) {
|
||||
// Test system certs are unaffected.
|
||||
assertEqualCerts(tls.getCACertificates('system'), systemCerts);
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// Flags: --no-use-system-ca
|
||||
|
||||
|
||||
// This tests appending certificates to existing defaults should work correctly
|
||||
// with fetch.
|
||||
|
||||
import * as common from '../common/index.mjs';
|
||||
import { once } from 'node:events';
|
||||
import * as fixtures from '../common/fixtures.mjs';
|
||||
import assert from 'node:assert';
|
||||
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const { includesCert } = await import('../common/tls.js');
|
||||
const { default: https } = await import('node:https');
|
||||
const { default: tls } = await import('node:tls');
|
||||
|
||||
const bundledCerts = tls.getCACertificates('bundled');
|
||||
const fixtureCert = fixtures.readKey('fake-startcom-root-cert.pem');
|
||||
if (includesCert(bundledCerts, fixtureCert)) {
|
||||
common.skip('fake-startcom-root-cert is already in bundled certificates, skipping test');
|
||||
}
|
||||
|
||||
// Test HTTPS connection fails with bundled CA, succeeds after adding custom CA
|
||||
const server = https.createServer({
|
||||
cert: fixtures.readKey('agent8-cert.pem'),
|
||||
key: fixtures.readKey('agent8-key.pem'),
|
||||
}, common.mustCall((req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end('hello world');
|
||||
}, 1));
|
||||
server.listen(0);
|
||||
await once(server, 'listening');
|
||||
const url = `https://localhost:${server.address().port}/hello-world`;
|
||||
|
||||
// First attempt should fail without custom CA.
|
||||
await assert.rejects(
|
||||
fetch(url),
|
||||
(err) => {
|
||||
assert.strictEqual(err.cause.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
|
||||
return true;
|
||||
},
|
||||
);
|
||||
|
||||
// Now enable custom CA certificate.
|
||||
tls.setDefaultCACertificates([fixtureCert]);
|
||||
|
||||
// Second attempt should succeed.
|
||||
const response = await fetch(url);
|
||||
assert.strictEqual(response.status, 200);
|
||||
const text = await response.text();
|
||||
assert.strictEqual(text, 'hello world');
|
||||
|
||||
server.close();
|
|
@ -0,0 +1,71 @@
|
|||
'use strict';
|
||||
|
||||
// This tests appending certificates to existing defaults should work correctly
|
||||
// with https.request().
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const https = require('https');
|
||||
const tls = require('tls');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const { includesCert } = require('../common/tls');
|
||||
|
||||
const bundledCerts = tls.getCACertificates('bundled');
|
||||
const fixtureCert = fixtures.readKey('fake-startcom-root-cert.pem');
|
||||
if (includesCert(bundledCerts, fixtureCert)) {
|
||||
common.skip('fake-startcom-root-cert is already in bundled certificates, skipping test');
|
||||
}
|
||||
|
||||
// Test HTTPS connection fails with bundled CA, succeeds after adding custom CA
|
||||
const server = https.createServer({
|
||||
cert: fixtures.readKey('agent8-cert.pem'),
|
||||
key: fixtures.readKey('agent8-key.pem'),
|
||||
}, (req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end('success');
|
||||
});
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const port = server.address().port;
|
||||
|
||||
// Set to bundled CA certificates - connection should fail
|
||||
tls.setDefaultCACertificates(bundledCerts);
|
||||
|
||||
const req1 = https.request({
|
||||
hostname: 'localhost',
|
||||
port: port,
|
||||
path: '/',
|
||||
method: 'GET'
|
||||
}, common.mustNotCall('Should not succeed with bundled CA only'));
|
||||
|
||||
req1.on('error', common.mustCall((err) => {
|
||||
console.log(err);
|
||||
// Should fail with certificate verification error
|
||||
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
|
||||
|
||||
// Now add the fake-startcom-root-cert to bundled certs - connection should succeed
|
||||
tls.setDefaultCACertificates([...bundledCerts, fixtureCert]);
|
||||
|
||||
const req2 = https.request({
|
||||
hostname: 'localhost',
|
||||
port: port,
|
||||
path: '/',
|
||||
method: 'GET'
|
||||
}, common.mustCall((res) => {
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
let data = '';
|
||||
res.on('data', (chunk) => data += chunk);
|
||||
res.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(data, 'success');
|
||||
server.close();
|
||||
}));
|
||||
}));
|
||||
|
||||
req2.on('error', common.mustNotCall('Should not error with correct CA added'));
|
||||
req2.end();
|
||||
}));
|
||||
|
||||
req1.end();
|
||||
}));
|
|
@ -0,0 +1,39 @@
|
|||
// Flags: --no-use-system-ca
|
||||
'use strict';
|
||||
|
||||
// This tests tls.setDefaultCACertificates() support ArrayBufferView.
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const tls = require('tls');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const { assertEqualCerts } = require('../common/tls');
|
||||
|
||||
const fixtureCert = fixtures.readKey('fake-startcom-root-cert.pem');
|
||||
|
||||
// Should accept Buffer.
|
||||
tls.setDefaultCACertificates([Buffer.from(fixtureCert)]);
|
||||
const result = tls.getCACertificates('default');
|
||||
assertEqualCerts(result, [fixtureCert]);
|
||||
|
||||
// Reset it to empty.
|
||||
tls.setDefaultCACertificates([]);
|
||||
assertEqualCerts(tls.getCACertificates('default'), []);
|
||||
|
||||
// Should accept Uint8Array.
|
||||
const encoder = new TextEncoder();
|
||||
const uint8Cert = encoder.encode(fixtureCert);
|
||||
tls.setDefaultCACertificates([uint8Cert]);
|
||||
const uint8Result = tls.getCACertificates('default');
|
||||
assertEqualCerts(uint8Result, [fixtureCert]);
|
||||
|
||||
// Reset it to empty.
|
||||
tls.setDefaultCACertificates([]);
|
||||
assertEqualCerts(tls.getCACertificates('default'), []);
|
||||
|
||||
// Should accept DataView.
|
||||
const dataViewCert = new DataView(uint8Cert.buffer, uint8Cert.byteOffset, uint8Cert.byteLength);
|
||||
tls.setDefaultCACertificates([dataViewCert]);
|
||||
const dataViewResult = tls.getCACertificates('default');
|
||||
assertEqualCerts(dataViewResult, [fixtureCert]);
|
58
test/parallel/test-tls-set-default-ca-certificates-basic.js
Normal file
58
test/parallel/test-tls-set-default-ca-certificates-basic.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
'use strict';
|
||||
|
||||
// This tests the basic functionality of tls.setDefaultCACertificates().
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const tls = require('tls');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const { assertEqualCerts } = require('../common/tls');
|
||||
|
||||
const originalBundled = tls.getCACertificates('bundled');
|
||||
const originalSystem = tls.getCACertificates('system');
|
||||
const fixtureCert = fixtures.readKey('fake-startcom-root-cert.pem');
|
||||
|
||||
function testSetCertificates(certs) {
|
||||
// Test setting it can be verified with tls.getCACertificates().
|
||||
tls.setDefaultCACertificates(certs);
|
||||
const result = tls.getCACertificates('default');
|
||||
assertEqualCerts(result, certs);
|
||||
|
||||
// Verify that other certificate types are unchanged
|
||||
const newBundled = tls.getCACertificates('bundled');
|
||||
const newSystem = tls.getCACertificates('system');
|
||||
assertEqualCerts(newBundled, originalBundled);
|
||||
assertEqualCerts(newSystem, originalSystem);
|
||||
|
||||
// Test implicit defaults.
|
||||
const implicitDefaults = tls.getCACertificates();
|
||||
assertEqualCerts(implicitDefaults, certs);
|
||||
|
||||
// Test cached results.
|
||||
const cachedResult = tls.getCACertificates('default');
|
||||
assertEqualCerts(cachedResult, certs);
|
||||
const cachedImplicitDefaults = tls.getCACertificates();
|
||||
assertEqualCerts(cachedImplicitDefaults, certs);
|
||||
}
|
||||
|
||||
// Test setting with fixture certificate.
|
||||
testSetCertificates([fixtureCert]);
|
||||
|
||||
// Test setting with empty array.
|
||||
testSetCertificates([]);
|
||||
|
||||
// Test setting with bundled certificates
|
||||
testSetCertificates(originalBundled);
|
||||
|
||||
// Test combining bundled and extra certificates.
|
||||
testSetCertificates([...originalBundled, fixtureCert]);
|
||||
|
||||
// Test setting with a subset of bundled certificates
|
||||
if (originalBundled.length >= 3) {
|
||||
testSetCertificates(originalBundled.slice(0, 3));
|
||||
}
|
||||
|
||||
// Test duplicate certificates
|
||||
tls.setDefaultCACertificates([fixtureCert, fixtureCert, fixtureCert]);
|
||||
assertEqualCerts(tls.getCACertificates('default'), [fixtureCert]);
|
41
test/parallel/test-tls-set-default-ca-certificates-error.js
Normal file
41
test/parallel/test-tls-set-default-ca-certificates-error.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
'use strict';
|
||||
|
||||
// This tests input validation of tls.setDefaultCACertificates().
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const fixtures = require('../common/fixtures');
|
||||
const assert = require('assert');
|
||||
const tls = require('tls');
|
||||
const { assertEqualCerts } = require('../common/tls');
|
||||
|
||||
const defaultCerts = tls.getCACertificates('default');
|
||||
const fixtureCert = fixtures.readKey('fake-startcom-root-cert.pem');
|
||||
|
||||
for (const invalid of [null, undefined, 'string', 42, {}, true]) {
|
||||
// Test input validation - should throw when not passed an array
|
||||
assert.throws(() => tls.setDefaultCACertificates(invalid), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /The "certs" argument must be an instance of Array/
|
||||
});
|
||||
// Verify that default certificates remain unchanged after error.
|
||||
assertEqualCerts(tls.getCACertificates('default'), defaultCerts);
|
||||
}
|
||||
|
||||
for (const invalid of [null, undefined, 42, {}, true]) {
|
||||
// Test input validation - should throw when passed an array with invalid elements
|
||||
assert.throws(() => tls.setDefaultCACertificates([invalid]), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /The "certs\[0\]" argument must be of type string or an instance of ArrayBufferView/
|
||||
});
|
||||
// Verify that default certificates remain unchanged after error.
|
||||
assertEqualCerts(tls.getCACertificates('default'), defaultCerts);
|
||||
|
||||
assert.throws(() => tls.setDefaultCACertificates([fixtureCert, invalid]), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /The "certs\[1\]" argument must be of type string or an instance of ArrayBufferView/
|
||||
});
|
||||
// Verify that default certificates remain unchanged after error.
|
||||
assertEqualCerts(tls.getCACertificates('default'), defaultCerts);
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
'use strict';
|
||||
|
||||
// This tests that tls.setDefaultCACertificates() properly overrides certificates
|
||||
// added through NODE_EXTRA_CA_CERTS environment variable.
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const fixtures = require('../common/fixtures');
|
||||
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
|
||||
|
||||
spawnSyncAndExitWithoutError(process.execPath, [
|
||||
fixtures.path('tls-extra-ca-override.js'),
|
||||
], {
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem')
|
||||
}
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
'use strict';
|
||||
|
||||
// This tests mixed input types for tls.setDefaultCACertificates().
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const tls = require('tls');
|
||||
const { assertEqualCerts } = require('../common/tls');
|
||||
|
||||
const bundledCerts = tls.getCACertificates('bundled');
|
||||
if (bundledCerts.length < 4) {
|
||||
common.skip('Not enough bundled CA certificates available');
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
// Test mixed array with string and Buffer.
|
||||
{
|
||||
tls.setDefaultCACertificates([bundledCerts[0], Buffer.from(bundledCerts[1], 'utf8')]);
|
||||
const result = tls.getCACertificates('default');
|
||||
assertEqualCerts(result, [bundledCerts[0], bundledCerts[1]]);
|
||||
}
|
||||
|
||||
// Test mixed array with string and Uint8Array.
|
||||
{
|
||||
tls.setDefaultCACertificates([bundledCerts[1], encoder.encode(bundledCerts[2])]);
|
||||
const result = tls.getCACertificates('default');
|
||||
assertEqualCerts(result, [bundledCerts[1], bundledCerts[2]]);
|
||||
}
|
||||
|
||||
// Test mixed array with string and DataView.
|
||||
{
|
||||
const uint8Cert = encoder.encode(bundledCerts[3]);
|
||||
const dataViewCert = new DataView(uint8Cert.buffer, uint8Cert.byteOffset, uint8Cert.byteLength);
|
||||
tls.setDefaultCACertificates([bundledCerts[1], dataViewCert]);
|
||||
const result = tls.getCACertificates('default');
|
||||
assertEqualCerts(result, [bundledCerts[1], bundledCerts[3]]);
|
||||
}
|
||||
|
||||
// Test mixed array with Buffer and Uint8Array.
|
||||
{
|
||||
tls.setDefaultCACertificates([Buffer.from(bundledCerts[0], 'utf8'), encoder.encode(bundledCerts[2])]);
|
||||
const result = tls.getCACertificates('default');
|
||||
assertEqualCerts(result, [bundledCerts[0], bundledCerts[2]]);
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
'use strict';
|
||||
|
||||
// This tests that per-connection ca option overrides bundled default CA certificates.
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const https = require('https');
|
||||
const tls = require('tls');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const { includesCert } = require('../common/tls');
|
||||
|
||||
const server = https.createServer({
|
||||
cert: fixtures.readKey('agent8-cert.pem'),
|
||||
key: fixtures.readKey('agent8-key.pem'),
|
||||
}, common.mustCall((req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end('override works');
|
||||
}, 1));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const port = server.address().port;
|
||||
const bundledCerts = tls.getCACertificates('bundled');
|
||||
const fakeStartcomCert = fixtures.readKey('fake-startcom-root-cert.pem');
|
||||
|
||||
// Set default CA to bundled certs (which don't include fake-startcom-root-cert)
|
||||
tls.setDefaultCACertificates(bundledCerts);
|
||||
|
||||
// Verify that fake-startcom-root-cert is not in default
|
||||
const defaultCerts = tls.getCACertificates('default');
|
||||
assert(!includesCert(defaultCerts, fakeStartcomCert));
|
||||
|
||||
// Connection with per-connection ca should succeed despite wrong default
|
||||
const req = https.request({
|
||||
hostname: 'localhost',
|
||||
port: port,
|
||||
path: '/',
|
||||
method: 'GET',
|
||||
ca: [fakeStartcomCert] // This should override the bundled defaults
|
||||
}, common.mustCall((res) => {
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
let data = '';
|
||||
res.on('data', (chunk) => data += chunk);
|
||||
res.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(data, 'override works');
|
||||
server.close();
|
||||
}));
|
||||
}));
|
||||
|
||||
req.on('error', common.mustNotCall('Should not error with per-connection ca option'));
|
||||
req.end();
|
||||
}));
|
|
@ -0,0 +1,51 @@
|
|||
'use strict';
|
||||
|
||||
// This tests that per-connection ca option overrides empty default CA certificates
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const https = require('https');
|
||||
const tls = require('tls');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const server = https.createServer({
|
||||
cert: fixtures.readKey('agent8-cert.pem'),
|
||||
key: fixtures.readKey('agent8-key.pem'),
|
||||
}, common.mustCall((req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end('per-connection ca works');
|
||||
}, 1));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const port = server.address().port;
|
||||
const fakeStartcomCert = fixtures.readKey('fake-startcom-root-cert.pem');
|
||||
|
||||
// Set default CA to empty array - connections should normally fail
|
||||
tls.setDefaultCACertificates([]);
|
||||
|
||||
// Verify that default CA is empty
|
||||
const defaultCerts = tls.getCACertificates('default');
|
||||
assert.deepStrictEqual(defaultCerts, []);
|
||||
|
||||
// Connection with per-connection ca option should succeed despite empty default
|
||||
const req = https.request({
|
||||
hostname: 'localhost',
|
||||
port: port,
|
||||
path: '/',
|
||||
method: 'GET',
|
||||
ca: [fakeStartcomCert] // This should override the empty default
|
||||
}, common.mustCall((res) => {
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
let data = '';
|
||||
res.on('data', (chunk) => data += chunk);
|
||||
res.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(data, 'per-connection ca works');
|
||||
server.close();
|
||||
}));
|
||||
}));
|
||||
|
||||
req.on('error', common.mustNotCall('Should not error with per-connection ca option'));
|
||||
req.end();
|
||||
}));
|
|
@ -0,0 +1,43 @@
|
|||
'use strict';
|
||||
|
||||
// This tests error recovery and fallback behavior for tls.setDefaultCACertificates()
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const tls = require('tls');
|
||||
const fixtures = require('../common/fixtures');
|
||||
const { assertEqualCerts } = require('../common/tls');
|
||||
|
||||
const fixtureCert = fixtures.readKey('fake-startcom-root-cert.pem');
|
||||
|
||||
// Test recovery from errors when setting default CA certificates.
|
||||
function testRecovery(expectedCerts) {
|
||||
{
|
||||
const invalidCert = 'not a valid certificate';
|
||||
assert.throws(() => tls.setDefaultCACertificates([invalidCert]), {
|
||||
code: 'ERR_CRYPTO_OPERATION_FAILED',
|
||||
message: /No valid certificates found in the provided array/
|
||||
});
|
||||
assertEqualCerts(tls.getCACertificates('default'), expectedCerts);
|
||||
}
|
||||
|
||||
// Test with mixed valid and invalid certificate formats.
|
||||
{
|
||||
const invalidCert = '-----BEGIN CERTIFICATE-----\nvalid cert content\n-----END CERTIFICATE-----';
|
||||
assert.throws(() => tls.setDefaultCACertificates([fixtureCert, invalidCert]), {
|
||||
code: 'ERR_OSSL_PEM_ASN1_LIB',
|
||||
});
|
||||
assertEqualCerts(tls.getCACertificates('default'), expectedCerts);
|
||||
}
|
||||
}
|
||||
|
||||
const originalDefaultCerts = tls.getCACertificates('default');
|
||||
testRecovery(originalDefaultCerts);
|
||||
|
||||
// Check that recovery still works after replacing the default certificates.
|
||||
const subset = tls.getCACertificates('bundled').slice(0, 3);
|
||||
tls.setDefaultCACertificates(subset);
|
||||
assertEqualCerts(tls.getCACertificates('default'), subset);
|
||||
testRecovery(subset);
|
|
@ -0,0 +1,47 @@
|
|||
// Flags: --no-use-system-ca
|
||||
|
||||
|
||||
// This tests appending certificates to existing defaults should work correctly
|
||||
// with fetch.
|
||||
|
||||
import * as common from '../common/index.mjs';
|
||||
import { once } from 'node:events';
|
||||
import * as fixtures from '../common/fixtures.mjs';
|
||||
import assert from 'node:assert';
|
||||
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const { default: https } = await import('node:https');
|
||||
const { default: tls } = await import('node:tls');
|
||||
|
||||
// Test HTTPS connection fails with bundled CA, succeeds after adding custom CA.
|
||||
const server = https.createServer({
|
||||
cert: fixtures.readKey('agent8-cert.pem'),
|
||||
key: fixtures.readKey('agent8-key.pem'),
|
||||
}, common.mustCall((req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end('hello world');
|
||||
}, 1));
|
||||
server.listen(0);
|
||||
await once(server, 'listening');
|
||||
|
||||
const fixturesCert = fixtures.readKey('fake-startcom-root-cert.pem');
|
||||
tls.setDefaultCACertificates([fixturesCert]);
|
||||
// First, verify connection works with custom CA.
|
||||
const response1 = await fetch(`https://localhost:${server.address().port}/custom-ca-test`);
|
||||
assert.strictEqual(response1.status, 200);
|
||||
const text1 = await response1.text();
|
||||
assert.strictEqual(text1, 'hello world');
|
||||
|
||||
// Now set empty CA store - connection should fail.
|
||||
tls.setDefaultCACertificates([]);
|
||||
// Use IP address to skip session cache.
|
||||
await assert.rejects(
|
||||
fetch(`https://127.0.0.1:${server.address().port}/empty-ca-test`),
|
||||
(err) => {
|
||||
assert.strictEqual(err.cause.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
|
||||
return true;
|
||||
},
|
||||
);
|
||||
|
||||
server.close();
|
|
@ -0,0 +1,62 @@
|
|||
'use strict';
|
||||
|
||||
// This tests that tls.setDefaultCACertificates() affects actual HTTPS connections
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const assert = require('assert');
|
||||
const https = require('https');
|
||||
const tls = require('tls');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
// Test HTTPS connection succeeds with proper CA, fails after removing it
|
||||
const server = https.createServer({
|
||||
cert: fixtures.readKey('agent8-cert.pem'),
|
||||
key: fixtures.readKey('agent8-key.pem'),
|
||||
}, common.mustCall((req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end('hello world');
|
||||
}, 1));
|
||||
|
||||
server.listen(0, common.mustCall(() => {
|
||||
const port = server.address().port;
|
||||
|
||||
// First, set the correct CA certificate - connection should succeed.
|
||||
tls.setDefaultCACertificates([fixtures.readKey('fake-startcom-root-cert.pem')]);
|
||||
|
||||
const req1 = https.request({
|
||||
hostname: 'localhost',
|
||||
port: port,
|
||||
path: '/',
|
||||
method: 'GET'
|
||||
}, common.mustCall((res) => {
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
let data = '';
|
||||
res.on('data', (chunk) => data += chunk);
|
||||
res.on('end', common.mustCall(() => {
|
||||
assert.strictEqual(data, 'hello world');
|
||||
|
||||
// Now set empty CA store - connection should fail.
|
||||
tls.setDefaultCACertificates([]);
|
||||
|
||||
const req2 = https.request({
|
||||
hostname: '127.0.0.1', // Use a different hostname to skip session cache.
|
||||
port: port,
|
||||
path: '/',
|
||||
method: 'GET'
|
||||
}, common.mustNotCall('Should not succeed with empty CA'));
|
||||
|
||||
req2.on('error', common.mustCall((err) => {
|
||||
// Should fail with certificate verification error.
|
||||
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
|
||||
server.close();
|
||||
}));
|
||||
|
||||
req2.end();
|
||||
}));
|
||||
}));
|
||||
|
||||
req1.on('error', common.mustNotCall('Should not error with correct CA'));
|
||||
req1.end();
|
||||
}));
|
|
@ -0,0 +1,49 @@
|
|||
// Flags: --no-use-system-ca
|
||||
|
||||
// This tests that tls.setDefaultCACertificates() can be used to remove
|
||||
// system CA certificates from the default CA store.
|
||||
// To run this test, install the certificates as described in README.md
|
||||
|
||||
import * as common from '../common/index.mjs';
|
||||
import assert from 'node:assert/strict';
|
||||
import fixtures from '../common/fixtures.js';
|
||||
import { once } from 'events';
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
common.skip('requires crypto');
|
||||
}
|
||||
|
||||
const { default: https } = await import('node:https');
|
||||
const { default: tls } = await import('node:tls');
|
||||
|
||||
const server = https.createServer({
|
||||
cert: fixtures.readKey('agent8-cert.pem'),
|
||||
key: fixtures.readKey('agent8-key.pem'),
|
||||
}, common.mustCall((req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end('hello world');
|
||||
}, 1));
|
||||
server.listen(0);
|
||||
await once(server, 'listening');
|
||||
|
||||
const url = `https://localhost:${server.address().port}/hello-world`;
|
||||
|
||||
// First attempt should fail without system certificates.
|
||||
await assert.rejects(
|
||||
fetch(url),
|
||||
(err) => {
|
||||
assert.strictEqual(err.cause.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
|
||||
return true;
|
||||
},
|
||||
);
|
||||
|
||||
// Now enable system CA certificates
|
||||
tls.setDefaultCACertificates(tls.getCACertificates('system'));
|
||||
|
||||
// Second attempt should succeed.
|
||||
const response = await fetch(url);
|
||||
assert.strictEqual(response.status, 200);
|
||||
const text = await response.text();
|
||||
assert.strictEqual(text, 'hello world');
|
||||
|
||||
server.close();
|
|
@ -0,0 +1,87 @@
|
|||
// Flags: --use-system-ca
|
||||
|
||||
|
||||
// This tests that tls.setDefaultCACertificates() can be used to dynamically
|
||||
// enable system CA certificates for HTTPS connections.
|
||||
// To run this test, install the certificates as described in README.md
|
||||
|
||||
import * as common from '../common/index.mjs';
|
||||
import assert from 'node:assert/strict';
|
||||
import fixtures from '../common/fixtures.js';
|
||||
import { once } from 'events';
|
||||
import { includesCert, assertEqualCerts } from '../common/tls.js';
|
||||
|
||||
if (!common.hasCrypto) {
|
||||
common.skip('requires crypto');
|
||||
}
|
||||
|
||||
const { default: https } = await import('node:https');
|
||||
const { default: tls } = await import('node:tls');
|
||||
|
||||
// Verify that system CA includes the fake-startcom-root-cert.
|
||||
const systemCerts = tls.getCACertificates('system');
|
||||
const fixturesCert = fixtures.readKey('fake-startcom-root-cert.pem');
|
||||
if (!includesCert(systemCerts, fixturesCert)) {
|
||||
common.skip('fake-startcom-root-cert.pem not found in system CA store. ' +
|
||||
'Please follow setup instructions in test/system-ca/README.md');
|
||||
}
|
||||
const bundledCerts = tls.getCACertificates('bundled');
|
||||
if (includesCert(bundledCerts, fixturesCert)) {
|
||||
common.skip('fake-startcom-root-cert.pem should not be in bundled CA store');
|
||||
}
|
||||
|
||||
const server = https.createServer({
|
||||
cert: fixtures.readKey('agent8-cert.pem'),
|
||||
key: fixtures.readKey('agent8-key.pem'),
|
||||
}, common.mustCall((req, res) => {
|
||||
const path = req.url;
|
||||
switch (path) {
|
||||
case '/system-ca-test':
|
||||
res.writeHead(200);
|
||||
res.end('system ca works');
|
||||
break;
|
||||
case '/bundled-ca-test':
|
||||
res.writeHead(200);
|
||||
res.end('bundled ca works');
|
||||
break;
|
||||
default:
|
||||
assert(false, `Unexpected path: ${path}`);
|
||||
}
|
||||
}, 1));
|
||||
|
||||
|
||||
server.listen(0);
|
||||
await once(server, 'listening');
|
||||
|
||||
const url = `https://localhost:${server.address().port}`;
|
||||
|
||||
// First, verify connection works with system CA (including fake-startcom-root-cert)
|
||||
const response1 = await fetch(`${url}/system-ca-test`);
|
||||
assert.strictEqual(response1.status, 200);
|
||||
const text1 = await response1.text();
|
||||
assert.strictEqual(text1, 'system ca works');
|
||||
|
||||
// Now override with bundled certs (which do not include fake-startcom-root-cert)
|
||||
tls.setDefaultCACertificates(bundledCerts);
|
||||
|
||||
// Connection should now fail because fake-startcom-root-cert is no longer in the CA store.
|
||||
// Use IP address to skip session cache.
|
||||
await assert.rejects(
|
||||
fetch(`https://127.0.0.1:${server.address().port}/bundled-ca-test`),
|
||||
(err) => {
|
||||
assert.strictEqual(err.cause.code, 'SELF_SIGNED_CERT_IN_CHAIN');
|
||||
return true;
|
||||
},
|
||||
);
|
||||
|
||||
// Verify that system CA type still returns original system certs
|
||||
const stillSystemCerts = tls.getCACertificates('system');
|
||||
assertEqualCerts(stillSystemCerts, systemCerts);
|
||||
assert(includesCert(stillSystemCerts, fixturesCert));
|
||||
|
||||
// Verify that default CA now returns bundled certs
|
||||
const currentDefaults = tls.getCACertificates('default');
|
||||
assertEqualCerts(currentDefaults, bundledCerts);
|
||||
assert(!includesCert(currentDefaults, fixturesCert));
|
||||
|
||||
server.close();
|
|
@ -0,0 +1,58 @@
|
|||
// Flags: --use-system-ca
|
||||
|
||||
// This tests various combinations of CA certificates with
|
||||
// tls.setDefaultCACertificates().
|
||||
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto) common.skip('missing crypto');
|
||||
|
||||
const tls = require('tls');
|
||||
const { assertEqualCerts } = require('../common/tls');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
const fixtureCert = fixtures.readKey('fake-startcom-root-cert.pem');
|
||||
const originalBundled = tls.getCACertificates('bundled');
|
||||
const originalSystem = tls.getCACertificates('system');
|
||||
|
||||
function testSetCertificates(certs) {
|
||||
// Test setting it can be verified with tls.getCACertificates().
|
||||
tls.setDefaultCACertificates(certs);
|
||||
const result = tls.getCACertificates('default');
|
||||
assertEqualCerts(result, certs);
|
||||
|
||||
// Verify that other certificate types are unchanged
|
||||
const newBundled = tls.getCACertificates('bundled');
|
||||
const newSystem = tls.getCACertificates('system');
|
||||
assertEqualCerts(newBundled, originalBundled);
|
||||
assertEqualCerts(newSystem, originalSystem);
|
||||
|
||||
// Test implicit defaults.
|
||||
const implicitDefaults = tls.getCACertificates();
|
||||
assertEqualCerts(implicitDefaults, certs);
|
||||
|
||||
// Test cached results.
|
||||
const cachedResult = tls.getCACertificates('default');
|
||||
assertEqualCerts(cachedResult, certs);
|
||||
const cachedImplicitDefaults = tls.getCACertificates();
|
||||
assertEqualCerts(cachedImplicitDefaults, certs);
|
||||
|
||||
// Test system CA certificates are not affected.
|
||||
const systemCerts = tls.getCACertificates('system');
|
||||
assertEqualCerts(systemCerts, originalSystem);
|
||||
}
|
||||
|
||||
// Test setting with fixture certificate.
|
||||
testSetCertificates([fixtureCert]);
|
||||
|
||||
// Test setting with empty array.
|
||||
testSetCertificates([]);
|
||||
|
||||
// Test setting with bundled certificates
|
||||
testSetCertificates(originalBundled);
|
||||
|
||||
// Test setting with a subset of bundled certificates
|
||||
if (originalBundled.length >= 3) {
|
||||
testSetCertificates(originalBundled.slice(0, 3));
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue