Skip to content

Commit 5d7dc6b

Browse files
committed
crypto: make --use-system-ca per-env rather than per-process
1 parent 37e4004 commit 5d7dc6b

16 files changed

+344
-75
lines changed

src/crypto/crypto_common.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ MaybeLocal<Value> GetValidationErrorReason(Environment* env, int err) {
6161
(err == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE) ||
6262
(err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) ||
6363
((err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT) &&
64-
!per_process::cli_options->use_system_ca);
64+
!env->options()->use_system_ca);
6565

6666
if (suggest_system_ca) {
6767
reason.append("; if the root CA is installed locally, "

src/crypto/crypto_context.cc

Lines changed: 81 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,11 @@ static thread_local X509_STORE* root_cert_store = nullptr;
101101
// from this set.
102102
static thread_local std::unique_ptr<X509Set> root_certs_from_users;
103103

104-
X509_STORE* GetOrCreateRootCertStore() {
104+
X509_STORE* GetOrCreateRootCertStore(Environment* env) {
105105
if (root_cert_store != nullptr) {
106106
return root_cert_store;
107107
}
108-
root_cert_store = NewRootCertStore();
108+
root_cert_store = NewRootCertStore(env);
109109
return root_cert_store;
110110
}
111111

@@ -870,23 +870,22 @@ static void LoadCACertificates(void* data) {
870870
"Started loading extra root certificates off-thread\n");
871871
GetExtraCACertificates();
872872
}
873+
}
873874

874-
{
875-
Mutex::ScopedLock cli_lock(node::per_process::cli_options_mutex);
876-
if (!per_process::cli_options->use_system_ca) {
877-
return;
878-
}
879-
}
880-
875+
static void LoadSystemCACertificates(void* data) {
881876
per_process::Debug(DebugCategory::CRYPTO,
882877
"Started loading system root certificates off-thread\n");
883878
GetSystemStoreCACertificates();
884879
}
885880

886881
static std::atomic<bool> tried_cert_loading_off_thread = false;
887882
static std::atomic<bool> cert_loading_thread_started = false;
883+
static std::atomic<bool> tried_system_cert_loading_off_thread = false;
884+
static std::atomic<bool> system_cert_loading_thread_started = false;
888885
static Mutex start_cert_loading_thread_mutex;
886+
static Mutex start_system_cert_loading_thread_mutex;
889887
static uv_thread_t cert_loading_thread;
888+
static uv_thread_t system_cert_loading_thread;
890889

891890
void StartLoadingCertificatesOffThread(
892891
const FunctionCallbackInfo<Value>& args) {
@@ -906,23 +905,45 @@ void StartLoadingCertificatesOffThread(
906905
}
907906
}
908907

908+
Environment* env = Environment::GetCurrent(args);
909+
const bool use_system_ca = env != nullptr && env->options()->use_system_ca;
910+
909911
// Only try to start the thread once. If it ever fails, we won't try again.
910-
if (tried_cert_loading_off_thread.load()) {
911-
return;
912-
}
913-
{
912+
// Quick check, if it's already tried, no need to lock.
913+
if (!tried_cert_loading_off_thread.load()) {
914914
Mutex::ScopedLock lock(start_cert_loading_thread_mutex);
915-
// Re-check under the lock.
916-
if (tried_cert_loading_off_thread.load()) {
917-
return;
915+
// Check again under the lock.
916+
if (!tried_cert_loading_off_thread.load()) {
917+
tried_cert_loading_off_thread.store(true);
918+
int r =
919+
uv_thread_create(&cert_loading_thread, LoadCACertificates, nullptr);
920+
cert_loading_thread_started.store(r == 0);
921+
if (r != 0) {
922+
FPrintF(stderr,
923+
"Warning: Failed to load CA certificates off thread: %s\n",
924+
uv_strerror(r));
925+
}
918926
}
919-
tried_cert_loading_off_thread.store(true);
920-
int r = uv_thread_create(&cert_loading_thread, LoadCACertificates, nullptr);
921-
cert_loading_thread_started.store(r == 0);
922-
if (r != 0) {
923-
FPrintF(stderr,
924-
"Warning: Failed to load CA certificates off thread: %s\n",
925-
uv_strerror(r));
927+
}
928+
929+
// If the system CA list hasn't been loaded off-thread yet, allow a worker
930+
// enabling --use-system-ca to trigger its off-thread loading.
931+
// Quick check, if it's already tried, no need to lock.
932+
if (use_system_ca && !has_cached_system_root_certs.load() &&
933+
!tried_system_cert_loading_off_thread.load()) {
934+
Mutex::ScopedLock lock(start_system_cert_loading_thread_mutex);
935+
if (!has_cached_system_root_certs.load() &&
936+
!tried_system_cert_loading_off_thread.load()) {
937+
tried_system_cert_loading_off_thread.store(true);
938+
int r = uv_thread_create(
939+
&system_cert_loading_thread, LoadSystemCACertificates, nullptr);
940+
system_cert_loading_thread_started.store(r == 0);
941+
if (r != 0) {
942+
FPrintF(
943+
stderr,
944+
"Warning: Failed to load system CA certificates off thread: %s\n",
945+
uv_strerror(r));
946+
}
926947
}
927948
}
928949
}
@@ -947,13 +968,13 @@ void StartLoadingCertificatesOffThread(
947968
// with all the other flags.
948969
// 7. Certificates from --use-bundled-ca, --use-system-ca and
949970
// NODE_EXTRA_CA_CERTS are cached after first load. Certificates
950-
// from --use-system-ca are not cached and always reloaded from
971+
// from --use-openssl-ca are not cached and always reloaded from
951972
// disk.
952973
// 8. If users have reset the root cert store by calling
953974
// tls.setDefaultCACertificates(), the store will be populated with
954975
// the certificates provided by users.
955976
// TODO(joyeecheung): maybe these rules need a bit of consolidation?
956-
X509_STORE* NewRootCertStore() {
977+
X509_STORE* NewRootCertStore(Environment* env) {
957978
X509_STORE* store = X509_STORE_new();
958979
CHECK_NOT_NULL(store);
959980

@@ -975,14 +996,26 @@ X509_STORE* NewRootCertStore() {
975996
}
976997
#endif
977998

978-
Mutex::ScopedLock cli_lock(node::per_process::cli_options_mutex);
979-
if (per_process::cli_options->ssl_openssl_cert_store) {
999+
bool use_system_ca = false;
1000+
bool ssl_openssl_cert_store = false;
1001+
{
1002+
Mutex::ScopedLock cli_lock(node::per_process::cli_options_mutex);
1003+
ssl_openssl_cert_store = per_process::cli_options->ssl_openssl_cert_store;
1004+
if (env != nullptr) {
1005+
use_system_ca = env->options()->use_system_ca;
1006+
} else if (per_process::cli_options->per_isolate != nullptr &&
1007+
per_process::cli_options->per_isolate->per_env != nullptr) {
1008+
use_system_ca =
1009+
per_process::cli_options->per_isolate->per_env->use_system_ca;
1010+
}
1011+
}
1012+
if (ssl_openssl_cert_store) {
9801013
CHECK_EQ(1, X509_STORE_set_default_paths(store));
9811014
} else {
9821015
for (X509* cert : GetBundledRootCertificates()) {
9831016
CHECK_EQ(1, X509_STORE_add_cert(store, cert));
9841017
}
985-
if (per_process::cli_options->use_system_ca) {
1018+
if (use_system_ca) {
9861019
for (X509* cert : GetSystemStoreCACertificates()) {
9871020
CHECK_EQ(1, X509_STORE_add_cert(store, cert));
9881021
}
@@ -1016,11 +1049,20 @@ void CleanupCachedRootCertificates() {
10161049
}
10171050
}
10181051

1019-
// Serialize with starter to avoid the race window.
1020-
Mutex::ScopedLock lock(start_cert_loading_thread_mutex);
1021-
if (tried_cert_loading_off_thread.load() &&
1022-
cert_loading_thread_started.load()) {
1023-
uv_thread_join(&cert_loading_thread);
1052+
// Serialize with starters to avoid the race window.
1053+
{
1054+
Mutex::ScopedLock lock(start_cert_loading_thread_mutex);
1055+
if (tried_cert_loading_off_thread.load() &&
1056+
cert_loading_thread_started.load()) {
1057+
uv_thread_join(&cert_loading_thread);
1058+
}
1059+
}
1060+
{
1061+
Mutex::ScopedLock lock(start_system_cert_loading_thread_mutex);
1062+
if (tried_system_cert_loading_off_thread.load() &&
1063+
system_cert_loading_thread_started.load()) {
1064+
uv_thread_join(&system_cert_loading_thread);
1065+
}
10241066
}
10251067
}
10261068

@@ -1187,9 +1229,7 @@ void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) {
11871229
X509_STORE_free(root_cert_store);
11881230
}
11891231

1190-
// TODO(joyeecheung): we can probably just reset it to nullptr
1191-
// and let the next call to NewRootCertStore() create a new one.
1192-
root_cert_store = NewRootCertStore();
1232+
root_cert_store = nullptr;
11931233
}
11941234

11951235
void GetSystemCACertificates(const FunctionCallbackInfo<Value>& args) {
@@ -1700,11 +1740,12 @@ void SecureContext::SetX509StoreFlag(unsigned long flags) {
17001740
}
17011741

17021742
X509_STORE* SecureContext::GetCertStoreOwnedByThisSecureContext() {
1743+
Environment* env = this->env();
17031744
if (own_cert_store_cache_ != nullptr) return own_cert_store_cache_;
17041745

17051746
X509_STORE* cert_store = SSL_CTX_get_cert_store(ctx_.get());
1706-
if (cert_store == GetOrCreateRootCertStore()) {
1707-
cert_store = NewRootCertStore();
1747+
if (cert_store == GetOrCreateRootCertStore(env)) {
1748+
cert_store = NewRootCertStore(env);
17081749
SSL_CTX_set_cert_store(ctx_.get(), cert_store);
17091750
}
17101751

@@ -1777,7 +1818,8 @@ void SecureContext::AddCRL(const FunctionCallbackInfo<Value>& args) {
17771818

17781819
void SecureContext::SetRootCerts() {
17791820
ClearErrorOnReturn clear_error_on_return;
1780-
auto store = GetOrCreateRootCertStore();
1821+
Environment* env = this->env();
1822+
auto store = GetOrCreateRootCertStore(env);
17811823

17821824
// Increment reference count so global store is not deleted along with CTX.
17831825
X509_STORE_up_ref(store);

src/crypto/crypto_context.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ constexpr int kMaxSupportedVersion = TLS1_3_VERSION;
1919
void GetRootCertificates(
2020
const v8::FunctionCallbackInfo<v8::Value>& args);
2121

22-
X509_STORE* NewRootCertStore();
22+
X509_STORE* NewRootCertStore(Environment* env);
2323

24-
X509_STORE* GetOrCreateRootCertStore();
24+
X509_STORE* GetOrCreateRootCertStore(Environment* env);
2525

2626
ncrypto::BIOPointer LoadBIO(Environment* env, v8::Local<v8::Value> v);
2727

src/node.cc

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -871,15 +871,6 @@ static ExitCode InitializeNodeWithArgsInternal(
871871
// default value.
872872
V8::SetFlagsFromString("--rehash-snapshot");
873873

874-
#if HAVE_OPENSSL
875-
// TODO(joyeecheung): make this a per-env option and move the normalization
876-
// into HandleEnvOptions.
877-
std::string use_system_ca;
878-
if (credentials::SafeGetenv("NODE_USE_SYSTEM_CA", &use_system_ca) &&
879-
use_system_ca == "1") {
880-
per_process::cli_options->use_system_ca = true;
881-
}
882-
#endif // HAVE_OPENSSL
883874
HandleEnvOptions(per_process::cli_options->per_isolate->per_env);
884875

885876
std::string node_options;

src/node_options.cc

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
10281028
&EnvironmentOptions::trace_env_native_stack,
10291029
kAllowedInEnvvar);
10301030

1031+
AddOption("--use-system-ca",
1032+
"use system's CA store",
1033+
&EnvironmentOptions::use_system_ca,
1034+
kAllowedInEnvvar);
1035+
10311036
AddOption(
10321037
"--trace-require-module",
10331038
"Print access to require(esm). Options are 'all' (print all usage) and "
@@ -1368,10 +1373,6 @@ PerProcessOptionsParser::PerProcessOptionsParser(
13681373
,
13691374
&PerProcessOptions::use_openssl_ca,
13701375
kAllowedInEnvvar);
1371-
AddOption("--use-system-ca",
1372-
"use system's CA store",
1373-
&PerProcessOptions::use_system_ca,
1374-
kAllowedInEnvvar);
13751376
AddOption("--use-bundled-ca",
13761377
"use bundled CA store"
13771378
#if !defined(NODE_OPENSSL_CERT_STORE)
@@ -2114,6 +2115,10 @@ void HandleEnvOptions(std::shared_ptr<EnvironmentOptions> env_options,
21142115

21152116
env_options->use_env_proxy = opt_getter("NODE_USE_ENV_PROXY") == "1";
21162117

2118+
#if HAVE_OPENSSL
2119+
env_options->use_system_ca = opt_getter("NODE_USE_SYSTEM_CA") == "1";
2120+
#endif // HAVE_OPENSSL
2121+
21172122
if (env_options->redirect_warnings.empty())
21182123
env_options->redirect_warnings = opt_getter("NODE_REDIRECT_WARNINGS");
21192124
}

src/node_options.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ class EnvironmentOptions : public Options {
222222
bool trace_env = false;
223223
bool trace_env_js_stack = false;
224224
bool trace_env_native_stack = false;
225+
bool use_system_ca = false;
225226
std::string trace_require_module;
226227
bool extra_info_on_fatal_exception = true;
227228
std::string unhandled_rejections;
@@ -359,7 +360,6 @@ class PerProcessOptions : public Options {
359360
bool ssl_openssl_cert_store = false;
360361
#endif
361362
bool use_openssl_ca = false;
362-
bool use_system_ca = false;
363363
bool use_bundled_ca = false;
364364
bool enable_fips_crypto = false;
365365
bool force_fips_crypto = false;

src/quic/endpoint.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,7 @@ void Endpoint::Listen(const Session::Options& options) {
892892
"not what you want.");
893893
}
894894

895-
auto context = TLSContext::CreateServer(options.tls_options);
895+
auto context = TLSContext::CreateServer(env(), options.tls_options);
896896
if (!*context) {
897897
THROW_ERR_INVALID_STATE(
898898
env(), "Failed to create TLS context: %s", context->validation_error());
@@ -925,7 +925,7 @@ BaseObjectPtr<Session> Endpoint::Connect(
925925
config,
926926
session_ticket.has_value() ? "yes" : "no");
927927

928-
auto tls_context = TLSContext::CreateClient(options.tls_options);
928+
auto tls_context = TLSContext::CreateClient(env(), options.tls_options);
929929
if (!*tls_context) {
930930
THROW_ERR_INVALID_STATE(env(),
931931
"Failed to create TLS context: %s",

src/quic/tlscontext.cc

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -281,16 +281,18 @@ bool OSSLContext::ConfigureClient() const {
281281

282282
// ============================================================================
283283

284-
std::shared_ptr<TLSContext> TLSContext::CreateClient(const Options& options) {
285-
return std::make_shared<TLSContext>(Side::CLIENT, options);
284+
std::shared_ptr<TLSContext> TLSContext::CreateClient(Environment* env,
285+
const Options& options) {
286+
return std::make_shared<TLSContext>(env, Side::CLIENT, options);
286287
}
287288

288-
std::shared_ptr<TLSContext> TLSContext::CreateServer(const Options& options) {
289-
return std::make_shared<TLSContext>(Side::SERVER, options);
289+
std::shared_ptr<TLSContext> TLSContext::CreateServer(Environment* env,
290+
const Options& options) {
291+
return std::make_shared<TLSContext>(env, Side::SERVER, options);
290292
}
291293

292-
TLSContext::TLSContext(Side side, const Options& options)
293-
: side_(side), options_(options), ctx_(Initialize()) {}
294+
TLSContext::TLSContext(Environment* env, Side side, const Options& options)
295+
: side_(side), options_(options), env_(env), ctx_(Initialize()) {}
294296

295297
TLSContext::operator SSL_CTX*() const {
296298
DCHECK(ctx_);
@@ -448,14 +450,14 @@ SSLCtxPointer TLSContext::Initialize() {
448450
{
449451
ClearErrorOnReturn clear_error_on_return;
450452
if (options_.ca.empty()) {
451-
auto store = crypto::GetOrCreateRootCertStore();
453+
auto store = crypto::GetOrCreateRootCertStore(env_);
452454
X509_STORE_up_ref(store);
453455
SSL_CTX_set_cert_store(ctx.get(), store);
454456
} else {
455457
for (const auto& ca : options_.ca) {
456458
uv_buf_t buf = ca;
457459
if (buf.len == 0) {
458-
auto store = crypto::GetOrCreateRootCertStore();
460+
auto store = crypto::GetOrCreateRootCertStore(env_);
459461
X509_STORE_up_ref(store);
460462
SSL_CTX_set_cert_store(ctx.get(), store);
461463
} else {
@@ -465,8 +467,8 @@ SSLCtxPointer TLSContext::Initialize() {
465467
while (
466468
auto x509 = X509Pointer(PEM_read_bio_X509_AUX(
467469
bio.get(), nullptr, crypto::NoPasswordCallback, nullptr))) {
468-
if (cert_store == crypto::GetOrCreateRootCertStore()) {
469-
cert_store = crypto::NewRootCertStore();
470+
if (cert_store == crypto::GetOrCreateRootCertStore(env_)) {
471+
cert_store = crypto::NewRootCertStore(env_);
470472
SSL_CTX_set_cert_store(ctx.get(), cert_store);
471473
}
472474
CHECK_EQ(1, X509_STORE_add_cert(cert_store, x509.get()));
@@ -523,8 +525,8 @@ SSLCtxPointer TLSContext::Initialize() {
523525
}
524526

525527
X509_STORE* cert_store = SSL_CTX_get_cert_store(ctx.get());
526-
if (cert_store == crypto::GetOrCreateRootCertStore()) {
527-
cert_store = crypto::NewRootCertStore();
528+
if (cert_store == crypto::GetOrCreateRootCertStore(env_)) {
529+
cert_store = crypto::NewRootCertStore(env_);
528530
SSL_CTX_set_cert_store(ctx.get(), cert_store);
529531
}
530532

0 commit comments

Comments
 (0)