From 670b53b78a80b59d2a9a33d0fefa9af6722b35dc Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 5 Aug 2025 01:42:09 +0900 Subject: [PATCH 01/34] tls: add 'as' option to getCACertificates() for X509Certificate output --- doc/api/tls.md | 21 ++++++++---- lib/tls.js | 34 ++++++++++++------- ...est-tls-get-ca-certificates-x509-option.js | 23 +++++++++++++ 3 files changed, 59 insertions(+), 19 deletions(-) create mode 100644 test/parallel/test-tls-get-ca-certificates-x509-option.js diff --git a/doc/api/tls.md b/doc/api/tls.md index dc4719d391d003..6f33b65bcdda4d 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2325,7 +2325,7 @@ const additionalCerts = ['-----BEGIN CERTIFICATE-----\n...']; tls.setDefaultCACertificates([...currentCerts, ...additionalCerts]); ``` -## `tls.getCACertificates([type])` +## `tls.getCACertificates([options])` -* `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. +* `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"`. + * `as` {string} The format of returned certificates. One of: + * `"buffer"` (default): Returns an array of certificate data as `Buffer` objects. + * `"x509"`: Returns an array of \[`X509Certificate`]\[] instances. -Returns an array containing the CA certificates from various sources, depending on `type`: +* Returns: {Array.\}\ + An array of certificates in the specified format. * `"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, @@ -2348,11 +2352,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. + * `"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. diff --git a/lib/tls.js b/lib/tls.js index 4c5dece3be0f66..3ae16191a742b4 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -69,6 +69,7 @@ const { canonicalizeIP } = internalBinding('cares_wrap'); const tlsCommon = require('internal/tls/common'); const tlsWrap = require('internal/tls/wrap'); const { validateString } = require('internal/validators'); +const { X509Certificate } = require('crypto'); const { namespace: { @@ -180,23 +181,32 @@ 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', as = 'buffer' } = options; validateString(type, 'type'); + 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 (as === 'x509') { + return certs.map((cert) => new X509Certificate(cert)); } + + return certs; } + exports.getCACertificates = getCACertificates; function setDefaultCACertificates(certs) { diff --git a/test/parallel/test-tls-get-ca-certificates-x509-option.js b/test/parallel/test-tls-get-ca-certificates-x509-option.js new file mode 100644 index 00000000000000..a660ef954cb3d5 --- /dev/null +++ b/test/parallel/test-tls-get-ca-certificates-x509-option.js @@ -0,0 +1,23 @@ +'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'); + +function testX509Option() { + const certs = tls.getCACertificates({ type: 'default', as: 'x509' }); + + assert.ok(Array.isArray(certs), 'should return an array'); + assert.ok(certs.length > 0, 'should return non-empty array'); + assert.ok(certs[0] instanceof X509Certificate, + 'each cert should be instance of X509Certificate'); + + assert.match(certs[0].serialNumber, /^[0-9A-F]+$/i, 'serialNumber should be hex string'); +} + +testX509Option(); + +console.log('Test passed: getCACertificates with as: "x509"'); From 7a5dbcdf07f0d5d86a5e6947c894100bbcacc90c Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 5 Aug 2025 01:57:16 +0900 Subject: [PATCH 02/34] doc: fix getCACertificates() types and remove invalid escapes --- doc/api/tls.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index 6f33b65bcdda4d..17361076ca6c35 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2333,16 +2333,16 @@ added: - v22.15.0 --> -* `options` {string|Object|undefined}\ - Optional. If a string, it is treated as the `type` of certificates to return.\ +* `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"`.\ + * `type` {string} The type of CA certificates to return. One of `"default"`, `"system"`, `"bundled"`, or `"extra"`. **Default:** `"default"`. * `as` {string} The format of returned certificates. One of: * `"buffer"` (default): Returns an array of certificate data as `Buffer` objects. - * `"x509"`: Returns an array of \[`X509Certificate`]\[] instances. + * `"x509"`: Returns an array of [`X509Certificate`][] instances. -* Returns: {Array.\}\ +* Returns: {Array.} An array of certificates in the specified format. * `"default"`: return the CA certificates that will be used by the Node.js TLS clients by default. From 6fe1c7b484dc5c8b38e31a280935a94e57e0213b Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 5 Aug 2025 02:04:30 +0900 Subject: [PATCH 03/34] doc: fix undefined reference for X509Certificate and format markdown --- doc/api/tls.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index 17361076ca6c35..3c63bc6af64c81 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2340,9 +2340,9 @@ added: **Default:** `"default"`. * `as` {string} The format of returned certificates. One of: * `"buffer"` (default): Returns an array of certificate data as `Buffer` objects. - * `"x509"`: Returns an array of [`X509Certificate`][] instances. + * `"x509"`: Returns an array of \[`X509Certificate`]\[] instances. -* Returns: {Array.} +* Returns: {Array.\} An array of certificates in the specified format. * `"default"`: return the CA certificates that will be used by the Node.js TLS clients by default. @@ -2480,6 +2480,7 @@ added: v0.11.3 [Session Resumption]: #session-resumption [Stream]: stream.md#stream [TLS recommendations]: https://wiki.mozilla.org/Security/Server_Side_TLS +[X509Certificate]: https://nodejs.org/api/crypto.html#class-x509certificate [`'newSession'`]: #event-newsession [`'resumeSession'`]: #event-resumesession [`'secure'`]: #event-secure From ac45e9d694e77423dd5bf1690a563e0248ce50ed Mon Sep 17 00:00:00 2001 From: Haram Jeong <91401364+haramj@users.noreply.github.com> Date: Tue, 5 Aug 2025 02:15:29 +0900 Subject: [PATCH 04/34] Update doc/api/tls.md Co-authored-by: James M Snell --- doc/api/tls.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index 3c63bc6af64c81..923dc6083d6410 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2352,7 +2352,6 @@ added: trusted store. * When [`NODE_EXTRA_CA_CERTS`][] is used, this would also include certificates loaded from the specified file. - * `"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. From 7c26f5ed4b9b9490ba472b3d0ff9cfd72d533034 Mon Sep 17 00:00:00 2001 From: Haram Jeong <91401364+haramj@users.noreply.github.com> Date: Tue, 5 Aug 2025 02:15:41 +0900 Subject: [PATCH 05/34] Update doc/api/tls.md Co-authored-by: James M Snell --- doc/api/tls.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index 923dc6083d6410..98c52bfd20f323 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2355,7 +2355,6 @@ added: * `"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`][]. From 6db6b806ce0630424b1e80386c9a4167bfb95c13 Mon Sep 17 00:00:00 2001 From: Haram Jeong <91401364+haramj@users.noreply.github.com> Date: Tue, 5 Aug 2025 02:15:52 +0900 Subject: [PATCH 06/34] Update doc/api/tls.md Co-authored-by: James M Snell --- doc/api/tls.md | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index 98c52bfd20f323..af4fc9572c0535 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2357,7 +2357,6 @@ added: 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. From 5e55e1f22a7f972e64c92f43e2782838d731d577 Mon Sep 17 00:00:00 2001 From: Haram Jeong <91401364+haramj@users.noreply.github.com> Date: Tue, 5 Aug 2025 02:21:09 +0900 Subject: [PATCH 07/34] Update test/parallel/test-tls-get-ca-certificates-x509-option.js Co-authored-by: James M Snell --- test/parallel/test-tls-get-ca-certificates-x509-option.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/parallel/test-tls-get-ca-certificates-x509-option.js b/test/parallel/test-tls-get-ca-certificates-x509-option.js index a660ef954cb3d5..ad05279725016a 100644 --- a/test/parallel/test-tls-get-ca-certificates-x509-option.js +++ b/test/parallel/test-tls-get-ca-certificates-x509-option.js @@ -20,4 +20,3 @@ function testX509Option() { testX509Option(); -console.log('Test passed: getCACertificates with as: "x509"'); From a9f099ca32e5092d70ec78f5ff5d573b1bed0d66 Mon Sep 17 00:00:00 2001 From: Haram Jeong <91401364+haramj@users.noreply.github.com> Date: Tue, 5 Aug 2025 02:31:30 +0900 Subject: [PATCH 08/34] Update doc/api/tls.md Co-authored-by: James M Snell --- doc/api/tls.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index af4fc9572c0535..b781269089908c 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2338,8 +2338,8 @@ added: If an object, it may contain: * `type` {string} The type of CA certificates to return. One of `"default"`, `"system"`, `"bundled"`, or `"extra"`. **Default:** `"default"`. - * `as` {string} The format of returned certificates. One of: - * `"buffer"` (default): Returns an array of certificate data as `Buffer` objects. + * `as` {string} The format of returned certificates. **Default**: `"buffer"`. + * `"buffer"`: Returns an array of certificate data as `Buffer` objects. * `"x509"`: Returns an array of \[`X509Certificate`]\[] instances. * Returns: {Array.\} From 150f80515025d291e9308a99127913a7911b3d97 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Wed, 6 Aug 2025 01:22:43 +0900 Subject: [PATCH 09/34] tls: validate 'as' option using validateOneOf --- lib/tls.js | 6 +++++- test/parallel/test-tls-get-ca-certificates-x509-option.js | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/tls.js b/lib/tls.js index 3ae16191a742b4..bc4e6d904fa792 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -68,7 +68,10 @@ 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'); const { @@ -190,6 +193,7 @@ function getCACertificates(options = {}) { const { type = 'default', as = 'buffer' } = options; validateString(type, 'type'); + validateOneOf(as, 'as', ['buffer', 'x509']); let certs; switch (type) { diff --git a/test/parallel/test-tls-get-ca-certificates-x509-option.js b/test/parallel/test-tls-get-ca-certificates-x509-option.js index ad05279725016a..bff8f0ef97e2ec 100644 --- a/test/parallel/test-tls-get-ca-certificates-x509-option.js +++ b/test/parallel/test-tls-get-ca-certificates-x509-option.js @@ -19,4 +19,3 @@ function testX509Option() { } testX509Option(); - From e69b7ed9bdf17d6ef63fa47b694002970105503e Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Wed, 6 Aug 2025 01:53:38 +0900 Subject: [PATCH 10/34] test: expand getCACertificates() with 'as' option and invalid inputs --- ...est-tls-get-ca-certificates-x509-option.js | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/test/parallel/test-tls-get-ca-certificates-x509-option.js b/test/parallel/test-tls-get-ca-certificates-x509-option.js index bff8f0ef97e2ec..be6d8e84a19c7a 100644 --- a/test/parallel/test-tls-get-ca-certificates-x509-option.js +++ b/test/parallel/test-tls-get-ca-certificates-x509-option.js @@ -1,4 +1,5 @@ 'use strict'; + const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); @@ -7,7 +8,7 @@ const assert = require('assert'); const tls = require('tls'); const { X509Certificate } = require('crypto'); -function testX509Option() { +{ const certs = tls.getCACertificates({ type: 'default', as: 'x509' }); assert.ok(Array.isArray(certs), 'should return an array'); @@ -15,7 +16,47 @@ function testX509Option() { assert.ok(certs[0] instanceof X509Certificate, 'each cert should be instance of X509Certificate'); - assert.match(certs[0].serialNumber, /^[0-9A-F]+$/i, 'serialNumber should be hex string'); + assert.match(certs[0].serialNumber, /^[0-9A-F]+$/i, + 'serialNumber should be hex string'); +} + +{ + const certs = tls.getCACertificates({ type: 'default', as: 'buffer' }); + assert.ok(Array.isArray(certs)); + assert.ok(certs.length > 0); + assert.ok(Buffer.isBuffer(certs[0])); +} + +{ + const certs = tls.getCACertificates({ type: 'default' }); + assert.ok(Array.isArray(certs)); + assert.ok(certs.length > 0); + assert.ok(Buffer.isBuffer(certs[0])); } -testX509Option(); +{ + assert.throws(() => { + tls.getCACertificates({ type: 'default', as: 'invalid' }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: /must be one of/ + }); +} + +{ + const certs = tls.getCACertificates({ as: 'buffer' }); + assert.ok(Array.isArray(certs)); + assert.ok(certs.length > 0); + assert.ok(Buffer.isBuffer(certs[0])); +} + +{ + assert.throws(() => { + tls.getCACertificates({ type: 'invalid', as: 'buffer' }); + }, { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + message: "The argument 'type' is invalid. Received 'invalid'" + }); +} From 2ac7e724c2dd8b47619deccfb9e9c2101ff65955 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Wed, 6 Aug 2025 01:57:13 +0900 Subject: [PATCH 11/34] test: expand test-tls-get-ca-certificates-x509-option.js coverage --- ...est-tls-get-ca-certificates-x509-option.js | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/test/parallel/test-tls-get-ca-certificates-x509-option.js b/test/parallel/test-tls-get-ca-certificates-x509-option.js index be6d8e84a19c7a..d20f3c47b96bc2 100644 --- a/test/parallel/test-tls-get-ca-certificates-x509-option.js +++ b/test/parallel/test-tls-get-ca-certificates-x509-option.js @@ -13,8 +13,11 @@ const { X509Certificate } = require('crypto'); assert.ok(Array.isArray(certs), 'should return an array'); assert.ok(certs.length > 0, 'should return non-empty array'); - assert.ok(certs[0] instanceof X509Certificate, - 'each cert should be instance of X509Certificate'); + + for (let i = 0; i < certs.length; i++) { + assert.ok(certs[i] instanceof X509Certificate, + `cert at index ${i} should be instance of X509Certificate`); + } assert.match(certs[0].serialNumber, /^[0-9A-F]+$/i, 'serialNumber should be hex string'); @@ -24,14 +27,22 @@ const { X509Certificate } = require('crypto'); const certs = tls.getCACertificates({ type: 'default', as: 'buffer' }); assert.ok(Array.isArray(certs)); assert.ok(certs.length > 0); - assert.ok(Buffer.isBuffer(certs[0])); + + for (let i = 0; i < certs.length; i++) { + assert.ok(Buffer.isBuffer(certs[i]), + `cert at index ${i} should be a Buffer`); + } } { const certs = tls.getCACertificates({ type: 'default' }); assert.ok(Array.isArray(certs)); assert.ok(certs.length > 0); - assert.ok(Buffer.isBuffer(certs[0])); + + for (let i = 0; i < certs.length; i++) { + assert.ok(Buffer.isBuffer(certs[i]), + `cert at index ${i} should be a Buffer`); + } } { @@ -48,7 +59,11 @@ const { X509Certificate } = require('crypto'); const certs = tls.getCACertificates({ as: 'buffer' }); assert.ok(Array.isArray(certs)); assert.ok(certs.length > 0); - assert.ok(Buffer.isBuffer(certs[0])); + + for (let i = 0; i < certs.length; i++) { + assert.ok(Buffer.isBuffer(certs[i]), + `cert at index ${i} should be a Buffer`); + } } { From c45c3041fab560ec9fbd18849a6704da80f0c833 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Wed, 6 Aug 2025 01:59:59 +0900 Subject: [PATCH 12/34] =?UTF-8?q?docs:=20add=20changes=20block=20for=20tls?= =?UTF-8?q?.getCACertificates=20=E2=80=98as=E2=80=99=20option=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/api/tls.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/api/tls.md b/doc/api/tls.md index b781269089908c..586e5e8c10304a 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2331,6 +2331,9 @@ tls.setDefaultCACertificates([...currentCerts, ...additionalCerts]); added: - v23.10.0 - v22.15.0 +changes: + - Added support for the `as` option to specify the return format of certificates (`buffer` or `x509`). + - Updated the default return format to be `buffer`. --> * `options` {string|Object|undefined} From d8a31388c9e4a9840706cfa31f8d9b5b09a8bb78 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Wed, 6 Aug 2025 02:09:57 +0900 Subject: [PATCH 13/34] doc: fix changes block --- doc/api/tls.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index 586e5e8c10304a..bd3f4f0a4f550f 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2332,8 +2332,10 @@ added: - v23.10.0 - v22.15.0 changes: - - Added support for the `as` option to specify the return format of certificates (`buffer` or `x509`). - - Updated the default return format to be `buffer`. + - version: + - REPLACEME + pr-url: https://github.com/nodejs/node/pull/59349 + description: Added optional `options.type` parameter to `getCACertificates()`. --> * `options` {string|Object|undefined} From 135ba8a1c3271be5bddfdbe497ddc4694933d8d1 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Mon, 11 Aug 2025 00:28:18 +0900 Subject: [PATCH 14/34] tls: rename 'as' to 'format' in getCACertificates, default 'string' --- doc/api/tls.md | 14 +++--- lib/tls.js | 43 ++++++++++++++++--- ...est-tls-get-ca-certificates-x509-option.js | 25 +++++++---- 3 files changed, 64 insertions(+), 18 deletions(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index bd3f4f0a4f550f..50e9c4010a8a05 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2343,12 +2343,16 @@ changes: If an object, it may contain: * `type` {string} The type of CA certificates to return. One of `"default"`, `"system"`, `"bundled"`, or `"extra"`. **Default:** `"default"`. - * `as` {string} The format of returned certificates. **Default**: `"buffer"`. + * `format` {string} The format of returned certificates. One of `"string"`, `"buffer"`, or `"x509"`. + **Default**: `"string"`. + * `"string"`: Returns PEM-encoded strings by default. See `encoding` for DER. * `"buffer"`: Returns an array of certificate data as `Buffer` objects. - * `"x509"`: Returns an array of \[`X509Certificate`]\[] instances. + * `"x509"`: Returns an array of [`X509Certificate`][x509certificate] instances. + * `encoding` {string} When `format` is `"string"`, the encoding of the returned strings. + One of `"pem"` (PEM-encoded) or `"der"` (base64 DER). **Default:** `"pem"`. -* Returns: {Array.\} - An array of certificates in the specified format. +* Returns: {Array} + An array of certificate data in the specified format (Buffer or X509Certificate). * `"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, @@ -2482,7 +2486,6 @@ added: v0.11.3 [Session Resumption]: #session-resumption [Stream]: stream.md#stream [TLS recommendations]: https://wiki.mozilla.org/Security/Server_Side_TLS -[X509Certificate]: https://nodejs.org/api/crypto.html#class-x509certificate [`'newSession'`]: #event-newsession [`'resumeSession'`]: #event-resumesession [`'secure'`]: #event-secure @@ -2532,3 +2535,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]: https://nodejs.org/api/crypto.html#class-x509certificate diff --git a/lib/tls.js b/lib/tls.js index bc4e6d904fa792..88996121036b77 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -191,9 +191,17 @@ function getCACertificates(options = {}) { throw new ERR_INVALID_ARG_TYPE('options', ['string', 'object'], options); } - const { type = 'default', as = 'buffer' } = options; + const { + type = 'default', + format = 'string', + encoding = 'pem', + } = options; + validateString(type, 'type'); - validateOneOf(as, 'as', ['buffer', 'x509']); + validateOneOf(format, 'format', ['string', 'buffer', 'x509']); + if (format === 'string') { + validateOneOf(encoding, 'encoding', ['pem', 'der']); + } let certs; switch (type) { @@ -204,13 +212,38 @@ function getCACertificates(options = {}) { default: throw new ERR_INVALID_ARG_VALUE('type', type); } - if (as === 'x509') { - return certs.map((cert) => new X509Certificate(cert)); + if (format === 'string' && encoding === 'pem') { + return certs; } - return certs; + if (format === 'buffer' || format === 'x509') { + 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); + }); + return (format === 'buffer') ? buffers : buffers.map((buf) => new X509Certificate(buf)); + } + + return certs.map((cert) => { + if (typeof cert === 'string') { + const base64 = cert + .replace(/-----BEGIN CERTIFICATE-----/g, '') + .replace(/-----END CERTIFICATE-----/g, '') + .replace(/\s+/g, ''); + return Buffer.from(base64, 'base64').toString('base64'); + } + return encoding === 'pem' ? cert.toString('utf8') : cert.toString('base64'); + }); } + exports.getCACertificates = getCACertificates; function setDefaultCACertificates(certs) { diff --git a/test/parallel/test-tls-get-ca-certificates-x509-option.js b/test/parallel/test-tls-get-ca-certificates-x509-option.js index d20f3c47b96bc2..414063ff4be33c 100644 --- a/test/parallel/test-tls-get-ca-certificates-x509-option.js +++ b/test/parallel/test-tls-get-ca-certificates-x509-option.js @@ -9,7 +9,7 @@ const tls = require('tls'); const { X509Certificate } = require('crypto'); { - const certs = tls.getCACertificates({ type: 'default', as: 'x509' }); + const certs = tls.getCACertificates({ type: 'default', format: 'x509' }); assert.ok(Array.isArray(certs), 'should return an array'); assert.ok(certs.length > 0, 'should return non-empty array'); @@ -24,7 +24,7 @@ const { X509Certificate } = require('crypto'); } { - const certs = tls.getCACertificates({ type: 'default', as: 'buffer' }); + const certs = tls.getCACertificates({ type: 'default', format: 'buffer' }); assert.ok(Array.isArray(certs)); assert.ok(certs.length > 0); @@ -38,16 +38,15 @@ const { X509Certificate } = require('crypto'); const certs = tls.getCACertificates({ type: 'default' }); assert.ok(Array.isArray(certs)); assert.ok(certs.length > 0); - for (let i = 0; i < certs.length; i++) { - assert.ok(Buffer.isBuffer(certs[i]), - `cert at index ${i} should be a Buffer`); + assert.strictEqual(typeof certs[i], 'string'); + assert.ok(certs[i].includes('-----BEGIN CERTIFICATE-----')); } } { assert.throws(() => { - tls.getCACertificates({ type: 'default', as: 'invalid' }); + tls.getCACertificates({ type: 'default', format: 'invalid' }); }, { name: 'TypeError', code: 'ERR_INVALID_ARG_VALUE', @@ -56,7 +55,7 @@ const { X509Certificate } = require('crypto'); } { - const certs = tls.getCACertificates({ as: 'buffer' }); + const certs = tls.getCACertificates({ format: 'buffer' }); assert.ok(Array.isArray(certs)); assert.ok(certs.length > 0); @@ -68,10 +67,20 @@ const { X509Certificate } = require('crypto'); { assert.throws(() => { - tls.getCACertificates({ type: 'invalid', as: 'buffer' }); + tls.getCACertificates({ type: 'invalid', format: 'buffer' }); }, { name: 'TypeError', code: 'ERR_INVALID_ARG_VALUE', message: "The argument 'type' is invalid. Received 'invalid'" }); } + +{ + const certs = tls.getCACertificates({ format: 'string', encoding: 'der' }); + assert.ok(Array.isArray(certs)); + assert.ok(certs.length > 0); + for (let i = 0; i < certs.length; i++) { + assert.strictEqual(typeof certs[i], 'string'); + assert.ok(/^[A-Za-z0-9+/]+=*$/.test(certs[i]), 'should be base64 string'); + } +} From d9e83578dce1a42d1563228442b5366806c1a0ad Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Mon, 11 Aug 2025 00:35:38 +0900 Subject: [PATCH 15/34] doc: format tls.md --- doc/api/tls.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/api/tls.md b/doc/api/tls.md index 50e9c4010a8a05..38f94ae38d79e3 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2361,11 +2361,14 @@ changes: trusted store. * When [`NODE_EXTRA_CA_CERTS`][] is used, this would also include certificates loaded from the specified file. + * `"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. From e3563a59389292bc21f641c7fc6bd9cb8476d7f8 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 19 Aug 2025 15:30:39 +0900 Subject: [PATCH 16/34] tls: simplify getCACertificates() API --- doc/api/tls.md | 8 ++-- lib/tls.js | 38 +++++++---------- .../test-tls-get-ca-certificates-bundled.js | 12 ++---- .../test-tls-get-ca-certificates-default.js | 13 +++--- .../test-tls-get-ca-certificates-system.js | 20 ++++----- ...est-tls-get-ca-certificates-x509-option.js | 42 +++++-------------- 6 files changed, 46 insertions(+), 87 deletions(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index 38f94ae38d79e3..a3c1c791def2bd 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2344,12 +2344,10 @@ changes: * `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 `"string"`, `"buffer"`, or `"x509"`. - **Default**: `"string"`. - * `"string"`: Returns PEM-encoded strings by default. See `encoding` for DER. - * `"buffer"`: Returns an array of certificate data as `Buffer` objects. + **Default:** `"string"`. + * `"string"`: Returns an array of PEM-encoded certificate strings. + * `"buffer"`: Returns an array of certificate data as `Buffer` objects in DER format. * `"x509"`: Returns an array of [`X509Certificate`][x509certificate] instances. - * `encoding` {string} When `format` is `"string"`, the encoding of the returned strings. - One of `"pem"` (PEM-encoded) or `"der"` (base64 DER). **Default:** `"pem"`. * Returns: {Array} An array of certificate data in the specified format (Buffer or X509Certificate). diff --git a/lib/tls.js b/lib/tls.js index 88996121036b77..d76577db1140fe 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -194,14 +194,10 @@ function getCACertificates(options = {}) { const { type = 'default', format = 'string', - encoding = 'pem', } = options; validateString(type, 'type'); validateOneOf(format, 'format', ['string', 'buffer', 'x509']); - if (format === 'string') { - validateOneOf(encoding, 'encoding', ['pem', 'der']); - } let certs; switch (type) { @@ -212,37 +208,33 @@ function getCACertificates(options = {}) { default: throw new ERR_INVALID_ARG_VALUE('type', type); } - if (format === 'string' && encoding === 'pem') { - return certs; - } - - if (format === 'buffer' || format === 'x509') { - 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'); - } + 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); }); - return (format === 'buffer') ? buffers : buffers.map((buf) => new X509Certificate(buf)); } - return certs.map((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').toString('base64'); + return Buffer.from(base64, 'base64'); } - return encoding === 'pem' ? cert.toString('utf8') : cert.toString('base64'); + throw new ERR_INVALID_ARG_VALUE('cert', cert); }); -} + if (format === 'buffer') { + return buffers; + } + + return buffers.map((buf) => new X509Certificate(buf)); +} exports.getCACertificates = getCACertificates; diff --git a/test/parallel/test-tls-get-ca-certificates-bundled.js b/test/parallel/test-tls-get-ca-certificates-bundled.js index 5dbe254bcabd5a..6e9391ab11bc84 100644 --- a/test/parallel/test-tls-get-ca-certificates-bundled.js +++ b/test/parallel/test-tls-get-ca-certificates-bundled.js @@ -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'); @@ -9,12 +8,9 @@ const assert = require('assert'); const tls = require('tls'); const { assertIsCAArray } = require('../common/tls'); -const certs = tls.getCACertificates('bundled'); +const certs = tls.getCACertificates({ type: 'bundled', format: 'string' }); 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' })); diff --git a/test/parallel/test-tls-get-ca-certificates-default.js b/test/parallel/test-tls-get-ca-certificates-default.js index 29fb2a29a8cb33..fc11916d9f6e4a 100644 --- a/test/parallel/test-tls-get-ca-certificates-default.js +++ b/test/parallel/test-tls-get-ca-certificates-default.js @@ -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'); @@ -10,11 +8,10 @@ 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); -// It's cached on subsequent accesses. -assert.strictEqual(certs, tls.getCACertificates('default')); +assert.deepStrictEqual(certs, tls.getCACertificates({ type: 'default', format: 'string' })); diff --git a/test/parallel/test-tls-get-ca-certificates-system.js b/test/parallel/test-tls-get-ca-certificates-system.js index 0dfed80af92ceb..affd4804c008d0 100644 --- a/test/parallel/test-tls-get-ca-certificates-system.js +++ b/test/parallel/test-tls-get-ca-certificates-system.js @@ -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'); @@ -10,23 +9,20 @@ 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)); +} -// It's cached on subsequent accesses. -assert.strictEqual(systemCerts, tls.getCACertificates('system')); +assert.deepStrictEqual(systemCerts, tls.getCACertificates({ type: 'system', format: 'string' })); diff --git a/test/parallel/test-tls-get-ca-certificates-x509-option.js b/test/parallel/test-tls-get-ca-certificates-x509-option.js index 414063ff4be33c..0f819df3f41f2f 100644 --- a/test/parallel/test-tls-get-ca-certificates-x509-option.js +++ b/test/parallel/test-tls-get-ca-certificates-x509-option.js @@ -10,27 +10,19 @@ const { X509Certificate } = require('crypto'); { const certs = tls.getCACertificates({ type: 'default', format: 'x509' }); - - assert.ok(Array.isArray(certs), 'should return an array'); - assert.ok(certs.length > 0, 'should return non-empty array'); - - for (let i = 0; i < certs.length; i++) { - assert.ok(certs[i] instanceof X509Certificate, - `cert at index ${i} should be instance of X509Certificate`); + assert.ok(Array.isArray(certs)); + assert.ok(certs.length > 0); + for (const cert of certs) { + assert.ok(cert instanceof X509Certificate); } - - assert.match(certs[0].serialNumber, /^[0-9A-F]+$/i, - 'serialNumber should be hex string'); } { const certs = tls.getCACertificates({ type: 'default', format: 'buffer' }); assert.ok(Array.isArray(certs)); assert.ok(certs.length > 0); - - for (let i = 0; i < certs.length; i++) { - assert.ok(Buffer.isBuffer(certs[i]), - `cert at index ${i} should be a Buffer`); + for (const cert of certs) { + assert.ok(Buffer.isBuffer(cert)); } } @@ -38,9 +30,9 @@ const { X509Certificate } = require('crypto'); const certs = tls.getCACertificates({ type: 'default' }); assert.ok(Array.isArray(certs)); assert.ok(certs.length > 0); - for (let i = 0; i < certs.length; i++) { - assert.strictEqual(typeof certs[i], 'string'); - assert.ok(certs[i].includes('-----BEGIN CERTIFICATE-----')); + for (const cert of certs) { + assert.strictEqual(typeof cert, 'string'); + assert.ok(cert.includes('-----BEGIN CERTIFICATE-----')); } } @@ -58,10 +50,8 @@ const { X509Certificate } = require('crypto'); const certs = tls.getCACertificates({ format: 'buffer' }); assert.ok(Array.isArray(certs)); assert.ok(certs.length > 0); - - for (let i = 0; i < certs.length; i++) { - assert.ok(Buffer.isBuffer(certs[i]), - `cert at index ${i} should be a Buffer`); + for (const cert of certs) { + assert.ok(Buffer.isBuffer(cert)); } } @@ -74,13 +64,3 @@ const { X509Certificate } = require('crypto'); message: "The argument 'type' is invalid. Received 'invalid'" }); } - -{ - const certs = tls.getCACertificates({ format: 'string', encoding: 'der' }); - assert.ok(Array.isArray(certs)); - assert.ok(certs.length > 0); - for (let i = 0; i < certs.length; i++) { - assert.strictEqual(typeof certs[i], 'string'); - assert.ok(/^[A-Za-z0-9+/]+=*$/.test(certs[i]), 'should be base64 string'); - } -} From fbf223ab120f5329c372e03c383116e5e3005fcb Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Wed, 20 Aug 2025 15:41:11 +0900 Subject: [PATCH 17/34] doc: fix broken anchor link for tls.getCACertificates() --- doc/api/tls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index a3c1c791def2bd..1eb52d7dafdba4 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2527,7 +2527,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 From 5129cb97bf93e79a9d470ba9100501c4011a1868 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Sun, 31 Aug 2025 16:38:16 +0900 Subject: [PATCH 18/34] doc: update tls.md `X509Certificate` anchor --- doc/api/tls.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index 1eb52d7dafdba4..fc99e207d9ddbe 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2347,7 +2347,7 @@ changes: **Default:** `"string"`. * `"string"`: Returns an array of PEM-encoded certificate strings. * `"buffer"`: Returns an array of certificate data as `Buffer` objects in DER format. - * `"x509"`: Returns an array of [`X509Certificate`][x509certificate] instances. + * `"x509"`: Returns an array of \[`X509Certificate`]\[x509certificate] instances. * Returns: {Array} An array of certificate data in the specified format (Buffer or X509Certificate). @@ -2536,4 +2536,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]: https://nodejs.org/api/crypto.html#class-x509certificate +[`X509Certificate`]: #class-x509certificate From 24b8bc2753d29392599a1f6b66579496958400b1 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Sun, 31 Aug 2025 16:53:18 +0900 Subject: [PATCH 19/34] doc: fix tls.md lint error --- doc/api/tls.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index fc99e207d9ddbe..a3eab62e179bef 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2347,7 +2347,7 @@ changes: **Default:** `"string"`. * `"string"`: Returns an array of PEM-encoded certificate strings. * `"buffer"`: Returns an array of certificate data as `Buffer` objects in DER format. - * `"x509"`: Returns an array of \[`X509Certificate`]\[x509certificate] instances. + * `"x509"`: Returns an array of [`X509Certificate`][x509certificate] instances. * Returns: {Array} An array of certificate data in the specified format (Buffer or X509Certificate). @@ -2536,4 +2536,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`]: #class-x509certificate +[x509certificate]: #class-x509certificate From 22ce735cf6e0aa2f93c6a09a86997d960345d59c Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Sun, 31 Aug 2025 17:00:37 +0900 Subject: [PATCH 20/34] doc: fix x509certificate anchor --- doc/api/tls.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index a3eab62e179bef..e10d2e136bf6d9 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2536,4 +2536,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]: #class-x509certificate +[x509certificate]: crypto.md#class-x509certificate From a6ebe6af3e7c02ea063f9cccd7ab9910361e68e7 Mon Sep 17 00:00:00 2001 From: Haram Jeong <91401364+haramj@users.noreply.github.com> Date: Mon, 1 Sep 2025 01:38:44 +0900 Subject: [PATCH 21/34] Update test/parallel/test-tls-get-ca-certificates-bundled.js Co-authored-by: Daeyeon Jeong --- test/parallel/test-tls-get-ca-certificates-bundled.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-tls-get-ca-certificates-bundled.js b/test/parallel/test-tls-get-ca-certificates-bundled.js index 6e9391ab11bc84..7b38a8ad2eede6 100644 --- a/test/parallel/test-tls-get-ca-certificates-bundled.js +++ b/test/parallel/test-tls-get-ca-certificates-bundled.js @@ -8,7 +8,8 @@ const assert = require('assert'); const tls = require('tls'); const { assertIsCAArray } = require('../common/tls'); -const certs = tls.getCACertificates({ type: 'bundled', format: 'string' }); +const certs = tls.getCACertificates('bundled'); +assert.deepStrictEqual(certs, tls.getCACertificates({ type: 'bundled', format: 'pem' })); assertIsCAArray(certs); assert.deepStrictEqual(certs, tls.rootCertificates); From a5aa8484737535245cb91a0aae614bbbb281a970 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Mon, 1 Sep 2025 01:42:01 +0900 Subject: [PATCH 22/34] doc: clarify format and return types in tls.getCACertificates() --- doc/api/tls.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index e10d2e136bf6d9..b5e29536429044 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2343,14 +2343,17 @@ changes: 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 `"string"`, `"buffer"`, or `"x509"`. - **Default:** `"string"`. - * `"string"`: Returns an array of PEM-encoded certificate strings. - * `"buffer"`: Returns an array of certificate data as `Buffer` objects in DER format. + * `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 (Buffer or X509Certificate). + 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, From ba15d9f50b595bdb01a0e6e7436bbe0ad2e962f5 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Mon, 1 Sep 2025 16:22:33 +0900 Subject: [PATCH 23/34] test: fix tls.getCACertificates bundled test to use format 'string' --- test/parallel/test-tls-get-ca-certificates-bundled.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-tls-get-ca-certificates-bundled.js b/test/parallel/test-tls-get-ca-certificates-bundled.js index 7b38a8ad2eede6..cff0fc01d3981b 100644 --- a/test/parallel/test-tls-get-ca-certificates-bundled.js +++ b/test/parallel/test-tls-get-ca-certificates-bundled.js @@ -9,7 +9,7 @@ const tls = require('tls'); const { assertIsCAArray } = require('../common/tls'); const certs = tls.getCACertificates('bundled'); -assert.deepStrictEqual(certs, tls.getCACertificates({ type: 'bundled', format: 'pem' })); +assert.deepStrictEqual(certs, tls.getCACertificates({ type: 'bundled' })); assertIsCAArray(certs); assert.deepStrictEqual(certs, tls.rootCertificates); From c24e23ec1ee734b120f1111836056e52772520dd Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 2 Sep 2025 11:03:14 +0900 Subject: [PATCH 24/34] test: keep shorthand arguments in getCACertificates tests --- test/parallel/test-tls-get-ca-certificates-bundled.js | 5 +++++ test/parallel/test-tls-get-ca-certificates-default.js | 5 +++++ test/parallel/test-tls-get-ca-certificates-system.js | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/test/parallel/test-tls-get-ca-certificates-bundled.js b/test/parallel/test-tls-get-ca-certificates-bundled.js index cff0fc01d3981b..601ac8c81af7e5 100644 --- a/test/parallel/test-tls-get-ca-certificates-bundled.js +++ b/test/parallel/test-tls-get-ca-certificates-bundled.js @@ -15,3 +15,8 @@ assertIsCAArray(certs); assert.deepStrictEqual(certs, tls.rootCertificates); assert.deepStrictEqual(certs, tls.getCACertificates({ type: 'bundled', format: 'string' })); + +const certs2 = tls.getCACertificates('bundled'); +assertIsCAArray(certs2); + +assert.deepStrictEqual(certs2, tls.rootCertificates); \ No newline at end of file diff --git a/test/parallel/test-tls-get-ca-certificates-default.js b/test/parallel/test-tls-get-ca-certificates-default.js index fc11916d9f6e4a..21aff0ce2c3486 100644 --- a/test/parallel/test-tls-get-ca-certificates-default.js +++ b/test/parallel/test-tls-get-ca-certificates-default.js @@ -15,3 +15,8 @@ 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); + +assert.deepStrictEqual(certs3, tls.rootCertificates); \ No newline at end of file diff --git a/test/parallel/test-tls-get-ca-certificates-system.js b/test/parallel/test-tls-get-ca-certificates-system.js index affd4804c008d0..1124bce968517f 100644 --- a/test/parallel/test-tls-get-ca-certificates-system.js +++ b/test/parallel/test-tls-get-ca-certificates-system.js @@ -26,3 +26,8 @@ for (const cert of systemSet) { } assert.deepStrictEqual(systemCerts, tls.getCACertificates({ type: 'system', format: 'string' })); + +const certs = tls.getCACertificates('bundled'); +assertIsCAArray(certs); + +assert.deepStrictEqual(certs, tls.rootCertificates); \ No newline at end of file From 1b254d2f03283dcb42a01cbffbd9690254ba08cf Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 2 Sep 2025 11:04:45 +0900 Subject: [PATCH 25/34] test: Add final newline --- test/parallel/test-tls-get-ca-certificates-bundled.js | 2 +- test/parallel/test-tls-get-ca-certificates-default.js | 2 +- test/parallel/test-tls-get-ca-certificates-system.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/parallel/test-tls-get-ca-certificates-bundled.js b/test/parallel/test-tls-get-ca-certificates-bundled.js index 601ac8c81af7e5..41f8bebd4cbf14 100644 --- a/test/parallel/test-tls-get-ca-certificates-bundled.js +++ b/test/parallel/test-tls-get-ca-certificates-bundled.js @@ -19,4 +19,4 @@ assert.deepStrictEqual(certs, tls.getCACertificates({ type: 'bundled', format: ' const certs2 = tls.getCACertificates('bundled'); assertIsCAArray(certs2); -assert.deepStrictEqual(certs2, tls.rootCertificates); \ No newline at end of file +assert.deepStrictEqual(certs2, tls.rootCertificates); diff --git a/test/parallel/test-tls-get-ca-certificates-default.js b/test/parallel/test-tls-get-ca-certificates-default.js index 21aff0ce2c3486..611d3f9dbb807a 100644 --- a/test/parallel/test-tls-get-ca-certificates-default.js +++ b/test/parallel/test-tls-get-ca-certificates-default.js @@ -19,4 +19,4 @@ assert.deepStrictEqual(certs, tls.getCACertificates({ type: 'default', format: ' const certs3 = tls.getCACertificates('bundled'); assertIsCAArray(certs3); -assert.deepStrictEqual(certs3, tls.rootCertificates); \ No newline at end of file +assert.deepStrictEqual(certs3, tls.rootCertificates); diff --git a/test/parallel/test-tls-get-ca-certificates-system.js b/test/parallel/test-tls-get-ca-certificates-system.js index 1124bce968517f..09c488b0c5d414 100644 --- a/test/parallel/test-tls-get-ca-certificates-system.js +++ b/test/parallel/test-tls-get-ca-certificates-system.js @@ -30,4 +30,4 @@ assert.deepStrictEqual(systemCerts, tls.getCACertificates({ type: 'system', form const certs = tls.getCACertificates('bundled'); assertIsCAArray(certs); -assert.deepStrictEqual(certs, tls.rootCertificates); \ No newline at end of file +assert.deepStrictEqual(certs, tls.rootCertificates); From 651a42d9ef5bd644a6f0d08099e4f7af8f7e6f98 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Sun, 7 Sep 2025 22:07:03 +0900 Subject: [PATCH 26/34] tls: Improve getCACertificates() caching and test --- lib/tls.js | 68 +++++++++++++------ .../test-tls-get-ca-certificates-bundled.js | 1 + .../test-tls-get-ca-certificates-default.js | 3 +- .../test-tls-get-ca-certificates-system.js | 1 + 4 files changed, 50 insertions(+), 23 deletions(-) diff --git a/lib/tls.js b/lib/tls.js index d76577db1140fe..e8dd6330510674 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -184,6 +184,8 @@ function cacheDefaultCACertificates() { return defaultCACertificates; } +const certificateCache = { __proto__: null }; + function getCACertificates(options = {}) { if (typeof options === 'string') { options = { type: options }; @@ -193,11 +195,37 @@ function getCACertificates(options = {}) { const { type = 'default', - format = 'string', + format = 'pem', } = options; validateString(type, 'type'); - validateOneOf(format, 'format', ['string', 'buffer', 'x509']); + validateOneOf(format, 'format', ['pem', 'der', 'x509', 'string', 'buffer']); + + let effectiveFormat = format; + if (format === 'string') { + effectiveFormat = 'pem'; + } else if (format === 'buffer') { + effectiveFormat = 'der'; + } + + if (certificateCache[type]) { + const cachedCerts = certificateCache[type]; + + if (effectiveFormat === 'pem') { + return cachedCerts; + } + + const buffers = cachedCerts.map((cert) => { + const base64 = cert.replace(/(?:\s|-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----)+/g, ''); + return Buffer.from(base64, 'base64'); + }); + + if (effectiveFormat === 'der') { + return buffers; + } + + return buffers.map((buf) => new X509Certificate(buf)); + } let certs; switch (type) { @@ -208,32 +236,28 @@ function getCACertificates(options = {}) { 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; + const pemCerts = certs.map((cert) => { if (typeof cert === 'string') { - const base64 = cert - .replace(/-----BEGIN CERTIFICATE-----/g, '') - .replace(/-----END CERTIFICATE-----/g, '') - .replace(/\s+/g, ''); - return Buffer.from(base64, 'base64'); + return cert; } - throw new ERR_INVALID_ARG_VALUE('cert', cert); + return `-----BEGIN CERTIFICATE-----\n${cert.toString('base64').match(/.{1,64}/g).join('\n')}\n-----END CERTIFICATE-----`; + }); + certificateCache[type] = pemCerts; + + if (effectiveFormat === 'pem') { + return pemCerts; + } + + const derBuffers = pemCerts.map((cert) => { + const base64 = cert.replace(/(?:\s|-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----)+/g, ''); + return Buffer.from(base64, 'base64'); }); - if (format === 'buffer') { - return buffers; + if (effectiveFormat === 'der') { + return derBuffers; } - return buffers.map((buf) => new X509Certificate(buf)); + return derBuffers.map((buf) => new X509Certificate(buf)); } exports.getCACertificates = getCACertificates; diff --git a/test/parallel/test-tls-get-ca-certificates-bundled.js b/test/parallel/test-tls-get-ca-certificates-bundled.js index 41f8bebd4cbf14..bbcb6abd09a75d 100644 --- a/test/parallel/test-tls-get-ca-certificates-bundled.js +++ b/test/parallel/test-tls-get-ca-certificates-bundled.js @@ -20,3 +20,4 @@ const certs2 = tls.getCACertificates('bundled'); assertIsCAArray(certs2); assert.deepStrictEqual(certs2, tls.rootCertificates); +assert.strictEqual(certs, tls.getCACertificates({ type: 'bundled', format: 'string' })); diff --git a/test/parallel/test-tls-get-ca-certificates-default.js b/test/parallel/test-tls-get-ca-certificates-default.js index 611d3f9dbb807a..412d3313349c0c 100644 --- a/test/parallel/test-tls-get-ca-certificates-default.js +++ b/test/parallel/test-tls-get-ca-certificates-default.js @@ -16,7 +16,8 @@ assert.deepStrictEqual(certs, certs2); assert.deepStrictEqual(certs, tls.getCACertificates({ type: 'default', format: 'string' })); -const certs3 = tls.getCACertificates('bundled'); +const certs3 = tls.getCACertificates('default'); assertIsCAArray(certs3); assert.deepStrictEqual(certs3, tls.rootCertificates); +assert.strictEqual(certs2, tls.getCACertificates({ type: 'default', format: 'string' })); diff --git a/test/parallel/test-tls-get-ca-certificates-system.js b/test/parallel/test-tls-get-ca-certificates-system.js index 09c488b0c5d414..a69f64ee01d23d 100644 --- a/test/parallel/test-tls-get-ca-certificates-system.js +++ b/test/parallel/test-tls-get-ca-certificates-system.js @@ -31,3 +31,4 @@ const certs = tls.getCACertificates('bundled'); assertIsCAArray(certs); assert.deepStrictEqual(certs, tls.rootCertificates); +assert.strictEqual(systemCerts, tls.getCACertificates({ type: 'system', format: 'string' })); From d4c3ac0d611bc820fc186ee3a1fce838b74be0c8 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 9 Sep 2025 15:55:47 +0900 Subject: [PATCH 27/34] tls: Test rollback and Update getCACertificates() --- lib/tls.js | 105 ++++++++---------- .../test-tls-get-ca-certificates-bundled.js | 17 ++- .../test-tls-get-ca-certificates-default.js | 18 +-- .../test-tls-get-ca-certificates-system.js | 26 ++--- 4 files changed, 72 insertions(+), 94 deletions(-) diff --git a/lib/tls.js b/lib/tls.js index e8dd6330510674..928a46ee872825 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -184,38 +184,57 @@ function cacheDefaultCACertificates() { return defaultCACertificates; } -const certificateCache = { __proto__: null }; - -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 = 'pem', - } = options; - - validateString(type, 'type'); - validateOneOf(format, 'format', ['pem', 'der', 'x509', 'string', 'buffer']); - - let effectiveFormat = format; - if (format === 'string') { - effectiveFormat = 'pem'; - } else if (format === 'buffer') { - effectiveFormat = 'der'; - } +function getCACertificates(options = undefined) { + if (typeof options === 'string' || options === undefined) { + const type = (typeof options === 'string') ? options : 'default'; + + validateString(type, 'type'); + + 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); + } + } else if (typeof options === 'object' && options !== null) { + const { + type = 'default', + format = 'pem', + } = options; + + validateString(type, 'type'); + validateOneOf(format, 'format', ['pem', 'der', 'x509', 'string', 'buffer']); + + let effectiveFormat = format; + if (format === 'string') { + effectiveFormat = 'pem'; + } else if (format === 'buffer') { + effectiveFormat = 'der'; + } - if (certificateCache[type]) { - const cachedCerts = certificateCache[type]; + let certs; + switch (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 (effectiveFormat === 'pem') { - return cachedCerts; + return certs.map((cert) => { + if (typeof cert === 'string') { + return cert; + } + return `-----BEGIN CERTIFICATE-----\n${cert.toString('base64').match(/.{1,64}/g).join('\n')}\n-----END CERTIFICATE-----`; + }); } - const buffers = cachedCerts.map((cert) => { + const buffers = certs.map((cert) => { + if (Buffer.isBuffer(cert)) { + return cert; + } const base64 = cert.replace(/(?:\s|-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----)+/g, ''); return Buffer.from(base64, 'base64'); }); @@ -227,37 +246,7 @@ function getCACertificates(options = {}) { return buffers.map((buf) => new X509Certificate(buf)); } - let certs; - switch (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); - } - - const pemCerts = certs.map((cert) => { - if (typeof cert === 'string') { - return cert; - } - return `-----BEGIN CERTIFICATE-----\n${cert.toString('base64').match(/.{1,64}/g).join('\n')}\n-----END CERTIFICATE-----`; - }); - certificateCache[type] = pemCerts; - - if (effectiveFormat === 'pem') { - return pemCerts; - } - - const derBuffers = pemCerts.map((cert) => { - const base64 = cert.replace(/(?:\s|-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----)+/g, ''); - return Buffer.from(base64, 'base64'); - }); - - if (effectiveFormat === 'der') { - return derBuffers; - } - - return derBuffers.map((buf) => new X509Certificate(buf)); + throw new ERR_INVALID_ARG_TYPE('options', ['string', 'object'], options); } exports.getCACertificates = getCACertificates; diff --git a/test/parallel/test-tls-get-ca-certificates-bundled.js b/test/parallel/test-tls-get-ca-certificates-bundled.js index bbcb6abd09a75d..5dbe254bcabd5a 100644 --- a/test/parallel/test-tls-get-ca-certificates-bundled.js +++ b/test/parallel/test-tls-get-ca-certificates-bundled.js @@ -1,5 +1,6 @@ 'use strict'; -// Test that tls.getCACertificates() returns the bundled certificates correctly. +// This tests that tls.getCACertificates() returns the bundled +// certificates correctly. const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); @@ -9,15 +10,11 @@ const tls = require('tls'); const { assertIsCAArray } = require('../common/tls'); const certs = tls.getCACertificates('bundled'); -assert.deepStrictEqual(certs, tls.getCACertificates({ type: 'bundled' })); assertIsCAArray(certs); -assert.deepStrictEqual(certs, tls.rootCertificates); +// It's the same as tls.rootCertificates - both are +// Mozilla CA stores across platform. +assert.strictEqual(certs, tls.rootCertificates); -assert.deepStrictEqual(certs, tls.getCACertificates({ type: 'bundled', format: 'string' })); - -const certs2 = tls.getCACertificates('bundled'); -assertIsCAArray(certs2); - -assert.deepStrictEqual(certs2, tls.rootCertificates); -assert.strictEqual(certs, tls.getCACertificates({ type: 'bundled', format: 'string' })); +// It's cached on subsequent accesses. +assert.strictEqual(certs, tls.getCACertificates('bundled')); diff --git a/test/parallel/test-tls-get-ca-certificates-default.js b/test/parallel/test-tls-get-ca-certificates-default.js index 412d3313349c0c..91298c072149ca 100644 --- a/test/parallel/test-tls-get-ca-certificates-default.js +++ b/test/parallel/test-tls-get-ca-certificates-default.js @@ -1,5 +1,7 @@ 'use strict'; -// Test that tls.getCACertificates() returns the default certificates correctly. + +// This tests that tls.getCACertificates() returns the default +// certificates correctly. const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); @@ -8,16 +10,8 @@ const assert = require('assert'); const tls = require('tls'); const { assertIsCAArray } = require('../common/tls'); -const certs = tls.getCACertificates({ format: 'string' }); +const certs = tls.getCACertificates(); assertIsCAArray(certs); -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('default'); -assertIsCAArray(certs3); - -assert.deepStrictEqual(certs3, tls.rootCertificates); -assert.strictEqual(certs2, tls.getCACertificates({ type: 'default', format: 'string' })); +// It's cached on subsequent accesses. +assert.strictEqual(certs, tls.getCACertificates('default')); diff --git a/test/parallel/test-tls-get-ca-certificates-system.js b/test/parallel/test-tls-get-ca-certificates-system.js index a69f64ee01d23d..0dfed80af92ceb 100644 --- a/test/parallel/test-tls-get-ca-certificates-system.js +++ b/test/parallel/test-tls-get-ca-certificates-system.js @@ -1,6 +1,7 @@ 'use strict'; // Flags: --use-system-ca -// Test that tls.getCACertificates() returns system certificates correctly. +// This tests that tls.getCACertificates() returns the system +// certificates correctly. const common = require('../common'); if (!common.hasCrypto) common.skip('missing crypto'); @@ -9,26 +10,23 @@ const assert = require('assert'); const tls = require('tls'); const { assertIsCAArray } = require('../common/tls'); -const systemCerts = tls.getCACertificates({ type: 'system', format: 'string' }); - +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). if (common.isWindows) { assertIsCAArray(systemCerts); } else { assert(Array.isArray(systemCerts)); } -const defaultCerts = tls.getCACertificates({ format: 'string' }); +// When --use-system-ca is true, default is a superset of system +// certificates. +const defaultCerts = tls.getCACertificates('default'); assert(defaultCerts.length >= systemCerts.length); const defaultSet = new Set(defaultCerts); const systemSet = new Set(systemCerts); -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); +assert.deepStrictEqual(defaultSet.intersection(systemSet), systemSet); -assert.deepStrictEqual(certs, tls.rootCertificates); -assert.strictEqual(systemCerts, tls.getCACertificates({ type: 'system', format: 'string' })); +// It's cached on subsequent accesses. +assert.strictEqual(systemCerts, tls.getCACertificates('system')); From cea963968f10deda22e0e0916fda0ac2762b650a Mon Sep 17 00:00:00 2001 From: Haram Jeong <91401364+haramj@users.noreply.github.com> Date: Wed, 10 Sep 2025 23:08:10 +0900 Subject: [PATCH 28/34] doc: tls.md remove the white space --- doc/api/tls.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/doc/api/tls.md b/doc/api/tls.md index b5e29536429044..9dfe85b9c112f1 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2362,14 +2362,11 @@ changes: trusted store. * When [`NODE_EXTRA_CA_CERTS`][] is used, this would also include certificates loaded from the specified file. - * `"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. From e7877979f79bf214a3eceb36c5f15563a07b2284 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Thu, 11 Sep 2025 12:43:42 +0900 Subject: [PATCH 29/34] tls: improve tls.getCACertificates() to simplify certificate handling --- lib/tls.js | 63 ++++++++++++++++++++++-------------------------------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/lib/tls.js b/lib/tls.js index 928a46ee872825..7446770c53ea30 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -184,20 +184,29 @@ function cacheDefaultCACertificates() { return defaultCACertificates; } +function getCACertificatesAsStrings(type = 'default') { + validateString(type, 'type'); + + 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); + } +} + function getCACertificates(options = undefined) { if (typeof options === 'string' || options === undefined) { - const type = (typeof options === 'string') ? options : 'default'; - - validateString(type, 'type'); + return getCACertificatesAsStrings(options); + } - 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); - } - } else if (typeof options === 'object' && options !== null) { + if (typeof options === 'object' && options !== null) { const { type = 'default', format = 'pem', @@ -206,44 +215,24 @@ function getCACertificates(options = undefined) { validateString(type, 'type'); validateOneOf(format, 'format', ['pem', 'der', 'x509', 'string', 'buffer']); - let effectiveFormat = format; - if (format === 'string') { - effectiveFormat = 'pem'; - } else if (format === 'buffer') { - effectiveFormat = 'der'; - } + const certs = getCACertificatesAsStrings(type); - let certs; - switch (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 === 'x509') { + return certs.map((cert) => new X509Certificate(cert)); } - if (effectiveFormat === 'pem') { - return certs.map((cert) => { - if (typeof cert === 'string') { - return cert; - } - return `-----BEGIN CERTIFICATE-----\n${cert.toString('base64').match(/.{1,64}/g).join('\n')}\n-----END CERTIFICATE-----`; - }); + if (format === 'pem' || format === 'string') { + return certs; } const buffers = certs.map((cert) => { - if (Buffer.isBuffer(cert)) { - return cert; - } const base64 = cert.replace(/(?:\s|-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----)+/g, ''); return Buffer.from(base64, 'base64'); }); - if (effectiveFormat === 'der') { + if (format === 'der' || format === 'buffer') { return buffers; } - - return buffers.map((buf) => new X509Certificate(buf)); } throw new ERR_INVALID_ARG_TYPE('options', ['string', 'object'], options); From f131075e99d278f013286f6e451188fe50f1e050 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 31 Mar 2026 14:28:22 +0900 Subject: [PATCH 30/34] tls: simplify getCACertificates() structure per review --- lib/tls.js | 48 ++++++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/lib/tls.js b/lib/tls.js index 7446770c53ea30..ce96f56f714f98 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -202,40 +202,36 @@ function getCACertificatesAsStrings(type = 'default') { } function getCACertificates(options = undefined) { - if (typeof options === 'string' || options === undefined) { - return getCACertificatesAsStrings(options); - } + let type = 'default'; + let format = 'pem'; if (typeof options === 'object' && options !== null) { - const { - type = 'default', - format = 'pem', - } = options; - - validateString(type, 'type'); - validateOneOf(format, 'format', ['pem', 'der', 'x509', 'string', 'buffer']); + ({ type = 'default', format = 'pem' } = options); + } else if (typeof options === 'string' || options === undefined) { + type = options ?? 'default'; + } else { + throw new ERR_INVALID_ARG_TYPE('options', ['string', 'object'], options); + } - const certs = getCACertificatesAsStrings(type); + validateString(type, 'type'); + validateOneOf(format, 'format', ['pem', 'der', 'x509', 'string', 'buffer']); - if (format === 'x509') { - return certs.map((cert) => new X509Certificate(cert)); - } + const certs = getCACertificatesAsStrings(type); - if (format === 'pem' || format === 'string') { - return certs; - } - - const buffers = certs.map((cert) => { - const base64 = cert.replace(/(?:\s|-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----)+/g, ''); - return Buffer.from(base64, 'base64'); - }); + if (format === 'pem' || format === 'string') { + return certs; + } - if (format === 'der' || format === 'buffer') { - return buffers; - } + if (format === 'x509') { + return certs.map((cert) => new X509Certificate(cert)); } - throw new ERR_INVALID_ARG_TYPE('options', ['string', 'object'], options); + const buffers = certs.map((cert) => { + const base64 = cert.replace(/(?:\s|-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----)+/g, ''); + return Buffer.from(base64, 'base64'); + }); + + return buffers; } exports.getCACertificates = getCACertificates; From c1e61f14740ffb6ca1c1bb4dab6ccba2fe70dde2 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 31 Mar 2026 15:19:24 +0900 Subject: [PATCH 31/34] tls: enhance test validation --- ...est-tls-get-ca-certificates-x509-option.js | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/test/parallel/test-tls-get-ca-certificates-x509-option.js b/test/parallel/test-tls-get-ca-certificates-x509-option.js index 0f819df3f41f2f..1d9c4958143952 100644 --- a/test/parallel/test-tls-get-ca-certificates-x509-option.js +++ b/test/parallel/test-tls-get-ca-certificates-x509-option.js @@ -8,28 +8,41 @@ const assert = require('assert'); const tls = require('tls'); const { X509Certificate } = require('crypto'); +const expectedPems = tls.getCACertificates({ type: 'default', format: 'pem' }); + { const certs = tls.getCACertificates({ type: 'default', format: 'x509' }); - assert.ok(Array.isArray(certs)); - assert.ok(certs.length > 0); - for (const cert of certs) { + assert.strictEqual(certs.length, expectedPems.length); + + for (let i = 0; i < certs.length; i++) { + const cert = certs[i]; + const expected = new X509Certificate(expectedPems[i]); + assert.ok(cert instanceof X509Certificate); + + assert.strictEqual(cert.fingerprint, expected.fingerprint); + assert.strictEqual(cert.serialNumber, expected.serialNumber); + assert.strictEqual(cert.subject, expected.subject); + assert.strictEqual(cert.raw.toString('hex'), expected.raw.toString('hex')); } } { const certs = tls.getCACertificates({ type: 'default', format: 'buffer' }); - assert.ok(Array.isArray(certs)); - assert.ok(certs.length > 0); - for (const cert of certs) { + assert.strictEqual(certs.length, expectedPems.length); + + for (let i = 0; i < certs.length; i++) { + const cert = certs[i]; + const expected = new X509Certificate(expectedPems[i]); + assert.ok(Buffer.isBuffer(cert)); + assert.strictEqual(cert.toString('hex'), expected.raw.toString('hex')); } } { const certs = tls.getCACertificates({ type: 'default' }); - assert.ok(Array.isArray(certs)); - assert.ok(certs.length > 0); + assert.strictEqual(certs.length, expectedPems.length); for (const cert of certs) { assert.strictEqual(typeof cert, 'string'); assert.ok(cert.includes('-----BEGIN CERTIFICATE-----')); @@ -61,6 +74,6 @@ const { X509Certificate } = require('crypto'); }, { name: 'TypeError', code: 'ERR_INVALID_ARG_VALUE', - message: "The argument 'type' is invalid. Received 'invalid'" + message: /The argument 'type' is invalid/ }); } From 849e3d36d1e2a8c9ccc7df44480137ebe19fc2c0 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 31 Mar 2026 15:20:03 +0900 Subject: [PATCH 32/34] fix: run make lint-js --- test/parallel/test-tls-get-ca-certificates-x509-option.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-tls-get-ca-certificates-x509-option.js b/test/parallel/test-tls-get-ca-certificates-x509-option.js index 1d9c4958143952..3f76d05dfca241 100644 --- a/test/parallel/test-tls-get-ca-certificates-x509-option.js +++ b/test/parallel/test-tls-get-ca-certificates-x509-option.js @@ -19,7 +19,7 @@ const expectedPems = tls.getCACertificates({ type: 'default', format: 'pem' }); const expected = new X509Certificate(expectedPems[i]); assert.ok(cert instanceof X509Certificate); - + assert.strictEqual(cert.fingerprint, expected.fingerprint); assert.strictEqual(cert.serialNumber, expected.serialNumber); assert.strictEqual(cert.subject, expected.subject); From f834d6a7d12b87492b2e708b4dd565d9adbc05a4 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 31 Mar 2026 15:27:43 +0900 Subject: [PATCH 33/34] test: simplify getCACertificates() and use common test helper --- ...est-tls-get-ca-certificates-x509-option.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/test/parallel/test-tls-get-ca-certificates-x509-option.js b/test/parallel/test-tls-get-ca-certificates-x509-option.js index 3f76d05dfca241..35528b13c461e4 100644 --- a/test/parallel/test-tls-get-ca-certificates-x509-option.js +++ b/test/parallel/test-tls-get-ca-certificates-x509-option.js @@ -7,6 +7,7 @@ if (!common.hasCrypto) const assert = require('assert'); const tls = require('tls'); const { X509Certificate } = require('crypto'); +const tlsCommon = require('../common/tls'); const expectedPems = tls.getCACertificates({ type: 'default', format: 'pem' }); @@ -14,29 +15,21 @@ const expectedPems = tls.getCACertificates({ type: 'default', format: 'pem' }); const certs = tls.getCACertificates({ type: 'default', format: 'x509' }); assert.strictEqual(certs.length, expectedPems.length); - for (let i = 0; i < certs.length; i++) { - const cert = certs[i]; - const expected = new X509Certificate(expectedPems[i]); + const certsRaw = certs.map((c) => c.raw); + tlsCommon.assertEqualCerts(certsRaw, expectedPems); + for (const cert of certs) { assert.ok(cert instanceof X509Certificate); - - assert.strictEqual(cert.fingerprint, expected.fingerprint); - assert.strictEqual(cert.serialNumber, expected.serialNumber); - assert.strictEqual(cert.subject, expected.subject); - assert.strictEqual(cert.raw.toString('hex'), expected.raw.toString('hex')); } } { const certs = tls.getCACertificates({ type: 'default', format: 'buffer' }); assert.strictEqual(certs.length, expectedPems.length); + tlsCommon.assertEqualCerts(certs, expectedPems); - for (let i = 0; i < certs.length; i++) { - const cert = certs[i]; - const expected = new X509Certificate(expectedPems[i]); - + for (const cert of certs) { assert.ok(Buffer.isBuffer(cert)); - assert.strictEqual(cert.toString('hex'), expected.raw.toString('hex')); } } From 625faaaa1992cbba88701b2a827d9db2c4c46837 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 31 Mar 2026 15:38:34 +0900 Subject: [PATCH 34/34] fix: fix lint format-md --- doc/api/tls.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/api/tls.md b/doc/api/tls.md index 9dfe85b9c112f1..b5e29536429044 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2362,11 +2362,14 @@ changes: trusted store. * When [`NODE_EXTRA_CA_CERTS`][] is used, this would also include certificates loaded from the specified file. + * `"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.