@@ -103,12 +103,39 @@ static thread_local X509_STORE* root_cert_store = nullptr;
103103// copy generated by NewRootCertStore() will then contain the certificates
104104// from this set.
105105static thread_local std::unique_ptr<X509Set> root_certs_from_users;
106+ static thread_local bool has_cleanup_hook = false ;
106107
107- X509_STORE * GetOrCreateRootCertStore () {
108+ static void CleanupRootCertStore (void *) {
109+ if (root_cert_store != nullptr ) {
110+ X509_STORE_free (root_cert_store);
111+ root_cert_store = nullptr ;
112+ }
113+
114+ if (root_certs_from_users != nullptr ) {
115+ for (X509 * cert : *root_certs_from_users) {
116+ X509_free (cert);
117+ }
118+ root_certs_from_users.reset ();
119+ }
120+
121+ has_cleanup_hook = false ;
122+ }
123+
124+ static void EnsureRootCertStoreCleanupHook (Environment* env) {
125+ if (env == nullptr || has_cleanup_hook) {
126+ return ;
127+ }
128+
129+ env->AddCleanupHook (CleanupRootCertStore, nullptr );
130+ has_cleanup_hook = true ;
131+ }
132+
133+ X509_STORE * GetOrCreateRootCertStore (Environment* env) {
134+ EnsureRootCertStoreCleanupHook (env);
108135 if (root_cert_store != nullptr ) {
109136 return root_cert_store;
110137 }
111- root_cert_store = NewRootCertStore ();
138+ root_cert_store = NewRootCertStore (env );
112139 return root_cert_store;
113140}
114141
@@ -932,23 +959,22 @@ static void LoadCACertificates(void* data) {
932959 " Started loading extra root certificates off-thread\n " );
933960 GetExtraCACertificates ();
934961 }
962+ }
935963
936- {
937- Mutex::ScopedLock cli_lock (node::per_process::cli_options_mutex);
938- if (!per_process::cli_options->use_system_ca ) {
939- return ;
940- }
941- }
942-
964+ static void LoadSystemCACertificates (void * data) {
943965 per_process::Debug (DebugCategory::CRYPTO ,
944966 " Started loading system root certificates off-thread\n " );
945967 GetSystemStoreCACertificates ();
946968}
947969
948970static std::atomic<bool > tried_cert_loading_off_thread = false ;
949971static std::atomic<bool > cert_loading_thread_started = false ;
972+ static std::atomic<bool > tried_system_cert_loading_off_thread = false ;
973+ static std::atomic<bool > system_cert_loading_thread_started = false ;
950974static Mutex start_cert_loading_thread_mutex;
975+ static Mutex start_system_cert_loading_thread_mutex;
951976static uv_thread_t cert_loading_thread;
977+ static uv_thread_t system_cert_loading_thread;
952978
953979void StartLoadingCertificatesOffThread (
954980 const FunctionCallbackInfo<Value>& args) {
@@ -968,23 +994,46 @@ void StartLoadingCertificatesOffThread(
968994 }
969995 }
970996
997+ Environment* env = Environment::GetCurrent (args);
998+ const bool use_system_ca = env != nullptr && env->options ()->use_system_ca ;
999+ per_process::Debug (
1000+ DebugCategory::CRYPTO , " StartLoadingCertificatesOffThread env=%p\n " , env);
9711001 // Only try to start the thread once. If it ever fails, we won't try again.
972- if (tried_cert_loading_off_thread.load ()) {
973- return ;
974- }
975- {
1002+ // Quick check, if it's already tried, no need to lock.
1003+ if (!tried_cert_loading_off_thread.load ()) {
9761004 Mutex::ScopedLock lock (start_cert_loading_thread_mutex);
977- // Re-check under the lock.
978- if (tried_cert_loading_off_thread.load ()) {
979- return ;
1005+ // Check again under the lock.
1006+ if (!tried_cert_loading_off_thread.load ()) {
1007+ tried_cert_loading_off_thread.store (true );
1008+ int r =
1009+ uv_thread_create (&cert_loading_thread, LoadCACertificates, nullptr );
1010+ cert_loading_thread_started.store (r == 0 );
1011+ if (r != 0 ) {
1012+ FPrintF (stderr,
1013+ " Warning: Failed to load CA certificates off thread: %s\n " ,
1014+ uv_strerror (r));
1015+ }
9801016 }
981- tried_cert_loading_off_thread.store (true );
982- int r = uv_thread_create (&cert_loading_thread, LoadCACertificates, nullptr );
983- cert_loading_thread_started.store (r == 0 );
984- if (r != 0 ) {
985- FPrintF (stderr,
986- " Warning: Failed to load CA certificates off thread: %s\n " ,
987- uv_strerror (r));
1017+ }
1018+
1019+ // If the system CA list hasn't been loaded off-thread yet, allow a worker
1020+ // enabling --use-system-ca to trigger its off-thread loading.
1021+ // Quick check, if it's already tried, no need to lock.
1022+ if (use_system_ca && !has_cached_system_root_certs.load () &&
1023+ !tried_system_cert_loading_off_thread.load ()) {
1024+ Mutex::ScopedLock lock (start_system_cert_loading_thread_mutex);
1025+ if (!has_cached_system_root_certs.load () &&
1026+ !tried_system_cert_loading_off_thread.load ()) {
1027+ tried_system_cert_loading_off_thread.store (true );
1028+ int r = uv_thread_create (
1029+ &system_cert_loading_thread, LoadSystemCACertificates, nullptr );
1030+ system_cert_loading_thread_started.store (r == 0 );
1031+ if (r != 0 ) {
1032+ FPrintF (
1033+ stderr,
1034+ " Warning: Failed to load system CA certificates off thread: %s\n " ,
1035+ uv_strerror (r));
1036+ }
9881037 }
9891038 }
9901039}
@@ -1009,13 +1058,13 @@ void StartLoadingCertificatesOffThread(
10091058// with all the other flags.
10101059// 7. Certificates from --use-bundled-ca, --use-system-ca and
10111060// NODE_EXTRA_CA_CERTS are cached after first load. Certificates
1012- // from --use-system -ca are not cached and always reloaded from
1061+ // from --use-openssl -ca are not cached and always reloaded from
10131062// disk.
10141063// 8. If users have reset the root cert store by calling
10151064// tls.setDefaultCACertificates(), the store will be populated with
10161065// the certificates provided by users.
10171066// TODO(joyeecheung): maybe these rules need a bit of consolidation?
1018- X509_STORE * NewRootCertStore () {
1067+ X509_STORE * NewRootCertStore (Environment* env ) {
10191068 X509_STORE * store = X509_STORE_new ();
10201069 CHECK_NOT_NULL (store);
10211070
@@ -1037,14 +1086,26 @@ X509_STORE* NewRootCertStore() {
10371086 }
10381087#endif
10391088
1040- Mutex::ScopedLock cli_lock (node::per_process::cli_options_mutex);
1041- if (per_process::cli_options->ssl_openssl_cert_store ) {
1089+ bool use_system_ca = false ;
1090+ bool ssl_openssl_cert_store = false ;
1091+ {
1092+ Mutex::ScopedLock cli_lock (node::per_process::cli_options_mutex);
1093+ ssl_openssl_cert_store = per_process::cli_options->ssl_openssl_cert_store ;
1094+ if (env != nullptr ) {
1095+ use_system_ca = env->options ()->use_system_ca ;
1096+ } else if (per_process::cli_options->per_isolate != nullptr &&
1097+ per_process::cli_options->per_isolate ->per_env != nullptr ) {
1098+ use_system_ca =
1099+ per_process::cli_options->per_isolate ->per_env ->use_system_ca ;
1100+ }
1101+ }
1102+ if (ssl_openssl_cert_store) {
10421103 CHECK_EQ (1 , X509_STORE_set_default_paths (store));
10431104 } else {
10441105 for (X509 * cert : GetBundledRootCertificates ()) {
10451106 CHECK_EQ (1 , X509_STORE_add_cert (store, cert));
10461107 }
1047- if (per_process::cli_options-> use_system_ca ) {
1108+ if (use_system_ca) {
10481109 for (X509 * cert : GetSystemStoreCACertificates ()) {
10491110 CHECK_EQ (1 , X509_STORE_add_cert (store, cert));
10501111 }
@@ -1061,6 +1122,22 @@ X509_STORE* NewRootCertStore() {
10611122}
10621123
10631124void CleanupCachedRootCertificates () {
1125+ // Serialize with starters to avoid the race window.
1126+ {
1127+ Mutex::ScopedLock lock (start_cert_loading_thread_mutex);
1128+ if (tried_cert_loading_off_thread.load () &&
1129+ cert_loading_thread_started.load ()) {
1130+ uv_thread_join (&cert_loading_thread);
1131+ }
1132+ }
1133+ {
1134+ Mutex::ScopedLock lock (start_system_cert_loading_thread_mutex);
1135+ if (tried_system_cert_loading_off_thread.load () &&
1136+ system_cert_loading_thread_started.load ()) {
1137+ uv_thread_join (&system_cert_loading_thread);
1138+ }
1139+ }
1140+
10641141 if (has_cached_bundled_root_certs.load ()) {
10651142 for (X509 * cert : GetBundledRootCertificates ()) {
10661143 X509_free (cert);
@@ -1077,13 +1154,6 @@ void CleanupCachedRootCertificates() {
10771154 X509_free (cert);
10781155 }
10791156 }
1080-
1081- // Serialize with starter to avoid the race window.
1082- Mutex::ScopedLock lock (start_cert_loading_thread_mutex);
1083- if (tried_cert_loading_off_thread.load () &&
1084- cert_loading_thread_started.load ()) {
1085- uv_thread_join (&cert_loading_thread);
1086- }
10871157}
10881158
10891159void GetBundledRootCertificates (const FunctionCallbackInfo<Value>& args) {
@@ -1195,6 +1265,8 @@ void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) {
11951265 Local<Context> context = args.GetIsolate ()->GetCurrentContext ();
11961266 CHECK (args[0 ]->IsArray ());
11971267 Local<Array> cert_array = args[0 ].As <Array>();
1268+ Environment* env = Environment::GetCurrent (context);
1269+ EnsureRootCertStoreCleanupHook (env);
11981270
11991271 if (cert_array->Length () == 0 ) {
12001272 // If the array is empty, just clear the user certs and reset the store.
@@ -1249,9 +1321,7 @@ void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) {
12491321 X509_STORE_free (root_cert_store);
12501322 }
12511323
1252- // TODO(joyeecheung): we can probably just reset it to nullptr
1253- // and let the next call to NewRootCertStore() create a new one.
1254- root_cert_store = NewRootCertStore ();
1324+ root_cert_store = nullptr ;
12551325}
12561326
12571327void GetSystemCACertificates (const FunctionCallbackInfo<Value>& args) {
@@ -1778,11 +1848,12 @@ void SecureContext::SetX509StoreFlag(unsigned long flags) {
17781848}
17791849
17801850X509_STORE * SecureContext::GetCertStoreOwnedByThisSecureContext () {
1851+ Environment* env = this ->env ();
17811852 if (own_cert_store_cache_ != nullptr ) return own_cert_store_cache_;
17821853
17831854 X509_STORE * cert_store = SSL_CTX_get_cert_store (ctx_.get ());
1784- if (cert_store == GetOrCreateRootCertStore ()) {
1785- cert_store = NewRootCertStore ();
1855+ if (cert_store == GetOrCreateRootCertStore (env )) {
1856+ cert_store = NewRootCertStore (env );
17861857 SSL_CTX_set_cert_store (ctx_.get (), cert_store);
17871858 }
17881859
@@ -1855,7 +1926,8 @@ void SecureContext::AddCRL(const FunctionCallbackInfo<Value>& args) {
18551926
18561927void SecureContext::SetRootCerts () {
18571928 ClearErrorOnReturn clear_error_on_return;
1858- auto store = GetOrCreateRootCertStore ();
1929+ Environment* env = this ->env ();
1930+ auto store = GetOrCreateRootCertStore (env);
18591931
18601932 // Increment reference count so global store is not deleted along with CTX.
18611933 X509_STORE_up_ref (store);
0 commit comments