Browse Source

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>
v4.x
Sam Roberts 8 years ago
committed by Myles Borins
parent
commit
1595328b44
No known key found for this signature in database GPG Key ID: 933B01F40B5CA946
  1. 15
      doc/api/cli.md
  2. 2
      src/node.cc
  3. 46
      src/node_crypto.cc
  4. 2
      src/node_crypto.h
  5. 43
      test/parallel/test-tls-env-bad-extra-ca.js
  6. 45
      test/parallel/test-tls-env-extra-ca.js

15
doc/api/cli.md

@ -225,6 +225,21 @@ Path to the file used to store the persistent REPL history. The default path is
to an empty string (`""` or `" "`) disables persistent REPL history.
### `NODE_EXTRA_CA_CERTS=file`
<!-- YAML
added: XXX
-->
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 printed to stderr (once)
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

2
src/node.cc

@ -4400,6 +4400,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);
// V8 on Windows doesn't have a good source of entropy. Seed it from
// OpenSSL's pool.
V8::SetEntropySource(crypto::EntropySource);

46
src/node_crypto.cc

@ -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;
// Just to generate static methods
@ -753,6 +755,38 @@ 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;
@ -782,6 +816,18 @@ void SecureContext::AddRootCerts(const FunctionCallbackInfo<Value>& args) {
BIO_free_all(bp);
X509_free(x509);
}
if (!extra_root_certs_file.empty()) {
unsigned long err = AddCertsFromFile( // NOLINT(runtime/int)
root_cert_store,
extra_root_certs_file.c_str());
if (err) {
fprintf(stderr,
"Warning: Ignoring extra certs from `%s`, load failed: %s\n",
extra_root_certs_file.c_str(),
ERR_error_string(err, nullptr));
}
}
}
sc->ca_store_ = root_cert_store;

2
src/node_crypto.h

@ -63,6 +63,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;

43
test/parallel/test-tls-env-bad-extra-ca.js

@ -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

@ -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…
Cancel
Save