Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto: allow adding extra certs to well-known CAs #9139

Merged
merged 2 commits into from
Nov 23, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2562,6 +2562,33 @@ void ClearFatalExceptionHandlers(Environment* env) {
Undefined(env->isolate())).FromJust();
}

// Call process.emitWarning(str), fmt is a snprintf() format string
void ProcessEmitWarning(Environment* env, const char* fmt, ...) {
char warning[1024];
va_list ap;

va_start(ap, fmt);
vsnprintf(warning, sizeof(warning), fmt, ap);
va_end(ap);

HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());

Local<Object> process = env->process_object();
MaybeLocal<Value> emit_warning = process->Get(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "emitWarning"));
Local<Value> arg = node::OneByteString(env->isolate(), warning);

Local<Value> f;

if (!emit_warning.ToLocal(&f)) return;
if (!f->IsFunction()) return;

// MakeCallback() unneeded, because emitWarning is internal code, it calls
// process.emit('warning', ..), but does so on the nextTick.
f.As<v8::Function>()->Call(process, 1, &arg);
}


static void Binding(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Expand Down Expand Up @@ -4509,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.
Expand Down
47 changes: 47 additions & 0 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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());
Expand All @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions src/node_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 2 additions & 0 deletions src/node_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ void AppendExceptionLine(Environment* env,

NO_RETURN void FatalError(const char* location, const char* message);

void ProcessEmitWarning(Environment* env, const char* fmt, ...);

v8::Local<v8::Value> BuildStatsObject(Environment* env, const uv_stat_t* s);

void SetupProcessObject(Environment* env,
Expand Down
43 changes: 43 additions & 0 deletions test/parallel/test-tls-env-bad-extra-ca.js
Original file line number Diff line number Diff line change
@@ -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 changes: 45 additions & 0 deletions test/parallel/test-tls-env-extra-ca.js
Original file line number Diff line number Diff line change
@@ -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');
}));
}));