-
-
Notifications
You must be signed in to change notification settings - Fork 34.6k
Make --use-system-ca per-env rather than per-process #60678
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -101,11 +101,11 @@ static thread_local X509_STORE* root_cert_store = nullptr; | |
| // from this set. | ||
| static thread_local std::unique_ptr<X509Set> root_certs_from_users; | ||
|
|
||
| X509_STORE* GetOrCreateRootCertStore() { | ||
| X509_STORE* GetOrCreateRootCertStore(Environment* env) { | ||
| if (root_cert_store != nullptr) { | ||
| return root_cert_store; | ||
| } | ||
| root_cert_store = NewRootCertStore(); | ||
| root_cert_store = NewRootCertStore(env); | ||
| return root_cert_store; | ||
| } | ||
|
|
||
|
|
@@ -870,23 +870,22 @@ static void LoadCACertificates(void* data) { | |
| "Started loading extra root certificates off-thread\n"); | ||
| GetExtraCACertificates(); | ||
| } | ||
| } | ||
|
|
||
| { | ||
| Mutex::ScopedLock cli_lock(node::per_process::cli_options_mutex); | ||
| if (!per_process::cli_options->use_system_ca) { | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| static void LoadSystemCACertificates(void* data) { | ||
| per_process::Debug(DebugCategory::CRYPTO, | ||
| "Started loading system root certificates off-thread\n"); | ||
| GetSystemStoreCACertificates(); | ||
| } | ||
|
|
||
| static std::atomic<bool> tried_cert_loading_off_thread = false; | ||
| static std::atomic<bool> cert_loading_thread_started = false; | ||
| static std::atomic<bool> tried_system_cert_loading_off_thread = false; | ||
| static std::atomic<bool> system_cert_loading_thread_started = false; | ||
| static Mutex start_cert_loading_thread_mutex; | ||
| static Mutex start_system_cert_loading_thread_mutex; | ||
| static uv_thread_t cert_loading_thread; | ||
| static uv_thread_t system_cert_loading_thread; | ||
|
|
||
| void StartLoadingCertificatesOffThread( | ||
| const FunctionCallbackInfo<Value>& args) { | ||
|
|
@@ -906,23 +905,45 @@ void StartLoadingCertificatesOffThread( | |
| } | ||
| } | ||
|
|
||
| Environment* env = Environment::GetCurrent(args); | ||
| const bool use_system_ca = env != nullptr && env->options()->use_system_ca; | ||
|
|
||
| // Only try to start the thread once. If it ever fails, we won't try again. | ||
| if (tried_cert_loading_off_thread.load()) { | ||
| return; | ||
| } | ||
| { | ||
| // Quick check, if it's already tried, no need to lock. | ||
| if (!tried_cert_loading_off_thread.load()) { | ||
joyeecheung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Mutex::ScopedLock lock(start_cert_loading_thread_mutex); | ||
| // Re-check under the lock. | ||
| if (tried_cert_loading_off_thread.load()) { | ||
| return; | ||
| // Check again under the lock. | ||
| if (!tried_cert_loading_off_thread.load()) { | ||
| tried_cert_loading_off_thread.store(true); | ||
| int r = | ||
| uv_thread_create(&cert_loading_thread, LoadCACertificates, nullptr); | ||
| cert_loading_thread_started.store(r == 0); | ||
| if (r != 0) { | ||
| FPrintF(stderr, | ||
| "Warning: Failed to load CA certificates off thread: %s\n", | ||
| uv_strerror(r)); | ||
| } | ||
| } | ||
| tried_cert_loading_off_thread.store(true); | ||
| int r = uv_thread_create(&cert_loading_thread, LoadCACertificates, nullptr); | ||
| cert_loading_thread_started.store(r == 0); | ||
| if (r != 0) { | ||
| FPrintF(stderr, | ||
| "Warning: Failed to load CA certificates off thread: %s\n", | ||
| uv_strerror(r)); | ||
| } | ||
|
|
||
| // If the system CA list hasn't been loaded off-thread yet, allow a worker | ||
| // enabling --use-system-ca to trigger its off-thread loading. | ||
| // Quick check, if it's already tried, no need to lock. | ||
| if (use_system_ca && !has_cached_system_root_certs.load() && | ||
| !tried_system_cert_loading_off_thread.load()) { | ||
| Mutex::ScopedLock lock(start_system_cert_loading_thread_mutex); | ||
| if (!has_cached_system_root_certs.load() && | ||
| !tried_system_cert_loading_off_thread.load()) { | ||
| tried_system_cert_loading_off_thread.store(true); | ||
| int r = uv_thread_create( | ||
| &system_cert_loading_thread, LoadSystemCACertificates, nullptr); | ||
| system_cert_loading_thread_started.store(r == 0); | ||
| if (r != 0) { | ||
| FPrintF( | ||
| stderr, | ||
| "Warning: Failed to load system CA certificates off thread: %s\n", | ||
| uv_strerror(r)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -947,13 +968,13 @@ void StartLoadingCertificatesOffThread( | |
| // with all the other flags. | ||
| // 7. Certificates from --use-bundled-ca, --use-system-ca and | ||
| // NODE_EXTRA_CA_CERTS are cached after first load. Certificates | ||
| // from --use-system-ca are not cached and always reloaded from | ||
| // from --use-openssl-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* NewRootCertStore(Environment* env) { | ||
| X509_STORE* store = X509_STORE_new(); | ||
| CHECK_NOT_NULL(store); | ||
|
|
||
|
|
@@ -975,14 +996,26 @@ X509_STORE* NewRootCertStore() { | |
| } | ||
| #endif | ||
|
|
||
| Mutex::ScopedLock cli_lock(node::per_process::cli_options_mutex); | ||
| if (per_process::cli_options->ssl_openssl_cert_store) { | ||
| bool use_system_ca = false; | ||
| bool ssl_openssl_cert_store = false; | ||
| { | ||
| Mutex::ScopedLock cli_lock(node::per_process::cli_options_mutex); | ||
| ssl_openssl_cert_store = per_process::cli_options->ssl_openssl_cert_store; | ||
| if (env != nullptr) { | ||
| use_system_ca = env->options()->use_system_ca; | ||
| } else if (per_process::cli_options->per_isolate != nullptr && | ||
| per_process::cli_options->per_isolate->per_env != nullptr) { | ||
| use_system_ca = | ||
| per_process::cli_options->per_isolate->per_env->use_system_ca; | ||
| } | ||
| } | ||
| if (ssl_openssl_cert_store) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we also now need to add |
||
| CHECK_EQ(1, X509_STORE_set_default_paths(store)); | ||
| } else { | ||
| for (X509* cert : GetBundledRootCertificates()) { | ||
| CHECK_EQ(1, X509_STORE_add_cert(store, cert)); | ||
| } | ||
| if (per_process::cli_options->use_system_ca) { | ||
| if (use_system_ca) { | ||
| for (X509* cert : GetSystemStoreCACertificates()) { | ||
| CHECK_EQ(1, X509_STORE_add_cert(store, cert)); | ||
| } | ||
|
|
@@ -1016,11 +1049,20 @@ void CleanupCachedRootCertificates() { | |
| } | ||
| } | ||
|
|
||
| // Serialize with starter to avoid the race window. | ||
| Mutex::ScopedLock lock(start_cert_loading_thread_mutex); | ||
| if (tried_cert_loading_off_thread.load() && | ||
| cert_loading_thread_started.load()) { | ||
| uv_thread_join(&cert_loading_thread); | ||
| // Serialize with starters to avoid the race window. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we now need to move the thread joining code before the freeing code to avoid races. |
||
| { | ||
| Mutex::ScopedLock lock(start_cert_loading_thread_mutex); | ||
| if (tried_cert_loading_off_thread.load() && | ||
| cert_loading_thread_started.load()) { | ||
| uv_thread_join(&cert_loading_thread); | ||
| } | ||
| } | ||
| { | ||
| Mutex::ScopedLock lock(start_system_cert_loading_thread_mutex); | ||
| if (tried_system_cert_loading_off_thread.load() && | ||
| system_cert_loading_thread_started.load()) { | ||
| uv_thread_join(&system_cert_loading_thread); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1187,9 +1229,7 @@ void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) { | |
| 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(); | ||
joyeecheung marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| root_cert_store = nullptr; | ||
| } | ||
|
|
||
| void GetSystemCACertificates(const FunctionCallbackInfo<Value>& args) { | ||
|
|
@@ -1700,11 +1740,12 @@ void SecureContext::SetX509StoreFlag(unsigned long flags) { | |
| } | ||
|
|
||
| X509_STORE* SecureContext::GetCertStoreOwnedByThisSecureContext() { | ||
| Environment* env = this->env(); | ||
| if (own_cert_store_cache_ != nullptr) return own_cert_store_cache_; | ||
|
|
||
| X509_STORE* cert_store = SSL_CTX_get_cert_store(ctx_.get()); | ||
| if (cert_store == GetOrCreateRootCertStore()) { | ||
| cert_store = NewRootCertStore(); | ||
| if (cert_store == GetOrCreateRootCertStore(env)) { | ||
| cert_store = NewRootCertStore(env); | ||
| SSL_CTX_set_cert_store(ctx_.get(), cert_store); | ||
| } | ||
|
|
||
|
|
@@ -1777,7 +1818,8 @@ void SecureContext::AddCRL(const FunctionCallbackInfo<Value>& args) { | |
|
|
||
| void SecureContext::SetRootCerts() { | ||
| ClearErrorOnReturn clear_error_on_return; | ||
| auto store = GetOrCreateRootCertStore(); | ||
| Environment* env = this->env(); | ||
| auto store = GetOrCreateRootCertStore(env); | ||
|
|
||
| // Increment reference count so global store is not deleted along with CTX. | ||
| X509_STORE_up_ref(store); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1028,6 +1028,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { | |
| &EnvironmentOptions::trace_env_native_stack, | ||
| kAllowedInEnvvar); | ||
|
|
||
| AddOption("--use-system-ca", | ||
| "use system's CA store", | ||
| &EnvironmentOptions::use_system_ca, | ||
| kAllowedInEnvvar); | ||
|
|
||
| AddOption( | ||
| "--trace-require-module", | ||
| "Print access to require(esm). Options are 'all' (print all usage) and " | ||
|
|
@@ -1368,10 +1373,6 @@ PerProcessOptionsParser::PerProcessOptionsParser( | |
| , | ||
| &PerProcessOptions::use_openssl_ca, | ||
| kAllowedInEnvvar); | ||
| AddOption("--use-system-ca", | ||
| "use system's CA store", | ||
| &PerProcessOptions::use_system_ca, | ||
| kAllowedInEnvvar); | ||
| AddOption("--use-bundled-ca", | ||
| "use bundled CA store" | ||
| #if !defined(NODE_OPENSSL_CERT_STORE) | ||
|
|
@@ -2114,6 +2115,10 @@ void HandleEnvOptions(std::shared_ptr<EnvironmentOptions> env_options, | |
|
|
||
| env_options->use_env_proxy = opt_getter("NODE_USE_ENV_PROXY") == "1"; | ||
|
|
||
| #if HAVE_OPENSSL | ||
| env_options->use_system_ca = opt_getter("NODE_USE_SYSTEM_CA") == "1"; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a test for the per-worker env var NODE_USE_SYSTEM_CA? And that --no-use-system-ca overrides it? |
||
| #endif // HAVE_OPENSSL | ||
|
|
||
| if (env_options->redirect_warnings.empty()) | ||
| env_options->redirect_warnings = opt_getter("NODE_REDIRECT_WARNINGS"); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a bit of log here to check whether it's hit on the env == nullptr path.