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

Suggest --use-system-ca when a certificate error occurs #57362

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions doc/api/tls.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,12 @@ description are taken from deps/openssl/openssl/crypto/x509/x509_txt.c
* `'CERT_REJECTED'`: Certificate rejected.
* `'HOSTNAME_MISMATCH'`: Hostname mismatch.

When certificate errors like `UNABLE_TO_VERIFY_LEAF_SIGNATURE`,
`DEPTH_ZERO_SELF_SIGNED_CERT`, or `UNABLE_TO_GET_ISSUER_CERT` occur, Node.js
appends a hint suggesting that if the root CA is installed locally,
try running with the `--use-system-ca` flag to direct developers towards a
secure solution, to prevent unsafe workarounds.

## Class: `tls.CryptoStream`

<!-- YAML
Expand Down
14 changes: 13 additions & 1 deletion src/crypto/crypto_common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,20 @@ SSLSessionPointer GetTLSSession(const unsigned char* buf, size_t length) {
}

MaybeLocal<Value> GetValidationErrorReason(Environment* env, int err) {
auto reason = X509Pointer::ErrorReason(err).value_or("");
auto reason = std::string(X509Pointer::ErrorReason(err).value_or(""));
if (reason == "") return Undefined(env->isolate());

// Suggest --use-system-ca if the error indicates a certificate issue
bool suggest_system_ca =
(err == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE) ||
(err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) ||
((err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT) &&
!per_process::cli_options->use_system_ca);

if (suggest_system_ca) {
reason.append("; if the root CA is installed locally, "
"try running Node.js with --use-system-ca");
}
return OneByteString(env->isolate(), reason);
}

Expand Down
6 changes: 3 additions & 3 deletions test/parallel/test-https-agent-create-connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const options = {

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

const checkRequest = (socket, server) => {
let result = '';
Expand Down Expand Up @@ -112,7 +112,7 @@ function createServer() {
const options = null;
const socket = agent.createConnection(port, host, options);
socket.on('error', common.mustCall((e) => {
assert.match(e.toString(), expectCertError);
assert.match(e.code, expectCertError);
server.close();
}));
}));
Expand All @@ -127,7 +127,7 @@ function createServer() {
const options = undefined;
const socket = agent.createConnection(port, host, options);
socket.on('error', common.mustCall((e) => {
assert.match(e.toString(), expectCertError);
assert.match(e.code, expectCertError);
server.close();
}));
}));
Expand Down
2 changes: 1 addition & 1 deletion test/parallel/test-tls-addca.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ connect({
server: serverOptions,
}, common.mustCall((err, pair, cleanup) => {
assert(err);
assert.strictEqual(err.message, 'unable to verify the first certificate');
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
cleanup();

// This time it should connect because contextWithCert includes the needed CA
Expand Down
1 change: 0 additions & 1 deletion test/parallel/test-tls-friendly-error-message.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,5 @@ tls.createServer({ key, cert }).on('connection', common.mustCall(function() {
const options = { port: this.address().port, rejectUnauthorized: true };
tls.connect(options).on('error', common.mustCall(function(err) {
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
assert.strictEqual(err.message, 'unable to verify the first certificate');
}));
}));
12 changes: 6 additions & 6 deletions test/parallel/test-tls-set-secure-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const assert = require('assert');
const events = require('events');
const https = require('https');
const timers = require('timers/promises');
const { hasOpenSSL3 } = require('../common/crypto');
const fixtures = require('../common/fixtures');
const credentialOptions = [
{
Expand Down Expand Up @@ -57,17 +56,18 @@ server.listen(0, common.mustCall(() => {

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

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

server.setSecureContext(credentialOptions[1]);
firstResponse.end('fun!');
await assert.rejects(makeRequest(port, 5), errorMessageRegex);
await assert.rejects(makeRequest(port, 5), {
code: 'DEPTH_ZERO_SELF_SIGNED_CERT',
});

assert.strictEqual(await firstRequest, 'multi-request-success-fun!');
server.close();
Expand Down
10 changes: 5 additions & 5 deletions test/parallel/test-tls-socket-default-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ const {
} = require(fixtures.path('tls-connect'));

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

test({}, (err) => {
assert.strictEqual(err.message, 'unable to verify the first certificate');
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
});

test(
Expand All @@ -30,8 +30,8 @@ test(
test(
{ secureContext: tls.createSecureContext(), ca: keys.agent1.ca },
(err) => {
assert.strictEqual(err.message,
'unable to verify the first certificate');
assert.strictEqual(err.code,
'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
});

function test(client, callback) {
Expand All @@ -42,7 +42,7 @@ function test(client, callback) {
cert: keys.agent1.cert,
},
}, function(err, pair, cleanup) {
assert.strictEqual(err.message, 'unable to verify the first certificate');
assert.strictEqual(err.code, 'UNABLE_TO_VERIFY_LEAF_SIGNATURE');
let recv = '';
pair.server.server.once('secureConnection', common.mustCall((conn) => {
conn.on('data', (data) => recv += data);
Expand Down
Loading