Skip to content

Commit 71a657a

Browse files
codebyterenodejs-github-bot
authored andcommitted
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: #66411 Refs: #66239 Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent a2b4f55 commit 71a657a

3 files changed

Lines changed: 75 additions & 41 deletions

File tree

‎src/crypto/crypto_context.cc‎

Lines changed: 33 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -95,37 +95,27 @@ struct X509Less {
9595
};
9696
using X509Set = std::set<ncrypto::X509Pointer, X509Less>;
9797

98-
// Per-thread root cert store. See NewRootCertStore() on what it contains.
99-
static thread_local DeleteFnPtr<X509_STORE, X509_STORE_free> root_cert_store;
100-
// If the user calls tls.setDefaultCACertificates() this will be used
101-
// to hold the user-provided certificates, the root_cert_store and any new
102-
// copy generated by NewRootCertStore() will then contain the certificates
103-
// from this set.
104-
static thread_local std::unique_ptr<X509Set> root_certs_from_users;
105-
static thread_local bool has_cleanup_hook = false;
106-
107-
static void CleanupRootCertStore(void*) {
108-
root_cert_store.reset();
109-
root_certs_from_users.reset();
110-
has_cleanup_hook = false;
111-
}
112-
113-
static void EnsureRootCertStoreCleanupHook(Environment* env) {
114-
if (env == nullptr || has_cleanup_hook) {
115-
return;
116-
}
98+
struct RootCertStore {
99+
// See NewRootCertStore() on what it contains.
100+
DeleteFnPtr<X509_STORE, X509_STORE_free> store;
101+
// Set by tls.setDefaultCACertificates(). Once set, NewRootCertStore()
102+
// copies these certificates instead of loading the defaults.
103+
std::unique_ptr<X509Set> certs_from_users;
104+
};
117105

118-
env->AddCleanupHook(CleanupRootCertStore, nullptr);
119-
has_cleanup_hook = true;
106+
void FreeRootCertStore(RootCertStore* root_certs) {
107+
delete root_certs;
108+
}
109+
110+
static RootCertStore* GetRootCertStore(Environment* env) {
111+
if (!env->root_cert_store) env->root_cert_store.reset(new RootCertStore());
112+
return env->root_cert_store.get();
120113
}
121114

122115
X509_STORE* GetOrCreateRootCertStore(Environment* env) {
123-
EnsureRootCertStoreCleanupHook(env);
124-
if (root_cert_store != nullptr) {
125-
return root_cert_store.get();
126-
}
127-
root_cert_store.reset(NewRootCertStore(env));
128-
return root_cert_store.get();
116+
RootCertStore* root_certs = GetRootCertStore(env);
117+
if (!root_certs->store) root_certs->store.reset(NewRootCertStore(env));
118+
return root_certs->store.get();
129119
}
130120

131121
// Takes a string or buffer and loads it into a BIO.
@@ -1062,8 +1052,10 @@ X509_STORE* NewRootCertStore(Environment* env) {
10621052
// If the root cert store is already reset by users through
10631053
// tls.setDefaultCACertificates(), just create a copy from the
10641054
// user-provided certificates.
1065-
if (root_certs_from_users != nullptr) {
1066-
for (const auto& cert : *root_certs_from_users) {
1055+
const X509Set* certs_from_users =
1056+
env != nullptr ? GetRootCertStore(env)->certs_from_users.get() : nullptr;
1057+
if (certs_from_users != nullptr) {
1058+
for (const auto& cert : *certs_from_users) {
10671059
CHECK_EQ(1, X509_STORE_add_cert(store, cert.get()));
10681060
}
10691061
return store;
@@ -1230,12 +1222,13 @@ MaybeLocal<Array> X509sToArrayOfStrings(Environment* env,
12301222

12311223
void GetUserRootCertificates(const FunctionCallbackInfo<Value>& args) {
12321224
Environment* env = Environment::GetCurrent(args);
1233-
CHECK_NOT_NULL(root_certs_from_users);
1225+
const auto& certs_from_users = GetRootCertStore(env)->certs_from_users;
1226+
CHECK(certs_from_users);
12341227
Local<Array> results;
12351228
if (X509sToArrayOfStrings(env,
1236-
root_certs_from_users->begin(),
1237-
root_certs_from_users->end(),
1238-
root_certs_from_users->size())
1229+
certs_from_users->begin(),
1230+
certs_from_users->end(),
1231+
certs_from_users->size())
12391232
.ToLocal(&results)) {
12401233
args.GetReturnValue().Set(results);
12411234
}
@@ -1246,12 +1239,12 @@ void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) {
12461239
CHECK(args[0]->IsArray());
12471240
Local<Array> cert_array = args[0].As<Array>();
12481241
Environment* env = Environment::GetCurrent(context);
1249-
EnsureRootCertStoreCleanupHook(env);
1242+
RootCertStore* root_certs = GetRootCertStore(env);
12501243

12511244
if (cert_array->Length() == 0) {
12521245
// If the array is empty, just clear the user certs and reset the store.
1253-
root_cert_store.reset();
1254-
root_certs_from_users = std::make_unique<X509Set>();
1246+
root_certs->store.reset();
1247+
root_certs->certs_from_users = std::make_unique<X509Set>();
12551248
return;
12561249
}
12571250

@@ -1263,7 +1256,6 @@ void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) {
12631256
}
12641257

12651258
if (certs->empty()) {
1266-
Environment* env = Environment::GetCurrent(context);
12671259
return THROW_ERR_CRYPTO_OPERATION_FAILED(
12681260
env, "No valid certificates found in the provided array");
12691261
}
@@ -1275,11 +1267,11 @@ void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) {
12751267
// is not consumed by insert (element already exists).
12761268
}
12771269

1278-
root_certs_from_users = std::move(new_set);
1270+
root_certs->certs_from_users = std::move(new_set);
12791271

1280-
// Reset the global root cert store so it will be recreated with the
1281-
// new certificates.
1282-
root_cert_store.reset();
1272+
// Reset the root cert store so it will be recreated with the new
1273+
// certificates.
1274+
root_certs->store.reset();
12831275
}
12841276

12851277
void GetSystemCACertificates(const FunctionCallbackInfo<Value>& args) {

‎src/env.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ class MacCache;
7676

7777
namespace node {
7878

79+
#if HAVE_OPENSSL
80+
namespace crypto {
81+
struct RootCertStore;
82+
void FreeRootCertStore(RootCertStore* root_certs);
83+
} // namespace crypto
84+
#endif // HAVE_OPENSSL
85+
7986
namespace shadow_realm {
8087
class ShadowRealm;
8188
}
@@ -1219,6 +1226,7 @@ class Environment final : public MemoryRetainer {
12191226
std::unique_ptr<ncrypto::MacCache> provider_mac_cache;
12201227
std::vector<std::string> supported_mac_algorithms;
12211228
bool supported_mac_algorithms_initialized = false;
1229+
DeleteFnPtr<crypto::RootCertStore, crypto::FreeRootCertStore> root_cert_store;
12221230
#endif // HAVE_OPENSSL
12231231

12241232
v8::Global<v8::Module> temporary_required_module_facade_original;

‎test/cctest/test_environment_shared_isolate.cc‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66

77
#include "cppgc/allocation.h"
88
#include "cppgc/garbage-collected.h"
9+
#include "env-inl.h"
910
#include "node_test_fixture.h"
1011
#include "v8-cppgc.h"
12+
#if HAVE_OPENSSL
13+
#include "crypto/crypto_context.h"
14+
#endif
1115

1216
#include <string>
1317
#include <vector>
@@ -446,6 +450,36 @@ TEST_P(SharedIsolateTest, FreeIsolateDataBeforeItsEnvironmentAsserts) {
446450
FreeInstance(std::move(instance));
447451
}
448452

453+
#if HAVE_OPENSSL
454+
TEST_P(SharedIsolateTest, RootCertStoreIsPerEnvironment) {
455+
const HandleScope handle_scope(isolate_);
456+
std::unique_ptr<Instance> first =
457+
CreateInstance(0, EnvironmentFlags::kNoCreateInspector);
458+
std::unique_ptr<Instance> second =
459+
CreateInstance(1, EnvironmentFlags::kNoCreateInspector);
460+
auto store_size = [](Instance* instance) {
461+
return sk_X509_OBJECT_num(X509_STORE_get0_objects(
462+
node::crypto::GetOrCreateRootCertStore(instance->env)));
463+
};
464+
auto set_default_ca_count = [this](Instance* instance, int count) {
465+
std::string source =
466+
"const tls = process.getBuiltinModule('tls');"
467+
"tls.setDefaultCACertificates(tls.rootCertificates.slice(0, " +
468+
std::to_string(count) + "))";
469+
Evaluate(instance, source.c_str());
470+
};
471+
472+
set_default_ca_count(first.get(), 1);
473+
set_default_ca_count(second.get(), 2);
474+
EXPECT_EQ(store_size(first.get()), 1);
475+
EXPECT_EQ(store_size(second.get()), 2);
476+
477+
FreeInstance(std::move(first));
478+
EXPECT_EQ(store_size(second.get()), 2);
479+
FreeInstance(std::move(second));
480+
}
481+
#endif // HAVE_OPENSSL
482+
449483
INSTANTIATE_TEST_SUITE_P(
450484
EnvironmentTest,
451485
SharedIsolateTest,

0 commit comments

Comments
 (0)