tls: expose built-in root certificates

Fixes: https://github.com/nodejs/node/issues/25824
PR-URL: https://github.com/nodejs/node/pull/26415
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Ron Korving <ron@ronkorving.nl>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com>
This commit is contained in:
Ben Noordhuis 2019-05-20 11:09:02 +02:00
parent cc7e15f850
commit f1a3968a01
6 changed files with 225 additions and 143 deletions

View File

@ -1384,6 +1384,7 @@ changes:
provided. provided.
For PEM encoded certificates, supported types are "TRUSTED CERTIFICATE", For PEM encoded certificates, supported types are "TRUSTED CERTIFICATE",
"X509 CERTIFICATE", and "CERTIFICATE". "X509 CERTIFICATE", and "CERTIFICATE".
See also [`tls.rootCertificates`].
* `cert` {string|string[]|Buffer|Buffer[]} Cert chains in PEM format. One cert * `cert` {string|string[]|Buffer|Buffer[]} Cert chains in PEM format. One cert
chain should be provided per private key. Each cert chain should consist of chain should be provided per private key. Each cert chain should consist of
the PEM formatted certificate for a provided private `key`, followed by the the PEM formatted certificate for a provided private `key`, followed by the
@ -1599,6 +1600,17 @@ TLSv1.2 and below.
console.log(tls.getCiphers()); // ['aes128-gcm-sha256', 'aes128-sha', ...] console.log(tls.getCiphers()); // ['aes128-gcm-sha256', 'aes128-sha', ...]
``` ```
## tls.rootCertificates
<!-- YAML
added: REPLACEME
-->
* {string[]}
An immutable array of strings representing the root certificates (in PEM format)
used for verifying peer certificates. This is the default value of the `ca`
option to [`tls.createSecureContext()`].
## tls.DEFAULT_ECDH_CURVE ## tls.DEFAULT_ECDH_CURVE
<!-- YAML <!-- YAML
added: v0.11.13 added: v0.11.13
@ -1784,6 +1796,7 @@ where `secureSocket` has the same API as `pair.cleartext`.
[`tls.createSecurePair()`]: #tls_tls_createsecurepair_context_isserver_requestcert_rejectunauthorized_options [`tls.createSecurePair()`]: #tls_tls_createsecurepair_context_isserver_requestcert_rejectunauthorized_options
[`tls.createServer()`]: #tls_tls_createserver_options_secureconnectionlistener [`tls.createServer()`]: #tls_tls_createserver_options_secureconnectionlistener
[`tls.getCiphers()`]: #tls_tls_getciphers [`tls.getCiphers()`]: #tls_tls_getciphers
[`tls.rootCertificates`]: #tls_tls_rootcertificates
[Chrome's 'modern cryptography' setting]: https://www.chromium.org/Home/chromium-security/education/tls#TOC-Cipher-Suites [Chrome's 'modern cryptography' setting]: https://www.chromium.org/Home/chromium-security/education/tls#TOC-Cipher-Suites
[DHE]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange [DHE]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
[ECDHE]: https://en.wikipedia.org/wiki/Elliptic_curve_Diffie%E2%80%93Hellman [ECDHE]: https://en.wikipedia.org/wiki/Elliptic_curve_Diffie%E2%80%93Hellman

View File

