Skip to content

Commit b50fc42

Browse files
joyeecheungtargos
authored andcommitted
crypto: support --use-system-ca on non-Windows and non-macOS
On other platforms, load from the OpenSSL default certificate file and diretory. This is different from --use-openssl-ca in that it caches the certificates on first load, instead of always reading from disk every time a new root store is needed. When used together with the statically-linked OpenSSL, the default configuration usually leads to this behavior: - If SSL_CERT_FILE is used, load from SSL_CERT_FILE. Otherwise load from /etc/ssl/cert.pem - If SSL_CERT_DIR is used, load from all the files under SSL_CERT_DIR. Otherwise, load from all the files under /etc/ssl/certs PR-URL: #57009 Reviewed-By: Richard Lau <rlau@redhat.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 5b2dfad commit b50fc42

File tree

3 files changed

+111
-15
lines changed

3 files changed

+111
-15
lines changed

doc/api/cli.md

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2838,12 +2838,15 @@ The following values are valid for `mode`:
28382838
### `--use-system-ca`
28392839

28402840
Node.js uses the trusted CA certificates present in the system store along with
2841-
the `--use-bundled-ca`, `--use-openssl-ca` options.
2841+
the `--use-bundled-ca` option and the `NODE_EXTRA_CA_CERTS` environment variable.
2842+
On platforms other than Windows and macOS, this loads certificates from the directory
2843+
and file trusted by OpenSSL, similar to `--use-openssl-ca`, with the difference being
2844+
that it caches the certificates after first load.
28422845

