Skip to content

Commit a38fa24

Browse files
committed
tls: add output formats to getCACertificates()
Signed-off-by: haramjeong <04harams77@gmail.com>
1 parent 449b950 commit a38fa24

4 files changed

Lines changed: 138 additions & 17 deletions

File tree

‎doc/api/tls.md‎

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2388,21 +2388,35 @@ const additionalCerts = ['-----BEGIN CERTIFICATE-----\n...'];
23882388
tls.setDefaultCACertificates([...currentCerts, ...additionalCerts]);
23892389
```
23902390

2391-
## `tls.getCACertificates([type])`
2391+
## `tls.getCACertificates([options])`
23922392

23932393
<!-- YAML
23942394
added:
23952395
- v23.10.0
23962396
- v22.15.0
2397-
-->
2398-
2399-
* `type` {string|undefined} The type of CA certificates that will be returned. Valid values
2400-
are `"default"`, `"system"`, `"bundled"` and `"extra"`.
2401-
**Default:** `"default"`.
2402-
* Returns: {string\[]} An array of PEM-encoded certificates. The array may contain duplicates
2403-
if the same certificate is repeatedly stored in multiple sources.
2404-
2405-
Returns an array containing the CA certificates from various sources, depending on `type`:
2397+
changes:
2398+
- version: REPLACEME
2399+
pr-url: https://github.com/nodejs/node/pull/59349
2400+
description: Added the `format` option and support for passing the `type`
2401+
as an `options` object to `getCACertificates()`.
2402+
-->
2403+
2404+
* `options` {string|Object|undefined}
2405+
Optional. If a string, it is treated as the `type` of certificates to return.
2406+
If an object, it may contain:
2407+
* `type` {string} The type of CA certificates to return. One of `"default"`, `"system"`, `"bundled"`, or `"extra"`.
2408+
**Default:** `"default"`.
2409+
* `format` {string} The format of returned certificates. One of `"pem"`, `"der"`, or `"x509"`.
2410+
**Default:** `"pem"`.
2411+
* `"pem"` (alias: `"string"`): Returns an array of PEM-encoded certificate strings.
2412+
* `"der"` (alias: `"buffer"`): Returns an array of certificate data as `Buffer` objects in DER format.
2413+
* `"x509"`: Returns an array of [`X509Certificate`][x509certificate] instances.
2414+
2415+
* Returns: {Array}
2416+
An array of certificate data in the specified format:
2417+
* PEM strings when `format` is `"pem"` (or `"string"`).
2418+
* `Buffer` objects containing DER data when `format` is `"der"` (or `"buffer"`).
2419+
* [`X509Certificate`][x509certificate] instances when `format` is `"x509"`.
24062420

24072421
* `"default"`: return the CA certificates that will be used by the Node.js TLS clients by default.
24082422
* When [`--use-bundled-ca`][] is enabled (default), or [`--use-openssl-ca`][] is not enabled,
@@ -2411,11 +2425,14 @@ Returns an array containing the CA certificates from various sources, depending
24112425
trusted store.
24122426
* When [`NODE_EXTRA_CA_CERTS`][] is used, this would also include certificates loaded from the specified
24132427
file.
2428+
24142429
* `"system"`: return the CA certificates that are loaded from the system's trusted store, according
24152430
to rules set by [`--use-system-ca`][]. This can be used to get the certificates from the system
24162431
when [`--use-system-ca`][] is not enabled.
2432+
24172433
* `"bundled"`: return the CA certificates from the bundled Mozilla CA store. This would be the same
24182434
as [`tls.rootCertificates`][].
2435+
24192436
* `"extra"`: return the CA certificates loaded from [`NODE_EXTRA_CA_CERTS`][]. It's an empty array if
24202437
[`NODE_EXTRA_CA_CERTS`][] is not set.
24212438

@@ -2602,7 +2619,7 @@ added: v0.11.3
26022619
[`tls.connect()`]: #tlsconnectoptions-callback
26032620
[`tls.createSecureContext()`]: #tlscreatesecurecontextoptions
26042621
[`tls.createServer()`]: #tlscreateserveroptions-secureconnectionlistener
2605-
[`tls.getCACertificates()`]: #tlsgetcacertificatestype
2622+
[`tls.getCACertificates()`]: #tlsgetcacertificatesoptions
26062623
[`tls.getCiphers()`]: #tlsgetciphers
26072624
[`tls.rootCertificates`]: #tlsrootcertificates
26082625
[`x509.checkHost()`]: crypto.md#x509checkhostname-options
@@ -2611,3 +2628,4 @@ added: v0.11.3
26112628
[cipher list format]: https://www.openssl.org/docs/man1.1.1/man1/ciphers.html#CIPHER-LIST-FORMAT
26122629
[forward secrecy]: https://en.wikipedia.org/wiki/Perfect_forward_secrecy
26132630
[perfect forward secrecy]: #perfect-forward-secrecy
2631+
[x509certificate]: crypto.md#class-x509certificate

‎lib/tls.js‎

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ const { canonicalizeIP } = internalBinding('cares_wrap');
7070
const tlsCommon = require('internal/tls/common');
7171
const tlsWrap = require('internal/tls/wrap');
7272
const { domainToASCII } = require('internal/url');
73-
const { validateArray, validateString } = require('internal/validators');
73+
const { validateArray, validateOneOf, validateString } = require('internal/validators');
74+
const { X509Certificate } = require('crypto');
7475

7576
const {
7677
namespace: {
@@ -186,8 +187,7 @@ function cacheDefaultCACertificates() {
186187
return defaultCACertificates;
187188
}
188189

189-
// TODO(joyeecheung): support X509Certificate output?
190-
function getCACertificates(type = 'default') {
190+
function getCACertificatesAsStrings(type = 'default') {
191191
validateString(type, 'type');
192192

193193
switch (type) {
@@ -203,6 +203,40 @@ function getCACertificates(type = 'default') {
203203
throw new ERR_INVALID_ARG_VALUE('type', type);
204204
}
205205
}
206+
207+
function getCACertificates(options = undefined) {
208+
let type = 'default';
209+
let format = 'pem';
210+
211+
if (typeof options === 'object' && options !== null) {
212+
({ type = 'default', format = 'pem' } = options);
213+
} else if (typeof options === 'string' || options === undefined) {
214+
type = options ?? 'default';
215+
} else {
216+
throw new ERR_INVALID_ARG_TYPE('options', ['string', 'object'], options);
217+
}
218+
219+
validateString(type, 'type');
220+
validateOneOf(format, 'format', ['pem', 'der', 'x509', 'string', 'buffer']);
221+
222+
const certs = getCACertificatesAsStrings(type);
223+
224+
if (format === 'pem' || format === 'string') {
225+
return certs;
226+
}
227+
228+
if (format === 'x509') {
229+
return certs.map((cert) => new X509Certificate(cert));
230+
}
231+
232+
const buffers = certs.map((cert) => {
233+
const base64 = cert.replace(/(?:\s|-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----)+/g, '');
234+
return Buffer.from(base64, 'base64');
235+
});
236+
237+
return buffers;
238+
}
239+
206240
exports.getCACertificates = getCACertificates;
207241

208242
function setDefaultCACertificates(certs) {

‎test/parallel/test-tls-get-ca-certificates-default.js‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,5 @@ const { assertIsCAArray } = require('../common/tls');
1313
const certs = tls.getCACertificates();
1414
assertIsCAArray(certs);
1515

16-
const certs2 = tls.getCACertificates('default');
17-
assert.strictEqual(certs, certs2);
18-
1916
// It's cached on subsequent accesses.
2017
assert.strictEqual(certs, tls.getCACertificates('default'));
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
const assert = require('assert');
8+
const tls = require('tls');
9+
const { X509Certificate } = require('crypto');
10+
const tlsCommon = require('../common/tls');
11+
12+
const expectedPems = tls.getCACertificates({ type: 'default', format: 'pem' });
13+
14+
{
15+
const certs = tls.getCACertificates({ type: 'default', format: 'x509' });
16+
assert.strictEqual(certs.length, expectedPems.length);
17+
18+
const certsRaw = certs.map((c) => c.raw);
19+
tlsCommon.assertEqualCerts(certsRaw, expectedPems);
20+
21+
for (const cert of certs) {
22+
assert.ok(cert instanceof X509Certificate);
23+
}
24+
}
25+
26+
{
27+
const certs = tls.getCACertificates({ type: 'default', format: 'buffer' });
28+
assert.strictEqual(certs.length, expectedPems.length);
29+
tlsCommon.assertEqualCerts(certs, expectedPems);
30+
31+
for (const cert of certs) {
32+
assert.ok(Buffer.isBuffer(cert));
33+
}
34+
}
35+
36+
{
37+
const certs = tls.getCACertificates({ type: 'default' });
38+
assert.strictEqual(certs.length, expectedPems.length);
39+
for (const cert of certs) {
40+
assert.strictEqual(typeof cert, 'string');
41+
assert.ok(cert.includes('-----BEGIN CERTIFICATE-----'));
42+
}
43+
}
44+
45+
{
46+
assert.throws(() => {
47+
tls.getCACertificates({ type: 'default', format: 'invalid' });
48+
}, {
49+
name: 'TypeError',
50+
code: 'ERR_INVALID_ARG_VALUE',
51+
message: /must be one of/
52+
});
53+
}
54+
55+
{
56+
const certs = tls.getCACertificates({ format: 'buffer' });
57+
assert.ok(Array.isArray(certs));
58+
assert.ok(certs.length > 0);
59+
for (const cert of certs) {
60+
assert.ok(Buffer.isBuffer(cert));
61+
}
62+
}
63+
64+
{
65+
assert.throws(() => {
66+
tls.getCACertificates({ type: 'invalid', format: 'buffer' });
67+
}, {
68+
name: 'TypeError',
69+
code: 'ERR_INVALID_ARG_VALUE',
70+
message: /The argument 'type' is invalid/
71+
});
72+
}

0 commit comments

Comments
 (0)