Skip to content

Commit 18ea88b

Browse files
joyeecheungtargos
authored andcommitted
crypto: cleanup root certificates and skip PEM deserialization
- We do not actually need them in PEM format, so just pass them around as X509 direcrtly. - The cached global X509 structures were previously never cleaned up. Clean them up at process teardown. - Use function-local static to ensure thread-safety in initialization. - Add more comments about how the various options differ. PR-URL: #56999 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent c011271 commit 18ea88b

File tree

3 files changed

+146
-113
lines changed

3 files changed

+146
-113
lines changed

src/crypto/crypto_context.cc

+141-113
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ using ncrypto::MarkPopErrorOnReturn;
4040
using ncrypto::SSLPointer;
4141
using ncrypto::StackOfX509;
4242
using ncrypto::X509Pointer;
43-
using ncrypto::X509View;
4443
using v8::Array;
4544
using v8::ArrayBufferView;
4645
using v8::Boolean;
@@ -74,6 +73,10 @@ static const char system_cert_path[] = NODE_OPENSSL_SYSTEM_CERT_PATH;
7473

7574
static std::string extra_root_certs_file; // NOLINT(runtime/string)
7675

76+
static std::atomic<bool> has_cached_bundled_root_certs{false};
77+
static std::atomic<bool> has_cached_system_root_certs{false};
78+
static std::atomic<bool> has_cached_extra_root_certs{false};
79+
7780
X509_STORE* GetOrCreateRootCertStore() {
7881
// Guaranteed thread-safe by standard, just don't use -fno-threadsafe-statics.
7982
static X509_STORE* store = NewRootCertStore();
@@ -261,35 +264,6 @@ bool isSelfIssued(X509* cert) {
261264
return X509_NAME_cmp(subject, issuer) == 0;
262265
}
263266

264-
// TODO(joyeecheung): it is a bit excessive to do this X509 -> PEM -> X509
265-
// dance when we could've just pass everything around in binary. Change the
266-
// root_certs to be embedded as DER so that we can save the serialization
267-
// and deserialization.
268-
void X509VectorToPEMVector(const std::vector<X509Pointer>& src,
269-
std::vector<std::string>* dest) {
270-
for (size_t i = 0; i < src.size(); i++) {
271-
X509View x509_view(src[i].get());
272-
273-
auto pem_bio = x509_view.toPEM();
274-
if (!pem_bio) {
275-
fprintf(stderr,
276-
"Warning: converting system certificate to PEM format failed\n");
277-
continue;
278-
}
279-
280-
char* pem_data = nullptr;
281-
auto pem_size = BIO_get_mem_data(pem_bio.get(), &pem_data);
282-
if (pem_size <= 0 || !pem_data) {
283-
fprintf(
284-
stderr,
285-
"Warning: cannot read PEM-encoded data from system certificate\n");
286-
continue;
287-
}
288-
289-
dest->emplace_back(pem_data, pem_size);
290-
}
291-
}
292-
293267
// The following code is loosely based on
294268
// https://github.com/chromium/chromium/blob/54bd8e3/net/cert/internal/trust_store_mac.cc
295269
// and
@@ -482,7 +456,7 @@ bool IsCertificateTrustedForPolicy(X509* cert, SecCertificateRef ref) {
482456
}
483457

484458
void ReadMacOSKeychainCertificates(
485-
std::vector<std::string>* system_root_certificates) {
459+
std::vector<X509*>* system_root_certificates_X509) {
486460
CFTypeRef search_keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef};
487461
CFTypeRef search_values[] = {
488462
kSecClassCertificate, kSecMatchLimitAll, kCFBooleanTrue};
@@ -504,7 +478,6 @@ void ReadMacOSKeychainCertificates(
504478

505479
CFIndex count = CFArrayGetCount(curr_anchors);
506480

507-
std::vector<X509Pointer> system_root_certificates_X509;
508481
for (int i = 0; i < count; ++i) {
509482
SecCertificateRef cert_ref = reinterpret_cast<SecCertificateRef>(
510483
const_cast<void*>(CFArrayGetValueAtIndex(curr_anchors, i)));
@@ -521,13 +494,10 @@ void ReadMacOSKeychainCertificates(
521494
CFRelease(der_data);
522495
bool is_valid = IsCertificateTrustedForPolicy(cert, cert_ref);
523496
if (is_valid) {
524-
system_root_certificates_X509.emplace_back(cert);
497+
system_root_certificates_X509->emplace_back(cert);
525498
}
526499
}
527500
CFRelease(curr_anchors);
528-
529-
X509VectorToPEMVector(system_root_certificates_X509,
530-
system_root_certificates);
531501
}
532502
#endif // __APPLE__
533503

@@ -596,7 +566,7 @@ bool IsCertTrustedForServerAuth(PCCERT_CONTEXT cert) {
596566
return false;
597567
}
598568

599-
void GatherCertsForLocation(std::vector<X509Pointer>* vector,
569+
void GatherCertsForLocation(std::vector<X509*>* vector,
600570
DWORD location,
601571
LPCWSTR store_name) {
602572
if (!(location == CERT_SYSTEM_STORE_LOCAL_MACHINE ||
@@ -639,114 +609,142 @@ void GatherCertsForLocation(std::vector<X509Pointer>* vector,
639609
}
640610

641611
void ReadWindowsCertificates(
642-
std::vector<std::string>* system_root_certificates) {
643-
std::vector<X509Pointer> system_root_certificates_X509;
612+
std::vector<X509*>* system_root_certificates_X509) {
644613
// TODO(joyeecheung): match Chromium's policy, collect more certificates
645614
// from user-added CAs and support disallowed (revoked) certificates.
646615

647616
// Grab the user-added roots.
648617
GatherCertsForLocation(
649-
&system_root_certificates_X509, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"ROOT");
650-
GatherCertsForLocation(&system_root_certificates_X509,
618+
system_root_certificates_X509, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"ROOT");
619+
GatherCertsForLocation(system_root_certificates_X509,
651620
CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY,
652621
L"ROOT");
653-
GatherCertsForLocation(&system_root_certificates_X509,
622+
GatherCertsForLocation(system_root_certificates_X509,
654623
CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
655624
L"ROOT");
656625
GatherCertsForLocation(
657-
&system_root_certificates_X509, CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT");
658-
GatherCertsForLocation(&system_root_certificates_X509,
626+
system_root_certificates_X509, CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT");
627+
GatherCertsForLocation(system_root_certificates_X509,
659628
CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY,
660629
L"ROOT");
661630

662631
// Grab the user-added trusted server certs. Trusted end-entity certs are
663632
// only allowed for server auth in the "local machine" store, but not in the
664633
// "current user" store.
665-
GatherCertsForLocation(&system_root_certificates_X509,
634+
GatherCertsForLocation(system_root_certificates_X509,
666635
CERT_SYSTEM_STORE_LOCAL_MACHINE,
667636
L"TrustedPeople");
668-
GatherCertsForLocation(&system_root_certificates_X509,
637+
GatherCertsForLocation(system_root_certificates_X509,
669638
CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY,
670639
L"TrustedPeople");
671-
GatherCertsForLocation(&system_root_certificates_X509,
640+
GatherCertsForLocation(system_root_certificates_X509,
672641
CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
673642
L"TrustedPeople");
674-
675-
X509VectorToPEMVector(system_root_certificates_X509,
676-
system_root_certificates);
677643
}
678644
#endif
679645

680-
void ReadSystemStoreCertificates(
681-
std::vector<std::string>* system_root_certificates) {
682-
#ifdef __APPLE__
683-
ReadMacOSKeychainCertificates(system_root_certificates);
684-
#endif
685-
#ifdef _WIN32
686-
ReadWindowsCertificates(system_root_certificates);
687-
#endif
688-
}
689-
690-
std::vector<std::string> getCombinedRootCertificates() {
691-
std::vector<std::string> combined_root_certs;
646+
static std::vector<X509*> InitializeBundledRootCertificates() {
647+
// Read the bundled certificates in node_root_certs.h into
648+
// bundled_root_certs_vector.
649+
std::vector<X509*> bundled_root_certs;
650+
size_t bundled_root_cert_count = arraysize(root_certs);
651+
bundled_root_certs.reserve(bundled_root_cert_count);
652+
for (size_t i = 0; i < bundled_root_cert_count; i++) {
653+
X509* x509 = PEM_read_bio_X509(
654+
NodeBIO::NewFixed(root_certs[i], strlen(root_certs[i])).get(),
655+
nullptr, // no re-use of X509 structure
656+
NoPasswordCallback,
657+
nullptr); // no callback data
692658

693-
for (size_t i = 0; i < arraysize(root_certs); i++) {
694-
combined_root_certs.emplace_back(root_certs[i]);
695-
}
659+
// Parse errors from the built-in roots are fatal.
660+
CHECK_NOT_NULL(x509);
696661

697-
if (per_process::cli_options->use_system_ca) {
698-
ReadSystemStoreCertificates(&combined_root_certs);
662+
bundled_root_certs.push_back(x509);
699663
}
664+
return bundled_root_certs;
665+
}
700666

701-
return combined_root_certs;
667+
// TODO(joyeecheung): it is a bit excessive to do this PEM -> X509
668+
// dance when we could've just pass everything around in binary. Change the
669+
// root_certs to be embedded as DER so that we can save the serialization
670+
// and deserialization.
671+
static std::vector<X509*>& GetBundledRootCertificates() {
672+
// Use function-local static to guarantee thread safety.
673+
static std::vector<X509*> bundled_root_certs =
674+
InitializeBundledRootCertificates();
675+
has_cached_bundled_root_certs.store(true);
676+
return bundled_root_certs;
702677
}
703678

679+
static std::vector<X509*> InitializeSystemStoreCertificates() {
680+
std::vector<X509*> system_store_certs;
681+
#ifdef __APPLE__
682+
ReadMacOSKeychainCertificates(&system_store_certs);
683+
#endif
684+
#ifdef _WIN32
685+
ReadWindowsCertificates(&system_store_certs);
686+
#endif
687+
return system_store_certs;
688+
}
689+
690+
static std::vector<X509*>& GetSystemStoreRootCertificates() {
691+
// Use function-local static to guarantee thread safety.
692+
static std::vector<X509*> system_store_certs =
693+
InitializeSystemStoreCertificates();
694+
has_cached_system_root_certs.store(true);
695+
return system_store_certs;
696+
}
697+
698+
static std::vector<X509*> InitializeExtraCACertificates() {
699+
std::vector<X509*> extra_certs;
700+
unsigned long err = LoadCertsFromFile( // NOLINT(runtime/int)
701+
&extra_certs,
702+
extra_root_certs_file.c_str());
703+
if (err) {
704+
char buf[256];
705+
ERR_error_string_n(err, buf, sizeof(buf));
706+
fprintf(stderr,
707+
"Warning: Ignoring extra certs from `%s`, load failed: %s\n",
708+
extra_root_certs_file.c_str(),
709+
buf);
710+
}
711+
return extra_certs;
712+
}
713+
714+
static std::vector<X509*>& GetExtraCACertificates() {
715+
// Use function-local static to guarantee thread safety.
716+
static std::vector<X509*> extra_certs = InitializeExtraCACertificates();
717+
has_cached_extra_root_certs.store(true);
718+
return extra_certs;
719+
}
720+
721+
// Due to historical reasons the various options of CA certificates
722+
// may invalid one another. The current rule is:
723+
// 1. If the configure-time option --openssl-use-def-ca-store is NOT used
724+
// (default):
725+
// a. If the runtime option --use-openssl-ca is used, load the
726+
// CA certificates from the default locations respected by OpenSSL.
727+
// b. Otherwise, --use-bundled-ca is assumed to be the default, and we
728+
// use the bundled CA certificates.
729+
// 2. If the configure-time option --openssl-use-def-ca-store IS used,
730+
// --use-openssl-ca is assumed to be the default, with the default
731+
// location set to the path specified by the configure-time option.
732+
// 3. --use-openssl-ca and --use-bundled-ca are mutually exclusive.
733+
// 4. --use-openssl-ca and --use-system-ca are mutually exclusive.
734+
// 5. --use-bundled-ca and --use-system-ca can be used together.
735+
// The certificates can be combined.
736+
// 6. Independent of all other flags, NODE_EXTRA_CA_CERTS always
737+
// adds extra certificates from the specified path, so it works
738+
// with all the other flags.
739+
// 7. Certificates from --use-bundled-ca, --use-system-ca and
740+
// NODE_EXTRA_CA_CERTS are cached after first load. Certificates
741+
// from --use-system-ca are not cached and always reloaded from
742+
// disk.
743+
// TODO(joyeecheung): maybe these rules need a bit of consolidation?
704744
X509_STORE* NewRootCertStore() {
705-
static std::vector<X509*> root_certs_vector;
706-
static bool root_certs_vector_loaded = false;
707-
static Mutex root_certs_vector_mutex;
708-
Mutex::ScopedLock lock(root_certs_vector_mutex);
709-
710-
if (!root_certs_vector_loaded) {
711-
if (per_process::cli_options->ssl_openssl_cert_store == false) {
712-
std::vector<std::string> combined_root_certs =
713-
getCombinedRootCertificates();
714-
715-
for (size_t i = 0; i < combined_root_certs.size(); i++) {
716-
X509* x509 =
717-
PEM_read_bio_X509(NodeBIO::NewFixed(combined_root_certs[i].data(),
718-
combined_root_certs[i].length())
719-
.get(),
720-
nullptr, // no re-use of X509 structure
721-
NoPasswordCallback,
722-
nullptr); // no callback data
723-
724-
// Parse errors from the built-in roots are fatal.
725-
CHECK_NOT_NULL(x509);
726-
727-
root_certs_vector.push_back(x509);
728-
}
729-
}
730-
731-
if (!extra_root_certs_file.empty()) {
732-
unsigned long err = LoadCertsFromFile( // NOLINT(runtime/int)
733-
&root_certs_vector,
734-
extra_root_certs_file.c_str());
735-
if (err) {
736-
char buf[256];
737-
ERR_error_string_n(err, buf, sizeof(buf));
738-
fprintf(stderr,
739-
"Warning: Ignoring extra certs from `%s`, load failed: %s\n",
740-
extra_root_certs_file.c_str(),
741-
buf);
742-
}
743-
}
744-
745-
root_certs_vector_loaded = true;
746-
}
747-
748745
X509_STORE* store = X509_STORE_new();
749746
CHECK_NOT_NULL(store);
747+
750748
if (*system_cert_path != '\0') {
751749
ERR_set_mark();
752750
X509_STORE_load_locations(store, system_cert_path, nullptr);
@@ -756,15 +754,45 @@ X509_STORE* NewRootCertStore() {
756754
Mutex::ScopedLock cli_lock(node::per_process::cli_options_mutex);
757755
if (per_process::cli_options->ssl_openssl_cert_store) {
758756
CHECK_EQ(1, X509_STORE_set_default_paths(store));
757+
} else {
758+
for (X509* cert : GetBundledRootCertificates()) {
759+
CHECK_EQ(1, X509_STORE_add_cert(store, cert));
760+
}
761+
if (per_process::cli_options->use_system_ca) {
762+
for (X509* cert : GetSystemStoreRootCertificates()) {
763+
CHECK_EQ(1, X509_STORE_add_cert(store, cert));
764+
}
765+
}
759766
}
760767

761-
for (X509* cert : root_certs_vector) {
762-
CHECK_EQ(1, X509_STORE_add_cert(store, cert));
768+
if (!extra_root_certs_file.empty()) {
769+
for (X509* cert : GetExtraCACertificates()) {
770+
CHECK_EQ(1, X509_STORE_add_cert(store, cert));
771+
}
763772
}
764773

765774
return store;
766775
}
767776

777+
void CleanupCachedRootCertificates() {
778+
if (has_cached_bundled_root_certs.load()) {
779+
for (X509* cert : GetBundledRootCertificates()) {
780+
X509_free(cert);
781+
}
782+
}
783+
if (has_cached_system_root_certs.load()) {
784+
for (X509* cert : GetSystemStoreRootCertificates()) {
785+
X509_free(cert);
786+
}
787+
}
788+
789+
if (has_cached_extra_root_certs.load()) {
790+
for (X509* cert : GetExtraCACertificates()) {
791+
X509_free(cert);
792+
}
793+
}
794+
}
795+
768796
void GetRootCertificates(const FunctionCallbackInfo<Value>& args) {
769797
Environment* env = Environment::GetCurrent(args);
770798
Local<Value> result[arraysize(root_certs)];

src/crypto/crypto_util.h

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ void InitCryptoOnce();
6262
void InitCrypto(v8::Local<v8::Object> target);
6363

6464
extern void UseExtraCaCerts(std::string_view file);
65+
void CleanupCachedRootCertificates();
6566

6667
int PasswordCallback(char* buf, int size, int rwflag, void* u);
6768
int NoPasswordCallback(char* buf, int size, int rwflag, void* u);

src/node.cc

+4
Original file line numberDiff line numberDiff line change
@@ -1258,6 +1258,10 @@ void TearDownOncePerProcess() {
12581258
// will never be fully cleaned up.
12591259
per_process::v8_platform.Dispose();
12601260
}
1261+
1262+
#if HAVE_OPENSSL
1263+
crypto::CleanupCachedRootCertificates();
1264+
#endif // HAVE_OPENSSL
12611265
}
12621266

12631267
ExitCode GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr,

0 commit comments

Comments
 (0)