| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -61,7 +61,7 @@ MaybeLocal<Value> GetValidationErrorReason(Environment* env, int err) { | |||
| 61 | 61 | (err == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE) || | |
| 62 | 62 | (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) || | |
| 63 | 63 | ((err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT) && | |
| 64 | - !per_process::cli_options->use_system_ca); | ||
| 64 | + !env->options()->use_system_ca); | ||
| 65 | 65 | ||
| 66 | 66 | if (suggest_system_ca) { | |
| 67 | 67 | reason.append("; if the root CA is installed locally, " | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -100,12 +100,39 @@ static thread_local X509_STORE* root_cert_store = nullptr; | |||
| 100 | 100 | // copy generated by NewRootCertStore() will then contain the certificates | |
| 101 | 101 | // from this set. | |
| 102 | 102 | static thread_local std::unique_ptr<X509Set> root_certs_from_users; | |
| 103 | + static thread_local bool has_cleanup_hook = false; | ||
| 103 | 104 | ||
| 104 | - X509_STORE* GetOrCreateRootCertStore() { | ||
| 105 | + static void CleanupRootCertStore(void*) { | ||
| 106 | + if (root_cert_store != nullptr) { | ||
| 107 | + X509_STORE_free(root_cert_store); | ||
| 108 | + root_cert_store = nullptr; | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + if (root_certs_from_users != nullptr) { | ||
| 112 | + for (X509* cert : *root_certs_from_users) { | ||
| 113 | + X509_free(cert); | ||
| 114 | + } | ||
| 115 | + root_certs_from_users.reset(); | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + has_cleanup_hook = false; | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + static void EnsureRootCertStoreCleanupHook(Environment* env) { | ||
| 122 | + if (env == nullptr || has_cleanup_hook) { | ||
| 123 | + return; | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + env->AddCleanupHook(CleanupRootCertStore, nullptr); | ||
| 127 | + has_cleanup_hook = true; | ||
| 128 | + } | ||
| 129 | + | ||
| 130 | + X509_STORE* GetOrCreateRootCertStore(Environment* env) { | ||
| 131 | + EnsureRootCertStoreCleanupHook(env); | ||
| 105 | 132 | if (root_cert_store != nullptr) { | |
| 106 | 133 | return root_cert_store; | |
| 107 | 134 | } | |
| 108 | - root_cert_store = NewRootCertStore(); | ||
| 135 | + root_cert_store = NewRootCertStore(env); | ||
| 109 | 136 | return root_cert_store; | |
| 110 | 137 | } | |
| 111 | 138 | ||
@@ -870,23 +897,22 @@ static void LoadCACertificates(void* data) { | |||
| 870 | 897 | "Started loading extra root certificates off-thread\n"); | |
| 871 | 898 | GetExtraCACertificates(); | |
| 872 | 899 | } | |
| 900 | + } | ||
| 873 | 901 | ||
| 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 | - | ||
| 902 | + static void LoadSystemCACertificates(void* data) { | ||
| 881 | 903 | per_process::Debug(DebugCategory::CRYPTO, | |
| 882 | 904 | "Started loading system root certificates off-thread\n"); | |
| 883 | 905 | GetSystemStoreCACertificates(); | |
| 884 | 906 | } | |
| 885 | 907 | ||
| 886 | 908 | static std::atomic<bool> tried_cert_loading_off_thread = false; | |
| 887 | 909 | static std::atomic<bool> cert_loading_thread_started = false; | |
| 910 | + static std::atomic<bool> tried_system_cert_loading_off_thread = false; | ||
| 911 | + static std::atomic<bool> system_cert_loading_thread_started = false; | ||
| 888 | 912 | static Mutex start_cert_loading_thread_mutex; | |
| 913 | + static Mutex start_system_cert_loading_thread_mutex; | ||
| 889 | 914 | static uv_thread_t cert_loading_thread; | |
| 915 | + static uv_thread_t system_cert_loading_thread; | ||
| 890 | 916 | ||
| 891 | 917 | void StartLoadingCertificatesOffThread( | |
| 892 | 918 | const FunctionCallbackInfo<Value>& args) { | |
@@ -906,23 +932,46 @@ void StartLoadingCertificatesOffThread( | |||
| 906 | 932 | } | |
| 907 | 933 | } | |
| 908 | 934 | ||
| 935 | + Environment* env = Environment::GetCurrent(args); | ||
| 936 | + const bool use_system_ca = env != nullptr && env->options()->use_system_ca; | ||
| 937 | + per_process::Debug( | ||
| 938 | + DebugCategory::CRYPTO, "StartLoadingCertificatesOffThread env=%p\n", env); | ||
| 909 | 939 | // 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 | - { | ||
| 940 | + // Quick check, if it's already tried, no need to lock. | ||
| 941 | + if (!tried_cert_loading_off_thread.load()) { | ||
| 914 | 942 | Mutex::ScopedLock lock(start_cert_loading_thread_mutex); | |
| 915 | - // Re-check under the lock. | ||
| 916 | - if (tried_cert_loading_off_thread.load()) { | ||
| 917 | - return; | ||
| 943 | + // Check again under the lock. | ||
| 944 | + if (!tried_cert_loading_off_thread.load()) { | ||
| 945 | + tried_cert_loading_off_thread.store(true); | ||
| 946 | + int r = | ||
| 947 | + uv_thread_create(&cert_loading_thread, LoadCACertificates, nullptr); | ||
| 948 | + cert_loading_thread_started.store(r == 0); | ||
| 949 | + if (r != 0) { | ||
| 950 | + FPrintF(stderr, | ||
| 951 | + "Warning: Failed to load CA certificates off thread: %s\n", | ||
| 952 | + uv_strerror(r)); | ||
| 953 | + } | ||
| 918 | 954 | } | |
| 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)); | ||
| 955 | + } | ||
| 956 | + | ||
| 957 | + // If the system CA list hasn't been loaded off-thread yet, allow a worker | ||
| 958 | + // enabling --use-system-ca to trigger its off-thread loading. | ||
| 959 | + // Quick check, if it's already tried, no need to lock. | ||
| 960 | + if (use_system_ca && !has_cached_system_root_certs.load() && | ||
| 961 | + !tried_system_cert_loading_off_thread.load()) { | ||
| 962 | + Mutex::ScopedLock lock(start_system_cert_loading_thread_mutex); | ||
| 963 | + if (!has_cached_system_root_certs.load() && | ||
| 964 | + !tried_system_cert_loading_off_thread.load()) { | ||
| 965 | + tried_system_cert_loading_off_thread.store(true); | ||
| 966 | + int r = uv_thread_create( | ||
| 967 | + &system_cert_loading_thread, LoadSystemCACertificates, nullptr); | ||
| 968 | + system_cert_loading_thread_started.store(r == 0); | ||
| 969 | + if (r != 0) { | ||
| 970 | + FPrintF( | ||
| 971 | + stderr, | ||
| 972 | + "Warning: Failed to load system CA certificates off thread: %s\n", | ||
| 973 | + uv_strerror(r)); | ||
| 974 | + } | ||
| 926 | 975 | } | |
| 927 | 976 | } | |
| 928 | 977 | } | |
@@ -947,13 +996,13 @@ void StartLoadingCertificatesOffThread( | |||
| 947 | 996 | // with all the other flags. | |
| 948 | 997 | // 7. Certificates from --use-bundled-ca, --use-system-ca and | |
| 949 | 998 | // NODE_EXTRA_CA_CERTS are cached after first load. Certificates | |
| 950 | - // from --use-system-ca are not cached and always reloaded from | ||
| 999 | + // from --use-openssl-ca are not cached and always reloaded from | ||
| 951 | 1000 | // disk. | |
| 952 | 1001 | // 8. If users have reset the root cert store by calling | |
| 953 | 1002 | // tls.setDefaultCACertificates(), the store will be populated with | |
| 954 | 1003 | // the certificates provided by users. | |
| 955 | 1004 | // TODO(joyeecheung): maybe these rules need a bit of consolidation? | |
| 956 | - X509_STORE* NewRootCertStore() { | ||
| 1005 | + X509_STORE* NewRootCertStore(Environment* env) { | ||
| 957 | 1006 | X509_STORE* store = X509_STORE_new(); | |
| 958 | 1007 | CHECK_NOT_NULL(store); | |
| 959 | 1008 | ||
@@ -975,14 +1024,26 @@ X509_STORE* NewRootCertStore() { | |||
| 975 | 1024 | } | |
| 976 | 1025 | #endif | |
| 977 | 1026 | ||
| 978 | - Mutex::ScopedLock cli_lock(node::per_process::cli_options_mutex); | ||
| 979 | - if (per_process::cli_options->ssl_openssl_cert_store) { | ||
| 1027 | + bool use_system_ca = false; | ||
| 1028 | + bool ssl_openssl_cert_store = false; | ||
| 1029 | + { | ||
| 1030 | + Mutex::ScopedLock cli_lock(node::per_process::cli_options_mutex); | ||
| 1031 | + ssl_openssl_cert_store = per_process::cli_options->ssl_openssl_cert_store; | ||
| 1032 | + if (env != nullptr) { | ||
| 1033 | + use_system_ca = env->options()->use_system_ca; | ||
| 1034 | + } else if (per_process::cli_options->per_isolate != nullptr && | ||
| 1035 | + per_process::cli_options->per_isolate->per_env != nullptr) { | ||
| 1036 | + use_system_ca = | ||
| 1037 | + per_process::cli_options->per_isolate->per_env->use_system_ca; | ||
| 1038 | + } | ||
| 1039 | + } | ||
| 1040 | + if (ssl_openssl_cert_store) { | ||
| 980 | 1041 | CHECK_EQ(1, X509_STORE_set_default_paths(store)); | |
| 981 | 1042 | } else { | |
| 982 | 1043 | for (X509* cert : GetBundledRootCertificates()) { | |
| 983 | 1044 | CHECK_EQ(1, X509_STORE_add_cert(store, cert)); | |
| 984 | 1045 | } | |
| 985 | - if (per_process::cli_options->use_system_ca) { | ||
| 1046 | + if (use_system_ca) { | ||
| 986 | 1047 | for (X509* cert : GetSystemStoreCACertificates()) { | |
| 987 | 1048 | CHECK_EQ(1, X509_STORE_add_cert(store, cert)); | |
| 988 | 1049 | } | |
@@ -999,6 +1060,22 @@ X509_STORE* NewRootCertStore() { | |||
| 999 | 1060 | } | |
| 1000 | 1061 | ||
| 1001 | 1062 | void CleanupCachedRootCertificates() { | |
| 1063 | + // Serialize with starters to avoid the race window. | ||
| 1064 | + { | ||
| 1065 | + Mutex::ScopedLock lock(start_cert_loading_thread_mutex); | ||
| 1066 | + if (tried_cert_loading_off_thread.load() && | ||
| 1067 | + cert_loading_thread_started.load()) { | ||
| 1068 | + uv_thread_join(&cert_loading_thread); | ||
| 1069 | + } | ||
| 1070 | + } | ||
| 1071 | + { | ||
| 1072 | + Mutex::ScopedLock lock(start_system_cert_loading_thread_mutex); | ||
| 1073 | + if (tried_system_cert_loading_off_thread.load() && | ||
| 1074 | + system_cert_loading_thread_started.load()) { | ||
| 1075 | + uv_thread_join(&system_cert_loading_thread); | ||
| 1076 | + } | ||
| 1077 | + } | ||
| 1078 | + | ||
| 1002 | 1079 | if (has_cached_bundled_root_certs.load()) { | |
| 1003 | 1080 | for (X509* cert : GetBundledRootCertificates()) { | |
| 1004 | 1081 | X509_free(cert); | |
@@ -1015,13 +1092,6 @@ void CleanupCachedRootCertificates() { | |||
| 1015 | 1092 | X509_free(cert); | |
| 1016 | 1093 | } | |
| 1017 | 1094 | } | |
| 1018 | - | ||
| 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); | ||
| 1024 | - } | ||
| 1025 | 1095 | } | |
| 1026 | 1096 | ||
| 1027 | 1097 | void GetBundledRootCertificates(const FunctionCallbackInfo<Value>& args) { | |
@@ -1133,6 +1203,8 @@ void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) { | |||
| 1133 | 1203 | Local<Context> context = args.GetIsolate()->GetCurrentContext(); | |
| 1134 | 1204 | CHECK(args[0]->IsArray()); | |
| 1135 | 1205 | Local<Array> cert_array = args[0].As<Array>(); | |
| 1206 | + Environment* env = Environment::GetCurrent(context); | ||
| 1207 | + EnsureRootCertStoreCleanupHook(env); | ||
| 1136 | 1208 | ||
| 1137 | 1209 | if (cert_array->Length() == 0) { | |
| 1138 | 1210 | // If the array is empty, just clear the user certs and reset the store. | |
@@ -1187,9 +1259,7 @@ void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) { | |||
| 1187 | 1259 | X509_STORE_free(root_cert_store); | |
| 1188 | 1260 | } | |
| 1189 | 1261 | ||
| 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(); | ||
| 1262 | + root_cert_store = nullptr; | ||
| 1193 | 1263 | } | |
| 1194 | 1264 | ||
| 1195 | 1265 | void GetSystemCACertificates(const FunctionCallbackInfo<Value>& args) { | |
@@ -1700,11 +1770,12 @@ void SecureContext::SetX509StoreFlag(unsigned long flags) { | |||
| 1700 | 1770 | } | |
| 1701 | 1771 | ||
| 1702 | 1772 | X509_STORE* SecureContext::GetCertStoreOwnedByThisSecureContext() { | |
| 1773 | + Environment* env = this->env(); | ||
| 1703 | 1774 | if (own_cert_store_cache_ != nullptr) return own_cert_store_cache_; | |
| 1704 | 1775 | ||
| 1705 | 1776 | X509_STORE* cert_store = SSL_CTX_get_cert_store(ctx_.get()); | |
| 1706 | - if (cert_store == GetOrCreateRootCertStore()) { | ||
| 1707 | - cert_store = NewRootCertStore(); | ||
| 1777 | + if (cert_store == GetOrCreateRootCertStore(env)) { | ||
| 1778 | + cert_store = NewRootCertStore(env); | ||
| 1708 | 1779 | SSL_CTX_set_cert_store(ctx_.get(), cert_store); | |
| 1709 | 1780 | } | |
| 1710 | 1781 | ||
@@ -1777,7 +1848,8 @@ void SecureContext::AddCRL(const FunctionCallbackInfo<Value>& args) { | |||
| 1777 | 1848 | ||
| 1778 | 1849 | void SecureContext::SetRootCerts() { | |
| 1779 | 1850 | ClearErrorOnReturn clear_error_on_return; | |
| 1780 | - auto store = GetOrCreateRootCertStore(); | ||
| 1851 | + Environment* env = this->env(); | ||
| 1852 | + auto store = GetOrCreateRootCertStore(env); | ||
| 1781 | 1853 | ||
| 1782 | 1854 | // Increment reference count so global store is not deleted along with CTX. | |
| 1783 | 1855 | X509_STORE_up_ref(store); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -19,9 +19,9 @@ constexpr int kMaxSupportedVersion = TLS1_3_VERSION; | |||
| 19 | 19 | void GetRootCertificates( | |
| 20 | 20 | const v8::FunctionCallbackInfo<v8::Value>& args); | |
| 21 | 21 | ||
| 22 | - X509_STORE* NewRootCertStore(); | ||
| 22 | + X509_STORE* NewRootCertStore(Environment* env); | ||
| 23 | 23 | ||
| 24 | - X509_STORE* GetOrCreateRootCertStore(); | ||
| 24 | + X509_STORE* GetOrCreateRootCertStore(Environment* env); | ||
| 25 | 25 | ||
| 26 | 26 | ncrypto::BIOPointer LoadBIO(Environment* env, v8::Local<v8::Value> v); | |
| 27 | 27 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -867,15 +867,6 @@ static ExitCode InitializeNodeWithArgsInternal( | |||
| 867 | 867 | // default value. | |
| 868 | 868 | V8::SetFlagsFromString("--rehash-snapshot"); | |
| 869 | 869 | ||
| 870 | - #if HAVE_OPENSSL | ||
| 871 | - // TODO(joyeecheung): make this a per-env option and move the normalization | ||
| 872 | - // into HandleEnvOptions. | ||
| 873 | - std::string use_system_ca; | ||
| 874 | - if (credentials::SafeGetenv("NODE_USE_SYSTEM_CA", &use_system_ca) && | ||
| 875 | - use_system_ca == "1") { | ||
| 876 | - per_process::cli_options->use_system_ca = true; | ||
| 877 | - } | ||
| 878 | - #endif // HAVE_OPENSSL | ||
| 879 | 870 | HandleEnvOptions(per_process::cli_options->per_isolate->per_env); | |
| 880 | 871 | ||
| 881 | 872 | std::string node_options; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -208,6 +208,13 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors, | |||
| 208 | 208 | "used, not both"); | |
| 209 | 209 | } | |
| 210 | 210 | ||
| 211 | + #if HAVE_OPENSSL | ||
| 212 | + if (use_system_ca && per_process::cli_options->use_openssl_ca) { | ||
| 213 | + errors->push_back("either --use-openssl-ca or --use-system-ca can be " | ||
| 214 | + "used, not both"); | ||
| 215 | + } | ||
| 216 | + #endif // HAVE_OPENSSL | ||
| 217 | + | ||
| 211 | 218 | if (heap_snapshot_near_heap_limit < 0) { | |
| 212 | 219 | errors->push_back("--heapsnapshot-near-heap-limit must not be negative"); | |
| 213 | 220 | } | |
@@ -1052,6 +1059,13 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { | |||
| 1052 | 1059 | &EnvironmentOptions::trace_env_native_stack, | |
| 1053 | 1060 | kAllowedInEnvvar); | |
| 1054 | 1061 | ||
| 1062 | + #if HAVE_OPENSSL | ||
| 1063 | + AddOption("--use-system-ca", | ||
| 1064 | + "use system's CA store", | ||
| 1065 | + &EnvironmentOptions::use_system_ca, | ||
| 1066 | + kAllowedInEnvvar); | ||
| 1067 | + #endif // HAVE_OPENSSL | ||
| 1068 | + | ||
| 1055 | 1069 | AddOption( | |
| 1056 | 1070 | "--trace-require-module", | |
| 1057 | 1071 | "Print access to require(esm). Options are 'all' (print all usage) and " | |
@@ -1392,10 +1406,6 @@ PerProcessOptionsParser::PerProcessOptionsParser( | |||
| 1392 | 1406 | , | |
| 1393 | 1407 | &PerProcessOptions::use_openssl_ca, | |
| 1394 | 1408 | kAllowedInEnvvar); | |
| 1395 | - AddOption("--use-system-ca", | ||
| 1396 | - "use system's CA store", | ||
| 1397 | - &PerProcessOptions::use_system_ca, | ||
| 1398 | - kAllowedInEnvvar); | ||
| 1399 | 1409 | AddOption("--use-bundled-ca", | |
| 1400 | 1410 | "use bundled CA store" | |
| 1401 | 1411 | #if !defined(NODE_OPENSSL_CERT_STORE) | |
@@ -2162,6 +2172,10 @@ void HandleEnvOptions(std::shared_ptr<EnvironmentOptions> env_options, | |||
| 2162 | 2172 | ||
| 2163 | 2173 | env_options->use_env_proxy = opt_getter("NODE_USE_ENV_PROXY") == "1"; | |
| 2164 | 2174 | ||
| 2175 | + #if HAVE_OPENSSL | ||
| 2176 | + env_options->use_system_ca = opt_getter("NODE_USE_SYSTEM_CA") == "1"; | ||
| 2177 | + #endif // HAVE_OPENSSL | ||
| 2178 | + | ||
| 2165 | 2179 | if (env_options->redirect_warnings.empty()) | |
| 2166 | 2180 | env_options->redirect_warnings = opt_getter("NODE_REDIRECT_WARNINGS"); | |
| 2167 | 2181 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -223,6 +223,7 @@ class EnvironmentOptions : public Options { | |||
| 223 | 223 | bool trace_env = false; | |
| 224 | 224 | bool trace_env_js_stack = false; | |
| 225 | 225 | bool trace_env_native_stack = false; | |
| 226 | + bool use_system_ca = false; | ||
| 226 | 227 | std::string trace_require_module; | |
| 227 | 228 | bool extra_info_on_fatal_exception = true; | |
| 228 | 229 | std::string unhandled_rejections; | |
@@ -360,7 +361,6 @@ class PerProcessOptions : public Options { | |||
| 360 | 361 | bool ssl_openssl_cert_store = false; | |
| 361 | 362 | #endif | |
| 362 | 363 | bool use_openssl_ca = false; | |
| 363 | - bool use_system_ca = false; | ||
| 364 | 364 | bool use_bundled_ca = false; | |
| 365 | 365 | bool enable_fips_crypto = false; | |
| 366 | 366 | bool force_fips_crypto = false; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -892,7 +892,7 @@ void Endpoint::Listen(const Session::Options& options) { | |||
| 892 | 892 | "not what you want."); | |
| 893 | 893 | } | |
| 894 | 894 | ||
| 895 | - auto context = TLSContext::CreateServer(options.tls_options); | ||
| 895 | + auto context = TLSContext::CreateServer(env(), options.tls_options); | ||
| 896 | 896 | if (!*context) { | |
| 897 | 897 | THROW_ERR_INVALID_STATE( | |
| 898 | 898 | env(), "Failed to create TLS context: %s", context->validation_error()); | |
@@ -925,7 +925,7 @@ BaseObjectPtr<Session> Endpoint::Connect( | |||
| 925 | 925 | config, | |
| 926 | 926 | session_ticket.has_value() ? "yes" : "no"); | |
| 927 | 927 | ||
| 928 | - auto tls_context = TLSContext::CreateClient(options.tls_options); | ||
| 928 | + auto tls_context = TLSContext::CreateClient(env(), options.tls_options); | ||
| 929 | 929 | if (!*tls_context) { | |
| 930 | 930 | THROW_ERR_INVALID_STATE(env(), | |
| 931 | 931 | "Failed to create TLS context: %s", | |
| Back | FazBrowse Home | New Git URL |
0 commit comments