Skip to content

Commit 75f11ae

Browse files
joyeecheungaduh95
authored andcommittedMar 9, 2025
tls: implement tls.getCACertificates()
To accompany --use-system-ca, this adds a new API that allows querying various kinds of CA certificates. - If the first argument `type` is `"default"` or undefined, it returns the CA certificates that will be used by Node.js TLS clients by default, which includes the Mozilla CA if --use-bundled-ca is enabled or --use-openssl-ca is not enabled, and the system certificates if --use-system-ca is enabled, and the extra certificates if NODE_EXTRA_CA_CERTS is used. - If `type` is `"system"` this returns the system certificates, regardless of whether --use-system-ca is enabeld or not. - If `type` is `"bundled"` this is the same as `tls.rootCertificates` and returns the Mozilla CA certificates. - If `type` is `"extra"` this returns the certificates parsed from the path specified by NODE_EXTRA_CA_CERTS. Drive-by: remove the inaccurate description in `tls.rootCertificates` about including system certificates, since it in fact does not include them, and also it is contradicting the previous description about `tls.rootCertificates` always returning the Mozilla CA store and staying the same across platforms. PR-URL: #57107 Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 4d86a42 commit 75f11ae

16 files changed

+468
-26
lines changed
 

‎doc/api/tls.md

+49-6
Original file line numberDiff line numberDiff line change
@@ -1985,9 +1985,13 @@ changes:
19851985
* `allowPartialTrustChain` {boolean} Treat intermediate (non-self-signed)
19861986
certificates in the trust CA certificate list as trusted.
19871987
* `ca` {string|string\[]|Buffer|Buffer\[]} Optionally override the trusted CA
1988-
certificates. Default is to trust the well-known CAs curated by Mozilla.
1989-
Mozilla's CAs are completely replaced when CAs are explicitly specified
1990-
using this option. The value can be a string or `Buffer`, or an `Array` of
1988+
certificates. If not specified, the CA certificates trusted by default are
1989+
the same as the ones returned by [`tls.getCACertificates()`][] using the
1990+
`default` type. If specified, the default list would be completely replaced
1991+
(instead of being concatenated) by the certificates in the `ca` option.
1992+
Users need to concatenate manually if they wish to add additional certificates
1993+
instead of completely overriding the default.
1994+
The value can be a string or `Buffer`, or an `Array` of
19911995
strings and/or `Buffer`s. Any string or `Buffer` can contain multiple PEM
19921996
CAs concatenated together. The peer's certificate must be chainable to a CA
19931997
trusted by the server for the connection to be authenticated. When using
@@ -2001,7 +2005,6 @@ changes:
20012005
provided.
20022006
For PEM encoded certificates, supported types are "TRUSTED CERTIFICATE",
20032007
"X509 CERTIFICATE", and "CERTIFICATE".
2004-
See also [`tls.rootCertificates`][].
20052008
* `cert` {string|string\[]|Buffer|Buffer\[]} Cert chains in PEM format. One
20062009
cert chain should be provided per private key. Each cert chain should
20072010
consist of the PEM formatted certificate for a provided private `key`,
@@ -2364,6 +2367,39 @@ openssl pkcs12 -certpbe AES-256-CBC -export -out client-cert.pem \
23642367
The server can be tested by connecting to it using the example client from
23652368
[`tls.connect()`][].
23662369

