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
25 changes: 20 additions & 5 deletions doc/api/tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -2353,7 +2353,7 @@ const additionalCerts = ['-----BEGIN CERTIFICATE-----\n...'];
tls.setDefaultCACertificates([...currentCerts, ...additionalCerts]);
```

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

<!-- YAML
added:
Expand All @@ -2362,8 +2362,14 @@ added:
-->

* `type` {string|undefined} The type of CA certificates that will be returned. Valid values
are `"default"`, `"system"`, `"bundled"` and `"extra"`.
are `"default"`, `"system"`, `"bundled"`, `"extra"` and `"openssl"`.
**Default:** `"default"`.
* `cert` {string|ArrayBufferView|X509Certificate|undefined} The certificate used
to look up its issuer in the selected CA store. Strings must contain a
PEM-encoded certificate. An `ArrayBufferView` may contain a PEM or DER encoded
certificate.
Required when `type` is `"openssl"`, or when `type` is `"default"` and
[`--use-openssl-ca`][] is enabled.
* Returns: {string\[]} An array of PEM-encoded certificates. The array may contain duplicates
if the same certificate is repeatedly stored in multiple sources.

Expand All @@ -2372,17 +2378,26 @@ Returns an array containing the CA certificates from various sources, depending
* `"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,
this would include CA certificates from the bundled Mozilla CA store.
* When OpenSSL's default CA store is selected, either at build time or through
[`--use-openssl-ca`][], `cert` is required. OpenSSL's default certificate
locations form the base of the default CA store. Certificate directories
use subject-hash lookup and cannot be enumerated without a certificate
lookup key. Calling this method without `cert` throws.
* When [`--use-system-ca`][] is enabled, this would also include certificates from the system's
trusted store.
* When [`NODE_EXTRA_CA_CERTS`][] is used, this would also include certificates loaded from the specified
file.
* When [`NODE_EXTRA_CA_CERTS`][] is used, certificates from the specified
file are included in the default CA store.
* `"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.
* `"openssl"`: return CA certificates looked up from OpenSSL's default CA
store using the issuer name of `cert`. This follows OpenSSL's
hashed-directory lookup behavior for certificates loaded from the default
certificate directory.

## `tls.getCiphers()`

Expand Down Expand Up @@ -2562,7 +2577,7 @@ added: v0.11.3
[`tls.connect()`]: #tlsconnectoptions-callback
[`tls.createSecureContext()`]: #tlscreatesecurecontextoptions
[`tls.createServer()`]: #tlscreateserveroptions-secureconnectionlistener
[`tls.getCACertificates()`]: #tlsgetcacertificatestype
[`tls.getCACertificates()`]: #tlsgetcacertificatestype-cert
[`tls.getCiphers()`]: #tlsgetciphers
[`tls.rootCertificates`]: #tlsrootcertificates
[`x509.checkHost()`]: crypto.md#x509checkhostname-options
Expand Down
94 changes: 78 additions & 16 deletions lib/tls.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const {
JSONParse,
ObjectDefineProperty,
ObjectFreeze,
SafeMap,
StringFromCharCode,
} = primordials;

Expand All @@ -38,11 +39,14 @@ const {
ERR_OUT_OF_RANGE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_ARG_TYPE,
ERR_MISSING_ARGS,
} = require('internal/errors').codes;

const {
getBundledRootCertificates,
getExtraCACertificates,
lookupDefaultCACertificates,
lookupOpenSSLCACertificates,
getSystemCACertificates,
resetRootCertStore,
getUserRootCertificates,
Expand Down Expand Up @@ -71,6 +75,8 @@ const tlsCommon = require('internal/tls/common');
const tlsWrap = require('internal/tls/wrap');
const { domainToASCII } = require('internal/url');
const { validateString } = require('internal/validators');
const { isX509Certificate } = require('internal/crypto/x509');
const { kHandle } = require('internal/crypto/util');

const {
namespace: {
Expand Down Expand Up @@ -145,11 +151,30 @@ function cacheSystemCACertificates() {

return systemCACertificates;
}
let opensslCACertificateLookup;
function cacheOpenSSLCertificateLookup(cert) {
opensslCACertificateLookup ??= new SafeMap();

const key = isArrayBufferView(cert) ?
`buffer:${Buffer.from(
cert.buffer,
cert.byteOffset,
cert.byteLength).toString('base64')}` :
`cert:${cert}`;

const cached = opensslCACertificateLookup.get(key);
if (cached !== undefined) return cached;

const result = ObjectFreeze(lookupOpenSSLCACertificates(cert));
opensslCACertificateLookup.set(key, result);
return result;
}

let defaultCACertificates;
let defaultCACertificateLookup;
let hasResetDefaultCACertificates = false;

function cacheDefaultCACertificates() {
function cacheDefaultCACertificates(cert) {
if (defaultCACertificates) { return defaultCACertificates; }

if (hasResetDefaultCACertificates) {
Expand All @@ -158,26 +183,44 @@ function cacheDefaultCACertificates() {
return defaultCACertificates;
}

defaultCACertificates = [];

if (!getOptionValue('--use-openssl-ca')) {
const bundled = cacheBundledRootCertificates();
for (let i = 0; i < bundled.length; ++i) {
ArrayPrototypePush(defaultCACertificates, bundled[i]);
if (getOptionValue('[ssl_openssl_cert_store]')) {
if (cert === undefined) {
throw new ERR_MISSING_ARGS('cert');
}
if (getOptionValue('--use-system-ca')) {
const system = cacheSystemCACertificates();
for (let i = 0; i < system.length; ++i) {
cert = validateOpenSSLCACertificate(cert);

const key = isArrayBufferView(cert) ?
`buffer:${Buffer.from(
cert.buffer,
cert.byteOffset,
cert.byteLength).toString('base64')}` :
`cert:${cert}`;

defaultCACertificateLookup ??= new SafeMap();
const cached = defaultCACertificateLookup.get(key);
if (cached !== undefined) return cached;

const result = ObjectFreeze(lookupDefaultCACertificates(cert));
defaultCACertificateLookup.set(key, result);
return result;
}

ArrayPrototypePush(defaultCACertificates, system[i]);
}
defaultCACertificates = [];

const bundled = cacheBundledRootCertificates();
for (let i = 0; i < bundled.length; ++i) {
ArrayPrototypePush(defaultCACertificates, bundled[i]);
}
if (getOptionValue('--use-system-ca')) {
const system = cacheSystemCACertificates();
for (let i = 0; i < system.length; ++i) {
ArrayPrototypePush(defaultCACertificates, system[i]);
}
}

if (process.env.NODE_EXTRA_CA_CERTS) {
const extra = cacheExtraCACertificates();
for (let i = 0; i < extra.length; ++i) {

ArrayPrototypePush(defaultCACertificates, extra[i]);
}
}
Expand All @@ -186,19 +229,35 @@ function cacheDefaultCACertificates() {
return defaultCACertificates;
}

function validateOpenSSLCACertificate(cert) {
if (cert !== null &&
(typeof cert === 'object' || typeof cert === 'function') &&
isX509Certificate(cert)) {
return cert[kHandle].pem();
}
if (typeof cert !== 'string' && !isArrayBufferView(cert)) {
throw new ERR_INVALID_ARG_TYPE(
'cert', ['string', 'ArrayBufferView', 'X509Certificate'], cert);
}
return cert;
}

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

switch (type) {
case 'default':
return cacheDefaultCACertificates();
return cacheDefaultCACertificates(cert);
case 'bundled':
return cacheBundledRootCertificates();
case 'system':
return cacheSystemCACertificates();
case 'extra':
return cacheExtraCACertificates();
case 'openssl':
cert = validateOpenSSLCACertificate(cert);
return cacheOpenSSLCertificateLookup(cert);
default:
throw new ERR_INVALID_ARG_VALUE('type', type);
}
Expand All @@ -210,7 +269,7 @@ function setDefaultCACertificates(certs) {
throw new ERR_INVALID_ARG_TYPE('certs', 'Array', certs);
}

// Verify that all elements in the array are strings
// Verify that all elements are strings or ArrayBufferViews.
for (let i = 0; i < certs.length; i++) {
if (typeof certs[i] !== 'string' && !isArrayBufferView(certs[i])) {
throw new ERR_INVALID_ARG_TYPE(
Expand All @@ -220,6 +279,7 @@ function setDefaultCACertificates(certs) {

resetRootCertStore(certs);
defaultCACertificates = undefined; // Reset the cached default certificates
defaultCACertificateLookup = undefined;
hasResetDefaultCACertificates = true;
}

Expand All @@ -231,6 +291,8 @@ if (isBuildingSnapshot()) {
// Bundled certificates are immutable so they are spared.
extraCACertificates = undefined;
systemCACertificates = undefined;
opensslCACertificateLookup = undefined;
defaultCACertificateLookup = undefined;
if (hasResetDefaultCACertificates) {
defaultCACertificates = undefined;
}
Expand Down
Loading
Loading