From 716aecf7d76fa84c7cbb58217de7d8dee204ea9b Mon Sep 17 00:00:00 2001 From: Harshil Jain Date: Thu, 6 Oct 2022 18:57:33 +0530 Subject: [PATCH 1/3] crypto: added support for reading system store certificates in windows --- src/crypto/crypto_context.cc | 104 ++++++++++++++++++++++++++++++++--- src/node_options.cc | 4 ++ src/node_options.h | 1 + 3 files changed, 101 insertions(+), 8 deletions(-) diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index 94890c396c2c56..f5977c1a9972a2 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -18,6 +18,13 @@ #include #endif // !OPENSSL_NO_ENGINE +#ifdef _WIN32 +#include +#include + +#include "base64-inl.h" +#endif + namespace node { using v8::Array; @@ -190,6 +197,65 @@ int SSL_CTX_use_certificate_chain(SSL_CTX* ctx, } // namespace +void ReadSystemStoreCertificates( + std::vector* system_root_certificates) { + const HCERTSTORE hStore = CertOpenSystemStoreW(0, L"ROOT"); + CHECK_NE(hStore, NULLPTR); + + auto cleanup = + OnScopeLeave([hStore]() { CHECK_EQ(CertCloseStore(hStore, 0), TRUE); }); + + PCCERT_CONTEXT pCtx = nullptr; + + while ((pCtx = CertEnumCertificatesInStore(hStore, pCtx)) != nullptr) { + const DWORD cbSize = CertGetNameStringW( + pCtx, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, nullptr, 0); + + CHECK_GT(cbSize, 0); + + std::vector pszName(cbSize); + + CHECK_GT(CertGetNameStringW(pCtx, + CERT_NAME_SIMPLE_DISPLAY_TYPE, + 0, + nullptr, + pszName.data(), + cbSize), + 0); + + const char* certificate_src_ptr = + reinterpret_cast(pCtx->pbCertEncoded); + const size_t slen = pCtx->cbCertEncoded; + const size_t dlen = base64_encoded_size(slen); + + char* certificate_dst_ptr = UncheckedMalloc(dlen); + + CHECK_NOT_NULL(certificate_dst_ptr); + + auto cleanup = + OnScopeLeave([certificate_dst_ptr]() { free(certificate_dst_ptr); }); + + const size_t written = + base64_encode(certificate_src_ptr, slen, certificate_dst_ptr, dlen); + CHECK_EQ(written, dlen); + + std::string base64_string_output(certificate_dst_ptr, dlen); + + constexpr size_t distance = 72; + size_t pos = distance; + + while (pos < base64_string_output.size()) { + base64_string_output.insert(pos, "\n"); + pos += distance + 1; + } + + base64_string_output = "-----BEGIN CERTIFICATE-----\n" + + base64_string_output + "\n-----END CERTIFICATE-----"; + + system_root_certificates->emplace_back(std::move(base64_string_output)); + } +} + X509_STORE* NewRootCertStore() { static std::vector root_certs_vector; static Mutex root_certs_vector_mutex; @@ -197,11 +263,22 @@ X509_STORE* NewRootCertStore() { if (root_certs_vector.empty() && per_process::cli_options->ssl_openssl_cert_store == false) { + std::vector combined_root_certs; + for (size_t i = 0; i < arraysize(root_certs); i++) { + combined_root_certs.emplace_back(root_certs[i]); + } + + if (per_process::cli_options->node_use_system_ca) { + ReadSystemStoreCertificates(&combined_root_certs); + } + + for (size_t i = 0; i < combined_root_certs.size(); i++) { X509* x509 = - PEM_read_bio_X509(NodeBIO::NewFixed(root_certs[i], - strlen(root_certs[i])).get(), - nullptr, // no re-use of X509 structure + PEM_read_bio_X509(NodeBIO::NewFixed(combined_root_certs[i].c_str(), + combined_root_certs[i].length()) + .get(), + nullptr, // no re-use of X509 structure NoPasswordCallback, nullptr); // no callback data @@ -234,19 +311,30 @@ X509_STORE* NewRootCertStore() { void GetRootCertificates(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - Local result[arraysize(root_certs)]; + + std::vector combined_root_certs; for (size_t i = 0; i < arraysize(root_certs); i++) { + combined_root_certs.emplace_back(root_certs[i]); + } + + if (per_process::cli_options->node_use_system_ca) { + ReadSystemStoreCertificates(&combined_root_certs); + } + + std::vector> result(combined_root_certs.size()); + + for (size_t i = 0; i < combined_root_certs.size(); i++) { if (!String::NewFromOneByte( - env->isolate(), - reinterpret_cast(root_certs[i])) - .ToLocal(&result[i])) { + env->isolate(), + reinterpret_cast(combined_root_certs[i].c_str())) + .ToLocal(&result[i])) { return; } } args.GetReturnValue().Set( - Array::New(env->isolate(), result, arraysize(root_certs))); + Array::New(env->isolate(), result.data(), combined_root_certs.size())); } bool SecureContext::HasInstance(Environment* env, const Local& value) { diff --git a/src/node_options.cc b/src/node_options.cc index ce353bbcf5cbbf..c0cb56f5aa397f 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -866,6 +866,10 @@ PerProcessOptionsParser::PerProcessOptionsParser( "use an alternative default TLS cipher list", &PerProcessOptions::tls_cipher_list, kAllowedInEnvvar); + AddOption("--node-use-system-ca", + "use system's store CA", + &PerProcessOptions::node_use_system_ca, + kAllowedInEnvvar); AddOption("--use-openssl-ca", "use OpenSSL's default CA store" #if defined(NODE_OPENSSL_CERT_STORE) diff --git a/src/node_options.h b/src/node_options.h index 02beddccdf01c8..00ff3f85bfba80 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -273,6 +273,7 @@ class PerProcessOptions : public Options { #else bool ssl_openssl_cert_store = false; #endif + bool node_use_system_ca = false; bool use_openssl_ca = false; bool use_bundled_ca = false; bool enable_fips_crypto = false; From 9d628a78235c18fcf16db54b596dee7803fdb3a2 Mon Sep 17 00:00:00 2001 From: Harshil Jain Date: Thu, 6 Oct 2022 18:57:48 +0530 Subject: [PATCH 2/3] crypto: added documentation for the newer node option --- doc/api/cli.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/api/cli.md b/doc/api/cli.md index 1f18552dd0cb3b..77d6a043ec244d 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -1529,6 +1529,14 @@ environment variables. See `SSL_CERT_DIR` and `SSL_CERT_FILE`. +### `--node-use-system-ca` + +Node.js uses the trusted CA certificates present in the system store along with +the `--use-bundled-ca`, `--use-openssl-ca` options. + +Note, Only current user certificates are accessible using this method, not the +local machine store. This option is available to Windows only. + ### `--use-largepages=mode`