@@ -103,12 +103,39 @@ static thread_local X509_STORE* root_cert_store = nullptr; |
103 | 103 | // copy generated by NewRootCertStore() will then contain the certificates |
104 | 104 | // from this set. |
105 | 105 | static thread_local std::unique_ptr<X509Set> root_certs_from_users; |
| 106 | +static thread_local bool has_cleanup_hook = false; |
106 | 107 |
|
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); |
108 | 135 | if (root_cert_store != nullptr) { |
109 | 136 | return root_cert_store; |
110 | 137 | } |
111 | | - root_cert_store = NewRootCertStore(); |
| 138 | + root_cert_store = NewRootCertStore(env); |
112 | 139 | return root_cert_store; |
113 | 140 | } |
114 | 141 |
|
@@ -932,23 +959,22 @@ static void LoadCACertificates(void* data) { |
932 | 959 | "Started loading extra root certificates off-thread\n"); |
933 | 960 | GetExtraCACertificates(); |
934 | 961 | } |
| 962 | +} |
935 | 963 |
|
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) { |
943 | 965 | per_process::Debug(DebugCategory::CRYPTO, |
944 | 966 | "Started loading system root certificates off-thread\n"); |
945 | 967 | GetSystemStoreCACertificates(); |
946 | 968 | } |
947 | 969 |
|
948 | 970 | static std::atomic<bool> tried_cert_loading_off_thread = false; |
949 | 971 | static 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; |
950 | 974 | static Mutex start_cert_loading_thread_mutex; |
| 975 | +static Mutex start_system_cert_loading_thread_mutex; |
951 | 976 | static uv_thread_t cert_loading_thread; |
| 977 | +static uv_thread_t system_cert_loading_thread; |
952 | 978 |
|
953 | 979 | void StartLoadingCertificatesOffThread( |
954 | 980 | const FunctionCallbackInfo<Value>& args) { |
@@ -968,23 +994,46 @@ void StartLoadingCertificatesOffThread( |
968 | 994 | } |
969 | 995 | } |
970 | 996 |
|
| 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); |
971 | 1001 | // 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()) { |
976 | 1004 | 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 | + } |
980 | 1016 | } |
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 | + } |
988 | 1037 | } |
989 | 1038 | } |
990 | 1039 | } |
@@ -1009,13 +1058,13 @@ void StartLoadingCertificatesOffThread( |
1009 | 1058 | // with all the other flags. |
1010 | 1059 | // 7. Certificates from --use-bundled-ca, --use-system-ca and |
1011 | 1060 | // 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 |
1013 | 1062 | // disk. |
1014 | 1063 | // 8. If users have reset the root cert store by calling |
1015 | 1064 | // tls.setDefaultCACertificates(), the store will be populated with |
1016 | 1065 | // the certificates provided by users. |
1017 | 1066 | // TODO(joyeecheung): maybe these rules need a bit of consolidation? |
1018 | | -X509_STORE* NewRootCertStore() { |
| 1067 | +X509_STORE* NewRootCertStore(Environment* env) { |
1019 | 1068 | X509_STORE* store = X509_STORE_new(); |
1020 | 1069 | CHECK_NOT_NULL(store); |
1021 | 1070 |
|
@@ -1037,14 +1086,26 @@ X509_STORE* NewRootCertStore() { |
1037 | 1086 | } |
1038 | 1087 | #endif |
1039 | 1088 |
|
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) { |
1042 | 1103 | CHECK_EQ(1, X509_STORE_set_default_paths(store)); |
1043 | 1104 | } else { |
1044 | 1105 | for (X509* cert : GetBundledRootCertificates()) { |
1045 | 1106 | CHECK_EQ(1, X509_STORE_add_cert(store, cert)); |
1046 | 1107 | } |
1047 | | - if (per_process::cli_options->use_system_ca) { |
| 1108 | + if (use_system_ca) { |
1048 | 1109 | for (X509* cert : GetSystemStoreCACertificates()) { |
1049 | 1110 | CHECK_EQ(1, X509_STORE_add_cert(store, cert)); |
1050 | 1111 | } |
@@ -1061,6 +1122,22 @@ X509_STORE* NewRootCertStore() { |
1061 | 1122 | } |
1062 | 1123 |
|
1063 | 1124 | void 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 | + |
1064 | 1141 | if (has_cached_bundled_root_certs.load()) { |
1065 | 1142 | for (X509* cert : GetBundledRootCertificates()) { |
1066 | 1143 | X509_free(cert); |
@@ -1077,13 +1154,6 @@ void CleanupCachedRootCertificates() { |
1077 | 1154 | X509_free(cert); |
1078 | 1155 | } |
1079 | 1156 | } |
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 | | - } |
1087 | 1157 | } |
1088 | 1158 |
|
1089 | 1159 | void GetBundledRootCertificates(const FunctionCallbackInfo<Value>& args) { |
@@ -1195,6 +1265,8 @@ void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) { |
1195 | 1265 | Local<Context> context = args.GetIsolate()->GetCurrentContext(); |
1196 | 1266 | CHECK(args[0]->IsArray()); |
1197 | 1267 | Local<Array> cert_array = args[0].As<Array>(); |
| 1268 | + Environment* env = Environment::GetCurrent(context); |
| 1269 | + EnsureRootCertStoreCleanupHook(env); |
1198 | 1270 |
|
1199 | 1271 | if (cert_array->Length() == 0) { |
1200 | 1272 | // If the array is empty, just clear the user certs and reset the store. |
@@ -1249,9 +1321,7 @@ void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) { |
1249 | 1321 | X509_STORE_free(root_cert_store); |
1250 | 1322 | } |
1251 | 1323 |
|
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; |
1255 | 1325 | } |
1256 | 1326 |
|
1257 | 1327 | void GetSystemCACertificates(const FunctionCallbackInfo<Value>& args) { |
@@ -1778,11 +1848,12 @@ void SecureContext::SetX509StoreFlag(unsigned long flags) { |
1778 | 1848 | } |
1779 | 1849 |
|
1780 | 1850 | X509_STORE* SecureContext::GetCertStoreOwnedByThisSecureContext() { |
| 1851 | + Environment* env = this->env(); |
1781 | 1852 | if (own_cert_store_cache_ != nullptr) return own_cert_store_cache_; |
1782 | 1853 |
|
1783 | 1854 | 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); |
1786 | 1857 | SSL_CTX_set_cert_store(ctx_.get(), cert_store); |
1787 | 1858 | } |
1788 | 1859 |
|
@@ -1855,7 +1926,8 @@ void SecureContext::AddCRL(const FunctionCallbackInfo<Value>& args) { |
1855 | 1926 |
|
1856 | 1927 | void SecureContext::SetRootCerts() { |
1857 | 1928 | ClearErrorOnReturn clear_error_on_return; |
1858 | | - auto store = GetOrCreateRootCertStore(); |
| 1929 | + Environment* env = this->env(); |
| 1930 | + auto store = GetOrCreateRootCertStore(env); |
1859 | 1931 |
|
1860 | 1932 | // Increment reference count so global store is not deleted along with CTX. |
1861 | 1933 | X509_STORE_up_ref(store); |
|