Skip to content

Commit 2ab0b3c

Browse files
committed
src: add NODE_EXTRA_CA_CERTS to modified cert stores
Store loaded NODE_EXTRA_CA_CERTS into root_certs_vector, allowing them to be added to secure contexts when NewRootCertStore() is called. When NODE_EXTRA_CA_CERTS is specified, the bundled root certificates will no longer be preloaded at startup. This improves Node.js startup time and makes the behavior of NODE_EXTRA_CA_CERTS consistent with the default behavior when NODE_EXTRA_CA_CERTS is ommitted. Fixes: nodejs#32010 Refs: nodejs#40524
1 parent bb4dff7 commit 2ab0b3c

4 files changed

+178
-31
lines changed

src/crypto/crypto_context.cc

+28-31
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ static const char system_cert_path[] = NODE_OPENSSL_SYSTEM_CERT_PATH;
4949

5050
static X509_STORE* root_cert_store;
5151

52+
static std::vector<X509Pointer> root_certs_vector;
53+
static Mutex root_certs_vector_mutex;
54+
static bool root_certs_loaded = false;
55+
5256
static bool extra_root_certs_loaded = false;
5357

5458
// Takes a string or buffer and loads it into a BIO.
@@ -191,25 +195,25 @@ int SSL_CTX_use_certificate_chain(SSL_CTX* ctx,
191195
} // namespace
192196

