Skip to content

Commit d615a3c

Browse files
authored
src: suggest --use-system-ca when a certificate error occurs
PR-URL: #57362 Reviewed-By: Yagiz Nizipli <[email protected]> Reviewed-By: Joyee Cheung <[email protected]>
1 parent 59f00d7 commit d615a3c

7 files changed

+34
-17
lines changed

doc/api/tls.md

+6
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,12 @@ description are taken from deps/openssl/openssl/crypto/x509/x509_txt.c
547547
* `'CERT_REJECTED'`: Certificate rejected.
548548
* `'HOSTNAME_MISMATCH'`: Hostname mismatch.
549549

550+
When certificate errors like `UNABLE_TO_VERIFY_LEAF_SIGNATURE`,
551+
`DEPTH_ZERO_SELF_SIGNED_CERT`, or `UNABLE_TO_GET_ISSUER_CERT` occur, Node.js
552+
appends a hint suggesting that if the root CA is installed locally,
553+
try running with the `--use-system-ca` flag to direct developers towards a
554+
secure solution, to prevent unsafe workarounds.
555+
550556
## Class: `tls.CryptoStream`
551557

552558
<!-- YAML

src/crypto/crypto_common.cc

+13-1
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,20 @@ SSLSessionPointer GetTLSSession(const unsigned char* buf, size_t length) {
5353
}
5454

5555
MaybeLocal<Value> GetValidationErrorReason(Environment* env, int err) {
56-
auto reason = X509Pointer::ErrorReason(err).value_or("");
56+
auto reason = std::string(X509Pointer::ErrorReason(err).value_or(""));
5757
if (reason == "") return Undefined(env->isolate());
58+
59+
// Suggest --use-system-ca if the error indicates a certificate issue
60+
bool suggest_system_ca =
61+
(err == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE) ||
62+
(err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) ||
63+
((err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT) &&
64+
!per_process::cli_options->use_system_ca);
65+
66+
if (suggest_system_ca) {
67+
reason.append("; if the root CA is installed locally, "
68+
"try running Node.js with --use-system-ca");
69+
}
5870
return OneByteString(env->isolate(), reason);
5971
}
6072

test/parallel/test-https-agent-create-connection.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const options = {
1717

1818
const expectedHeader = /^HTTP\/1\.1 200 OK/;
1919
const expectedBody = /hello world\n/;
20-
const expectCertError = /^Error: unable to verify the first certificate$/;
20+
const expectCertError = /^UNABLE_TO_VERIFY_LEAF_SIGNATURE$/;
2121

2222
const checkRequest = (socket, server) => {
2323
let result = '';
@@ -112,7 +112,7 @@ function createServer() {
112112
const options = null;
113113
const socket = agent.createConnection(port, host, options);
114114
socket.on('error', common.mustCall((e) => {
115-
assert.match(e.toString(), expectCertError);
115+
assert.match(e.code, expectCertError);
116116
server.close();
117117
}));
118118
}));
@@ -127,7 +127,7 @@ function createServer() {
127127
const options = undefined;
128128
const socket = agent.createConnection(port, host, options);
129129
socket.on('error', common.mustCall((e) => {
130-
assert.match(e.toString(), expectCertError);
130+
assert.match(e.code, expectCertError);
131131
server.close();
132132
}));
133133
}));

test/parallel/test-tls-addca.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ connect({
3434
server: serverOptions,
3535
}, common.mustCall((err, pair, cleanup) => {
3636
assert(err);
37-
assert.strictEqual(err.message, 'unable to verify the first certificate');
37+
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
3838
cleanup();
3939

4040
// This time it should connect because contextWithCert includes the needed CA

test/parallel/test-tls-friendly-error-message.js

-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,5 @@ tls.createServer({ key, cert }).on('connection', common.mustCall(function() {
4040
const options = { port: this.address().port, rejectUnauthorized: true };
4141
tls.connect(options).on('error', common.mustCall(function(err) {
4242
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
43-
assert.strictEqual(err.message, 'unable to verify the first certificate');
4443
}));
4544
}));

test/parallel/test-tls-set-secure-context.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ const assert = require('assert');
1313
const events = require('events');
1414
const https = require('https');
1515
const timers = require('timers/promises');
16-
const { hasOpenSSL3 } = require('../common/crypto');
1716
const fixtures = require('../common/fixtures');
1817
const credentialOptions = [
1918
{
@@ -57,17 +56,18 @@ server.listen(0, common.mustCall(() => {
5756

5857
server.setSecureContext(credentialOptions[1]);
5958
firstResponse.write('request-');
60-
const errorMessageRegex = hasOpenSSL3 ?
61-
/^Error: self-signed certificate$/ :
62-
/^Error: self signed certificate$/;
63-
await assert.rejects(makeRequest(port, 3), errorMessageRegex);
59+
await assert.rejects(makeRequest(port, 3), {
60+
code: 'DEPTH_ZERO_SELF_SIGNED_CERT',
61+
});
6462

6563
server.setSecureContext(credentialOptions[0]);
6664
assert.strictEqual(await makeRequest(port, 4), 'success');
6765

6866
server.setSecureContext(credentialOptions[1]);
6967
firstResponse.end('fun!');
70-
await assert.rejects(makeRequest(port, 5), errorMessageRegex);
68+
await assert.rejects(makeRequest(port, 5), {
69+
code: 'DEPTH_ZERO_SELF_SIGNED_CERT',
70+
});
7171

7272
assert.strictEqual(await firstRequest, 'multi-request-success-fun!');
7373
server.close();

test/parallel/test-tls-socket-default-options.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ const {
1010
} = require(fixtures.path('tls-connect'));
1111

1212
test(undefined, (err) => {
13-
assert.strictEqual(err.message, 'unable to verify the first certificate');
13+
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
1414
});
1515

1616
test({}, (err) => {
17-
assert.strictEqual(err.message, 'unable to verify the first certificate');
17+
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
1818
});
1919

2020
test(
@@ -30,8 +30,8 @@ test(
3030
test(
3131
{ secureContext: tls.createSecureContext(), ca: keys.agent1.ca },
3232
(err) => {
33-
assert.strictEqual(err.message,
34-
'unable to verify the first certificate');
33+
assert.strictEqual(err.code,
34+
'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
3535
});
3636

3737
function test(client, callback) {
@@ -42,7 +42,7 @@ function test(client, callback) {
4242
cert: keys.agent1.cert,
4343
},
4444
}, function(err, pair, cleanup) {
45-
assert.strictEqual(err.message, 'unable to verify the first certificate');
45+
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
4646
let recv = '';
4747
pair.server.server.once('secureConnection', common.mustCall((conn) => {
4848
conn.on('data', (data) => recv += data);

0 commit comments

Comments
 (0)