crypto: add tls.setDefaultCACertificates() · nodejs/node@eeeb40e
@@ -27,6 +27,8 @@
2727#include <wincrypt.h>
2828#endif
292930+#include <set>
31+3032namespace node {
31333234using ncrypto::BignumPointer;
@@ -83,10 +85,28 @@ static std::atomic<bool> has_cached_bundled_root_certs{false};
8385static std::atomic<bool> has_cached_system_root_certs{false};
8486static std::atomic<bool> has_cached_extra_root_certs{false};
858788+// Used for sets of X509.
89+struct X509Less {
90+bool operator()(const X509* lhs, const X509* rhs) const noexcept {
91+return X509_cmp(const_cast<X509*>(lhs), const_cast<X509*>(rhs)) < 0;
92+ }
93+};
94+using X509Set = std::set<X509*, X509Less>;
95+96+// Per-thread root cert store. See NewRootCertStore() on what it contains.
97+static thread_local X509_STORE* root_cert_store = nullptr;
98+// If the user calls tls.setDefaultCACertificates() this will be used
99+// to hold the user-provided certificates, the root_cert_store and any new
100+// copy generated by NewRootCertStore() will then contain the certificates
101+// from this set.
102+static thread_local std::unique_ptr<X509Set> root_certs_from_users;
103+86104X509_STORE* GetOrCreateRootCertStore() {
87-// Guaranteed thread-safe by standard, just don't use -fno-threadsafe-statics.
88-static X509_STORE* store = NewRootCertStore();
89-return store;
105+if (root_cert_store != nullptr) {
106+return root_cert_store;
107+ }
108+ root_cert_store = NewRootCertStore();
109+return root_cert_store;
90110}
9111192112// Takes a string or buffer and loads it into a BIO.
@@ -227,14 +247,11 @@ int SSL_CTX_use_certificate_chain(SSL_CTX* ctx,
227247 issuer);
228248}
229249230-static unsigned long LoadCertsFromFile( // NOLINT(runtime/int)
250+static unsigned long LoadCertsFromBIO( // NOLINT(runtime/int)
231251 std::vector<X509*>* certs,
232-const char* file) {
252+BIOPointer bio) {
233253 MarkPopErrorOnReturn mark_pop_error_on_return;
234254235-auto bio = BIOPointer::NewFile(file, "r");
236-if (!bio) return ERR_get_error();
237-238255while (X509* x509 = PEM_read_bio_X509(
239256 bio.get(), nullptr, NoPasswordCallback, nullptr)) {
240257 certs->push_back(x509);
@@ -250,6 +267,17 @@ static unsigned long LoadCertsFromFile( // NOLINT(runtime/int)
250267 }
251268}
252269270+static unsigned long LoadCertsFromFile( // NOLINT(runtime/int)
271+ std::vector<X509*>* certs,
272+const char* file) {
273+ MarkPopErrorOnReturn mark_pop_error_on_return;
274+275+auto bio = BIOPointer::NewFile(file, "r");
276+if (!bio) return ERR_get_error();
277+278+return LoadCertsFromBIO(certs, std::move(bio));
279+}
280+253281// Indicates the trust status of a certificate.
254282enum class TrustStatus {
255283// Trust status is unknown / uninitialized.
@@ -831,11 +859,24 @@ static std::vector<X509*>& GetExtraCACertificates() {
831859// NODE_EXTRA_CA_CERTS are cached after first load. Certificates
832860// from --use-system-ca are not cached and always reloaded from
833861// disk.
862+// 8. If users have reset the root cert store by calling
863+// tls.setDefaultCACertificates(), the store will be populated with
864+// the certificates provided by users.
834865// TODO(joyeecheung): maybe these rules need a bit of consolidation?
835866X509_STORE* NewRootCertStore() {
836867X509_STORE* store = X509_STORE_new();
837868CHECK_NOT_NULL(store);
838869870+// If the root cert store is already reset by users through
871+// tls.setDefaultCACertificates(), just create a copy from the
872+// user-provided certificates.
873+if (root_certs_from_users != nullptr) {
874+for (X509* cert : *root_certs_from_users) {
875+CHECK_EQ(1, X509_STORE_add_cert(store, cert));
876+ }
877+return store;
878+ }
879+839880#ifdef NODE_OPENSSL_SYSTEM_CERT_PATH
840881if constexpr (sizeof(NODE_OPENSSL_SYSTEM_CERT_PATH) > 1) {
841882ERR_set_mark();
@@ -903,14 +944,57 @@ void GetBundledRootCertificates(const FunctionCallbackInfo<Value>& args) {
903944Array::New(env->isolate(), result, arraysize(root_certs)));
904945}
905946947+bool ArrayOfStringsToX509s(Local<Context> context,
948+ Local<Array> cert_array,
949+ std::vector<X509*>* certs) {
950+ ClearErrorOnReturn clear_error_on_return;
951+ Isolate* isolate = context->GetIsolate();
952+ Environment* env = Environment::GetCurrent(context);
953+uint32_t array_length = cert_array->Length();
954+955+ std::vector<v8::Global<Value>> cert_items;
956+if (FromV8Array(context, cert_array, &cert_items).IsNothing()) {
957+return false;
958+ }
959+960+for (uint32_t i = 0; i < array_length; i++) {
961+ Local<Value> cert_val = cert_items[i].Get(isolate);
962+// Parse the PEM certificate.
963+ BIOPointer bio(LoadBIO(env, cert_val));
964+if (!bio) {
965+ThrowCryptoError(env, ERR_get_error(), "Failed to load certificate data");
966+return false;
967+ }
968+969+// Read all certificates from this PEM string
970+size_t start = certs->size();
971+auto err = LoadCertsFromBIO(certs, std::move(bio));
972+if (err != 0) {
973+size_t end = certs->size();
974+// Clean up any certificates we've already parsed upon failure.
975+for (size_t j = start; j < end; ++j) {
976+X509_free((*certs)[j]);
977+ }
978+ThrowCryptoError(env, err, "Failed to parse certificate");
979+return false;
980+ }
981+ }
982+983+return true;
984+}
985+986+template <typename It>
906987MaybeLocal<Array> X509sToArrayOfStrings(Environment* env,
907-const std::vector<X509*>& certs) {
988+ It first,
989+ It last,
990+size_t size) {
908991 ClearErrorOnReturn clear_error_on_return;
909992 EscapableHandleScope scope(env->isolate());
910993911- LocalVector<Value> result(env->isolate(), certs.size());
912-for (size_t i = 0; i < certs.size(); ++i) {
913- X509View view(certs[i]);
994+ LocalVector<Value> result(env->isolate(), size);
995+size_t i = 0;
996+for (It cur = first; cur != last; ++cur, ++i) {
997+ X509View view(*cur);
914998auto pem_bio = view.toPEM();
915999if (!pem_bio) {
9161000ThrowCryptoError(env, ERR_get_error(), "X509 to PEM conversion");
@@ -935,10 +1019,87 @@ MaybeLocal<Array> X509sToArrayOfStrings(Environment* env,
9351019return scope.Escape(Array::New(env->isolate(), result.data(), result.size()));
9361020}
93710211022+void GetUserRootCertificates(const FunctionCallbackInfo<Value>& args) {
1023+ Environment* env = Environment::GetCurrent(args);
1024+CHECK_NOT_NULL(root_certs_from_users);
1025+ Local<Array> results;
1026+if (X509sToArrayOfStrings(env,
1027+ root_certs_from_users->begin(),
1028+ root_certs_from_users->end(),
1029+ root_certs_from_users->size())
1030+ .ToLocal(&results)) {
1031+ args.GetReturnValue().Set(results);
1032+ }
1033+}
1034+1035+void ResetRootCertStore(const FunctionCallbackInfo<Value>& args) {
1036+ Local<Context> context = args.GetIsolate()->GetCurrentContext();
1037+CHECK(args[0]->IsArray());
1038+ Local<Array> cert_array = args[0].As<Array>();
1039+1040+if (cert_array->Length() == 0) {
1041+// If the array is empty, just clear the user certs and reset the store.
1042+if (root_cert_store != nullptr) {
1043+X509_STORE_free(root_cert_store);
1044+ root_cert_store = nullptr;
1045+ }
1046+1047+// Free any existing certificates in the old set.
1048+if (root_certs_from_users != nullptr) {
1049+for (X509* cert : *root_certs_from_users) {
1050+X509_free(cert);
1051+ }
1052+ }
1053+ root_certs_from_users = std::make_unique<X509Set>();
1054+return;
1055+ }
1056+1057+// Parse certificates from the array
1058+ std::unique_ptr<std::vector<X509*>> certs =
1059+ std::make_unique<std::vector<X509*>>();
1060+if (!ArrayOfStringsToX509s(context, cert_array, certs.get())) {
1061+// Error already thrown by ArrayOfStringsToX509s
1062+return;
1063+ }
1064+1065+if (certs->empty()) {
1066+ Environment* env = Environment::GetCurrent(context);
1067+return THROW_ERR_CRYPTO_OPERATION_FAILED(
1068+ env, "No valid certificates found in the provided array");
1069+ }
1070+1071+auto new_set = std::make_unique<X509Set>();
1072+for (X509* cert : *certs) {
1073+auto [it, inserted] = new_set->insert(cert);
1074+if (!inserted) { // Free duplicate certificates from the vector.
1075+X509_free(cert);
1076+ }
1077+ }
1078+1079+// Free any existing certificates in the old set.
1080+if (root_certs_from_users != nullptr) {
1081+for (X509* cert : *root_certs_from_users) {
1082+X509_free(cert);
1083+ }
1084+ }
1085+std::swap(root_certs_from_users, new_set);
1086+1087+// Reset the global root cert store and create a new one with the
1088+// certificates.
1089+if (root_cert_store != nullptr) {
1090+X509_STORE_free(root_cert_store);
1091+ }
1092+1093+// TODO(joyeecheung): we can probably just reset it to nullptr
1094+// and let the next call to NewRootCertStore() create a new one.
1095+ root_cert_store = NewRootCertStore();
1096+}
1097+9381098void GetSystemCACertificates(const FunctionCallbackInfo<Value>& args) {
9391099 Environment* env = Environment::GetCurrent(args);
9401100 Local<Array> results;
941-if (X509sToArrayOfStrings(env, GetSystemStoreCACertificates())
1101+ std::vector<X509*>& certs = GetSystemStoreCACertificates();
1102+if (X509sToArrayOfStrings(env, certs.begin(), certs.end(), certs.size())
9421103 .ToLocal(&results)) {
9431104 args.GetReturnValue().Set(results);
9441105 }
@@ -950,7 +1111,9 @@ void GetExtraCACertificates(const FunctionCallbackInfo<Value>& args) {
9501111return args.GetReturnValue().Set(Array::New(env->isolate()));
9511112 }
9521113 Local<Array> results;
953-if (X509sToArrayOfStrings(env, GetExtraCACertificates()).ToLocal(&results)) {
1114+ std::vector<X509*>& certs = GetExtraCACertificates();
1115+if (X509sToArrayOfStrings(env, certs.begin(), certs.end(), certs.size())
1116+ .ToLocal(&results)) {
9541117 args.GetReturnValue().Set(results);
9551118 }
9561119}
@@ -1046,6 +1209,9 @@ void SecureContext::Initialize(Environment* env, Local<Object> target) {
10461209 context, target, "getSystemCACertificates", GetSystemCACertificates);
10471210SetMethodNoSideEffect(
10481211 context, target, "getExtraCACertificates", GetExtraCACertificates);
1212+SetMethod(context, target, "resetRootCertStore", ResetRootCertStore);
1213+SetMethodNoSideEffect(
1214+ context, target, "getUserRootCertificates", GetUserRootCertificates);
10491215}
1050121610511217void SecureContext::RegisterExternalReferences(
@@ -1088,6 +1254,8 @@ void SecureContext::RegisterExternalReferences(
10881254 registry->Register(GetBundledRootCertificates);
10891255 registry->Register(GetSystemCACertificates);
10901256 registry->Register(GetExtraCACertificates);
1257+ registry->Register(ResetRootCertStore);
1258+ registry->Register(GetUserRootCertificates);
10911259}
1092126010931261SecureContext* SecureContext::Create(Environment* env) {