2843-
This option is only supported on Windows and macOS, and the certificate trust policy
2844-
is planned to follow [Chromium's policy for locally trusted certificates][]:
2846+
On Windows and macOS, the certificate trust policy is planned to follow
2847+
[Chromium's policy for locally trusted certificates][]:
28452848

2846-
On macOS, the following certifcates are trusted:
2849+
On macOS, the following settings are respected:
28472850

28482851
* Default and System Keychains
28492852
* Trust:
@@ -2853,8 +2856,8 @@ On macOS, the following certifcates are trusted:
28532856
* Any certificate where the “When using this certificate” flag is set to “Never Trust” or
28542857
* Any certificate where the “Secure Sockets Layer (SSL)” flag is set to “Never Trust.”
28552858

2856-
On Windows, the following certificates are currently trusted (unlike
2857-
Chromium's policy, distrust is not currently supported):
2859+
On Windows, the following settings are respected (unlike Chromium's policy, distrust
2860+
and intermediate CA are not currently supported):
28582861

28592862
* Local Machine (accessed via `certlm.msc`)
28602863
* Trust:
@@ -2869,8 +2872,19 @@ Chromium's policy, distrust is not currently supported):
28692872
* Trusted Root Certification Authorities
28702873
* Enterprise Trust -> Group Policy -> Trusted Root Certification Authorities
28712874

2872-
On any supported system, Node.js would check that the certificate's key usage and extended key
2873-
usage are consistent with TLS use cases before using it for server authentication.
2875+
On Windows and macOS, Node.js would check that the user settings for the certificates
2876+
do not forbid them for TLS server authentication before using them.
2877+
2878+
On other systems, Node.js loads certificates from the default certificate file
2879+
(typically `/etc/ssl/cert.pem`) and default certificate directory (typically
2880+
`/etc/ssl/certs`) that the version of OpenSSL that Node.js links to respects.
2881+
This typically works with the convention on major Linux distributions and other
2882+
Unix-like systems. If the overriding OpenSSL environment variables
2883+
(typically `SSL_CERT_FILE` and `SSL_CERT_DIR`, depending on the configuration
2884+
of the OpenSSL that Node.js links to) are set, the specified paths will be used to load
2885+
certificates instead. These environment variables can be used as workarounds
2886+
if the conventional paths used by the version of OpenSSL Node.js links to are
2887+
not consistent with the system configuration that the users have for some reason.
28742888

28752889
### `--v8-options`
28762890

@@ -3512,7 +3526,8 @@ variable is ignored.
35123526
added: v7.7.0
35133527
-->
35143528

3515-
If `--use-openssl-ca` is enabled, this overrides and sets OpenSSL's directory
3529+
If `--use-openssl-ca` is enabled, or if `--use-system-ca` is enabled on
3530+
platforms other than macOS and Windows, this overrides and sets OpenSSL's directory
35163531
containing trusted certificates.
35173532

35183533
Be aware that unless the child environment is explicitly set, this environment
@@ -3525,7 +3540,8 @@ may cause them to trust the same CAs as node.
35253540
added: v7.7.0
35263541
-->
35273542

3528-
If `--use-openssl-ca` is enabled, this overrides and sets OpenSSL's file
3543+
If `--use-openssl-ca` is enabled, or if `--use-system-ca` is enabled on
3544+
platforms other than macOS and Windows, this overrides and sets OpenSSL's file
35293545
containing trusted certificates.
35303546

35313547
Be aware that unless the child environment is explicitly set, this environment

src/crypto/crypto_context.cc

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ int SSL_CTX_use_certificate_chain(SSL_CTX* ctx,
223223
issuer);
224224
}
225225

226-
unsigned long LoadCertsFromFile( // NOLINT(runtime/int)
226+
static unsigned long LoadCertsFromFile( // NOLINT(runtime/int)
227227
std::vector<X509*>* certs,
228228
const char* file) {
229229
MarkPopErrorOnReturn mark_pop_error_on_return;
@@ -645,6 +645,74 @@ void ReadWindowsCertificates(
645645
}
646646
#endif
647647

648+
static void LoadCertsFromDir(std::vector<X509*>* certs,
649+
std::string_view cert_dir) {
650+
uv_fs_t dir_req;
651+
auto cleanup = OnScopeLeave([&dir_req]() { uv_fs_req_cleanup(&dir_req); });
652+
int err = uv_fs_scandir(nullptr, &dir_req, cert_dir.data(), 0, nullptr);
653+
if (err < 0) {
654+
fprintf(stderr,
655+
"Cannot open directory %s to load OpenSSL certificates.\n",
656+
cert_dir.data());
657+
return;
658+
}
659+
660+
uv_fs_t stats_req;
661+
auto cleanup_stats =
662+
OnScopeLeave([&stats_req]() { uv_fs_req_cleanup(&stats_req); });
663+
for (;;) {
664+
uv_dirent_t ent;
665+
666+
int r = uv_fs_scandir_next(&dir_req, &ent);
667+
if (r == UV_EOF) {
668+
break;
669+
}
670+
if (r < 0) {
671+
char message[64];
672+
uv_strerror_r(r, message, sizeof(message));
673+
fprintf(stderr,
674+
"Cannot scan directory %s to load OpenSSL certificates.\n",
675+
cert_dir.data());
676+
return;
677+
}
678+
679+
std::string file_path = std::string(cert_dir) + "/" + ent.name;
680+
int stats_r = uv_fs_stat(nullptr, &stats_req, file_path.c_str(), nullptr);
681+
if (stats_r == 0 &&
682+
(static_cast<uv_stat_t*>(stats_req.ptr)->st_mode & S_IFREG)) {
683+
LoadCertsFromFile(certs, file_path.c_str());
684+
}
685+
}
686+
}
687+
688+
// Loads CA certificates from the default certificate paths respected by
689+
// OpenSSL.
690+
void GetOpenSSLSystemCertificates(std::vector<X509*>* system_store_certs) {
691+
std::string cert_file;
692+
// While configurable when OpenSSL is built, this is usually SSL_CERT_FILE.
693+
if (!credentials::SafeGetenv(X509_get_default_cert_file_env(), &cert_file)) {
694+
// This is usually /etc/ssl/cert.pem if we are using the OpenSSL statically
695+
// linked and built with default configurations.
696+
cert_file = X509_get_default_cert_file();
697+
}
698+
699+
std::string cert_dir;
700+
// While configurable when OpenSSL is built, this is usually SSL_CERT_DIR.
701+
if (!credentials::SafeGetenv(X509_get_default_cert_dir_env(), &cert_dir)) {
702+
// This is usually /etc/ssl/certs if we are using the OpenSSL statically
703+
// linked and built with default configurations.
704+
cert_dir = X509_get_default_cert_dir();
705+
}
706+
707+
if (!cert_file.empty()) {
708+
LoadCertsFromFile(system_store_certs, cert_file.c_str());
709+
}
710+
711+
if (!cert_dir.empty()) {
712+
LoadCertsFromDir(system_store_certs, cert_dir.c_str());
713+
}
714+
}
715+
648716
static std::vector<X509*> InitializeBundledRootCertificates() {
649717
// Read the bundled certificates in node_root_certs.h into
650718
// bundled_root_certs_vector.
@@ -685,6 +753,9 @@ static std::vector<X509*> InitializeSystemStoreCertificates() {
685753
#endif
686754
#ifdef _WIN32
687755
ReadWindowsCertificates(&system_store_certs);
756+
#endif
757+
#if !defined(__APPLE__) && !defined(_WIN32)
758+
GetOpenSSLSystemCertificates(&system_store_certs);
688759
#endif
689760
return system_store_certs;
690761
}

test/parallel/test-native-certs.mjs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ import fixtures from '../common/fixtures.js';
77
import { it, beforeEach, afterEach, describe } from 'node:test';
88
import { once } from 'events';
99

10-
if (!common.isMacOS && !common.isWindows) {
11-
common.skip('--use-system-ca is only supported on macOS and Windows');
12-
}
13-
1410
if (!common.hasCrypto) {
1511
common.skip('requires crypto');
1612
}
@@ -34,6 +30,19 @@ if (!common.hasCrypto) {
3430
// $ $thumbprint = (Get-ChildItem -Path Cert:\CurrentUser\Root | \
3531
// Where-Object { $_.Subject -match "StartCom Certification Authority" }).Thumbprint
3632
// $ Remove-Item -Path "Cert:\CurrentUser\Root\$thumbprint"
33+
//
34+
// On Debian/Ubuntu:
35+
// 1. To add the certificate:
36+
// $ sudo cp test/fixtures/keys/fake-startcom-root-cert.pem \
37+
// /usr/local/share/ca-certificates/fake-startcom-root-cert.crt
38+
// $ sudo update-ca-certificates
39+
// 2. To remove the certificate
40+
// $ sudo rm /usr/local/share/ca-certificates/fake-startcom-root-cert.crt
41+
// $ sudo update-ca-certificates --fresh
42+
//
43+
// For other Unix-like systems, consult their manuals, there are usually
44+
// file-based processes similar to the Debian/Ubuntu one but with different
45+
// file locations and update commands.
3746
const handleRequest = (req, res) => {
3847
const path = req.url;
3948
switch (path) {

0 commit comments

Comments
 (0)