Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added support for reading certificates from windows system store #44532

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

twitharshil
Copy link
Contributor

@twitharshil twitharshil commented Sep 6, 2022

Description of Change

MITM-based proxy environments like ZScaler intercepts the requests sent to a certain endpoint from the user machine and serve a redirect to their own servers ( for eg: https://gateway.zscloud.net/ ) before finally redirecting back to the original endpoint. We have observed root certificates of these proxy vendors present on the user system store. These certificates are needed for the request's SSL certificate verification.

Chrome reads certificates from the system store hence the certificate verification step passes when a request is sent out of the user machine through chrome as a client.
Node uses a statically compiled, hardcoded list of certificate authorities, rather than relying on the system's trust store, hence request going out from node as a client fails at the SSL verification step behind such environments.

This PR targets to read the certificates from the user system store and embed them with the existing list of root certificates with the node.

Note:

  1. These changes will make a huge impact on the applications that use open source projects like electron which uses node in their networking layers.

  2. In my current implementation, I've added logic to read the certificates from the system store and embed it with the existing list together in a single file. I am open to suggestions if we want the certificate read logic to be written somewhere else.

  3. This PR only targets the windows platform for now. All the users from which we collected feedback were windows users only. Changes made in the PR are feature flagged behind a runtime CLI option named --node-use-system-ca

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/crypto

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. crypto Issues and PRs related to the crypto subsystem. needs-ci PRs that need a full CI run. labels Sep 6, 2022
@twitharshil twitharshil force-pushed the feature/add-support-for-reading-system-certificates-on-windows branch from 285a83d to f35fe3c Compare September 6, 2022 07:34
@bnoordhuis
Copy link
Member

Related: #39657 (comment)

Having such functionality in node isn't completely out of the question but the default behavior should be consistent across platforms, meaning it should almost certainly be opt-in.

@twitharshil
Copy link
Contributor Author

@bnoordhuis Thanks for taking a look at the draft PR. I've taken a quick look at the thread mentioned in your comment. It seems like for windows we can go ahead with the current approach of reading system store and implement the solution behind an opt-in flag. I believe it will be a build-time flag.

@RaisinTen
Copy link
Contributor

I believe it will be a build-time flag.

Why not a regular cli option?

@twitharshil
Copy link
Contributor Author

@RaisinTen I choose a build time flag rather than a regular cli option more from my understanding so far. I see a similar use case with node which reads the default OpenSSL cert store for certificates when built with openssl-use-def-ca-store.

@RaisinTen
Copy link
Contributor

@twitharshil that one is actually a runtime flag accompanied by a build time flag. --use-bundled-ca and --use-openssl-ca are the runtime flags. --openssl-use-def-ca-store is the build time flag that is used to select which one of those 2 runtime flags would be enabled by default.

@twitharshil twitharshil marked this pull request as ready for review October 6, 2022 13:11
@twitharshil twitharshil force-pushed the feature/add-support-for-reading-system-certificates-on-windows branch 6 times, most recently from 193496f to 87a7def Compare October 10, 2022 05:21
@twitharshil
Copy link
Contributor Author

Hey @bnoordhuis @RaisinTen
I've opened the PR for review, PTAL. Thanks :)

Copy link
Member

@bnoordhuis bnoordhuis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments. I've commented on some of the technical aspects but I'm still on the fence as to whether it's a desirable feature.

Speaking for myself, the fact that so far it's Windows-only counts against it.

src/crypto/crypto_context.cc Outdated Show resolved Hide resolved
src/crypto/crypto_context.cc Outdated Show resolved Hide resolved
}

base64_string_output = "-----BEGIN CERTIFICATE-----\n" +
base64_string_output + "\n-----END CERTIFICATE-----";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This manual formatting of a certificate is kind of questionable. If you have the certificate in DER format, you can pass it to openssl directly:

X509Pointer cert(d2i_X509(nullptr, buf, len));
if (cert) {
  // ...
}

openssl has utility functions like X509_print_ex() for formatting it as PEM, which I guess you're doing so they show up in tls.rootCertificates?