@ -21,6 +21,8 @@
'use strict'; 'use strict';
const { Object } = primordials;
const { const {
ERR_TLS_CERT_ALTNAME_INVALID, ERR_TLS_CERT_ALTNAME_INVALID,
ERR_OUT_OF_RANGE ERR_OUT_OF_RANGE
@ -33,7 +35,7 @@ const { isArrayBufferView } = require('internal/util/types');
const net = require('net'); const net = require('net');
const { getOptionValue } = require('internal/options'); const { getOptionValue } = require('internal/options');
const url = require('url'); const url = require('url');
const binding = internalBinding('crypto'); const { getRootCertificates, getSSLCiphers } = internalBinding('crypto');
const { Buffer } = require('buffer'); const { Buffer } = require('buffer');
const EventEmitter = require('events'); const EventEmitter = require('events');
const { URL } = require('internal/url'); const { URL } = require('internal/url');
@ -76,9 +78,25 @@ else
exports.getCiphers = internalUtil.cachedResult( exports.getCiphers = internalUtil.cachedResult(
() => internalUtil.filterDuplicateStrings(binding.getSSLCiphers(), true) () => internalUtil.filterDuplicateStrings(getSSLCiphers(), true)
); );
let rootCertificates;
function cacheRootCertificates() {
rootCertificates = Object.freeze(getRootCertificates());
}
Object.defineProperty(exports, 'rootCertificates', {
configurable: false,
enumerable: true,
get: () => {
// Out-of-line caching to promote inlining the getter.
if (!rootCertificates) cacheRootCertificates();
return rootCertificates;
},
});
// Convert protocols array into valid OpenSSL protocols list // Convert protocols array into valid OpenSSL protocols list
// ("\x06spdy/2\x08http/1.1\x08http/1.0") // ("\x06spdy/2\x08http/1.1\x08http/1.0")
function convertProtocols(protocols) { function convertProtocols(protocols) {

View File

@ -944,6 +944,24 @@ static X509_STORE* NewRootCertStore() {
} }
void GetRootCertificates(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Local<Array> result = Array::New(env->isolate(), arraysize(root_certs));
for (size_t i = 0; i < arraysize(root_certs); i++) {
Local<Value> value;
if (!String::NewFromOneByte(env->isolate(),
reinterpret_cast<const uint8_t*>(root_certs[i]),
NewStringType::kNormal).ToLocal(&value) ||
!result->Set(env->context(), i, value).FromMaybe(false)) {
return;
}
}
args.GetReturnValue().Set(result);
}
void SecureContext::AddCACert(const FunctionCallbackInfo<Value>& args) { void SecureContext::AddCACert(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args); Environment* env = Environment::GetCurrent(args);
@ -6870,6 +6888,8 @@ void Initialize(Local<Object> target,
env->SetMethodNoSideEffect(target, "certVerifySpkac", VerifySpkac); env->SetMethodNoSideEffect(target, "certVerifySpkac", VerifySpkac);
env->SetMethodNoSideEffect(target, "certExportPublicKey", ExportPublicKey); env->SetMethodNoSideEffect(target, "certExportPublicKey", ExportPublicKey);
env->SetMethodNoSideEffect(target, "certExportChallenge", ExportChallenge); env->SetMethodNoSideEffect(target, "certExportChallenge", ExportChallenge);
env->SetMethodNoSideEffect(target, "getRootCertificates",
GetRootCertificates);
// Exposed for testing purposes only. // Exposed for testing purposes only.
env->SetMethodNoSideEffect(target, "isExtraRootCertsFileLoaded", env->SetMethodNoSideEffect(target, "isExtraRootCertsFileLoaded",
IsExtraRootCertsFileLoaded); IsExtraRootCertsFileLoaded);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto) common.skip('missing crypto');
const assert = require('assert');
const tls = require('tls');
assert(Array.isArray(tls.rootCertificates));
assert(tls.rootCertificates.length > 0);
// Getter should return the same object.
assert.strictEqual(tls.rootCertificates, tls.rootCertificates);
// Array is immutable...
assert.throws(() => tls.rootCertificates[0] = 0, /TypeError/);
assert.throws(() => tls.rootCertificates.sort(), /TypeError/);
// ...and so is the property.
assert.throws(() => tls.rootCertificates = 0, /TypeError/);
// Does not contain duplicates.
assert.strictEqual(tls.rootCertificates.length,
new Set(tls.rootCertificates).size);
assert(tls.rootCertificates.every((s) => {
return s.startsWith('-----BEGIN CERTIFICATE-----\n');
}));
assert(tls.rootCertificates.every((s) => {
return s.endsWith('\n-----END CERTIFICATE-----');
}));

View File

@ -265,7 +265,7 @@ while (<TXT>) {
$encoded =~ s/(.{1,${opt_w}})/"$1\\n"\n/g; $encoded =~ s/(.{1,${opt_w}})/"$1\\n"\n/g;
my $pem = "\"-----BEGIN CERTIFICATE-----\\n\"\n" my $pem = "\"-----BEGIN CERTIFICATE-----\\n\"\n"
. $encoded . $encoded
. "\"-----END CERTIFICATE-----\\n\",\n"; . "\"-----END CERTIFICATE-----\",\n";
print CRT "\n/* $caname */\n"; print CRT "\n/* $caname */\n";
my $maxStringLength = length($caname); my $maxStringLength = length($caname);