@@ -40,7 +40,6 @@ using ncrypto::MarkPopErrorOnReturn;
4040using ncrypto::SSLPointer;
4141using ncrypto::StackOfX509;
4242using ncrypto::X509Pointer;
43- using ncrypto::X509View;
4443using v8::Array;
4544using v8::ArrayBufferView;
4645using v8::Boolean;
@@ -74,6 +73,10 @@ static const char system_cert_path[] = NODE_OPENSSL_SYSTEM_CERT_PATH;
7473
7574static 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+
7780X509_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
484458void 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
641611void 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?
704744X509_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+
768796void GetRootCertificates (const FunctionCallbackInfo<Value>& args) {
769797 Environment* env = Environment::GetCurrent (args);
770798 Local<Value> result[arraysize (root_certs)];
0 commit comments