crypto: support --use-system-ca on Windows · nodejs/node@3e207bd
@@ -22,6 +22,11 @@
2222#include <Security/Security.h>
2323#endif
242425+#ifdef _WIN32
26+#include <Windows.h>
27+#include <wincrypt.h>
28+#endif
29+2530namespace node {
26312732using ncrypto::BignumPointer;
@@ -285,13 +290,15 @@ void X509VectorToPEMVector(const std::vector<X509Pointer>& src,
285290 }
286291}
287292288-#ifdef __APPLE__
289-// This code is loosely based on
293+// The following code is loosely based on
290294// https://github.com/chromium/chromium/blob/54bd8e3/net/cert/internal/trust_store_mac.cc
295+// and
296+// https://github.com/chromium/chromium/blob/0192587/net/cert/internal/trust_store_win.cc
291297// Copyright 2015 The Chromium Authors
292298// Licensed under a BSD-style license
293299// See https://chromium.googlesource.com/chromium/src/+/HEAD/LICENSE for
294300// details.
301+#ifdef __APPLE__
295302TrustStatus IsTrustDictionaryTrustedForPolicy(CFDictionaryRef trust_dict,
296303bool is_self_issued) {
297304// Trust settings may be scoped to a single application
@@ -524,11 +531,160 @@ void ReadMacOSKeychainCertificates(
524531}
525532#endif // __APPLE__
526533534+#ifdef _WIN32
535+536+// Returns true if the cert can be used for server authentication, based on
537+// certificate properties.
538+//
539+// While there are a variety of certificate properties that can affect how
540+// trust is computed, the main property is CERT_ENHKEY_USAGE_PROP_ID, which
541+// is intersected with the certificate's EKU extension (if present).
542+// The intersection is documented in the Remarks section of
543+// CertGetEnhancedKeyUsage, and is as follows:
544+// - No EKU property, and no EKU extension = Trusted for all purpose
545+// - Either an EKU property, or EKU extension, but not both = Trusted only
546+// for the listed purposes
547+// - Both an EKU property and an EKU extension = Trusted for the set
548+// intersection of the listed purposes
549+// CertGetEnhancedKeyUsage handles this logic, and if an empty set is
550+// returned, the distinction between the first and third case can be
551+// determined by GetLastError() returning CRYPT_E_NOT_FOUND.
552+//
553+// See:
554+// https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certgetenhancedkeyusage
555+//
556+// If we run into any errors reading the certificate properties, we fail
557+// closed.
558+bool IsCertTrustedForServerAuth(PCCERT_CONTEXT cert) {
559+DWORD usage_size = 0;
560+561+if (!CertGetEnhancedKeyUsage(cert, 0, nullptr, &usage_size)) {
562+return false;
563+ }
564+565+ std::vector<BYTE> usage_bytes(usage_size);
566+CERT_ENHKEY_USAGE* usage =
567+reinterpret_cast<CERT_ENHKEY_USAGE*>(usage_bytes.data());
568+if (!CertGetEnhancedKeyUsage(cert, 0, usage, &usage_size)) {
569+return false;
570+ }
571+572+if (usage->cUsageIdentifier == 0) {
573+// check GetLastError
574+HRESULT error_code = GetLastError();
575+576+switch (error_code) {
577+case CRYPT_E_NOT_FOUND:
578+return true;
579+case S_OK:
580+return false;
581+default:
582+return false;
583+ }
584+ }
585+586+// SAFETY: `usage->rgpszUsageIdentifier` is an array of LPSTR (pointer to null
587+// terminated string) of length `usage->cUsageIdentifier`.
588+for (DWORD i = 0; i < usage->cUsageIdentifier; ++i) {
589+ std::string_view eku(usage->rgpszUsageIdentifier[i]);
590+if ((eku == szOID_PKIX_KP_SERVER_AUTH) ||
591+ (eku == szOID_ANY_ENHANCED_KEY_USAGE)) {
592+return true;
593+ }
594+ }
595+596+return false;
597+}
598+599+void GatherCertsForLocation(std::vector<X509Pointer>* vector,
600+DWORD location,
601+LPCWSTR store_name) {
602+if (!(location == CERT_SYSTEM_STORE_LOCAL_MACHINE ||
603+ location == CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY ||
604+ location == CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE ||
605+ location == CERT_SYSTEM_STORE_CURRENT_USER ||
606+ location == CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY)) {
607+return;
608+ }
609+610+DWORD flags =
611+ location | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG;
612+613+HCERTSTORE opened_store(
614+CertOpenStore(CERT_STORE_PROV_SYSTEM,
615+0,
616+// The Windows API only accepts NULL for hCryptProv.
617+NULL, /* NOLINT (readability/null_usage) */
618+ flags,
619+ store_name));
620+if (!opened_store) {
621+return;
622+ }
623+624+auto cleanup = OnScopeLeave(
625+ [opened_store]() { CHECK_EQ(CertCloseStore(opened_store, 0), TRUE); });
626+627+PCCERT_CONTEXT cert_from_store = nullptr;
628+while ((cert_from_store = CertEnumCertificatesInStore(
629+ opened_store, cert_from_store)) != nullptr) {
630+if (!IsCertTrustedForServerAuth(cert_from_store)) {
631+continue;
632+ }
633+const unsigned char* cert_data =
634+reinterpret_cast<const unsigned char*>(cert_from_store->pbCertEncoded);
635+const size_t cert_size = cert_from_store->cbCertEncoded;
636+637+ vector->emplace_back(d2i_X509(nullptr, &cert_data, cert_size));
638+ }
639+}
640+641+void ReadWindowsCertificates(
642+ std::vector<std::string>* system_root_certificates) {
643+ std::vector<X509Pointer> system_root_certificates_X509;
644+// TODO(joyeecheung): match Chromium's policy, collect more certificates
645+// from user-added CAs and support disallowed (revoked) certificates.
646+647+// Grab the user-added roots.
648+GatherCertsForLocation(
649+ &system_root_certificates_X509, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"ROOT");
650+GatherCertsForLocation(&system_root_certificates_X509,
651+CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY,
652+L"ROOT");
653+GatherCertsForLocation(&system_root_certificates_X509,
654+CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
655+L"ROOT");
656+GatherCertsForLocation(
657+ &system_root_certificates_X509, CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT");
658+GatherCertsForLocation(&system_root_certificates_X509,
659+CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY,
660+L"ROOT");
661+662+// Grab the user-added trusted server certs. Trusted end-entity certs are
663+// only allowed for server auth in the "local machine" store, but not in the
664+// "current user" store.
665+GatherCertsForLocation(&system_root_certificates_X509,
666+CERT_SYSTEM_STORE_LOCAL_MACHINE,
667+L"TrustedPeople");
668+GatherCertsForLocation(&system_root_certificates_X509,
669+CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY,
670+L"TrustedPeople");
671+GatherCertsForLocation(&system_root_certificates_X509,
672+CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
673+L"TrustedPeople");
674+675+X509VectorToPEMVector(system_root_certificates_X509,
676+ system_root_certificates);
677+}
678+#endif
679+527680void ReadSystemStoreCertificates(
528681 std::vector<std::string>* system_root_certificates) {
529682#ifdef __APPLE__
530683ReadMacOSKeychainCertificates(system_root_certificates);
531684#endif
685+#ifdef _WIN32
686+ReadWindowsCertificates(system_root_certificates);
687+#endif
532688}
533689534690std::vector<std::string> getCombinedRootCertificates() {