2370+
## `tls.getCACertificates([type])`
2371+
2372+
<!-- YAML
2373+
added: REPLACEME
2374+
-->
2375+
2376+
* `type` {string|undefined} The type of CA certificates that will be returned. Valid values
2377+
are `"default"`, `"system"`, `"bundled"` and `"extra"`.
2378+
**Default:** `"default"`.
2379+
* Returns: {string\[]} An array of PEM-encoded certificates. The array may contain duplicates
2380+
if the same certificate is repeatedly stored in multiple sources.
2381+
2382+
Returns an array containing the CA certificates from various sources, depending on `type`:
2383+
2384+
* `"default"`: return the CA certificates that will be used by the Node.js TLS clients by default.
2385+
* When [`--use-bundled-ca`][] is enabled (default), or [`--use-openssl-ca`][] is not enabled,
2386+
this would include CA certificates from the bundled Mozilla CA store.
2387+
* When [`--use-system-ca`][] is enabled, this would also include certificates from the system's
2388+
trusted store.
2389+
* When [`NODE_EXTRA_CA_CERTS`][] is used, this would also include certificates loaded from the specified
2390+
file.
2391+
* `"system"`: return the CA certificates that are loaded from the system's trusted store, according
2392+
to rules set by [`--use-system-ca`][]. This can be used to get the certificates from the system
2393+
when [`--use-system-ca`][] is not enabled.
2394+
* `"bundled"`: return the CA certificates from the bundled Mozilla CA store. This would be the same
2395+
as [`tls.rootCertificates`][].
2396+
* `"extra"`: return the CA certificates loaded from [`NODE_EXTRA_CA_CERTS`][]. It's an empty array if
2397+
[`NODE_EXTRA_CA_CERTS`][] is not set.
2398+
2399+
<!-- YAML
2400+
added: v0.10.2
2401+
-->
2402+
23672403
## `tls.getCiphers()`
23682404

23692405
<!-- YAML
@@ -2400,8 +2436,10 @@ from the bundled Mozilla CA store as supplied by the current Node.js version.
24002436
The bundled CA store, as supplied by Node.js, is a snapshot of Mozilla CA store
24012437
that is fixed at release time. It is identical on all supported platforms.
24022438

2403-
On macOS if `--use-system-ca` is passed then trusted certificates
2404-
from the user and system keychains are also included.
2439+
To get the actual CA certificates used by the current Node.js instance, which
2440+
may include certificates loaded from the system store (if `--use-system-ca` is used)
2441+
or loaded from a file indicated by `NODE_EXTRA_CA_CERTS`, use
2442+
[`tls.getCACertificates()`][].
24052443

24062444
## `tls.DEFAULT_ECDH_CURVE`
24072445

