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