2727#include <wincrypt.h>
2828#endif
2929
30+ #include <set>
31+
3032namespace node {
3133
3234using ncrypto::BignumPointer;
@@ -81,10 +83,28 @@ static std::atomic<bool> has_cached_bundled_root_certs{false};
8183static std::atomic<bool> has_cached_system_root_certs{false};
8284static std::atomic<bool> has_cached_extra_root_certs{false};
8385
86+ // Used for sets of X509.
87+ struct X509Less {
88+ bool operator()(const X509* lhs, const X509* rhs) const noexcept {
89+ return X509_cmp(const_cast<X509*>(lhs), const_cast<X509*>(rhs)) < 0;
90+ }
91+ };
92+ using X509Set = std::set<X509*, X509Less>;
93+
94+ // Per-thread root cert store. See NewRootCertStore() on what it contains.
95+ static thread_local X509_STORE* root_cert_store = nullptr;
96+ // If the user calls tls.setDefaultCACertificates() this will be used
97+ // to hold the user-provided certificates, the root_cert_store and any new
98+ // copy generated by NewRootCertStore() will then contain the certificates
99+ // from this set.
100+ static thread_local std::unique_ptr<X509Set> root_certs_from_users;
101+
84102X509_STORE* GetOrCreateRootCertStore() {
85- // Guaranteed thread-safe by standard, just don't use -fno-threadsafe-statics.
86- static X509_STORE* store = NewRootCertStore();
87- return store;
103+ if (root_cert_store != nullptr) {
104+ return root_cert_store;
105+ }
106+ root_cert_store = NewRootCertStore();
107+ return root_cert_store;
88108}
89109
90110// Takes a string or buffer and loads it into a BIO.
@@ -225,14 +245,11 @@ int SSL_CTX_use_certificate_chain(SSL_CTX* ctx,
225245 issuer);
226246}
227247
228- static unsigned long LoadCertsFromFile ( // NOLINT(runtime/int)
248+ static unsigned long LoadCertsFromBIO ( // NOLINT(runtime/int)
229249 std::vector<X509*>* certs,
230- const char* file ) {
250+ BIOPointer bio ) {
231251 MarkPopErrorOnReturn mark_pop_error_on_return;
232252
233- auto bio = BIOPointer::NewFile(file, "r");
234- if (!bio) return ERR_get_error();
235-
236253 while (X509* x509 = PEM_read_bio_X509(
237254 bio.get(), nullptr, NoPasswordCallback, nullptr)) {
238255 certs->push_back(x509);
@@ -248,6 +265,17 @@ static unsigned long LoadCertsFromFile( // NOLINT(runtime/int)
248265 }
249266}
250267
268+ static unsigned long LoadCertsFromFile( // NOLINT(runtime/int)
269+ std::vector<X509*>* certs,
270+ const char* file) {
271+ MarkPopErrorOnReturn mark_pop_error_on_return;
272+
273+ auto bio = BIOPointer::NewFile(file, "r");
274+ if (!bio) return ERR_get_error();
275+
276+ return LoadCertsFromBIO(certs, std::move(bio));
277+ }
278+
251279// Indicates the trust status of a certificate.
252280enum class TrustStatus {
253281 // Trust status is unknown / uninitialized.
@@ -829,11 +857,24 @@ static std::vector<X509*>& GetExtraCACertificates() {
829857// NODE_EXTRA_CA_CERTS are cached after first load. Certificates
830858// from --use-system-ca are not cached and always reloaded from
831859// disk.
860+ // 8. If users have reset the root cert store by calling
861+ // tls.setDefaultCACertificates(), the store will be populated with
862+ // the certificates provided by users.
832863// TODO(joyeecheung): maybe these rules need a bit of consolidation?
833864X509_STORE* NewRootCertStore() {
834865 X509_STORE* store = X509_STORE_new();
835866 CHECK_NOT_NULL(store);
836867
868+ // If the root cert store is already reset by users through
869+ // tls.setDefaultCACertificates(), just create a copy from the
870+ // user-provided certificates.
871+ if (root_certs_from_users != nullptr) {
872+ for (X509* cert : *root_certs_from_users) {
873+ CHECK_EQ(1, X509_STORE_add_cert(store, cert));
874+ }
875+ return store;
876+ }
877+
837878#ifdef NODE_OPENSSL_SYSTEM_CERT_PATH
838879 if constexpr (sizeof(NODE_OPENSSL_SYSTEM_CERT_PATH) > 1) {
839880 ERR_set_mark();
@@ -901,14 +942,57 @@ void GetBundledRootCertificates(const FunctionCallbackInfo<Value>& args) {
901942 Array::New(env->isolate(), result, arraysize(root_certs)));
902943}
903944
945+ bool ArrayOfStringsToX509s(Local<Context> context,
946+ Local<Array> cert_array,
947+ std::vector<X509*>* certs) {
948+ ClearErrorOnReturn clear_error_on_return;
949+ Isolate* isolate = context->GetIsolate();
950+ Environment* env = Environment::GetCurrent(context);
951+ uint32_t array_length = cert_array->Length();
952+
953+ std::vector<v8::Global<Value>> cert_items;
954+ if (FromV8Array(context, cert_array, &cert_items).IsNothing()) {
955+ return false;
956+ }
957+
958+ for (uint32_t i = 0; i < array_length; i++) {
959+ Local<Value> cert_val = cert_items[i].Get(isolate);
960+ // Parse the PEM certificate.
961+ BIOPointer bio(LoadBIO(env, cert_val));
962+ if (!bio) {
963+ ThrowCryptoError(env, ERR_get_error(), "Failed to load certificate data");
964+ return false;
965+ }
966+
967+ // Read all certificates from this PEM string
968+ size_t start = certs->size();
969+ auto err = LoadCertsFromBIO(certs, std::move(bio));
970+ if (err != 0) {
971+ size_t end = certs->size();
972+ // Clean up any certificates we've already parsed upon failure.
973+ for (size_t j = start; j < end; ++j) {
974+ X509_free((*certs)[j]);
975+ }
976+ ThrowCryptoError(env, err, "Failed to parse certificate");
977+ return false;
978+ }
979+ }
980+
981+ return true;
982+ }
983+
984+ template <typename It>
904985MaybeLocal<Array> X509sToArrayOfStrings(Environment* env,
905- const std::vector<X509*>& certs) {
986+ It first,
987+ It last,
988+ size_t size) {
906989 ClearErrorOnReturn clear_error_on_return;
907990 EscapableHandleScope scope(env->isolate());
908991
909- LocalVector<Value> result(env->isolate(), certs.size());
910- for (size_t i = 0; i < certs.size(); ++i) {
911- X509View view(certs[i]);
992+ LocalVector<Value> result(env->isolate(), size);
993+ size_t i = 0;
994+ for (It cur = first; cur != last; ++cur, ++i) {
995+ X509View view(*cur);
912996 auto pem_bio = view.toPEM();
913997 if (!pem_bio) {
914998 ThrowCryptoError(env, ERR_get_error(), "X509 to PEM conversion");
@@ -933,10 +1017,87 @@ MaybeLocal<Array> X509sToArrayOfStrings(Environment* env,
9331017 return scope.Escape(Array::New(env->isolate(), result.data(), result.size()));
9341018}
9351019
1020+ void GetUserRootCertificates(const FunctionCallbackInfo<Value>& args) {
1021+ Environment* env = Environment::GetCurrent(args);
1022+ CHECK_NOT_NULL(root_certs_from_users);
1023+ Local<Array> results;
1024+ if (X509sToArrayOfStrings(env,
1025+ root_certs_from_users->begin(),
1026+ root_certs_from_users->end(),
1027+ root_certs_from_users->size())
1028+ .ToLocal(&results)) {
1029+ args.GetReturnValue().Set(results);
1030+ }
1031+ }
1032+
1033+ void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) {
1034+ Local<Context> context = args.GetIsolate()->GetCurrentContext();
1035+ CHECK(args[0]->IsArray());
1036+ Local<Array> cert_array = args[0].As<Array>();
1037+
1038+ if (cert_array->Length() == 0) {
1039+ // If the array is empty, just clear the user certs and reset the store.
1040+ if (root_cert_store != nullptr) {
1041+ X509_STORE_free(root_cert_store);
1042+ root_cert_store = nullptr;
1043+ }
1044+
1045+ // Free any existing certificates in the old set.
1046+ if (root_certs_from_users != nullptr) {
1047+ for (X509* cert : *root_certs_from_users) {
1048+ X509_free(cert);
1049+ }
1050+ }
1051+ root_certs_from_users = std::make_unique<X509Set>();
1052+ return;
1053+ }
1054+
1055+ // Parse certificates from the array
1056+ std::unique_ptr<std::vector<X509*>> certs =
1057+ std::make_unique<std::vector<X509*>>();
1058+ if (!ArrayOfStringsToX509s(context, cert_array, certs.get())) {
1059+ // Error already thrown by ArrayOfStringsToX509s
1060+ return;
1061+ }
1062+
1063+ if (certs->empty()) {
1064+ Environment* env = Environment::GetCurrent(context);
1065+ return THROW_ERR_CRYPTO_OPERATION_FAILED(
1066+ env, "No valid certificates found in the provided array");
1067+ }
1068+
1069+ auto new_set = std::make_unique<X509Set>();
1070+ for (X509* cert : *certs) {
1071+ auto [it, inserted] = new_set->insert(cert);
1072+ if (!inserted) { // Free duplicate certificates from the vector.
1073+ X509_free(cert);
1074+ }
1075+ }
1076+
1077+ // Free any existing certificates in the old set.
1078+ if (root_certs_from_users != nullptr) {
1079+ for (X509* cert : *root_certs_from_users) {
1080+ X509_free(cert);
1081+ }
1082+ }
1083+ std::swap(root_certs_from_users, new_set);
1084+
1085+ // Reset the global root cert store and create a new one with the
1086+ // certificates.
1087+ if (root_cert_store != nullptr) {
1088+ X509_STORE_free(root_cert_store);
1089+ }
1090+
1091+ // TODO(joyeecheung): we can probably just reset it to nullptr
1092+ // and let the next call to NewRootCertStore() create a new one.
1093+ root_cert_store = NewRootCertStore();
1094+ }
1095+
9361096void GetSystemCACertificates(const FunctionCallbackInfo<Value>& args) {
9371097 Environment* env = Environment::GetCurrent(args);
9381098 Local<Array> results;
939- if (X509sToArrayOfStrings(env, GetSystemStoreCACertificates())
1099+ std::vector<X509*>& certs = GetSystemStoreCACertificates();
1100+ if (X509sToArrayOfStrings(env, certs.begin(), certs.end(), certs.size())
9401101 .ToLocal(&results)) {
9411102 args.GetReturnValue().Set(results);
9421103 }
@@ -948,7 +1109,9 @@ void GetExtraCACertificates(const FunctionCallbackInfo<Value>& args) {
9481109 return args.GetReturnValue().Set(Array::New(env->isolate()));
9491110 }
9501111 Local<Array> results;
951- if (X509sToArrayOfStrings(env, GetExtraCACertificates()).ToLocal(&results)) {
1112+ std::vector<X509*>& certs = GetExtraCACertificates();
1113+ if (X509sToArrayOfStrings(env, certs.begin(), certs.end(), certs.size())
1114+ .ToLocal(&results)) {
9521115 args.GetReturnValue().Set(results);
9531116 }
9541117}
@@ -1044,6 +1207,9 @@ void SecureContext::Initialize(Environment* env, Local<Object> target) {
10441207 context, target, "getSystemCACertificates", GetSystemCACertificates);
10451208 SetMethodNoSideEffect(
10461209 context, target, "getExtraCACertificates", GetExtraCACertificates);
1210+ SetMethod(context, target, "resetRootCertStore", ResetRootCertStore);
1211+ SetMethodNoSideEffect(
1212+ context, target, "getUserRootCertificates", GetUserRootCertificates);
10471213}
10481214
10491215void SecureContext::RegisterExternalReferences(
@@ -1086,6 +1252,8 @@ void SecureContext::RegisterExternalReferences(
10861252 registry->Register(GetBundledRootCertificates);
10871253 registry->Register(GetSystemCACertificates);
10881254 registry->Register(GetExtraCACertificates);
1255+ registry->Register(ResetRootCertStore);
1256+ registry->Register(GetUserRootCertificates);
10891257}
10901258
10911259SecureContext* SecureContext::Create(Environment* env) {
0 commit comments