Commit 71a657ac224 for nodejs
commit 71a657ac224ed1aceca64226d80488bfc74da97b
Author: Shelley Vohr <shelley.vohr@gmail.com>
Date: Sat Sep 26 10:50:59 2026 +0000
crypto: keep the root cert store per Environment
The root cert store and the certificates set through
tls.setDefaultCACertificates() were thread_local, with a cleanup hook on
whichever Environment used TLS first. When several Environments share a
thread, setting the default CA certificates in one of them replaced the
trusted CAs of the others. Keep both on the Environment, next to its
other OpenSSL state.
Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66411
Refs: https://github.com/nodejs/node/pull/66239
Reviewed-By: Anna Henningsen <anna@addaleax.net>
diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc
index 823d87e7f20..d314d15cc54 100644
--- a/src/crypto/crypto_context.cc
+++ b/src/crypto/crypto_context.cc
@@ -95,37 +95,27 @@ struct X509Less {
};
using X509Set = std::set<ncrypto::X509Pointer, X509Less>;
-// Per-thread root cert store. See NewRootCertStore() on what it contains.
-static thread_local DeleteFnPtr<X509_STORE, X509_STORE_free> root_cert_store;
-// 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;
-static thread_local bool has_cleanup_hook = false;
-
-static void CleanupRootCertStore(void*) {
- root_cert_store.reset();
- root_certs_from_users.reset();
- has_cleanup_hook = false;
-}
-
-static void EnsureRootCertStoreCleanupHook(Environment* env) {
- if (env == nullptr || has_cleanup_hook) {
- return;
- }
+struct RootCertStore {
+ // See NewRootCertStore() on what it contains.
+ DeleteFnPtr<X509_STORE, X509_STORE_free> store;
+ // Set by tls.setDefaultCACertificates(). Once set, NewRootCertStore()
+ // copies these certificates instead of loading the defaults.
+ std::unique_ptr<X509Set> certs_from_users;
+};
- env->AddCleanupHook(CleanupRootCertStore, nullptr);
- has_cleanup_hook = true;
+void FreeRootCertStore(RootCertStore* root_certs) {
+ delete root_certs;
+}
+
+static RootCertStore* GetRootCertStore(Environment* env) {
+ if (!env->root_cert_store) env->root_cert_store.reset(new RootCertStore());
+ return env->root_cert_store.get();
}
X509_STORE* GetOrCreateRootCertStore(Environment* env) {
- EnsureRootCertStoreCleanupHook(env);
- if (root_cert_store != nullptr) {
- return root_cert_store.get();
- }
- root_cert_store.reset(NewRootCertStore(env));
- return root_cert_store.get();
+ RootCertStore* root_certs = GetRootCertStore(env);
+ if (!root_certs->store) root_certs->store.reset(NewRootCertStore(env));
+ return root_certs->store.get();
}
// Takes a string or buffer and loads it into a BIO.
@@ -1062,8 +1052,10 @@ X509_STORE* NewRootCertStore(Environment* env) {
// 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 (const auto& cert : *root_certs_from_users) {
+ const X509Set* certs_from_users =
+ env != nullptr ? GetRootCertStore(env)->certs_from_users.get() : nullptr;
+ if (certs_from_users != nullptr) {
+ for (const auto& cert : *certs_from_users) {
CHECK_EQ(1, X509_STORE_add_cert(store, cert.get()));
}
return store;
@@ -1230,12 +1222,13 @@ MaybeLocal<Array> X509sToArrayOfStrings(Environment* env,
void GetUserRootCertificates(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
- CHECK_NOT_NULL(root_certs_from_users);
+ const auto& certs_from_users = GetRootCertStore(env)->certs_from_users;
+ CHECK(certs_from_users);
Local<Array> results;
if (X509sToArrayOfStrings(env,
- root_certs_from_users->begin(),
- root_certs_from_users->end(),
- root_certs_from_users->size())
+ certs_from_users->begin(),
+ certs_from_users->end(),
+ certs_from_users->size())
.ToLocal(&results)) {
args.GetReturnValue().Set(results);
}
@@ -1246,12 +1239,12 @@ void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsArray());
Local<Array> cert_array = args[0].As<Array>();
Environment* env = Environment::GetCurrent(context);
- EnsureRootCertStoreCleanupHook(env);
+ RootCertStore* root_certs = GetRootCertStore(env);
if (cert_array->Length() == 0) {
// If the array is empty, just clear the user certs and reset the store.
- root_cert_store.reset();
- root_certs_from_users = std::make_unique<X509Set>();
+ root_certs->store.reset();
+ root_certs->certs_from_users = std::make_unique<X509Set>();
return;
}
@@ -1263,7 +1256,6 @@ void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) {
}
if (certs->empty()) {
- Environment* env = Environment::GetCurrent(context);
return THROW_ERR_CRYPTO_OPERATION_FAILED(
env, "No valid certificates found in the provided array");
}
@@ -1275,11 +1267,11 @@ void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) {
// is not consumed by insert (element already exists).
}
- root_certs_from_users = std::move(new_set);
+ root_certs->certs_from_users = std::move(new_set);
- // Reset the global root cert store so it will be recreated with the
- // new certificates.
- root_cert_store.reset();
+ // Reset the root cert store so it will be recreated with the new
+ // certificates.
+ root_certs->store.reset();
}
void GetSystemCACertificates(const FunctionCallbackInfo<Value>& args) {
diff --git a/src/env.h b/src/env.h
index 63c52524695..0349add7544 100644
--- a/src/env.h
+++ b/src/env.h
@@ -76,6 +76,13 @@ class MacCache;
namespace node {
+#if HAVE_OPENSSL
+namespace crypto {
+struct RootCertStore;
+void FreeRootCertStore(RootCertStore* root_certs);
+} // namespace crypto
+#endif // HAVE_OPENSSL
+
namespace shadow_realm {
class ShadowRealm;
}
@@ -1219,6 +1226,7 @@ class Environment final : public MemoryRetainer {
std::unique_ptr<ncrypto::MacCache> provider_mac_cache;
std::vector<std::string> supported_mac_algorithms;
bool supported_mac_algorithms_initialized = false;
+ DeleteFnPtr<crypto::RootCertStore, crypto::FreeRootCertStore> root_cert_store;
#endif // HAVE_OPENSSL
v8::Global<v8::Module> temporary_required_module_facade_original;
diff --git a/test/cctest/test_environment_shared_isolate.cc b/test/cctest/test_environment_shared_isolate.cc
index e8787d45c10..be9638211bf 100644
--- a/test/cctest/test_environment_shared_isolate.cc
+++ b/test/cctest/test_environment_shared_isolate.cc
@@ -6,8 +6,12 @@
#include "cppgc/allocation.h"
#include "cppgc/garbage-collected.h"
+#include "env-inl.h"
#include "node_test_fixture.h"
#include "v8-cppgc.h"
+#if HAVE_OPENSSL
+#include "crypto/crypto_context.h"
+#endif
#include <string>
#include <vector>
@@ -446,6 +450,36 @@ TEST_P(SharedIsolateTest, FreeIsolateDataBeforeItsEnvironmentAsserts) {
FreeInstance(std::move(instance));
}
+#if HAVE_OPENSSL
+TEST_P(SharedIsolateTest, RootCertStoreIsPerEnvironment) {
+ const HandleScope handle_scope(isolate_);
+ std::unique_ptr<Instance> first =
+ CreateInstance(0, EnvironmentFlags::kNoCreateInspector);
+ std::unique_ptr<Instance> second =
+ CreateInstance(1, EnvironmentFlags::kNoCreateInspector);
+ auto store_size = [](Instance* instance) {
+ return sk_X509_OBJECT_num(X509_STORE_get0_objects(
+ node::crypto::GetOrCreateRootCertStore(instance->env)));
+ };
+ auto set_default_ca_count = [this](Instance* instance, int count) {
+ std::string source =
+ "const tls = process.getBuiltinModule('tls');"
+ "tls.setDefaultCACertificates(tls.rootCertificates.slice(0, " +
+ std::to_string(count) + "))";
+ Evaluate(instance, source.c_str());
+ };
+
+ set_default_ca_count(first.get(), 1);
+ set_default_ca_count(second.get(), 2);
+ EXPECT_EQ(store_size(first.get()), 1);
+ EXPECT_EQ(store_size(second.get()), 2);
+
+ FreeInstance(std::move(first));
+ EXPECT_EQ(store_size(second.get()), 2);
+ FreeInstance(std::move(second));
+}
+#endif // HAVE_OPENSSL
+
INSTANTIATE_TEST_SUITE_P(
EnvironmentTest,
SharedIsolateTest,