Files
node/test/parallel/test-tls-client-auth.js
T
Filip Skokan a2ce2c6def tls,tools: update BoringSSL pin and TLS errors
Use BoringSSL 0.20261020.0 for shared-library builds and expose the
package wrapper's version to the matrix updater.

The new verifier no longer emits UNABLE_TO_VERIFY_LEAF_SIGNATURE
after Node's verification callback accepts a missing-issuer error.
Expect UNABLE_TO_GET_ISSUER_CERT_LOCALLY for the affected leaf
certificates and suggest --use-system-ca for that error.

Drop the SSL_get_server_tmp_key() fallback now that BoringSSL supplies
SSL_get_peer_tmp_key() as a compatibility stub.

Refs: https://boringssl-review.googlesource.com/c/boringssl/+/103849
Refs: https://github.com/electron/electron/pull/55110/changes/b457e70e339e8bb3066b210edfb6f96582cdb374
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66922
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
2026-10-24 22:04:13 +00:00

364 lines
8.3 KiB
JavaScript

'use strict';
const common = require('../common');
const fixtures = require('../common/fixtures');
if (!common.hasCrypto) {
common.skip('missing crypto');
}
const { hasOpenSSL, isBoringSSL } = require('../common/crypto');
const {
assert, connect, keys, tls
} = require(fixtures.path('tls-connect'));
const { untrustedLeafErrorCode } = require('../common/tls');
// Use ec10 and agent10, they are the only identities with intermediate CAs.
const client = keys.ec10;
const server = keys.agent10;
// The certificates aren't for "localhost", so override the identity check.
function checkServerIdentity(hostname, cert) {
assert.strictEqual(hostname, 'localhost');
assert.strictEqual(cert.subject.CN, 'agent10.example.com');
}
// Split out the single end-entity cert and the subordinate CA for later use.
split(client.cert, client);
split(server.cert, server);
function split(file, into) {
const certs = /([^]*END CERTIFICATE-----\r?\n)(-----BEGIN[^]*)/.exec(file);
assert.strictEqual(certs.length, 3);
into.single = certs[1];
into.subca = certs[2];
}
// Typical setup, nothing special, complete cert chains sent to peer.
connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
}, common.mustSucceed((pair, cleanup) => {
return cleanup();
}));
// As above, but without requesting client's cert.
connect({
client: {
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
},
}, common.mustSucceed((pair, cleanup) => {
return cleanup();
}));
// Request cert from TLS1.2 client that doesn't have one.
connect({
client: {
maxVersion: 'TLSv1.2',
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
}, common.mustCall((err, pair, cleanup) => {
assert.strictEqual(pair.server.err.code,
'ERR_SSL_PEER_DID_NOT_RETURN_A_CERTIFICATE');
const expectedErr = hasOpenSSL(4, 0) ?
'ERR_SSL_TLS_ALERT_HANDSHAKE_FAILURE' : hasOpenSSL(3, 2) ?
'ERR_SSL_SSL/TLS_ALERT_HANDSHAKE_FAILURE' : 'ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE';
assert.strictEqual(pair.client.err.code,
expectedErr);
return cleanup();
}));
// Request cert from TLS1.3 client that doesn't have one.
if (tls.DEFAULT_MAX_VERSION === 'TLSv1.3') connect({
client: {
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
}, common.mustCall((err, pair, cleanup) => {
assert.strictEqual(pair.server.err.code,
'ERR_SSL_PEER_DID_NOT_RETURN_A_CERTIFICATE');
// TLS1.3 client completes handshake before server, and its only after the
// server handshakes, requests certs, gets back a zero-length list of certs,
// and sends a fatal Alert to the client that the client discovers there has
// been a fatal error.
pair.client.conn.once('error', common.mustCall((err) => {
const expectedErr = isBoringSSL ?
'ERR_SSL_TLSV1_ALERT_CERTIFICATE_REQUIRED' :
'ERR_SSL_TLSV13_ALERT_CERTIFICATE_REQUIRED';
assert.strictEqual(err.code, expectedErr);
cleanup();
}));
}));
// Typical configuration error, incomplete cert chains sent, we have to know the
// peer's subordinate CAs in order to verify the peer.
connect({
client: {
key: client.key,
cert: client.single,
ca: [server.ca, server.subca],
checkServerIdentity,
},
server: {
key: server.key,
cert: server.single,
ca: [client.ca, client.subca],
requestCert: true,
},
}, common.mustSucceed((pair, cleanup) => {
return cleanup();
}));
// Like above, but provide root CA and subordinate CA as multi-PEM.
connect({
client: {
key: client.key,
cert: client.single,
ca: server.ca + '\n' + server.subca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.single,
ca: client.ca + '\n' + client.subca,
requestCert: true,
},
}, common.mustSucceed((pair, cleanup) => {
return cleanup();
}));
// Like above, but provide multi-PEM in an array.
connect({
client: {
key: client.key,
cert: client.single,
ca: [server.ca + '\n' + server.subca],
checkServerIdentity,
},
server: {
key: server.key,
cert: server.single,
ca: [client.ca + '\n' + client.subca],
requestCert: true,
},
}, common.mustSucceed((pair, cleanup) => {
return cleanup();
}));
// Fail to complete server's chain
connect({
client: {
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.single,
},
}, common.mustCall((err, pair, cleanup) => {
assert.strictEqual(err.code, untrustedLeafErrorCode);
return cleanup();
}));
// Fail to complete client's chain.
connect({
client: {
key: client.key,
cert: client.single,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
}, common.mustCall((err, pair, cleanup) => {
assert.ifError(pair.client.error);
assert.ifError(pair.server.error);
assert.strictEqual(err.code, 'ECONNRESET');
return cleanup();
}));
// Fail to find CA for server.
connect({
client: {
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
},
}, common.mustCall((err, pair, cleanup) => {
assert.strictEqual(err.code, 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY');
return cleanup();
}));
// Server sent their CA, but CA cannot be trusted if it is not locally known.
connect({
client: {
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert + '\n' + server.ca,
},
}, common.mustCall((err, pair, cleanup) => {
assert.strictEqual(err.code, 'SELF_SIGNED_CERT_IN_CHAIN');
return cleanup();
}));
// Server sent their CA, wrongly, but its OK since we know the CA locally.
connect({
client: {
checkServerIdentity,
ca: server.ca,
},
server: {
key: server.key,
cert: server.cert + '\n' + server.ca,
},
}, common.mustSucceed((pair, cleanup) => {
return cleanup();
}));
// Fail to complete client's chain.
connect({
client: {
key: client.key,
cert: client.single,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
}, common.mustCall((err, pair, cleanup) => {
assert.strictEqual(err.code, 'ECONNRESET');
return cleanup();
}));
// Fail to find CA for client.
connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
requestCert: true,
},
}, common.mustCall((err, pair, cleanup) => {
assert.strictEqual(err.code, 'ECONNRESET');
return cleanup();
}));
// Confirm support for "BEGIN TRUSTED CERTIFICATE".
connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca.replace(/CERTIFICATE/g, 'TRUSTED CERTIFICATE'),
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
}, common.mustSucceed((pair, cleanup) => {
return cleanup();
}));
// Confirm support for "BEGIN TRUSTED CERTIFICATE".
connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca.replace(/CERTIFICATE/g, 'TRUSTED CERTIFICATE'),
requestCert: true,
},
}, common.mustSucceed((pair, cleanup) => {
return cleanup();
}));
// Confirm support for "BEGIN X509 CERTIFICATE".
connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca.replace(/CERTIFICATE/g, 'X509 CERTIFICATE'),
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca,
requestCert: true,
},
}, common.mustSucceed((pair, cleanup) => {
return cleanup();
}));
// Confirm support for "BEGIN X509 CERTIFICATE".
connect({
client: {
key: client.key,
cert: client.cert,
ca: server.ca,
checkServerIdentity,
},
server: {
key: server.key,
cert: server.cert,
ca: client.ca.replace(/CERTIFICATE/g, 'X509 CERTIFICATE'),
requestCert: true,
},
}, common.mustSucceed((pair, cleanup) => {
return cleanup();
}));