From c4ca69e9c50c1cc9b41db1ea2052fe398b4aaa12 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 5 Aug 2025 01:42:09 +0900 Subject: [PATCH 01/29] 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 8ceb1350e11289..98092cb9454eff 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2308,7 +2308,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, @@ -2331,11 +2335,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 3ad4bf77086f4d..5c268b6eb9d278 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -61,6 +61,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'); // Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations // every {CLIENT_RENEG_WINDOW} seconds. An error event is emitted if more @@ -164,23 +165,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 ee04b404f88c5a835f96566c7f80a84089bbba40 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 5 Aug 2025 01:57:16 +0900 Subject: [PATCH 02/29] 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 98092cb9454eff..11ae73fe61a384 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2316,16 +2316,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 1b8197b0ce5ad782a296b31ee05dc208388ea8b4 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 5 Aug 2025 02:04:30 +0900 Subject: [PATCH 03/29] 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 11ae73fe61a384..55587426322824 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2323,9 +2323,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. @@ -2463,6 +2463,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 [`'secureConnect'`]: #event-secureconnect From 187c86db55e5be1cbbbaec6b38e69302bfd5fe61 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/29] 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 55587426322824..87da25f9f01c13 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2335,7 +2335,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 4048b549b8f5ded301d11fd8babf996791c21fc9 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/29] 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 87da25f9f01c13..8984608080e457 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2338,7 +2338,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 eae9df7bdee5cc91136dbbbe44b1b8bfccb42acf 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/29] 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 8984608080e457..6ac463ed3c3b59 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2340,7 +2340,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 e013568e5f1a3c0561fee2d0d00c2368dd6aa762 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/29] 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 195125ffbd4987bb35405e24003a9dd8417b85d7 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/29] 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 6ac463ed3c3b59..b05026be2d2bcb 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2321,8 +2321,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 b1eacfb628cbff0d27ceac8452d24a9216734025 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Wed, 6 Aug 2025 01:22:43 +0900 Subject: [PATCH 09/29] 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 5c268b6eb9d278..9b3a111e74d996 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -60,7 +60,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'); // Allow {CLIENT_RENEG_LIMIT} client-initiated session renegotiations @@ -174,6 +177,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 603967fa55002c34676321002327564ba133215a Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Wed, 6 Aug 2025 01:53:38 +0900 Subject: [PATCH 10/29] 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 36e8a2e15eb454699c4ba2bf78c1d6ab2c5f20ec Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Wed, 6 Aug 2025 01:57:13 +0900 Subject: [PATCH 11/29] 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 62ec1482312d883401c778984fb9f298f81d7f1b Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Wed, 6 Aug 2025 01:59:59 +0900 Subject: [PATCH 12/29] =?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 b05026be2d2bcb..cd0aa2a69059bf 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2314,6 +2314,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 361c79967106f677f6f0c5c07d383a6b05b62712 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Wed, 6 Aug 2025 02:09:57 +0900 Subject: [PATCH 13/29] 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 cd0aa2a69059bf..396ecb182f9304 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2315,8 +2315,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 358b2bdd6b5bb5e01bffbfcfcab6e9f6aab2d080 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Mon, 11 Aug 2025 00:28:18 +0900 Subject: [PATCH 14/29] 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 396ecb182f9304..4984b4751ee76f 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2326,12 +2326,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, @@ -2465,7 +2469,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 [`'secureConnect'`]: #event-secureconnect @@ -2513,3 +2516,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 9b3a111e74d996..0981a40184e20a 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -175,9 +175,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) { @@ -188,13 +196,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; + } + + 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; + 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 f9dfc626b9dcf0fc967d1638b90c69a4d73eed5d Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Mon, 11 Aug 2025 00:35:38 +0900 Subject: [PATCH 15/29] 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 4984b4751ee76f..a1fe17c622342c 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2344,11 +2344,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 cdf69dc196f753d8ff8adc84276b8f3ab3ce8135 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 19 Aug 2025 15:30:39 +0900 Subject: [PATCH 16/29] 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 a1fe17c622342c..3ae9b18c54e7b4 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2327,12 +2327,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 0981a40184e20a..482f54bfd4b35e 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -178,14 +178,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) { @@ -196,37 +192,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 d978079fe9d597308e06b907743b77b6b2b0a2b3 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Wed, 20 Aug 2025 15:41:11 +0900 Subject: [PATCH 17/29] 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 3ae9b18c54e7b4..652796c7b98cc7 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2508,7 +2508,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 8cf2dbe602b9b46ce65905b42a9fc21b59a3f3f7 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Sun, 31 Aug 2025 16:38:16 +0900 Subject: [PATCH 18/29] 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 652796c7b98cc7..e423752e818fd5 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2330,7 +2330,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). @@ -2517,4 +2517,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 e6de951765cfb377c7aa10cd4899ecd71e8c2d23 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Sun, 31 Aug 2025 16:53:18 +0900 Subject: [PATCH 19/29] 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 e423752e818fd5..4d42e55a3fcfba 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2330,7 +2330,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). @@ -2517,4 +2517,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 da83ccf4d6990482857d38e05e6b76c9fe4c6897 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Sun, 31 Aug 2025 17:00:37 +0900 Subject: [PATCH 20/29] 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 4d42e55a3fcfba..a3cb4310cc9e2e 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2517,4 +2517,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 10e9225605fe8a7aa383695ea90a1b60ef50eef0 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/29] 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 ab76b14e128b9cc084795a9717e5fac1c74c6253 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Mon, 1 Sep 2025 01:42:01 +0900 Subject: [PATCH 22/29] 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 a3cb4310cc9e2e..d58556f93cba01 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2326,14 +2326,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 5e8dd7a2f4e2937af668e8266292b33a0c86f370 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Mon, 1 Sep 2025 16:22:33 +0900 Subject: [PATCH 23/29] 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 f84c311e3a17f213d1912b84691e1fe4bfff2885 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 2 Sep 2025 11:03:14 +0900 Subject: [PATCH 24/29] 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 a335306abcb59b4e2cdf542e0ee8826fcc780170 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 2 Sep 2025 11:04:45 +0900 Subject: [PATCH 25/29] 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 319841f1e0c2b0e6919578bf4c491b75f824c2d2 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Sun, 7 Sep 2025 22:07:03 +0900 Subject: [PATCH 26/29] 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 482f54bfd4b35e..c47db728e57ba0 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -168,6 +168,8 @@ function cacheDefaultCACertificates() { return defaultCACertificates; } +const certificateCache = { __proto__: null }; + function getCACertificates(options = {}) { if (typeof options === 'string') { options = { type: options }; @@ -177,11 +179,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) { @@ -192,32 +220,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 fd0de18c10b6afd6809bcb68096a48fa7c03bbdb Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Tue, 9 Sep 2025 15:55:47 +0900 Subject: [PATCH 27/29] 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 c47db728e57ba0..d3eaf9a7ba4a22 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -168,38 +168,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'); }); @@ -211,37 +230,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 95c07aa98d8c5937de5fcca7cf0f51f56c2feb7c 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/29] 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 d58556f93cba01..813953e9fee79b 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -2345,14 +2345,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 e2879a5926e49cc5ecd13cf78bf8cff917a91fc1 Mon Sep 17 00:00:00 2001 From: haramjeong <04harams77@gmail.com> Date: Thu, 11 Sep 2025 12:43:42 +0900 Subject: [PATCH 29/29] 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 d3eaf9a7ba4a22..372eba6bcea083 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -168,20 +168,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', @@ -190,44 +199,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);