test/parallel/test-cli-node-print-help.js Outdated Show resolved Hide resolved
doc/api/cli.md Outdated Show resolved Hide resolved
@twitharshil
Copy link
Contributor Author

Left some comments. I've commented on some of the technical aspects, but I'm still unsure whether it's a desirable feature.

Speaking for myself, the fact that so far it's Windows-only counts against it.

@bnoordhuis I think this feature has been one of the most demanding features for quite some time now. In recent times I worked closely to support these MITM-based proxies environment only for the windows platform so I thought of upstreaming this feature to node.

Over the years no one picked up this work, so I believe supporting the windows platform at least is better than nothing? We can iteratively get this done for other platforms as well in separate PRs. I am open if someone else wants to pick this work for macOS & Linux otherwise I will pick it up once I find more time.

@twitharshil twitharshil force-pushed the feature/add-support-for-reading-system-certificates-on-windows branch 4 times, most recently from 8fce134 to f61d113 Compare October 19, 2022 17:53
@twitharshil twitharshil force-pushed the feature/add-support-for-reading-system-certificates-on-windows branch 3 times, most recently from fed9bf6 to 012f11e Compare October 20, 2022 05:43
@twitharshil twitharshil force-pushed the feature/add-support-for-reading-system-certificates-on-windows branch 3 times, most recently from 52fbf17 to 5082ab3 Compare October 21, 2022 08:27
@twitharshil twitharshil force-pushed the feature/add-support-for-reading-system-certificates-on-windows branch from 5082ab3 to 1b0265f Compare November 2, 2022 07:52
@twitharshil twitharshil force-pushed the feature/add-support-for-reading-system-certificates-on-windows branch 2 times, most recently from 8493223 to f17bec7 Compare November 2, 2022 10:28
@twitharshil twitharshil force-pushed the feature/add-support-for-reading-system-certificates-on-windows branch from 69d7cff to 00f666a Compare November 3, 2022 07:52
@twitharshil
Copy link
Contributor Author

Hey, @bnoordhuis @jasnell I've addressed the PR comments, can I get another round of review on my PR?

Copy link
Member

@bnoordhuis bnoordhuis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally LGTM but this should probably be reviewed by someone more familiar with the Windows crypto API than I am.

void ReadSystemStoreCertificates(
std::vector<std::string>* system_root_certificates) {
#ifdef _WIN32
const HCERTSTORE hStore = CertOpenSystemStoreW(0, L"ROOT");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const HCERTSTORE hStore = CertOpenSystemStoreW(0, L"ROOT");
const HCERTSTORE hStore = CertOpenSystemStoreW(nullptr, L"ROOT");

Because the first arg is a pointer, right? How likely is it for this method to fail?

Comment on lines +213 to +214
while ((certificate_context_ptr = CertEnumCertificatesInStore(
hStore, certificate_context_ptr)) != nullptr) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer something like this for readability reasons:

Suggested change
while ((certificate_context_ptr = CertEnumCertificatesInStore(
hStore, certificate_context_ptr)) != nullptr) {
for (;;) {
certificate_context_ptr =
CertEnumCertificatesInStore(hStore, certificate_context_ptr);
if (certificate_context_ptr == nullptr) break;

Comment on lines +258 to +259

bio.reset();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bio.reset();

Not necessary to reset manually, the destructor does that automatically when it goes out of scope.

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(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
PEM_read_bio_X509(NodeBIO::NewFixed(combined_root_certs[i].c_str(),
PEM_read_bio_X509(NodeBIO::NewFixed(combined_root_certs[i].data(),

reinterpret_cast<const uint8_t*>(root_certs[i]))
.ToLocal(&result[i])) {
env->isolate(),
reinterpret_cast<const uint8_t*>(combined_root_certs[i].c_str()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor optimization:

Suggested change
reinterpret_cast<const uint8_t*>(combined_root_certs[i].c_str()))
reinterpret_cast<const uint8_t*>(combined_root_certs[i].data()),
v8::NewStringType::kNormal,
combined_root_certs[i].size())

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++. crypto Issues and PRs related to the crypto subsystem. needs-ci PRs that need a full CI run.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants