Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

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

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

<!-- YAML
added:
- v23.10.0
- v22.15.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/59349
description: Added the `options` argument with `format` option.
-->

* `type` {string|undefined} The type of CA certificates that will be returned. Valid values
* `type` {string} 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` {Object}
* `format` {string} The format of returned certificates. One of `"pem"`, `"der"`, or `"x509"`.
**Default:** `"pem"`.
* `"pem"` (alias: `"string"`): Returns an array of PEM-encoded certificate strings.
* `"der"` (alias: `"buffer"`): Returns an array of certificate data as `Buffer` objects in DER format.
* `"x509"`: Returns an array of [`X509Certificate`][x509certificate] instances.

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

Returns an array containing the CA certificates from various sources, depending on `type`:

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

const {
namespace: {
Expand Down Expand Up @@ -186,8 +187,7 @@ function cacheDefaultCACertificates() {
return defaultCACertificates;
}

// TODO(joyeecheung): support X509Certificate output?
function getCACertificates(type = 'default') {
function getCACertificatesAsStrings(type = 'default') {
validateString(type, 'type');

switch (type) {
Expand All @@ -203,6 +203,39 @@ function getCACertificates(type = 'default') {
throw new ERR_INVALID_ARG_VALUE('type', type);
}
}

function getCACertificates(type = 'default', options) {
validateString(type, 'type');

let format = 'pem';

if (options != null) {
if (typeof options !== 'object') {
throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
}
({ format = 'pem' } = options);
}

validateOneOf(format, 'format', ['pem', 'der', 'x509', 'string', 'buffer']);

const certs = getCACertificatesAsStrings(type);

if (format === 'pem' || format === 'string') {
return certs;
}

if (format === 'x509') {
return certs.map((cert) => new X509Certificate(cert));
}

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;

function setDefaultCACertificates(certs) {
Expand Down
3 changes: 3 additions & 0 deletions test/parallel/test-tls-get-ca-certificates-bundled.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ assert.strictEqual(certs, tls.rootCertificates);

// It's cached on subsequent accesses.
assert.strictEqual(certs, tls.getCACertificates('bundled'));

// The options form with format: 'pem' returns the same result.
assert.deepStrictEqual(certs, tls.getCACertificates('bundled', { format: 'pem' }));
3 changes: 3 additions & 0 deletions test/parallel/test-tls-get-ca-certificates-default.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ assert.strictEqual(certs, certs2);

// It's cached on subsequent accesses.
assert.strictEqual(certs, tls.getCACertificates('default'));

// The options form with format: 'pem' returns the same result.
assert.deepStrictEqual(certs, tls.getCACertificates('default', { format: 'pem' }));
3 changes: 3 additions & 0 deletions test/parallel/test-tls-get-ca-certificates-system.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ assert.deepStrictEqual(defaultSet.intersection(systemSet), systemSet);

// It's cached on subsequent accesses.
assert.strictEqual(systemCerts, tls.getCACertificates('system'));

// The options form with format: 'pem' returns the same result.
assert.deepStrictEqual(systemCerts, tls.getCACertificates('system', { format: 'pem' }));
72 changes: 72 additions & 0 deletions test/parallel/test-tls-get-ca-certificates-x509-option.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use strict';

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

const assert = require('assert');
const tls = require('tls');
const { X509Certificate } = require('crypto');
const tlsCommon = require('../common/tls');

const expectedPems = tls.getCACertificates('default');

{
const certs = tls.getCACertificates('default', { format: 'x509' });
assert.strictEqual(certs.length, expectedPems.length);

const certsRaw = certs.map((c) => c.raw);
tlsCommon.assertEqualCerts(certsRaw, expectedPems);

for (const cert of certs) {
assert.ok(cert instanceof X509Certificate);
}
}

{
const certs = tls.getCACertificates('default', { format: 'buffer' });
assert.strictEqual(certs.length, expectedPems.length);
tlsCommon.assertEqualCerts(certs, expectedPems);

for (const cert of certs) {
assert.ok(Buffer.isBuffer(cert));
}
}

{
const certs = tls.getCACertificates('default');
assert.strictEqual(certs.length, expectedPems.length);
for (const cert of certs) {
assert.strictEqual(typeof cert, 'string');
assert.ok(cert.includes('-----BEGIN CERTIFICATE-----'));
}
}

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

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

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