crypto: allow adding extra certs to well-known CAs by sam-github · Pull Request #9139 · nodejs/node · GitHub
Skip to content
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
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