193197
X509_STORE* NewRootCertStore() {
194-
static std::vector<X509*> root_certs_vector;
195-
static Mutex root_certs_vector_mutex;
196198
Mutex::ScopedLock lock(root_certs_vector_mutex);
197199

198-
if (root_certs_vector.empty() &&
200+
if (!root_certs_loaded &&
199201
per_process::cli_options->ssl_openssl_cert_store == false) {
200202
for (size_t i = 0; i < arraysize(root_certs); i++) {
201-
X509* x509 =
203+
X509Pointer x509 = X509Pointer(
202204
PEM_read_bio_X509(NodeBIO::NewFixed(root_certs[i],
203205
strlen(root_certs[i])).get(),
204-
nullptr, // no re-use of X509 structure
206+
nullptr, // no re-use of X509 structure
205207
NoPasswordCallback,
206-
nullptr); // no callback data
208+
nullptr)); // no callback data
207209

208210
// Parse errors from the built-in roots are fatal.
209211
CHECK_NOT_NULL(x509);
210212

211-
root_certs_vector.push_back(x509);
213+
root_certs_vector.push_back(std::move(x509));
212214
}
215+
216+
root_certs_loaded = true;
213217
}
214218

215219
X509_STORE* store = X509_STORE_new();
@@ -223,10 +227,8 @@ X509_STORE* NewRootCertStore() {
223227
if (per_process::cli_options->ssl_openssl_cert_store) {
224228
X509_STORE_set_default_paths(store);
225229
} else {
226-
for (X509* cert : root_certs_vector) {
227-
X509_up_ref(cert);
228-
X509_STORE_add_cert(store, cert);
229-
}
230+
for (X509Pointer& cert : root_certs_vector)
231+
X509_STORE_add_cert(store, cert.get());
230232
}
231233

232234
return store;
@@ -1299,7 +1301,7 @@ void SecureContext::GetCertificate(const FunctionCallbackInfo<Value>& args) {
12991301

13001302
namespace {
13011303
unsigned long AddCertsFromFile( // NOLINT(runtime/int)
1302-
X509_STORE* store,
1304+
std::vector<X509Pointer>& certs,
13031305
const char* file) {
13041306
ERR_clear_error();
13051307
MarkPopErrorOnReturn mark_pop_error_on_return;
@@ -1308,10 +1310,9 @@ unsigned long AddCertsFromFile( // NOLINT(runtime/int)
13081310
if (!bio)
13091311
return ERR_get_error();
13101312

1311-
while (X509* x509 =
1312-
PEM_read_bio_X509(bio.get(), nullptr, NoPasswordCallback, nullptr)) {
1313-
X509_STORE_add_cert(store, x509);
1314-
X509_free(x509);
1313+
while (X509Pointer x509 = X509Pointer(
1314+
PEM_read_bio_X509(bio.get(), nullptr, NoPasswordCallback, nullptr))) {
1315+
certs.push_back(std::move(x509));
13151316
}
13161317

13171318
unsigned long err = ERR_peek_error(); // NOLINT(runtime/int)
@@ -1329,21 +1330,17 @@ unsigned long AddCertsFromFile( // NOLINT(runtime/int)
13291330
void UseExtraCaCerts(const std::string& file) {
13301331
ClearErrorOnReturn clear_error_on_return;
13311332

1332-
if (root_cert_store == nullptr) {
1333-
root_cert_store = NewRootCertStore();
1334-
1335-
if (!file.empty()) {
1336-
unsigned long err = AddCertsFromFile( // NOLINT(runtime/int)
1337-
root_cert_store,
1338-
file.c_str());
1339-
if (err) {
1340-
fprintf(stderr,
1341-
"Warning: Ignoring extra certs from `%s`, load failed: %s\n",
1342-
file.c_str(),
1343-
ERR_error_string(err, nullptr));
1344-
} else {
1345-
extra_root_certs_loaded = true;
1346-
}
1333+
if (!file.empty()) {
1334+
unsigned long err = AddCertsFromFile( // NOLINT(runtime/int)
1335+
root_certs_vector,
1336+
file.c_str());
1337+
if (err) {
1338+
fprintf(stderr,
1339+
"Warning: Ignoring extra certs from `%s`, load failed: %s\n",
1340+
file.c_str(),
1341+
ERR_error_string(err, nullptr));
1342+
} else {
1343+
extra_root_certs_loaded = true;
13471344
}
13481345
}
13491346
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
5+
if (!common.hasCrypto)
6+
common.skip('missing crypto');
7+
8+
const assert = require('assert');
9+
const tls = require('tls');
10+
const fixtures = require('../common/fixtures');
11+
12+
const { fork } = require('child_process');
13+
14+
if (process.env.CHILD) {
15+
const copts = {
16+
port: process.env.PORT,
17+
checkServerIdentity: common.mustCall()
18+
};
19+
20+
// New secure contexts have the well-known root CAs.
21+
copts.secureContext = tls.createSecureContext();
22+
23+
// Explicit calls to addCACert() add to the root certificates,
24+
// instead of replacing, so connection still succeeds.
25+
copts.secureContext.context.addCACert(
26+
fixtures.readKey('ca1-cert.pem')
27+
);
28+
29+
const client = tls.connect(copts, common.mustCall(() => {
30+
client.end('hi');
31+
}));
32+
33+
return;
34+
}
35+
36+
const options = {
37+
key: fixtures.readKey('agent3-key.pem'),
38+
cert: fixtures.readKey('agent3-cert.pem')
39+
};
40+
41+
const server = tls.createServer(options, common.mustCall((socket) => {
42+
socket.end('bye');
43+
server.close();
44+
})).listen(0, common.mustCall(() => {
45+
const env = Object.assign({}, process.env, {
46+
CHILD: 'yes',
47+
PORT: server.address().port,
48+
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'ca2-cert.pem')
49+
});
50+
51+
fork(__filename, { env }).on('exit', common.mustCall((status) => {
52+
// Client did not succeed in connecting
53+
assert.strictEqual(status, 0);
54+
}));
55+
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
5+
if (!common.hasCrypto)
6+
common.skip('missing crypto');
7+
8+
const assert = require('assert');
9+
const tls = require('tls');
10+
const fixtures = require('../common/fixtures');
11+
12+
const { fork } = require('child_process');
13+
14+
if (process.env.CHILD) {
15+
const copts = {
16+
port: process.env.PORT,
17+
checkServerIdentity: common.mustCall(),
18+
crl: fixtures.readKey('ca2-crl.pem')
19+
};
20+
21+
const client = tls.connect(copts, common.mustCall(() => {
22+
client.end('hi');
23+
}));
24+
25+
return;
26+
}
27+
28+
const options = {
29+
key: fixtures.readKey('agent3-key.pem'),
30+
cert: fixtures.readKey('agent3-cert.pem')
31+
};
32+
33+
const server = tls.createServer(options, common.mustCall((socket) => {
34+
socket.end('bye');
35+
server.close();
36+
})).listen(0, common.mustCall(() => {
37+
const env = Object.assign({}, process.env, {
38+
CHILD: 'yes',
39+
PORT: server.address().port,
40+
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'ca2-cert.pem')
41+
});
42+
43+
fork(__filename, { env }).on('exit', common.mustCall((status) => {
44+
// Client did not succeed in connecting
45+
assert.strictEqual(status, 0);
46+
}));
47+
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
5+
if (!common.hasCrypto)
6+
common.skip('missing crypto');
7+
8+
const assert = require('assert');
9+
const tls = require('tls');
10+
const fixtures = require('../common/fixtures');
11+
12+
const { fork } = require('child_process');
13+
14+
if (process.env.CHILD) {
15+
const copts = {
16+
port: process.env.PORT,
17+
checkServerIdentity: common.mustCall(),
18+
pfx: fixtures.readKey('agent1.pfx'),
19+
passphrase: 'sample'
20+
};
21+
22+
const client = tls.connect(copts, common.mustCall(() => {
23+
client.end('hi');
24+
}));
25+
26+
return;
27+
}
28+
29+
const options = {
30+
key: fixtures.readKey('agent3-key.pem'),
31+
cert: fixtures.readKey('agent3-cert.pem')
32+
};
33+
34+
const server = tls.createServer(options, common.mustCall((socket) => {
35+
socket.end('bye');
36+
server.close();
37+
})).listen(0, common.mustCall(() => {
38+
const env = Object.assign({}, process.env, {
39+
CHILD: 'yes',
40+
PORT: server.address().port,
41+
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'ca2-cert.pem')
42+
});
43+
44+
fork(__filename, { env }).on('exit', common.mustCall((status) => {
45+
// Client did not succeed in connecting
46+
assert.strictEqual(status, 0);
47+
}));
48+
}));

0 commit comments

Comments
 (0)