mirror of https://github.com/nodejs/node.git
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
|
||||
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
|
||||
[debugger]: debugger.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);
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
if (const char* extra = secure_getenv("NODE_EXTRA_CA_CERTS"))
|
||||
crypto::UseExtraCaCerts(extra);
|
||||
#ifdef NODE_FIPS_MODE
|
||||
// In the case of FIPS builds we should make sure
|
||||
// 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)
|
||||
};
|
||||
|
||||
std::string extra_root_certs_file; // NOLINT(runtime/string)
|
||||
|
||||
X509_STORE* root_cert_store;
|
||||
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) {
|
||||
SecureContext* sc;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
|
||||
|
@ -797,6 +832,18 @@ void SecureContext::AddRootCerts(const FunctionCallbackInfo<Value>& args) {
|
|||
|
||||
if (!root_cert_store) {
|
||||
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.
|
||||
|
|
|
@ -65,6 +65,8 @@ extern int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx);
|
|||
|
||||
extern X509_STORE* root_cert_store;
|
||||
|
||||
extern void UseExtraCaCerts(const std::string& file);
|
||||
|
||||
// Forward declaration
|
||||
class Connection;
|
||||
|
||||
|
|
|
@ -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;
|
||||
});
|
|
@ -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…
Reference in New Issue