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:
parent
cc7e15f850
commit
f1a3968a01
@ -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
|
||||||
|
22
lib/tls.js
22
lib/tls.js
@ -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) {
|
||||||
|
@ -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
31
test/parallel/test-tls-root-certificates.js
Normal file
31
test/parallel/test-tls-root-certificates.js
Normal 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-----');
|
||||||
|
}));
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user