crypto: allow adding extra certs to well-known CAs
In closed environments, self-signed or privately signed certificates are commonly used, and rejected by Node.js since their root CAs are not well-known. Allow extending the set of well-known compiled-in CAs via environment, so they can be set as a matter of policy. PR-URL: https://github.com/nodejs/node/pull/9139 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Fedor Indutny <fedor.indutny@gmail.com>
This commit is contained in:
parent
213134f66d
commit
fd644f51f8
@ -315,7 +315,18 @@ asynchronous when outputting to a TTY on platforms which support async stdio.
|
|||||||
Setting this will void any guarantee that stdio will not be interleaved or
|
Setting this will void any guarantee that stdio will not be interleaved or
|
||||||
dropped at program exit. **Use of this mode is not recommended.**
|
dropped at program exit. **Use of this mode is not recommended.**
|
||||||
|
|
||||||
|
### `NODE_EXTRA_CA_CERTS=file`
|
||||||
|
|
||||||
|
When set, the well known "root" CAs (like VeriSign) will be extended with the
|
||||||
|
extra certificates in `file`. The file should consist of one or more trusted
|
||||||
|
certificates in PEM format. A message will be emitted (once) with
|
||||||
|
[`process.emitWarning()`][emit_warning] if the file is missing or
|
||||||
|
misformatted, but any errors are otherwise ignored.
|
||||||
|
|
||||||
|
Note that neither the well known nor extra certificates are used when the `ca`
|
||||||
|
options property is explicitly specified for a TLS or HTTPS client or server.
|
||||||
|
|
||||||
|
[emit_warning]: process.html#process_process_emitwarning_warning_name_ctor
|
||||||
[Buffer]: buffer.html#buffer_buffer
|
[Buffer]: buffer.html#buffer_buffer
|
||||||
[debugger]: debugger.html
|
[debugger]: debugger.html
|
||||||
[REPL]: repl.html
|
[REPL]: repl.html
|
||||||
|
@ -4536,6 +4536,8 @@ int Start(int argc, char** argv) {
|
|||||||
Init(&argc, const_cast<const char**>(argv), &exec_argc, &exec_argv);
|
Init(&argc, const_cast<const char**>(argv), &exec_argc, &exec_argv);
|
||||||
|
|
||||||
#if HAVE_OPENSSL
|
#if HAVE_OPENSSL
|
||||||
|
if (const char* extra = secure_getenv("NODE_EXTRA_CA_CERTS"))
|
||||||
|
crypto::UseExtraCaCerts(extra);
|
||||||
#ifdef NODE_FIPS_MODE
|
#ifdef NODE_FIPS_MODE
|
||||||
// In the case of FIPS builds we should make sure
|
// In the case of FIPS builds we should make sure
|
||||||
// the random source is properly initialized first.
|
// the random source is properly initialized first.
|
||||||
|
@ -120,6 +120,8 @@ const char* const root_certs[] = {
|
|||||||
#include "node_root_certs.h" // NOLINT(build/include_order)
|
#include "node_root_certs.h" // NOLINT(build/include_order)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::string extra_root_certs_file; // NOLINT(runtime/string)
|
||||||
|
|
||||||
X509_STORE* root_cert_store;
|
X509_STORE* root_cert_store;
|
||||||
std::vector<X509*>* root_certs_vector;
|
std::vector<X509*>* root_certs_vector;
|
||||||
|
|
||||||
@ -789,6 +791,39 @@ void SecureContext::AddCRL(const FunctionCallbackInfo<Value>& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UseExtraCaCerts(const std::string& file) {
|
||||||
|
extra_root_certs_file = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static unsigned long AddCertsFromFile( // NOLINT(runtime/int)
|
||||||
|
X509_STORE* store,
|
||||||
|
const char* file) {
|
||||||
|
ERR_clear_error();
|
||||||
|
MarkPopErrorOnReturn mark_pop_error_on_return;
|
||||||
|
|
||||||
|
BIO* bio = BIO_new_file(file, "r");
|
||||||
|
if (!bio) {
|
||||||
|
return ERR_get_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (X509* x509 =
|
||||||
|
PEM_read_bio_X509(bio, nullptr, CryptoPemCallback, nullptr)) {
|
||||||
|
X509_STORE_add_cert(store, x509);
|
||||||
|
X509_free(x509);
|
||||||
|
}
|
||||||
|
BIO_free_all(bio);
|
||||||
|
|
||||||
|
unsigned long err = ERR_peek_error(); // NOLINT(runtime/int)
|
||||||
|
// Ignore error if its EOF/no start line found.
|
||||||
|
if (ERR_GET_LIB(err) == ERR_LIB_PEM &&
|
||||||
|
ERR_GET_REASON(err) == PEM_R_NO_START_LINE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
void SecureContext::AddRootCerts(const FunctionCallbackInfo<Value>& args) {
|
void SecureContext::AddRootCerts(const FunctionCallbackInfo<Value>& args) {
|
||||||
SecureContext* sc;
|
SecureContext* sc;
|
||||||
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
||||||
@ -797,6 +832,18 @@ void SecureContext::AddRootCerts(const FunctionCallbackInfo<Value>& args) {
|
|||||||
|
|
||||||
if (!root_cert_store) {
|
if (!root_cert_store) {
|
||||||
root_cert_store = NewRootCertStore();
|
root_cert_store = NewRootCertStore();
|
||||||
|
|
||||||
|
if (!extra_root_certs_file.empty()) {
|
||||||
|
unsigned long err = AddCertsFromFile( // NOLINT(runtime/int)
|
||||||
|
root_cert_store,
|
||||||
|
extra_root_certs_file.c_str());
|
||||||
|
if (err) {
|
||||||
|
ProcessEmitWarning(sc->env(),
|
||||||
|
"Ignoring extra certs from `%s`, load failed: %s\n",
|
||||||
|
extra_root_certs_file.c_str(),
|
||||||
|
ERR_error_string(err, nullptr));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment reference count so global store is not deleted along with CTX.
|
// Increment reference count so global store is not deleted along with CTX.
|
||||||
|
@ -65,6 +65,8 @@ extern int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx);
|
|||||||
|
|
||||||
extern X509_STORE* root_cert_store;
|
extern X509_STORE* root_cert_store;
|
||||||
|
|
||||||
|
extern void UseExtraCaCerts(const std::string& file);
|
||||||
|
|
||||||
// Forward declaration
|
// Forward declaration
|
||||||
class Connection;
|
class Connection;
|
||||||
|
|
||||||
|
43
test/parallel/test-tls-env-bad-extra-ca.js
Normal file
43
test/parallel/test-tls-env-bad-extra-ca.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Setting NODE_EXTRA_CA_CERTS to non-existent file emits a warning
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
|
||||||
|
if (!common.hasCrypto) {
|
||||||
|
common.skip('missing crypto');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const tls = require('tls');
|
||||||
|
const fork = require('child_process').fork;
|
||||||
|
|
||||||
|
if (process.env.CHILD) {
|
||||||
|
// This will try to load the extra CA certs, and emit a warning when it fails.
|
||||||
|
return tls.createServer({});
|
||||||
|
}
|
||||||
|
|
||||||
|
const env = {
|
||||||
|
CHILD: 'yes',
|
||||||
|
NODE_EXTRA_CA_CERTS: common.fixturesDir + '/no-such-file-exists',
|
||||||
|
};
|
||||||
|
|
||||||
|
var opts = {
|
||||||
|
env: env,
|
||||||
|
silent: true,
|
||||||
|
};
|
||||||
|
var stderr = '';
|
||||||
|
|
||||||
|
fork(__filename, opts)
|
||||||
|
.on('exit', common.mustCall(function(status) {
|
||||||
|
assert.equal(status, 0, 'client did not succeed in connecting');
|
||||||
|
}))
|
||||||
|
.on('close', common.mustCall(function() {
|
||||||
|
assert(stderr.match(new RegExp(
|
||||||
|
'Warning: Ignoring extra certs from.*no-such-file-exists' +
|
||||||
|
'.* load failed:.*No such file or directory'
|
||||||
|
)), stderr);
|
||||||
|
}))
|
||||||
|
.stderr.setEncoding('utf8').on('data', function(str) {
|
||||||
|
stderr += str;
|
||||||
|
});
|
45
test/parallel/test-tls-env-extra-ca.js
Normal file
45
test/parallel/test-tls-env-extra-ca.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Certs in NODE_EXTRA_CA_CERTS are used for TLS peer validation
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
|
||||||
|
if (!common.hasCrypto) {
|
||||||
|
common.skip('missing crypto');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const tls = require('tls');
|
||||||
|
const fork = require('child_process').fork;
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
if (process.env.CHILD) {
|
||||||
|
const copts = {
|
||||||
|
port: process.env.PORT,
|
||||||
|
checkServerIdentity: function() {},
|
||||||
|
};
|
||||||
|
const client = tls.connect(copts, function() {
|
||||||
|
client.end('hi');
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'),
|
||||||
|
cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const server = tls.createServer(options, function(s) {
|
||||||
|
s.end('bye');
|
||||||
|
server.close();
|
||||||
|
}).listen(0, common.mustCall(function() {
|
||||||
|
const env = {
|
||||||
|
CHILD: 'yes',
|
||||||
|
PORT: this.address().port,
|
||||||
|
NODE_EXTRA_CA_CERTS: common.fixturesDir + '/keys/ca1-cert.pem',
|
||||||
|
};
|
||||||
|
|
||||||
|
fork(__filename, {env: env}).on('exit', common.mustCall(function(status) {
|
||||||
|
assert.equal(status, 0, 'client did not succeed in connecting');
|
||||||
|
}));
|
||||||
|
}));
|
Loading…
x
Reference in New Issue
Block a user