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 <[email protected]> Reviewed-By: James M Snell <[email protected]>
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)