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

src: fix CAs missing from secure contexts #32315

Closed
wants to merge 4 commits into from
Closed
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
49 changes: 26 additions & 23 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ static const char* const root_certs[] = {
static const char system_cert_path[] = NODE_OPENSSL_SYSTEM_CERT_PATH;

static X509_STORE* root_cert_store;
static std::vector<X509*> root_certs_vector;
static Mutex root_certs_vector_mutex;

static bool extra_root_certs_loaded = false;

Expand Down Expand Up @@ -968,9 +970,7 @@ void SecureContext::SetCert(const FunctionCallbackInfo<Value>& args) {
}


static X509_STORE* NewRootCertStore() {
static std::vector<X509*> root_certs_vector;
static Mutex root_certs_vector_mutex;
static void EnsureRootCerts() {
Mutex::ScopedLock lock(root_certs_vector_mutex);

if (root_certs_vector.empty()) {
Expand All @@ -988,6 +988,11 @@ static X509_STORE* NewRootCertStore() {
root_certs_vector.push_back(x509);
}
}
}


static X509_STORE* NewRootCertStore() {
EnsureRootCerts();

X509_STORE* store = X509_STORE_new();
if (*system_cert_path != '\0') {
Expand All @@ -998,10 +1003,9 @@ static X509_STORE* NewRootCertStore() {
if (per_process::cli_options->ssl_openssl_cert_store) {
X509_STORE_set_default_paths(store);
} else {
for (X509* cert : root_certs_vector) {
X509_up_ref(cert);
Mutex::ScopedLock lock(root_certs_vector_mutex);
for (X509* cert : root_certs_vector)
X509_STORE_add_cert(store, cert);
}
}

return store;
Expand Down Expand Up @@ -1071,8 +1075,7 @@ void SecureContext::AddCRL(const FunctionCallbackInfo<Value>& args) {
}


static unsigned long AddCertsFromFile( // NOLINT(runtime/int)
X509_STORE* store,
static unsigned long AddRootCertsFromFile( // NOLINT(runtime/int)
const char* file) {
ERR_clear_error();
MarkPopErrorOnReturn mark_pop_error_on_return;
Expand All @@ -1081,10 +1084,13 @@ static unsigned long AddCertsFromFile( // NOLINT(runtime/int)
if (!bio)
return ERR_get_error();

while (X509* x509 =
PEM_read_bio_X509(bio.get(), nullptr, NoPasswordCallback, nullptr)) {
X509_STORE_add_cert(store, x509);
X509_free(x509);
{
Mutex::ScopedLock lock(root_certs_vector_mutex);
while (X509* x509 =
PEM_read_bio_X509(bio.get(), nullptr, NoPasswordCallback, nullptr)) {
X509_STORE_add_cert(root_cert_store, x509);
root_certs_vector.push_back(x509);
}
}

unsigned long err = ERR_peek_error(); // NOLINT(runtime/int)
Expand All @@ -1105,8 +1111,7 @@ void UseExtraCaCerts(const std::string& file) {
root_cert_store = NewRootCertStore();

if (!file.empty()) {
unsigned long err = AddCertsFromFile( // NOLINT(runtime/int)
root_cert_store,
unsigned long err = AddRootCertsFromFile( // NOLINT(runtime/int)
file.c_str());
if (err) {
fprintf(stderr,
Expand Down Expand Up @@ -6630,20 +6635,18 @@ void ExportChallenge(const FunctionCallbackInfo<Value>& args) {
void GetRootCertificates(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

if (root_cert_store == nullptr)
root_cert_store = NewRootCertStore();
ClearErrorOnReturn clear_error_on_return;

stack_st_X509_OBJECT* objs = X509_STORE_get0_objects(root_cert_store);
int num_objs = sk_X509_OBJECT_num(objs);
EnsureRootCerts();

std::vector<Local<Value>> result;
result.reserve(num_objs);

for (int i = 0; i < num_objs; i++) {
X509_OBJECT* obj = sk_X509_OBJECT_value(objs, i);
if (X509_OBJECT_get_type(obj) == X509_LU_X509) {
X509* cert = X509_OBJECT_get0_X509(obj);
{
Mutex::ScopedLock lock(root_certs_vector_mutex);

result.reserve(root_certs_vector.size());

for (X509* cert : root_certs_vector) {
Local<Value> value;
if (!X509ToPEM(env, cert).ToLocal(&value))
return;
Expand Down
55 changes: 55 additions & 0 deletions test/parallel/test-tls-env-extra-ca-with-ca.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use strict';

const common = require('../common');

if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');

const { fork } = require('child_process');

if (process.env.CHILD) {
const copts = {
port: process.env.PORT,
checkServerIdentity: common.mustCall()
};

// New secure contexts have the well-known root CAs.
copts.secureContext = tls.createSecureContext();

// Explicit calls to addCACert() add to the root certificates,
// instead of replacing, so connection still succeeds.
copts.secureContext.context.addCACert(
fixtures.readKey('ca1-cert.pem')
);

const client = tls.connect(copts, common.mustCall(() => {
client.end('hi');
}));

return;
}

const options = {
key: fixtures.readKey('agent3-key.pem'),
cert: fixtures.readKey('agent3-cert.pem')
};

const server = tls.createServer(options, common.mustCall((socket) => {
socket.end('bye');
server.close();
})).listen(0, common.mustCall(() => {
const env = Object.assign({}, process.env, {
CHILD: 'yes',
PORT: server.address().port,
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'ca2-cert.pem')
});

fork(__filename, { env }).on('exit', common.mustCall((status) => {
// Client did not succeed in connecting
assert.strictEqual(status, 0);
}));
}));
47 changes: 47 additions & 0 deletions test/parallel/test-tls-env-extra-ca-with-crl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use strict';

const common = require('../common');

if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');

const { fork } = require('child_process');

if (process.env.CHILD) {
const copts = {
port: process.env.PORT,
checkServerIdentity: common.mustCall(),
crl: fixtures.readKey('ca2-crl.pem')
};

const client = tls.connect(copts, common.mustCall(() => {
client.end('hi');
}));

return;
}

const options = {
key: fixtures.readKey('agent3-key.pem'),
cert: fixtures.readKey('agent3-cert.pem')
};

const server = tls.createServer(options, common.mustCall((socket) => {
socket.end('bye');
server.close();
})).listen(0, common.mustCall(() => {
const env = Object.assign({}, process.env, {
CHILD: 'yes',
PORT: server.address().port,
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'ca2-cert.pem')
});

fork(__filename, { env }).on('exit', common.mustCall((status) => {
// Client did not succeed in connecting
assert.strictEqual(status, 0);
}));
}));
48 changes: 48 additions & 0 deletions test/parallel/test-tls-env-extra-ca-with-pfx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';

const common = require('../common');

if (!common.hasCrypto)
common.skip('missing crypto');

const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');

const { fork } = require('child_process');

if (process.env.CHILD) {
const copts = {
port: process.env.PORT,
checkServerIdentity: common.mustCall(),
pfx: fixtures.readKey('agent1.pfx'),
passphrase: 'sample'
};

const client = tls.connect(copts, common.mustCall(() => {
client.end('hi');
}));

return;
}

const options = {
key: fixtures.readKey('agent3-key.pem'),
cert: fixtures.readKey('agent3-cert.pem')
};

const server = tls.createServer(options, common.mustCall((socket) => {
socket.end('bye');
server.close();
})).listen(0, common.mustCall(() => {
const env = Object.assign({}, process.env, {
CHILD: 'yes',
PORT: server.address().port,
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'ca2-cert.pem')
});

fork(__filename, { env }).on('exit', common.mustCall((status) => {
// Client did not succeed in connecting
assert.strictEqual(status, 0);
}));
}));