@@ -2487,7 +2525,11 @@ added:
24872525
[`'secureConnection'`]: #event-secureconnection
24882526
[`'session'`]: #event-session
24892527
[`--tls-cipher-list`]: cli.md#--tls-cipher-listlist
2528+
[`--use-bundled-ca`]: cli.md#--use-bundled-ca---use-openssl-ca
2529+
[`--use-openssl-ca`]: cli.md#--use-bundled-ca---use-openssl-ca
2530+
[`--use-system-ca`]: cli.md#--use-system-ca
24902531
[`Duplex`]: stream.md#class-streamduplex
2532+
[`NODE_EXTRA_CA_CERTS`]: cli.md#node_extra_ca_certsfile
24912533
[`NODE_OPTIONS`]: cli.md#node_optionsoptions
24922534
[`SSL_export_keying_material`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_export_keying_material.html
24932535
[`SSL_get_version`]: https://www.openssl.org/docs/man1.1.1/man3/SSL_get_version.html
@@ -2516,6 +2558,7 @@ added:
25162558
[`tls.createSecureContext()`]: #tlscreatesecurecontextoptions
25172559
[`tls.createSecurePair()`]: #tlscreatesecurepaircontext-isserver-requestcert-rejectunauthorized-options
25182560
[`tls.createServer()`]: #tlscreateserveroptions-secureconnectionlistener
2561+
[`tls.getCACertificates()`]: #tlsgetcacertificatestype
25192562
[`tls.getCiphers()`]: #tlsgetciphers
25202563
[`tls.rootCertificates`]: #tlsrootcertificates
25212564
[`x509.checkHost()`]: crypto.md#x509checkhostname-options

‎lib/tls.js

+79-9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
const {
2525
Array,
2626
ArrayIsArray,
27+
// eslint-disable-next-line no-restricted-syntax
28+
ArrayPrototypePush,
2729
JSONParse,
2830
ObjectDefineProperty,
2931
ObjectFreeze,
@@ -34,6 +36,7 @@ const {
3436
ERR_TLS_CERT_ALTNAME_FORMAT,
3537
ERR_TLS_CERT_ALTNAME_INVALID,
3638
ERR_OUT_OF_RANGE,
39+
ERR_INVALID_ARG_VALUE,
3740
} = require('internal/errors').codes;
3841
const internalUtil = require('internal/util');
3942
internalUtil.assertCrypto();
@@ -44,12 +47,18 @@ const {
4447

4548
const net = require('net');
4649
const { getOptionValue } = require('internal/options');
47-
const { getRootCertificates, getSSLCiphers } = internalBinding('crypto');
50+
const {
51+
getBundledRootCertificates,
52+
getExtraCACertificates,
53+
getSystemCACertificates,
54+
getSSLCiphers,
55+
} = internalBinding('crypto');
4856
const { Buffer } = require('buffer');
4957
const { canonicalizeIP } = internalBinding('cares_wrap');
5058
const _tls_common = require('_tls_common');
5159
const _tls_wrap = require('_tls_wrap');
5260
const { createSecurePair } = require('internal/tls/secure-pair');
61+
const { validateString } = require('internal/validators');
5362

5463
// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
5564
// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
@@ -85,23 +94,84 @@ exports.getCiphers = internalUtil.cachedResult(
8594
() => internalUtil.filterDuplicateStrings(getSSLCiphers(), true),
8695
);
8796

88-
let rootCertificates;
97+
let bundledRootCertificates;
98+
function cacheBundledRootCertificates() {
99+
bundledRootCertificates ||= ObjectFreeze(getBundledRootCertificates());
89100

90-
function cacheRootCertificates() {
91-
rootCertificates = ObjectFreeze(getRootCertificates());
101+
return bundledRootCertificates;
92102
}
93103

94104
ObjectDefineProperty(exports, 'rootCertificates', {
95105
__proto__: null,
96106
configurable: false,
97107
enumerable: true,
98-
get: () => {
99-
// Out-of-line caching to promote inlining the getter.
100-
if (!rootCertificates) cacheRootCertificates();
101-
return rootCertificates;
102-
},
108+
get: cacheBundledRootCertificates,
103109
});
104110

111+
let extraCACertificates;
112+
function cacheExtraCACertificates() {
113+
extraCACertificates ||= ObjectFreeze(getExtraCACertificates());
114+
115+
return extraCACertificates;
116+
}
117+
118+
let systemCACertificates;
119+
function cacheSystemCACertificates() {
120+
systemCACertificates ||= ObjectFreeze(getSystemCACertificates());
121+
122+
return systemCACertificates;
123+
}
124+
125+
let defaultCACertificates;
126+
function cacheDefaultCACertificates() {
127+
if (defaultCACertificates) { return defaultCACertificates; }
128+
defaultCACertificates = [];
129+
130+
if (!getOptionValue('--use-openssl-ca')) {
131+
const bundled = cacheBundledRootCertificates();
132+
for (let i = 0; i < bundled.length; ++i) {
133+
ArrayPrototypePush(defaultCACertificates, bundled[i]);
134+
}
135+
if (getOptionValue('--use-system-ca')) {
136+
const system = cacheSystemCACertificates();
137+
for (let i = 0; i < system.length; ++i) {
138+
139+
ArrayPrototypePush(defaultCACertificates, system[i]);
140+
}
141+
}
142+
}
143+
144+
if (process.env.NODE_EXTRA_CA_CERTS) {
145+
const extra = cacheExtraCACertificates();
146+
for (let i = 0; i < extra.length; ++i) {
147+
148+
ArrayPrototypePush(defaultCACertificates, extra[i]);
149+
}
150+
}
151+
152+
ObjectFreeze(defaultCACertificates);
153+
return defaultCACertificates;
154+
}
155+
156+
// TODO(joyeecheung): support X509Certificate output?
157+
function getCACertificates(type = 'default') {
158+
validateString(type, 'type');
159+
160+
switch (type) {
161+
case 'default':
162+
return cacheDefaultCACertificates();
163+
case 'bundled':
164+
return cacheBundledRootCertificates();
165+
case 'system':
166+
return cacheSystemCACertificates();
167+
case 'extra':
168+
return cacheExtraCACertificates();
169+
default:
170+
throw new ERR_INVALID_ARG_VALUE('type', type);
171+
}
172+
}
173+
exports.getCACertificates = getCACertificates;
174+
105175
// Convert protocols array into valid OpenSSL protocols list
106176
// ("\x06spdy/2\x08http/1.1\x08http/1.0")
107177
function convertProtocols(protocols) {

‎src/crypto/crypto_context.cc

+72-9
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@ using ncrypto::MarkPopErrorOnReturn;
4242
using ncrypto::SSLPointer;
4343
using ncrypto::StackOfX509;
4444
using ncrypto::X509Pointer;
45+
using ncrypto::X509View;
4546
using v8::Array;
4647
using v8::ArrayBufferView;
4748
using v8::Boolean;
4849
using v8::Context;
4950
using v8::DontDelete;
51+
using v8::EscapableHandleScope;
5052
using v8::Exception;
5153
using v8::External;
5254
using v8::FunctionCallbackInfo;
@@ -57,7 +59,9 @@ using v8::Integer;
5759
using v8::Isolate;
5860
using v8::JustVoid;
5961
using v8::Local;
62+
using v8::LocalVector;
6063
using v8::Maybe;
64+
using v8::MaybeLocal;
6165
using v8::Nothing;
6266
using v8::Object;
6367
using v8::PropertyAttribute;
@@ -672,9 +676,6 @@ static void LoadCertsFromDir(std::vector<X509*>* certs,
672676
return;
673677
}
674678

675-
uv_fs_t stats_req;
676-
auto cleanup_stats =
677-
OnScopeLeave([&stats_req]() { uv_fs_req_cleanup(&stats_req); });
678679
for (;;) {
679680
uv_dirent_t ent;
680681

@@ -691,12 +692,14 @@ static void LoadCertsFromDir(std::vector<X509*>* certs,
691692
return;
692693
}
693694

695+
uv_fs_t stats_req;
694696
std::string file_path = std::string(cert_dir) + "/" + ent.name;
695697
int stats_r = uv_fs_stat(nullptr, &stats_req, file_path.c_str(), nullptr);
696698
if (stats_r == 0 &&
697699
(static_cast<uv_stat_t*>(stats_req.ptr)->st_mode & S_IFREG)) {
698700
LoadCertsFromFile(certs, file_path.c_str());
699701
}
702+
uv_fs_req_cleanup(&stats_req);
700703
}
701704
}
702705

@@ -775,7 +778,7 @@ static std::vector<X509*> InitializeSystemStoreCertificates() {
775778
return system_store_certs;
776779
}
777780

778-
static std::vector<X509*>& GetSystemStoreRootCertificates() {
781+
static std::vector<X509*>& GetSystemStoreCACertificates() {
779782
// Use function-local static to guarantee thread safety.
780783
static std::vector<X509*> system_store_certs =
781784
InitializeSystemStoreCertificates();
@@ -847,7 +850,7 @@ X509_STORE* NewRootCertStore() {
847850
CHECK_EQ(1, X509_STORE_add_cert(store, cert));
848851
}
849852
if (per_process::cli_options->use_system_ca) {
850-
for (X509* cert : GetSystemStoreRootCertificates()) {
853+
for (X509* cert : GetSystemStoreCACertificates()) {
851854
CHECK_EQ(1, X509_STORE_add_cert(store, cert));
852855
}
853856
}
@@ -869,7 +872,7 @@ void CleanupCachedRootCertificates() {
869872
}
870873
}
871874
if (has_cached_system_root_certs.load()) {
872-
for (X509* cert : GetSystemStoreRootCertificates()) {
875+
for (X509* cert : GetSystemStoreCACertificates()) {
873876
X509_free(cert);
874877
}
875878
}
@@ -881,7 +884,7 @@ void CleanupCachedRootCertificates() {
881884
}
882885
}
883886

884-
void GetRootCertificates(const FunctionCallbackInfo<Value>& args) {
887+
void GetBundledRootCertificates(const FunctionCallbackInfo<Value>& args) {
885888
Environment* env = Environment::GetCurrent(args);
886889
Local<Value> result[arraysize(root_certs)];
887890

@@ -898,6 +901,58 @@ void GetRootCertificates(const FunctionCallbackInfo<Value>& args) {
898901
Array::New(env->isolate(), result, arraysize(root_certs)));
899902
}
900903

904+
MaybeLocal<Array> X509sToArrayOfStrings(Environment* env,
905+
const std::vector<X509*>& certs) {
906+
ClearErrorOnReturn clear_error_on_return;
907+
EscapableHandleScope scope(env->isolate());
908+
909+
LocalVector<Value> result(env->isolate(), certs.size());
910+
for (size_t i = 0; i < certs.size(); ++i) {
911+
X509View view(certs[i]);
912+
auto pem_bio = view.toPEM();
913+
if (!pem_bio) {
914+
ThrowCryptoError(env, ERR_get_error(), "X509 to PEM conversion");
915+
return MaybeLocal<Array>();
916+
}
917+
918+
char* pem_data = nullptr;
919+
auto pem_size = BIO_get_mem_data(pem_bio.get(), &pem_data);
920+
if (pem_size <= 0 || !pem_data) {
921+
ThrowCryptoError(env, ERR_get_error(), "Reading PEM data");
922+
return MaybeLocal<Array>();
923+
}
924+
// PEM is base64-encoded, so it must be one-byte.
925+
if (!String::NewFromOneByte(env->isolate(),
926+
reinterpret_cast<uint8_t*>(pem_data),
927+
v8::NewStringType::kNormal,
928+
pem_size)
929+
.ToLocal(&result[i])) {
930+
return MaybeLocal<Array>();
931+
}
932+
}
933+
return scope.Escape(Array::New(env->isolate(), result.data(), result.size()));
934+
}
935+
936+
void GetSystemCACertificates(const FunctionCallbackInfo<Value>& args) {
937+
Environment* env = Environment::GetCurrent(args);
938+
Local<Array> results;
939+
if (X509sToArrayOfStrings(env, GetSystemStoreCACertificates())
940+
.ToLocal(&results)) {
941+
args.GetReturnValue().Set(results);
942+
}
943+
}
944+
945+
void GetExtraCACertificates(const FunctionCallbackInfo<Value>& args) {
946+
Environment* env = Environment::GetCurrent(args);
947+
if (extra_root_certs_file.empty()) {
948+
return args.GetReturnValue().Set(Array::New(env->isolate()));
949+
}
950+
Local<Array> results;
951+
if (X509sToArrayOfStrings(env, GetExtraCACertificates()).ToLocal(&results)) {
952+
args.GetReturnValue().Set(results);
953+
}
954+
}
955+
901956
bool SecureContext::HasInstance(Environment* env, const Local<Value>& value) {
902957
return GetConstructorTemplate(env)->HasInstance(value);
903958
}
@@ -981,8 +1036,14 @@ void SecureContext::Initialize(Environment* env, Local<Object> target) {
9811036
GetConstructorTemplate(env),
9821037
SetConstructorFunctionFlag::NONE);
9831038

1039+
SetMethodNoSideEffect(context,
1040+
target,
1041+
"getBundledRootCertificates",
1042+
GetBundledRootCertificates);
1043+
SetMethodNoSideEffect(
1044+
context, target, "getSystemCACertificates", GetSystemCACertificates);
9841045
SetMethodNoSideEffect(
985-
context, target, "getRootCertificates", GetRootCertificates);
1046+
context, target, "getExtraCACertificates", GetExtraCACertificates);
9861047
}
9871048

9881049
void SecureContext::RegisterExternalReferences(
@@ -1022,7 +1083,9 @@ void SecureContext::RegisterExternalReferences(
10221083

10231084
registry->Register(CtxGetter);
10241085

1025-
registry->Register(GetRootCertificates);
1086+
registry->Register(GetBundledRootCertificates);
1087+
registry->Register(GetSystemCACertificates);
1088+
registry->Register(GetExtraCACertificates);
10261089
}
10271090

10281091
SecureContext* SecureContext::Create(Environment* env) {

‎test/common/tls.js

+13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
'use strict';
44
const crypto = require('crypto');
55
const net = require('net');
6+
const assert = require('assert');
67

78
exports.ccs = Buffer.from('140303000101', 'hex');
89

@@ -173,4 +174,16 @@ function P_hash(algo, secret, seed, size) {
173174
return result;
174175
}
175176

177+
exports.assertIsCAArray = function assertIsCAArray(certs) {
178+
assert(Array.isArray(certs));
179+
assert(certs.length > 0);
180+
181+
// The certificates looks PEM-encoded.
182+
for (const cert of certs) {
183+
const trimmed = cert.trim();
184+
assert.match(trimmed, /^-----BEGIN CERTIFICATE-----/);
185+
assert.match(trimmed, /-----END CERTIFICATE-----$/);
186+
}
187+
};
188+
176189
exports.TestTLSSocket = TestTLSSocket;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use strict';
2+
3+
const tls = require('tls');
4+
const assert = require('assert');
5+
6+
const defaultSet = new Set(tls.getCACertificates('default'));
7+
const extraSet = new Set(tls.getCACertificates('extra'));
8+
console.log(defaultSet.size, 'default certificates');
9+
console.log(extraSet.size, 'extra certificates')
10+
11+
// Parent process is supposed to call this with
12+
// NODE_EXTRA_CA_CERTS set to test/fixtures/keys/ca1-cert.pem.
13+
assert.strictEqual(extraSet.size, 1);
14+
15+
// Check that default set is a super set of extra set.
16+
assert.deepStrictEqual(defaultSet.intersection(extraSet),
17+
extraSet);
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use strict';
2+
// This fixture just writes tls.getCACertificates() outputs to process.env.CA_OUT
3+
const tls = require('tls');
4+
const fs = require('fs');
5+
const assert = require('assert');
6+
assert(process.env.CA_TYPE);
7+
assert(process.env.CA_OUT);
8+
9+
const certs = tls.getCACertificates(process.env.CA_TYPE);
10+
11+
fs.writeFileSync(process.env.CA_OUT, JSON.stringify(certs), 'utf8');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use strict';
2+
// Flags: --no-use-openssl-ca
3+
// This tests that tls.getCACertificates() returns the bundled
4+
// certificates correctly.
5+
6+
const common = require('../common');
7+
if (!common.hasCrypto) common.skip('missing crypto');
8+
9+
const assert = require('assert');
10+
const tls = require('tls');
11+
12+
const defaultSet = new Set(tls.getCACertificates('default'));
13+
const bundledSet = new Set(tls.getCACertificates('bundled'));
14+
15+
// When --use-openssl-ca is false (i.e. bundled CA is sued),
16+
// default is a superset of bundled certificates.
17+
assert.deepStrictEqual(defaultSet.intersection(bundledSet), bundledSet);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
// This tests that tls.getCACertificates() returns the bundled
3+
// certificates correctly.
4+
5+
const common = require('../common');
6+
if (!common.hasCrypto) common.skip('missing crypto');
7+
8+
const assert = require('assert');
9+
const tls = require('tls');
10+
const { assertIsCAArray } = require('../common/tls');
11+
12+
const certs = tls.getCACertificates('bundled');
13+
assertIsCAArray(certs);
14+
15+
// It's the same as tls.rootCertificates - both are
16+
// Mozilla CA stores across platform.
17+
assert.strictEqual(certs, tls.rootCertificates);
18+
19+
// It's cached on subsequent accesses.
20+
assert.strictEqual(certs, tls.getCACertificates('bundled'));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
// This tests that tls.getCACertificates() returns the default
4+
// certificates correctly.
5+
6+
const common = require('../common');
7+
if (!common.hasCrypto) common.skip('missing crypto');
8+
9+
const assert = require('assert');
10+
const tls = require('tls');
11+
const { assertIsCAArray } = require('../common/tls');
12+
13+
const certs = tls.getCACertificates();
14+
assertIsCAArray(certs);
15+
16+
const certs2 = tls.getCACertificates('default');
17+
assert.strictEqual(certs, certs2);
18+
19+
// It's cached on subsequent accesses.
20+
assert.strictEqual(certs, tls.getCACertificates('default'));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
// This tests that tls.getCACertificates() throws error when being
4+
// passed an invalid argument.
5+
6+
const common = require('../common');
7+
if (!common.hasCrypto) common.skip('missing crypto');
8+
9+
const assert = require('assert');
10+
const tls = require('tls');
11+
12+
for (const invalid of [1, null, () => {}, true]) {
13+
assert.throws(() => tls.getCACertificates(invalid), {
14+
code: 'ERR_INVALID_ARG_TYPE'
15+
});
16+
}
17+
18+
assert.throws(() => tls.getCACertificates('test'), {
19+
code: 'ERR_INVALID_ARG_VALUE'
20+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
// This tests that tls.getCACertificates('extra') returns an empty
3+
// array if NODE_EXTRA_CA_CERTS is empty.
4+
5+
const common = require('../common');
6+
if (!common.hasCrypto) common.skip('missing crypto');
7+
8+
const tmpdir = require('../common/tmpdir');
9+
const fs = require('fs');
10+
11+
const assert = require('assert');
12+
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
13+
const fixtures = require('../common/fixtures');
14+
15+
tmpdir.refresh();
16+
const certsJSON = tmpdir.resolve('certs.json');
17+
18+
// If NODE_EXTRA_CA_CERTS is not set, it should be an empty array.
19+
spawnSyncAndExitWithoutError(process.execPath, [fixtures.path('tls-get-ca-certificates.js')], {
20+
env: {
21+
...process.env,
22+
NODE_EXTRA_CA_CERTS: undefined,
23+
CA_TYPE: 'extra',
24+
CA_OUT: certsJSON,
25+
}
26+
});
27+
28+
const parsed = JSON.parse(fs.readFileSync(certsJSON, 'utf-8'));
29+
assert.deepStrictEqual(parsed, []);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use strict';
2+
// This tests that tls.getCACertificates('defulat') returns a superset
3+
// of tls.getCACertificates('extra') when NODE_EXTRA_CA_CERTS is used.
4+
5+
const common = require('../common');
6+
if (!common.hasCrypto) common.skip('missing crypto');
7+
8+
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
9+
const fixtures = require('../common/fixtures');
10+
11+
spawnSyncAndExitWithoutError(process.execPath, [fixtures.path('tls-check-extra-ca-certificates.js')], {
12+
env: {
13+
...process.env,
14+
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'ca1-cert.pem'),
15+
}
16+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
// This tests that tls.getCACertificates('extra') returns the extra
3+
// certificates from NODE_EXTRA_CA_CERTS correctly.
4+
5+
const common = require('../common');
6+
7+
if (!common.hasCrypto) common.skip('missing crypto');
8+
9+
const tmpdir = require('../common/tmpdir');
10+
const fs = require('fs');
11+
const assert = require('assert');
12+
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
13+
const fixtures = require('../common/fixtures');
14+
15+
tmpdir.refresh();
16+
const certsJSON = tmpdir.resolve('certs.json');
17+
18+
// If NODE_EXTRA_CA_CERTS is set, it should contain a list of certificates.
19+
spawnSyncAndExitWithoutError(process.execPath, [fixtures.path('tls-get-ca-certificates.js')], {
20+
env: {
21+
...process.env,
22+
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'ca1-cert.pem'),
23+
CA_TYPE: 'extra',
24+
CA_OUT: certsJSON,
25+
}
26+
});
27+
28+
const parsed = JSON.parse(fs.readFileSync(certsJSON, 'utf-8'));
29+
assert.deepStrictEqual(parsed, [fixtures.readKey('ca1-cert.pem', 'utf8')]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict';
2+
3+
// This tests that tls.getCACertificates() returns the system
4+
// certificates correctly when --use-system-ca is disabled.
5+
6+
const common = require('../common');
7+
if (!common.hasCrypto) common.skip('missing crypto');
8+
9+
const tmpdir = require('../common/tmpdir');
10+
const fs = require('fs');
11+
12+
const assert = require('assert');
13+
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
14+
const fixtures = require('../common/fixtures');
15+
const tls = require('tls');
16+
17+
const certs = tls.getCACertificates('system');
18+
if (certs.length === 0) {
19+
common.skip('No trusted system certificates installed. Skip.');
20+
}
21+
22+
tmpdir.refresh();
23+
const certsJSON = tmpdir.resolve('certs.json');
24+
spawnSyncAndExitWithoutError(process.execPath, [
25+
'--no-use-system-ca',
26+
fixtures.path('tls-get-ca-certificates.js'),
27+
], {
28+
env: {
29+
...process.env,
30+
CA_TYPE: 'system',
31+
CA_OUT: certsJSON,
32+
}
33+
});
34+
35+
const parsed = JSON.parse(fs.readFileSync(certsJSON, 'utf-8'));
36+
assert.deepStrictEqual(parsed, certs);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict';
2+
// Flags: --use-system-ca
3+
// This tests that tls.getCACertificates() returns the system
4+
// certificates correctly.
5+
6+
const common = require('../common');
7+
if (!common.hasCrypto) common.skip('missing crypto');
8+
9+
const assert = require('assert');
10+
const tls = require('tls');
11+
const { assertIsCAArray } = require('../common/tls');
12+
13+
const systemCerts = tls.getCACertificates('system');
14+
// Usually Windows come with some certificates installed by default.
15+
// This can't be said about other systems, in that case check that
16+
// at least systemCerts is an array (which may be empty).
17+
if (common.isWindows) {
18+
assertIsCAArray(systemCerts);
19+
} else {
20+
assert(Array.isArray(systemCerts));
21+
}
22+
23+
// When --use-system-ca is true, default is a superset of system
24+
// certificates.
25+
const defaultCerts = tls.getCACertificates('default');
26+
assert(defaultCerts.length >= systemCerts.length);
27+
const defaultSet = new Set(defaultCerts);
28+
const systemSet = new Set(systemCerts);
29+
assert.deepStrictEqual(defaultSet.intersection(systemSet), systemSet);
30+
31+
// It's cached on subsequent accesses.
32+
assert.strictEqual(systemCerts, tls.getCACertificates('system'));

‎test/testpy/__init__.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,22 @@ def GetCommand(self):
6868
# is currently when Node is configured --without-ssl and the tests should
6969
# still be runnable but skip any tests that require ssl (which includes the
7070
# inspector related tests). Also, if there is no ssl support the options
71-
# '--use-bundled-ca' and '--use-openssl-ca' will also cause a similar
72-
# failure so such tests are also skipped.
71+
# '--use-bundled-ca', '--use-system-ca' and '--use-openssl-ca' will also
72+
# cause a similar failure so such tests are also skipped.
7373
if (any(flag.startswith('--inspect') for flag in flags) and
7474
not self.context.v8_enable_inspector):
7575
print(': Skipping as node was compiled without inspector support')
7676
elif (('--use-bundled-ca' in flags or
7777
'--use-openssl-ca' in flags or
78+
'--use-system-ca' in flags or
79+
'--no-use-bundled-ca' in flags or
80+
'--no-use-openssl-ca' in flags or
81+
'--no-use-system-ca' in flags or
7882
'--tls-v1.0' in flags or
7983
'--tls-v1.1' in flags) and
8084
not self.context.node_has_crypto):
85+
# TODO(joyeecheung): add this to the status file variables so that we can
86+
# list the crypto dependency in the status files explicitly instead.
8187
print(': Skipping as node was compiled without crypto support')
8288
else:
8389
result += flags

0 commit comments

Comments
 (0)
Please sign in to comment.