Skip to content
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
c4ca69e
tls: add 'as' option to getCACertificates() for X509Certificate output
haramj Aug 4, 2025
ee04b40
doc: fix getCACertificates() types and remove invalid escapes
haramj Aug 4, 2025
1b8197b
doc: fix undefined reference for X509Certificate and format markdown
haramj Aug 4, 2025
187c86d
Update doc/api/tls.md
haramj Aug 4, 2025
4048b54
Update doc/api/tls.md
haramj Aug 4, 2025
eae9df7
Update doc/api/tls.md
haramj Aug 4, 2025
e013568
Update test/parallel/test-tls-get-ca-certificates-x509-option.js
haramj Aug 4, 2025
195125f
Update doc/api/tls.md
haramj Aug 4, 2025
b1eacfb
tls: validate 'as' option using validateOneOf
haramj Aug 5, 2025
603967f
test: expand getCACertificates() with 'as' option and invalid inputs
haramj Aug 5, 2025
36e8a2e
test: expand test-tls-get-ca-certificates-x509-option.js coverage
haramj Aug 5, 2025
62ec148
docs: add changes block for tls.getCACertificates ‘as’ option support
haramj Aug 5, 2025
361c799
doc: fix changes block
haramj Aug 5, 2025
358b2bd
tls: rename 'as' to 'format' in getCACertificates, default 'string'
haramj Aug 10, 2025
f9dfc62
doc: format tls.md
haramj Aug 10, 2025
cdf69dc
tls: simplify getCACertificates() API
haramj Aug 19, 2025
d978079
doc: fix broken anchor link for tls.getCACertificates()
haramj Aug 20, 2025
8cf2dbe
doc: update tls.md `X509Certificate` anchor
haramj Aug 31, 2025
e6de951
doc: fix tls.md lint error
haramj Aug 31, 2025
da83ccf
doc: fix x509certificate anchor
haramj Aug 31, 2025
10e9225
Update test/parallel/test-tls-get-ca-certificates-bundled.js
haramj Aug 31, 2025
ab76b14
doc: clarify format and return types in tls.getCACertificates()
haramj Aug 31, 2025
5e8dd7a
test: fix tls.getCACertificates bundled test to use format 'string'
haramj Sep 1, 2025
f84c311
test: keep shorthand arguments in getCACertificates tests
haramj Sep 2, 2025
a335306
test: Add final newline
haramj Sep 2, 2025
319841f
tls: Improve getCACertificates() caching and test
haramj Sep 7, 2025
fd0de18
tls: Test rollback and Update getCACertificates()
haramj Sep 9, 2025
95c07aa
doc: tls.md remove the white space
haramj Sep 10, 2025
e2879a5
tls: improve tls.getCACertificates() to simplify certificate handling
haramj Sep 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions doc/api/tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -2308,21 +2308,35 @@ const additionalCerts = ['-----BEGIN CERTIFICATE-----\n...'];
tls.setDefaultCACertificates([...currentCerts, ...additionalCerts]);
```

## `tls.getCACertificates([type])`
## `tls.getCACertificates([options])`

<!-- YAML
added:
- v23.10.0
- v22.15.0
-->

* `type` {string|undefined} The type of CA certificates that will be returned. Valid values
are `"default"`, `"system"`, `"bundled"` and `"extra"`.
**Default:** `"default"`.
* Returns: {string\[]} An array of PEM-encoded certificates. The array may contain duplicates
if the same certificate is repeatedly stored in multiple sources.

Returns an array containing the CA certificates from various sources, depending on `type`:
changes:
- version:
- REPLACEME
pr-url: https://github.com/nodejs/node/pull/59349
description: Added optional `options.type` parameter to `getCACertificates()`.
-->

* `options` {string|Object|undefined}
Optional. If a string, it is treated as the `type` of certificates to return.
If an object, it may contain:
* `type` {string} The type of CA certificates to return. One of `"default"`, `"system"`, `"bundled"`, or `"extra"`.
**Default:** `"default"`.
* `format` {string} The format of returned certificates. One of `"pem"`, `"der"`, or `"x509"`.
**Default:** `"pem"`.
* `"pem"` (alias: `"string"`): Returns an array of PEM-encoded certificate strings.
* `"der"` (alias: `"buffer"`): Returns an array of certificate data as `Buffer` objects in DER format.
* `"x509"`: Returns an array of [`X509Certificate`][x509certificate] instances.

* Returns: {Array}
An array of certificate data in the specified format:
* PEM strings when `format` is `"pem"` (or `"string"`).
* `Buffer` objects containing DER data when `format` is `"der"` (or `"buffer"`).
* [`X509Certificate`][x509certificate] instances when `format` is `"x509"`.

* `"default"`: return the CA certificates that will be used by the Node.js TLS clients by default.
* When [`--use-bundled-ca`][] is enabled (default), or [`--use-openssl-ca`][] is not enabled,
Expand All @@ -2331,11 +2345,14 @@ Returns an array containing the CA certificates from various sources, depending
trusted store.
* When [`NODE_EXTRA_CA_CERTS`][] is used, this would also include certificates loaded from the specified
file.

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

Copy link
Member

Choose a reason for hiding this comment

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

It seems the white spaces are still there? Can you remove them?

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay. I'll remove the white space

Copy link
Member Author

Choose a reason for hiding this comment

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

@joyeecheung Removing that white space will cause a format error in the document..

* `"system"`: return the CA certificates that are loaded from the system's trusted store, according
to rules set by [`--use-system-ca`][]. This can be used to get the certificates from the system
when [`--use-system-ca`][] is not enabled.

* `"bundled"`: return the CA certificates from the bundled Mozilla CA store. This would be the same
as [`tls.rootCertificates`][].

* `"extra"`: return the CA certificates loaded from [`NODE_EXTRA_CA_CERTS`][]. It's an empty array if
[`NODE_EXTRA_CA_CERTS`][] is not set.

Expand Down Expand Up @@ -2494,7 +2511,7 @@ added: v0.11.3
[`tls.connect()`]: #tlsconnectoptions-callback
[`tls.createSecureContext()`]: #tlscreatesecurecontextoptions
[`tls.createServer()`]: #tlscreateserveroptions-secureconnectionlistener
[`tls.getCACertificates()`]: #tlsgetcacertificatestype
[`tls.getCACertificates()`]: #tlsgetcacertificatesoptions
[`tls.getCiphers()`]: #tlsgetciphers
[`tls.rootCertificates`]: #tlsrootcertificates
[`x509.checkHost()`]: crypto.md#x509checkhostname-options
Expand All @@ -2503,3 +2520,4 @@ added: v0.11.3
[cipher list format]: https://www.openssl.org/docs/man1.1.1/man1/ciphers.html#CIPHER-LIST-FORMAT
[forward secrecy]: https://en.wikipedia.org/wiki/Perfect_forward_secrecy
[perfect forward secrecy]: #perfect-forward-secrecy
[x509certificate]: crypto.md#class-x509certificate
65 changes: 52 additions & 13 deletions lib/tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ const { Buffer } = require('buffer');
const { canonicalizeIP } = internalBinding('cares_wrap');
const tlsCommon = require('internal/tls/common');
const tlsWrap = require('internal/tls/wrap');
const { validateString } = require('internal/validators');
const {
validateOneOf,
validateString,
} = require('internal/validators');
const { X509Certificate } = require('crypto');

// Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations
// every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more
Expand Down Expand Up @@ -164,23 +168,58 @@ function cacheDefaultCACertificates() {
return defaultCACertificates;
}

// TODO(joyeecheung): support X509Certificate output?
function getCACertificates(type = 'default') {
function getCACertificates(options = {}) {
if (typeof options === 'string') {
options = { type: options };
} else if (typeof options !== 'object' || options === null) {
throw new ERR_INVALID_ARG_TYPE('options', ['string', 'object'], options);
}

const {
type = 'default',
format = 'string',
} = options;

validateString(type, 'type');
validateOneOf(format, 'format', ['string', 'buffer', 'x509']);

let certs;
switch (type) {
case 'default':
return cacheDefaultCACertificates();
case 'bundled':
return cacheBundledRootCertificates();
case 'system':
return cacheSystemCACertificates();
case 'extra':
return cacheExtraCACertificates();
default:
throw new ERR_INVALID_ARG_VALUE('type', type);
case 'default': certs = cacheDefaultCACertificates(); break;
case 'bundled': certs = cacheBundledRootCertificates(); break;
case 'system': certs = cacheSystemCACertificates(); break;
case 'extra': certs = cacheExtraCACertificates(); break;
default: throw new ERR_INVALID_ARG_VALUE('type', type);
}

if (format === 'string') {
// Return PEM strings directly
return certs.map((cert) => {
if (typeof cert === 'string') return cert;
if (Buffer.isBuffer(cert)) return cert.toString('ascii');
throw new ERR_INVALID_ARG_VALUE('cert', cert);
});
}

const buffers = certs.map((cert) => {
if (Buffer.isBuffer(cert)) return cert;
if (typeof cert === 'string') {
const base64 = cert
.replace(/-----BEGIN CERTIFICATE-----/g, '')
.replace(/-----END CERTIFICATE-----/g, '')
.replace(/\s+/g, '');
return Buffer.from(base64, 'base64');
}
throw new ERR_INVALID_ARG_VALUE('cert', cert);
});

if (format === 'buffer') {
return buffers;
}

return buffers.map((buf) => new X509Certificate(buf));
}

exports.getCACertificates = getCACertificates;

function setDefaultCACertificates(certs) {
Expand Down
16 changes: 9 additions & 7 deletions test/parallel/test-tls-get-ca-certificates-bundled.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use strict';
// This tests that tls.getCACertificates() returns the bundled
// certificates correctly.
// Test that tls.getCACertificates() returns the bundled certificates correctly.

const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
Expand All @@ -10,11 +9,14 @@ const tls = require('tls');
const { assertIsCAArray } = require('../common/tls');

const certs = tls.getCACertificates('bundled');
assert.deepStrictEqual(certs, tls.getCACertificates({ type: 'bundled' }));
assertIsCAArray(certs);

// It's the same as tls.rootCertificates - both are
// Mozilla CA stores across platform.
assert.strictEqual(certs, tls.rootCertificates);
assert.deepStrictEqual(certs, tls.rootCertificates);

// It's cached on subsequent accesses.
assert.strictEqual(certs, tls.getCACertificates('bundled'));
assert.deepStrictEqual(certs, tls.getCACertificates({ type: 'bundled', format: 'string' }));

const certs2 = tls.getCACertificates('bundled');
assertIsCAArray(certs2);

assert.deepStrictEqual(certs2, tls.rootCertificates);
18 changes: 10 additions & 8 deletions test/parallel/test-tls-get-ca-certificates-default.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
'use strict';

// This tests that tls.getCACertificates() returns the default
// certificates correctly.
// Test that tls.getCACertificates() returns the default certificates correctly.

const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
Expand All @@ -10,11 +8,15 @@ const assert = require('assert');
const tls = require('tls');
const { assertIsCAArray } = require('../common/tls');

const certs = tls.getCACertificates();
const certs = tls.getCACertificates({ format: 'string' });
assertIsCAArray(certs);

const certs2 = tls.getCACertificates('default');
assert.strictEqual(certs, certs2);
const certs2 = tls.getCACertificates({ type: 'default', format: 'string' });
assert.deepStrictEqual(certs, certs2);

assert.deepStrictEqual(certs, tls.getCACertificates({ type: 'default', format: 'string' }));

const certs3 = tls.getCACertificates('bundled');
assertIsCAArray(certs3);

// It's cached on subsequent accesses.
assert.strictEqual(certs, tls.getCACertificates('default'));
assert.deepStrictEqual(certs3, tls.rootCertificates);
25 changes: 13 additions & 12 deletions test/parallel/test-tls-get-ca-certificates-system.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';
// Flags: --use-system-ca
// This tests that tls.getCACertificates() returns the system
// certificates correctly.
// Test that tls.getCACertificates() returns system certificates correctly.

const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
Expand All @@ -10,23 +9,25 @@ const assert = require('assert');
const tls = require('tls');
const { assertIsCAArray } = require('../common/tls');

const systemCerts = tls.getCACertificates('system');
// Usually Windows come with some certificates installed by default.
// This can't be said about other systems, in that case check that
// at least systemCerts is an array (which may be empty).
const systemCerts = tls.getCACertificates({ type: 'system', format: 'string' });

if (common.isWindows) {
assertIsCAArray(systemCerts);
} else {
assert(Array.isArray(systemCerts));
}

// When --use-system-ca is true, default is a superset of system
// certificates.
const defaultCerts = tls.getCACertificates('default');
const defaultCerts = tls.getCACertificates({ format: 'string' });
assert(defaultCerts.length >= systemCerts.length);
const defaultSet = new Set(defaultCerts);
const systemSet = new Set(systemCerts);
assert.deepStrictEqual(defaultSet.intersection(systemSet), systemSet);
for (const cert of systemSet) {
assert(defaultSet.has(cert));
}

assert.deepStrictEqual(systemCerts, tls.getCACertificates({ type: 'system', format: 'string' }));

const certs = tls.getCACertificates('bundled');
assertIsCAArray(certs);

// It's cached on subsequent accesses.
assert.strictEqual(systemCerts, tls.getCACertificates('system'));
assert.deepStrictEqual(certs, tls.rootCertificates);
66 changes: 66 additions & 0 deletions test/parallel/test-tls-get-ca-certificates-x509-option.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
'use strict';

const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const tls = require('tls');
const { X509Certificate } = require('crypto');

{
const certs = tls.getCACertificates({ type: 'default', format: 'x509' });
assert.ok(Array.isArray(certs));
assert.ok(certs.length > 0);
for (const cert of certs) {
assert.ok(cert instanceof X509Certificate);
}
}

{
const certs = tls.getCACertificates({ type: 'default', format: 'buffer' });
assert.ok(Array.isArray(certs));
assert.ok(certs.length > 0);
for (const cert of certs) {
assert.ok(Buffer.isBuffer(cert));
}
}

{
const certs = tls.getCACertificates({ type: 'default' });
assert.ok(Array.isArray(certs));
assert.ok(certs.length > 0);
for (const cert of certs) {
assert.strictEqual(typeof cert, 'string');
assert.ok(cert.includes('-----BEGIN CERTIFICATE-----'));
}
}

{
assert.throws(() => {
tls.getCACertificates({ type: 'default', format: 'invalid' });
}, {
name: 'TypeError',
code: 'ERR_INVALID_ARG_VALUE',
message: /must be one of/
});
}

{
const certs = tls.getCACertificates({ format: 'buffer' });
assert.ok(Array.isArray(certs));
assert.ok(certs.length > 0);
for (const cert of certs) {
assert.ok(Buffer.isBuffer(cert));
}
}

{
assert.throws(() => {
tls.getCACertificates({ type: 'invalid', format: 'buffer' });
}, {
name: 'TypeError',
code: 'ERR_INVALID_ARG_VALUE',
message: "The argument 'type' is invalid. Received 'invalid'"
});
}
Loading